Merge "Make tv menu mode switch wait for focus change" into main
diff --git a/ADPF_OWNERS b/ADPF_OWNERS
new file mode 100644
index 0000000..e6ca8f4
--- /dev/null
+++ b/ADPF_OWNERS
@@ -0,0 +1,3 @@
+lpy@google.com
+mattbuckley@google.com
+xwxw@google.com
diff --git a/OWNERS b/OWNERS
index 4860acc..8ee488d 100644
--- a/OWNERS
+++ b/OWNERS
@@ -31,9 +31,6 @@
 per-file */res*/values*/*.xml = byi@google.com, delphij@google.com
 
 per-file **.bp,**.mk = hansson@google.com
-per-file *.bp = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
-per-file Android.mk = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
-per-file framework-jarjar-rules.txt = file:platform/build/soong:/OWNERS #{LAST_RESORT_SUGGESTION}
 per-file TestProtoLibraries.bp = file:platform/platform_testing:/libraries/health/OWNERS
 per-file TestProtoLibraries.bp = file:platform/tools/tradefederation:/OWNERS
 
diff --git a/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
index e805ab9..abb0fa7 100644
--- a/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
+++ b/apct-tests/perftests/core/src/android/graphics/perftests/RenderNodePerfTest.java
@@ -42,22 +42,6 @@
     }
 
     @Test
-    public void testCreateRenderNodeNoName() {
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        while (state.keepRunning()) {
-            RenderNode.create(null, null);
-        }
-    }
-
-    @Test
-    public void testCreateRenderNode() {
-        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        while (state.keepRunning()) {
-            RenderNode.create("LinearLayout", null);
-        }
-    }
-
-    @Test
     public void testIsValid() {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         RenderNode node = RenderNode.create("LinearLayout", null);
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java
index 3b5f11b..29afb27 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobService.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java
@@ -40,10 +40,26 @@
  * <p>This service executes each incoming job on a {@link android.os.Handler} running on your
  * application's main thread. This means that you <b>must</b> offload your execution logic to
  * another thread/handler/{@link android.os.AsyncTask} of your choosing. Not doing so will result
- * in blocking any future callbacks from the JobManager - specifically
+ * in blocking any future callbacks from the JobScheduler - specifically
  * {@link #onStopJob(android.app.job.JobParameters)}, which is meant to inform you that the
  * scheduling requirements are no longer being met.</p>
  *
+ * <p class="note">
+ * Since the introduction of JobScheduler, if an app did not return from
+ * {@link #onStartJob(JobParameters)} within several seconds, JobScheduler would consider the app
+ * unresponsive and clean up job execution. In such cases, the app was no longer considered
+ * to be running a job and therefore did not have any of the job lifecycle guarantees outlined
+ * in {@link JobScheduler}. However, prior to Android version
+ * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, the failure and cleanup were silent
+ * and apps had no indication that they no longer had job lifecycle guarantees.
+ * Starting with Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE},
+ * JobScheduler will explicitly trigger an ANR in such cases so that apps and developers
+ * can be aware of the issue.
+ * Similar behavior applies to the return time from {@link #onStopJob(JobParameters)} as well.
+ * <br /> <br />
+ * If you see ANRs, then the app may be doing too much work on the UI thread. Ensure that
+ * potentially long operations are moved to a worker thread.
+ *
  * <p>As a subclass of {@link Service}, there will only be one active instance of any JobService
  * subclasses, regardless of job ID. This means that if you schedule multiple jobs with different
  * job IDs but using the same JobService class, that JobService may receive multiple calls to
@@ -240,7 +256,7 @@
      * @param params The parameters identifying this job, similar to what was supplied to the job in
      *               the {@link #onStartJob(JobParameters)} callback, but with the stop reason
      *               included.
-     * @return {@code true} to indicate to the JobManager whether you'd like to reschedule
+     * @return {@code true} to indicate to the JobScheduler whether you'd like to reschedule
      * this job based on the retry criteria provided at job creation-time; or {@code false}
      * to end the job entirely (or, for a periodic job, to reschedule it according to its
      * requested periodic criteria). Regardless of the value returned, your job must stop executing.
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 76d1935..1be07fd 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -79,6 +79,7 @@
 import android.os.SystemClock;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.os.WearModeManagerInternal;
 import android.provider.DeviceConfig;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
@@ -126,6 +127,7 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 /**
@@ -373,10 +375,9 @@
     @GuardedBy("this")
     private boolean mBatterySaverEnabled;
     @GuardedBy("this")
-    private boolean mIsOffBody;
+    private boolean mModeManagerRequestedQuickDoze;
     @GuardedBy("this")
-    private boolean mForceBodyState;
-    private Sensor mOffBodySensor;
+    private boolean mForceModeManagerQuickDozeRequest;
 
     /** Time in the elapsed realtime timebase when this listener last received a motion event. */
     @GuardedBy("this")
@@ -435,7 +436,7 @@
     private static final int ACTIVE_REASON_FORCED = 6;
     private static final int ACTIVE_REASON_ALARM = 7;
     private static final int ACTIVE_REASON_EMERGENCY_CALL = 8;
-    private static final int ACTIVE_REASON_ONBODY = 9;
+    private static final int ACTIVE_REASON_MODE_MANAGER = 9;
 
     @VisibleForTesting
     static String stateToString(int state) {
@@ -832,64 +833,35 @@
         }
     }
 
-    /**
-     * LowLatencyOffBodyListener monitors if a device is on body or off body.
-     */
     @VisibleForTesting
-    final class LowLatencyOffBodyListener implements SensorEventListener {
+    class ModeManagerQuickDozeRequestConsumer implements Consumer<Boolean> {
         @Override
-        public void onSensorChanged(SensorEvent event) {
-            if (DEBUG) {
-                Slog.d(TAG, "LowLatencyOffBodyListener detects onSensorChanged event, values are: "
-                        + Arrays.toString(event.values));
-            }
-            if (event.values == null || event.values.length == 0) {
-                // The event returned should contain a single value to indicate off-body state.
-                // No value indicates something went wrong. Take no action and log an error.
-                Slog.e(TAG,
-                        "LowLatencyOffBodyListener detects onSensorChanged event but no event "
-                                + "value returns.");
-                return;
-            }
+        public void accept(Boolean enabled) {
+            Slog.d(TAG, "Mode manager quick doze request: " + enabled);
             synchronized (DeviceIdleController.this) {
-                final boolean isOffBody = (event.values[0] == 0);
-                if (!mForceBodyState && mIsOffBody != isOffBody) {
-                    // Only consider the sensor value change when mForceBodyState is false, which
-                    // is used to enforce the mIsOffBody to be set by the adb shell command.
-                    mIsOffBody = isOffBody;
-                    onOffBodyChangedLocked();
+                if (!mForceModeManagerQuickDozeRequest
+                        && mModeManagerRequestedQuickDoze != enabled) {
+                    mModeManagerRequestedQuickDoze = enabled;
+                    onModeManagerRequestChangedLocked();
                 }
             }
         }
 
         @GuardedBy("DeviceIdleController.this")
-        public void onOffBodyChangedLocked() {
-            // Get into quick doze faster when the device is off body instead of taking
+        public void onModeManagerRequestChangedLocked() {
+            // Get into quick doze faster when mode manager requests instead of taking
             // traditional multi-stage approach.
             updateQuickDozeFlagLocked();
-            if (!mIsOffBody && !mBatterySaverEnabled) {
-                mActiveReason = ACTIVE_REASON_ONBODY;
-                becomeActiveLocked("onbody", Process.myUid());
+            if (!mModeManagerRequestedQuickDoze && !mBatterySaverEnabled) {
+                mActiveReason = ACTIVE_REASON_MODE_MANAGER;
+                becomeActiveLocked("mode_manager", Process.myUid());
             }
         }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {}
-
-        public void registerLocked() {
-            mOffBodySensor =
-                    mSensorManager.getDefaultSensor(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT, true);
-            if (mOffBodySensor == null) {
-                Slog.w(TAG, "Body sensor is NULL, unable to register mOffBodySensor.");
-                return;
-            }
-            mSensorManager.registerListener(this, mOffBodySensor,
-                    SensorManager.SENSOR_DELAY_NORMAL);
-        }
     }
 
     @VisibleForTesting
-    final LowLatencyOffBodyListener mLowLatencyOffBodyListener = new LowLatencyOffBodyListener();
+    final ModeManagerQuickDozeRequestConsumer mModeManagerQuickDozeRequestConsumer =
+            new ModeManagerQuickDozeRequestConsumer();
 
     @VisibleForTesting
     final class MotionListener extends TriggerEventListener
@@ -1052,7 +1024,7 @@
          */
         private static final String KEY_WAIT_FOR_UNLOCK = "wait_for_unlock";
         private static final String KEY_USE_WINDOW_ALARMS = "use_window_alarms";
-        private static final String KEY_USE_BODY_SENSOR = "use_body_sensor";
+        private static final String KEY_USE_MODE_MANAGER = "use_mode_manager";
 
         private long mDefaultFlexTimeShort =
                 !COMPRESS_TIME ? 60 * 1000L : 5 * 1000L;
@@ -1112,7 +1084,7 @@
         private long mDefaultNotificationAllowlistDurationMs = 30 * 1000L;
         private boolean mDefaultWaitForUnlock = true;
         private boolean mDefaultUseWindowAlarms = true;
-        private boolean mDefaultUseBodySensor = false;
+        private boolean mDefaultUseModeManager = false;
 
         /**
          * A somewhat short alarm window size that we will tolerate for various alarm timings.
@@ -1356,7 +1328,7 @@
         /**
          * Whether to use an on/off body signal to affect state transition policy.
          */
-        public boolean USE_BODY_SENSOR = mDefaultUseBodySensor;
+        public boolean USE_MODE_MANAGER = mDefaultUseModeManager;
 
         private final boolean mSmallBatteryDevice;
 
@@ -1464,8 +1436,8 @@
                     com.android.internal.R.bool.device_idle_wait_for_unlock);
             mDefaultUseWindowAlarms = res.getBoolean(
                     com.android.internal.R.bool.device_idle_use_window_alarms);
-            mDefaultUseBodySensor = res.getBoolean(
-                    com.android.internal.R.bool.device_idle_use_body_sensor);
+            mDefaultUseModeManager = res.getBoolean(
+                    com.android.internal.R.bool.device_idle_use_mode_manager);
 
             FLEX_TIME_SHORT = mDefaultFlexTimeShort;
             LIGHT_IDLE_AFTER_INACTIVE_TIMEOUT = mDefaultLightIdleAfterInactiveTimeout;
@@ -1499,7 +1471,7 @@
             NOTIFICATION_ALLOWLIST_DURATION_MS = mDefaultNotificationAllowlistDurationMs;
             WAIT_FOR_UNLOCK = mDefaultWaitForUnlock;
             USE_WINDOW_ALARMS = mDefaultUseWindowAlarms;
-            USE_BODY_SENSOR = mDefaultUseBodySensor;
+            USE_MODE_MANAGER = mDefaultUseModeManager;
         }
 
         private long getTimeout(long defTimeout, long compTimeout) {
@@ -1661,9 +1633,9 @@
                             USE_WINDOW_ALARMS = properties.getBoolean(
                                     KEY_USE_WINDOW_ALARMS, mDefaultUseWindowAlarms);
                             break;
-                        case KEY_USE_BODY_SENSOR:
-                            USE_BODY_SENSOR = properties.getBoolean(
-                                    KEY_USE_BODY_SENSOR, mDefaultUseBodySensor);
+                        case KEY_USE_MODE_MANAGER:
+                            USE_MODE_MANAGER = properties.getBoolean(
+                                    KEY_USE_MODE_MANAGER, mDefaultUseModeManager);
                             break;
                         default:
                             Slog.e(TAG, "Unknown configuration key: " + name);
@@ -1802,8 +1774,8 @@
             pw.print("    "); pw.print(KEY_USE_WINDOW_ALARMS); pw.print("=");
             pw.println(USE_WINDOW_ALARMS);
 
-            pw.print("    "); pw.print(KEY_USE_BODY_SENSOR); pw.print("=");
-            pw.println(USE_BODY_SENSOR);
+            pw.print("    "); pw.print(KEY_USE_MODE_MANAGER); pw.print("=");
+            pw.println(USE_MODE_MANAGER);
         }
     }
 
@@ -2668,8 +2640,15 @@
                         mPowerSaveWhitelistAllAppIdArray, mPowerSaveWhitelistExceptIdleAppIdArray);
                 mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
 
-                if (mConstants.USE_BODY_SENSOR) {
-                    mLowLatencyOffBodyListener.registerLocked();
+                if (mConstants.USE_MODE_MANAGER) {
+                    WearModeManagerInternal modeManagerInternal = LocalServices.getService(
+                            WearModeManagerInternal.class);
+                    if (modeManagerInternal != null) {
+                        modeManagerInternal.addActiveStateChangeListener(
+                                WearModeManagerInternal.QUICK_DOZE_REQUEST_IDENTIFIER,
+                                AppSchedulingModuleThread.getExecutor(),
+                                mModeManagerQuickDozeRequestConsumer);
+                    }
                 }
                 mLocalPowerManager.registerLowPowerModeObserver(ServiceType.QUICK_DOZE,
                         state -> {
@@ -3374,9 +3353,10 @@
     /** Calls to {@link #updateQuickDozeFlagLocked(boolean)} by considering appropriate signals. */
     @GuardedBy("this")
     private void updateQuickDozeFlagLocked() {
-        if (mConstants.USE_BODY_SENSOR) {
-            // Only disable the quick doze flag when the device is on body and battery saver is off.
-            updateQuickDozeFlagLocked(mIsOffBody || mBatterySaverEnabled);
+        if (mConstants.USE_MODE_MANAGER) {
+            // Only disable the quick doze flag when mode manager request is false and
+            // battery saver is off.
+            updateQuickDozeFlagLocked(mModeManagerRequestedQuickDoze || mBatterySaverEnabled);
         } else {
             updateQuickDozeFlagLocked(mBatterySaverEnabled);
         }
@@ -4482,7 +4462,7 @@
         pw.println("  unforce");
         pw.println(
                 "    Resume normal functioning after force-idle or force-inactive or "
-                        + "force-offbody or force-onbody.");
+                        + "force-modemanager-quickdoze.");
         pw.println("  get [light|deep|force|screen|charging|network|offbody|forcebodystate]");
         pw.println("    Retrieve the current given state.");
         pw.println("  disable [light|deep|all]");
@@ -4517,14 +4497,9 @@
                 + "and any [-d] is ignored");
         pw.println("  motion");
         pw.println("    Simulate a motion event to bring the device out of deep doze");
-        pw.println("  force-offbody");
-        pw.println(
-                "    Simulate a low latency body sensor detecting a device is offbody. "
-                        + "mForceBodyState will be set to true to ignore body sensor reading.");
-        pw.println("  force-onbody");
-        pw.println(
-                "    Simulate a low latency body sensor detecting a device is onbody. "
-                        + "mForceBodyState will be set to true to ignore body sensor reading.");
+        pw.println("  force-modemanager-quickdoze [true|false]");
+        pw.println("    Simulate mode manager request to enable (true) or disable (false) "
+                + "quick doze. Mode manager changes will be ignored until unforce is called.");
     }
 
     class Shell extends ShellCommand {
@@ -4656,8 +4631,9 @@
                     pw.print(lightStateToString(mLightState));
                     pw.print(", deep state: ");
                     pw.println(stateToString(mState));
-                    mForceBodyState = false;
-                    pw.println("mForceBodyState: " + mForceBodyState);
+                    mForceModeManagerQuickDozeRequest = false;
+                    pw.println("mForceModeManagerQuickDozeRequest: "
+                            + mForceModeManagerQuickDozeRequest);
                 } finally {
                     Binder.restoreCallingIdentity(token);
                 }
@@ -4678,8 +4654,12 @@
                             case "screen": pw.println(mScreenOn); break;
                             case "charging": pw.println(mCharging); break;
                             case "network": pw.println(mNetworkConnected); break;
-                            case "offbody": pw.println(mIsOffBody); break;
-                            case "forcebodystate": pw.println(mForceBodyState); break;
+                            case "modemanagerquick":
+                                pw.println(mModeManagerRequestedQuickDoze);
+                                break;
+                            case "forcemodemanagerquick":
+                                pw.println(mForceModeManagerQuickDozeRequest);
+                                break;
                             default: pw.println("Unknown get option: " + arg); break;
                         }
                     } finally {
@@ -4976,35 +4956,31 @@
                     Binder.restoreCallingIdentity(token);
                 }
             }
-        } else if ("force-offbody".equals(cmd)) {
+        } else if ("force-modemanager-quickdoze".equals(cmd)) {
             getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
                     null);
-            synchronized (DeviceIdleController.this) {
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    mForceBodyState = true;
-                    pw.println("mForceBodyState: " + mForceBodyState);
-                    mIsOffBody = true;
-                    pw.println("mIsOffBody: " + mIsOffBody);
-                    mLowLatencyOffBodyListener.onOffBodyChangedLocked();
-                } finally {
-                    Binder.restoreCallingIdentity(token);
+            String arg = shell.getNextArg();
+
+            if ("true".equalsIgnoreCase(arg) || "false".equalsIgnoreCase(arg)) {
+                boolean enabled = Boolean.parseBoolean(arg);
+
+                synchronized (DeviceIdleController.this) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        mForceModeManagerQuickDozeRequest = true;
+                        pw.println("mForceModeManagerQuickDozeRequest: "
+                                + mForceModeManagerQuickDozeRequest);
+                        mModeManagerRequestedQuickDoze = enabled;
+                        pw.println("mModeManagerRequestedQuickDoze: "
+                                + mModeManagerRequestedQuickDoze);
+                        mModeManagerQuickDozeRequestConsumer.onModeManagerRequestChangedLocked();
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
                 }
-            }
-        } else if ("force-onbody".equals(cmd)) {
-            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
-                    null);
-            synchronized (DeviceIdleController.this) {
-                final long token = Binder.clearCallingIdentity();
-                try {
-                    mForceBodyState = true;
-                    pw.println("mForceBodyState: " + mForceBodyState);
-                    mIsOffBody = false;
-                    pw.println("mIsOffBody: " + mIsOffBody);
-                    mLowLatencyOffBodyListener.onOffBodyChangedLocked();
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                }
+            } else {
+                pw.println("Provide true or false argument after force-modemanager-quickdoze");
+                return -1;
             }
         } else {
             return shell.handleDefaultCommands(cmd);
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
index 6a4a52a..9ec799f 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -4,7 +4,6 @@
       "name": "CtsUsageStatsTestCases",
       "options": [
         {"include-filter": "android.app.usage.cts.UsageStatsTest"},
-        {"include-filter": "android.app.usage.cts.BroadcastResponseStatsTest"},
         {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.MediumTest"},
@@ -12,6 +11,13 @@
       ]
     },
     {
+      "name": "CtsBRSTestCases",
+      "options": [
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    },
+    {
       "name": "FrameworksServicesTests",
       "options": [
         {"include-filter": "com.android.server.usage"},
diff --git a/api/api.go b/api/api.go
index c568a45..a003aba 100644
--- a/api/api.go
+++ b/api/api.go
@@ -110,6 +110,7 @@
 	Api_surface         *string
 	Api_contributions   []string
 	Defaults_visibility []string
+	Previous_api        *string
 }
 
 type Bazel_module struct {
@@ -359,6 +360,7 @@
 		props.Api_contributions = transformArray(
 			modules, "", fmt.Sprintf(".stubs.source%s.api.contribution", apiSuffix))
 		props.Defaults_visibility = []string{"//visibility:public"}
+		props.Previous_api = proptools.StringPtr(":android.api.public.latest")
 		ctx.CreateModule(java.DefaultsFactory, &props)
 	}
 }
diff --git a/cmds/idmap2/idmap2d/Idmap2Service.cpp b/cmds/idmap2/idmap2d/Idmap2Service.cpp
index b94b3b4..d76ca5b 100644
--- a/cmds/idmap2/idmap2d/Idmap2Service.cpp
+++ b/cmds/idmap2/idmap2d/Idmap2Service.cpp
@@ -265,7 +265,8 @@
             res.configuration.value_or(std::string()));
     } else if (res.binaryData.has_value()) {
       builder.SetResourceValue(res.resourceName, res.binaryData->get(),
-            res.configuration.value_or(std::string()));
+                               res.binaryDataOffset, res.binaryDataSize,
+                               res.configuration.value_or(std::string()));
     } else {
       builder.SetResourceValue(res.resourceName, res.dataType, res.data,
             res.configuration.value_or(std::string()));
diff --git a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
index 3ad6d58..8ebd454 100644
--- a/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
+++ b/cmds/idmap2/idmap2d/aidl/core/android/os/FabricatedOverlayInternalEntry.aidl
@@ -26,4 +26,6 @@
     @nullable @utf8InCpp String stringData;
     @nullable ParcelFileDescriptor binaryData;
     @nullable @utf8InCpp String configuration;
+    long binaryDataOffset;
+    long binaryDataSize;
 }
\ No newline at end of file
diff --git a/cmds/idmap2/include/idmap2/FabricatedOverlay.h b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
index a29fa8f..1e7d4c2 100644
--- a/cmds/idmap2/include/idmap2/FabricatedOverlay.h
+++ b/cmds/idmap2/include/idmap2/FabricatedOverlay.h
@@ -49,6 +49,8 @@
 
     Builder& SetResourceValue(const std::string& resource_name,
                               std::optional<android::base::borrowed_fd>&& binary_value,
+                              off64_t data_binary_offset,
+                              size_t data_binary_size,
                               const std::string& configuration);
 
     inline Builder& setFrroPath(std::string frro_path) {
@@ -65,6 +67,8 @@
       DataValue data_value;
       std::string data_string_value;
       std::optional<android::base::borrowed_fd> data_binary_value;
+      off64_t data_binary_offset;
+      size_t data_binary_size;
       std::string configuration;
     };
 
@@ -76,6 +80,12 @@
     std::vector<Entry> entries_;
   };
 
+  struct BinaryData {
+    android::base::borrowed_fd file_descriptor;
+    off64_t offset;
+    size_t size;
+  };
+
   Result<Unit> ToBinaryStream(std::ostream& stream) const;
   static Result<FabricatedOverlay> FromBinaryStream(std::istream& stream);
 
@@ -92,13 +102,13 @@
 
   explicit FabricatedOverlay(pb::FabricatedOverlay&& overlay,
                              std::string&& string_pool_data_,
-                             std::vector<android::base::borrowed_fd> binary_files_,
+                             std::vector<FabricatedOverlay::BinaryData> binary_files_,
                              off_t total_binary_bytes_,
                              std::optional<uint32_t> crc_from_disk = {});
 
   pb::FabricatedOverlay overlay_pb_;
   std::string string_pool_data_;
-  std::vector<android::base::borrowed_fd> binary_files_;
+  std::vector<FabricatedOverlay::BinaryData> binary_files_;
   uint32_t total_binary_bytes_;
   std::optional<uint32_t> crc_from_disk_;
   mutable std::optional<SerializedData> data_;
diff --git a/cmds/idmap2/include/idmap2/ResourceUtils.h b/cmds/idmap2/include/idmap2/ResourceUtils.h
index c2b0abe..d4490ef4 100644
--- a/cmds/idmap2/include/idmap2/ResourceUtils.h
+++ b/cmds/idmap2/include/idmap2/ResourceUtils.h
@@ -43,6 +43,8 @@
   DataValue data_value;
   std::string data_string_value;
   std::optional<android::base::borrowed_fd> data_binary_value;
+  off64_t data_binary_offset;
+  size_t data_binary_size;
 };
 
 struct TargetValueWithConfig {
diff --git a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
index dd5be21c..47daf23 100644
--- a/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
+++ b/cmds/idmap2/libidmap2/FabricatedOverlay.cpp
@@ -55,7 +55,7 @@
 
 FabricatedOverlay::FabricatedOverlay(pb::FabricatedOverlay&& overlay,
                                      std::string&& string_pool_data,
-                                     std::vector<android::base::borrowed_fd> binary_files,
+                                     std::vector<FabricatedOverlay::BinaryData> binary_files,
                                      off_t total_binary_bytes,
                                      std::optional<uint32_t> crc_from_disk)
     : overlay_pb_(std::forward<pb::FabricatedOverlay>(overlay)),
@@ -81,7 +81,7 @@
     const std::string& resource_name, uint8_t data_type, uint32_t data_value,
     const std::string& configuration) {
   entries_.emplace_back(
-      Entry{resource_name, data_type, data_value, "", std::nullopt, configuration});
+      Entry{resource_name, data_type, data_value, "", std::nullopt, 0, 0, configuration});
   return *this;
 }
 
@@ -89,14 +89,15 @@
     const std::string& resource_name, uint8_t data_type, const std::string& data_string_value,
     const std::string& configuration) {
   entries_.emplace_back(
-      Entry{resource_name, data_type, 0, data_string_value, std::nullopt, configuration});
+      Entry{resource_name, data_type, 0, data_string_value, std::nullopt, 0, 0, configuration});
   return *this;
 }
 
 FabricatedOverlay::Builder& FabricatedOverlay::Builder::SetResourceValue(
     const std::string& resource_name, std::optional<android::base::borrowed_fd>&& binary_value,
-    const std::string& configuration) {
-  entries_.emplace_back(Entry{resource_name, 0, 0, "", binary_value, configuration});
+    off64_t data_binary_offset, size_t data_binary_size, const std::string& configuration) {
+  entries_.emplace_back(Entry{resource_name, 0, 0, "", binary_value,
+                              data_binary_offset, data_binary_size, configuration});
   return *this;
 }
 
@@ -148,7 +149,8 @@
     }
 
     value->second = TargetValue{res_entry.data_type, res_entry.data_value,
-        res_entry.data_string_value, res_entry.data_binary_value};
+                                res_entry.data_string_value, res_entry.data_binary_value,
+                                res_entry.data_binary_offset, res_entry.data_binary_size};
   }
 
   pb::FabricatedOverlay overlay_pb;
@@ -157,7 +159,7 @@
   overlay_pb.set_target_package_name(target_package_name_);
   overlay_pb.set_target_overlayable(target_overlayable_);
 
-  std::vector<android::base::borrowed_fd> binary_files;
+  std::vector<FabricatedOverlay::BinaryData> binary_files;
   size_t total_binary_bytes = 0;
   // 16 for the number of bytes in the frro file before the binary data
   const size_t FRRO_HEADER_SIZE = 16;
@@ -182,16 +184,15 @@
             pb_value->set_data_value(ref.index());
           } else if (value.second.data_binary_value.has_value()) {
               pb_value->set_data_type(Res_value::TYPE_STRING);
-              struct stat s;
-              if (fstat(value.second.data_binary_value->get(), &s) == -1) {
-                return Error("unable to get size of binary file: %d", errno);
-              }
               std::string uri
                   = StringPrintf("frro:/%s?offset=%d&size=%d", frro_path_.c_str(),
                                  static_cast<int> (FRRO_HEADER_SIZE + total_binary_bytes),
-                                 static_cast<int> (s.st_size));
-              total_binary_bytes += s.st_size;
-              binary_files.emplace_back(value.second.data_binary_value->get());
+                                 static_cast<int> (value.second.data_binary_size));
+              total_binary_bytes += value.second.data_binary_size;
+              binary_files.emplace_back(FabricatedOverlay::BinaryData{
+                  value.second.data_binary_value->get(),
+                  value.second.data_binary_offset,
+                  value.second.data_binary_size});
               auto ref = string_pool.MakeRef(std::move(uri));
               pb_value->set_data_value(ref.index());
           } else {
@@ -310,8 +311,9 @@
   Write32(stream, (*data)->pb_crc);
   Write32(stream, total_binary_bytes_);
   std::string file_contents;
-  for (const android::base::borrowed_fd fd : binary_files_) {
-    if (!ReadFdToString(fd, &file_contents)) {
+  for (const FabricatedOverlay::BinaryData fd : binary_files_) {
+    file_contents.resize(fd.size);
+    if (!ReadFullyAtOffset(fd.file_descriptor, file_contents.data(), fd.size, fd.offset)) {
       return Error("Failed to read binary file data.");
     }
     stream.write(file_contents.data(), file_contents.length());
diff --git a/cmds/idmap2/self_targeting/SelfTargeting.cpp b/cmds/idmap2/self_targeting/SelfTargeting.cpp
index a8aa033..c7f5cf3 100644
--- a/cmds/idmap2/self_targeting/SelfTargeting.cpp
+++ b/cmds/idmap2/self_targeting/SelfTargeting.cpp
@@ -52,6 +52,7 @@
         const auto dataType = entry_params.data_type;
         if (entry_params.data_binary_value.has_value()) {
             builder.SetResourceValue(entry_params.resource_name, *entry_params.data_binary_value,
+                                     entry_params.binary_data_offset, entry_params.binary_data_size,
                                      entry_params.configuration);
         } else  if (dataType >= Res_value::TYPE_FIRST_INT && dataType <= Res_value::TYPE_LAST_INT) {
            builder.SetResourceValue(entry_params.resource_name, dataType,
diff --git a/cmds/idmap2/tests/FabricatedOverlayTests.cpp b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
index e13a0eb..b460bb3 100644
--- a/cmds/idmap2/tests/FabricatedOverlayTests.cpp
+++ b/cmds/idmap2/tests/FabricatedOverlayTests.cpp
@@ -59,7 +59,7 @@
               Res_value::TYPE_STRING,
               "foobar",
               "en-rUS-normal-xxhdpi-v21")
-          .SetResourceValue("com.example.target:drawable/dr1", fd, "port-xxhdpi-v7")
+          .SetResourceValue("com.example.target:drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7")
           .setFrroPath("/foo/bar/biz.frro")
           .Build();
   ASSERT_TRUE(overlay);
diff --git a/cmds/idmap2/tests/IdmapTests.cpp b/cmds/idmap2/tests/IdmapTests.cpp
index f6e48ba..a3448fd 100644
--- a/cmds/idmap2/tests/IdmapTests.cpp
+++ b/cmds/idmap2/tests/IdmapTests.cpp
@@ -269,7 +269,7 @@
                   .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "land-xxhdpi-v7")
                   .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "land")
                   .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "xxhdpi-v7")
-                  .SetResourceValue("drawable/dr1", fd, "port-xxhdpi-v7")
+                  .SetResourceValue("drawable/dr1", fd, 0, 8341, "port-xxhdpi-v7")
                   .setFrroPath("/foo/bar/biz.frro")
                   .Build();
 
diff --git a/cmds/idmap2/tests/ResourceMappingTests.cpp b/cmds/idmap2/tests/ResourceMappingTests.cpp
index 380e462..40f98c2 100644
--- a/cmds/idmap2/tests/ResourceMappingTests.cpp
+++ b/cmds/idmap2/tests/ResourceMappingTests.cpp
@@ -212,7 +212,7 @@
                   .SetResourceValue("integer/int1", Res_value::TYPE_INT_DEC, 2U, "")
                   .SetResourceValue("string/str1", Res_value::TYPE_REFERENCE, 0x7f010000, "")
                   .SetResourceValue("string/str2", Res_value::TYPE_STRING, "foobar", "")
-                  .SetResourceValue("drawable/dr1", fd, "")
+                  .SetResourceValue("drawable/dr1", fd, 0, 8341, "")
                   .setFrroPath("/foo/bar/biz.frro")
                   .Build();
 
diff --git a/cmds/uinput/README.md b/cmds/uinput/README.md
index bdec8b9..82df555 100644
--- a/cmds/uinput/README.md
+++ b/cmds/uinput/README.md
@@ -128,7 +128,9 @@
 that time will be dropped. If you are controlling `uinput` by sending commands through standard
 input from an app, you need to wait for [`onInputDeviceAdded`][onInputDeviceAdded] to be called on
 an `InputDeviceListener` before issuing commands to the device. If you are passing a file to
-`uinput`, add a `delay` after the `register` command to let registration complete.
+`uinput`, add a `delay` after the `register` command to let registration complete. You can add a
+`sync` in certain positions, like at the end of the file to get a response when all commands have
+finished processing.
 
 [onInputDeviceAdded]: https://developer.android.com/reference/android/hardware/input/InputManager.InputDeviceListener.html
 
@@ -187,6 +189,38 @@
 }
 ```
 
+### `sync`
+
+A command used to get a response once the command is processed. When several `inject` and `delay`
+commands are used in a row, the `sync` command can be used to track the progress of the command
+queue.
+
+|    Field    |  Type   | Description                                  |
+|:-----------:|:-------:|:---------------------------------------------|
+|    `id`     | integer | Device ID                                    |
+|  `command`  | string  | Must be set to "sync"                        |
+| `syncToken` | string  | The token used to identify this sync command |
+
+Example:
+
+```json5
+{
+  "id": 1,
+  "command": "syncToken",
+  "syncToken": "finished_injecting_events"
+}
+```
+
+This command will result in the following response when it is processed:
+
+```json5
+{
+  "id": 1,
+  "result": "sync",
+  "syncToken": "finished_injecting_events"
+}
+```
+
 ## Notes
 
 The `getevent` utility can used to print out the key events for debugging purposes.
diff --git a/cmds/uinput/src/com/android/commands/uinput/Device.java b/cmds/uinput/src/com/android/commands/uinput/Device.java
index 6458eef..ad5e70f 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Device.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Device.java
@@ -45,6 +45,7 @@
     private static final int MSG_OPEN_UINPUT_DEVICE = 1;
     private static final int MSG_CLOSE_UINPUT_DEVICE = 2;
     private static final int MSG_INJECT_EVENT = 3;
+    private static final int MSG_SYNC_EVENT = 4;
 
     private final int mId;
     private final HandlerThread mThread;
@@ -122,6 +123,16 @@
     }
 
     /**
+     * Synchronize the uinput command queue by writing a sync response with the provided syncToken
+     * to the output stream when this event is processed.
+     *
+     * @param syncToken  The token for this sync command.
+     */
+    public void syncEvent(String syncToken) {
+        mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_SYNC_EVENT, syncToken), mTimeToSend);
+    }
+
+    /**
      * Close an uinput device.
      *
      */
@@ -174,6 +185,9 @@
                         mCond.notify();
                     }
                     break;
+                case MSG_SYNC_EVENT:
+                    handleSyncEvent((String) msg.obj);
+                    break;
                 default:
                     throw new IllegalArgumentException("Unknown device message");
             }
@@ -187,6 +201,18 @@
             getLooper().myQueue().removeSyncBarrier(mBarrierToken);
             mBarrierToken = 0;
         }
+
+        private void handleSyncEvent(String syncToken) {
+            final JSONObject json = new JSONObject();
+            try {
+                json.put("reason", "sync");
+                json.put("id", mId);
+                json.put("syncToken", syncToken);
+            } catch (JSONException e) {
+                throw new RuntimeException("Could not create JSON object ", e);
+            }
+            writeOutputObject(json);
+        }
     }
 
     private class DeviceCallback {
@@ -214,7 +240,7 @@
         }
 
         public void onDeviceVibrating(int value) {
-            JSONObject json = new JSONObject();
+            final JSONObject json = new JSONObject();
             try {
                 json.put("reason", "vibrating");
                 json.put("id", mId);
@@ -222,12 +248,7 @@
             } catch (JSONException e) {
                 throw new RuntimeException("Could not create JSON object ", e);
             }
-            try {
-                mOutputStream.write(json.toString().getBytes());
-                mOutputStream.flush();
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
+            writeOutputObject(json);
         }
 
         public void onDeviceError() {
@@ -238,6 +259,15 @@
         }
     }
 
+    private void writeOutputObject(JSONObject json) {
+        try {
+            mOutputStream.write(json.toString().getBytes());
+            mOutputStream.flush();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     static int getEvdevEventTypeByLabel(String label) {
         final var type = nativeGetEvdevEventTypeByLabel(label);
         if (type < 0) {
diff --git a/cmds/uinput/src/com/android/commands/uinput/Event.java b/cmds/uinput/src/com/android/commands/uinput/Event.java
index cddb407..9d8f1f4 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Event.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Event.java
@@ -39,9 +39,19 @@
 public class Event {
     private static final String TAG = "UinputEvent";
 
-    public static final String COMMAND_REGISTER = "register";
-    public static final String COMMAND_DELAY = "delay";
-    public static final String COMMAND_INJECT = "inject";
+    enum Command {
+        REGISTER("register"),
+        DELAY("delay"),
+        INJECT("inject"),
+        SYNC("sync");
+
+        final String mCommandName;
+
+        Command(String command) {
+            mCommandName = command;
+        }
+    }
+
     private static final int EV_KEY = 0x01;
     private static final int EV_REL = 0x02;
     private static final int EV_ABS = 0x03;
@@ -87,7 +97,7 @@
     }
 
     private int mId;
-    private String mCommand;
+    private Command mCommand;
     private String mName;
     private int mVid;
     private int mPid;
@@ -98,12 +108,13 @@
     private int mFfEffectsMax = 0;
     private String mInputport;
     private SparseArray<InputAbsInfo> mAbsInfo;
+    private String mSyncToken;
 
     public int getId() {
         return mId;
     }
 
-    public String getCommand() {
+    public Command getCommand() {
         return mCommand;
     }
 
@@ -147,6 +158,10 @@
         return mInputport;
     }
 
+    public String getSyncToken() {
+        return mSyncToken;
+    }
+
     /**
      * Convert an event to String.
      */
@@ -177,7 +192,14 @@
         }
 
         private void setCommand(String command) {
-            mEvent.mCommand = command;
+            Objects.requireNonNull(command, "Command must not be null");
+            for (Command cmd : Command.values()) {
+                if (cmd.mCommandName.equals(command)) {
+                    mEvent.mCommand = cmd;
+                    return;
+                }
+            }
+            throw new IllegalStateException("Unrecognized command: " + command);
         }
 
         public void setName(String name) {
@@ -220,27 +242,38 @@
             mEvent.mInputport = port;
         }
 
+        public void setSyncToken(String syncToken) {
+            mEvent.mSyncToken = Objects.requireNonNull(syncToken, "Sync token must not be null");
+        }
+
         public Event build() {
             if (mEvent.mId == -1) {
                 throw new IllegalStateException("No event id");
             } else if (mEvent.mCommand == null) {
                 throw new IllegalStateException("Event does not contain a command");
             }
-            if (COMMAND_REGISTER.equals(mEvent.mCommand)) {
-                if (mEvent.mConfiguration == null) {
-                    throw new IllegalStateException(
-                            "Device registration is missing configuration");
+            switch (mEvent.mCommand) {
+                case REGISTER -> {
+                    if (mEvent.mConfiguration == null) {
+                        throw new IllegalStateException(
+                                "Device registration is missing configuration");
+                    }
                 }
-            } else if (COMMAND_DELAY.equals(mEvent.mCommand)) {
-                if (mEvent.mDuration <= 0) {
-                    throw new IllegalStateException("Delay has missing or invalid duration");
+                case DELAY -> {
+                    if (mEvent.mDuration <= 0) {
+                        throw new IllegalStateException("Delay has missing or invalid duration");
+                    }
                 }
-            } else if (COMMAND_INJECT.equals(mEvent.mCommand)) {
-                if (mEvent.mInjections  == null) {
-                    throw new IllegalStateException("Inject command is missing injection data");
+                case INJECT -> {
+                    if (mEvent.mInjections == null) {
+                        throw new IllegalStateException("Inject command is missing injection data");
+                    }
                 }
-            } else {
-                throw new IllegalStateException("Unknown command " + mEvent.mCommand);
+                case SYNC -> {
+                    if (mEvent.mSyncToken == null) {
+                        throw new IllegalStateException("Sync command is missing sync token");
+                    }
+                }
             }
             return mEvent;
         }
@@ -307,6 +340,9 @@
                             case "port":
                                 eb.setInputport(mReader.nextString());
                                 break;
+                            case "syncToken":
+                                eb.setSyncToken(mReader.nextString());
+                                break;
                             default:
                                 mReader.skipValue();
                         }
diff --git a/cmds/uinput/src/com/android/commands/uinput/Uinput.java b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
index 740578e..47b7a354 100644
--- a/cmds/uinput/src/com/android/commands/uinput/Uinput.java
+++ b/cmds/uinput/src/com/android/commands/uinput/Uinput.java
@@ -25,6 +25,7 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.UnsupportedEncodingException;
+import java.util.Objects;
 
 /**
  * Uinput class encapsulates execution of "uinput" command. It parses the provided input stream
@@ -96,28 +97,27 @@
 
     private void process(Event e) {
         final int index = mDevices.indexOfKey(e.getId());
-        if (index >= 0) {
-            Device d = mDevices.valueAt(index);
-            if (Event.COMMAND_DELAY.equals(e.getCommand())) {
-                d.addDelay(e.getDuration());
-            } else if (Event.COMMAND_INJECT.equals(e.getCommand())) {
-                d.injectEvent(e.getInjections());
-            } else {
-                if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
-                    error("Device id=" + e.getId() + " is already registered. Ignoring event.");
-                } else {
-                    error("Unknown command \"" + e.getCommand() + "\". Ignoring event.");
-                }
+        if (index < 0) {
+            if (e.getCommand() != Event.Command.REGISTER) {
+                Log.e(TAG, "Unknown device id specified. Ignoring event.");
+                return;
             }
-        } else if (Event.COMMAND_REGISTER.equals(e.getCommand())) {
             registerDevice(e);
-        } else {
-            Log.e(TAG, "Unknown device id specified. Ignoring event.");
+            return;
+        }
+
+        final Device d = mDevices.valueAt(index);
+        switch (Objects.requireNonNull(e.getCommand())) {
+            case REGISTER ->
+                    error("Device id=" + e.getId() + " is already registered. Ignoring event.");
+            case INJECT -> d.injectEvent(e.getInjections());
+            case DELAY -> d.addDelay(e.getDuration());
+            case SYNC -> d.syncEvent(e.getSyncToken());
         }
     }
 
     private void registerDevice(Event e) {
-        if (!Event.COMMAND_REGISTER.equals(e.getCommand())) {
+        if (!Event.Command.REGISTER.equals(e.getCommand())) {
             throw new IllegalStateException(
                     "Tried to send command \"" + e.getCommand() + "\" to an unregistered device!");
         }
diff --git a/core/api/current.txt b/core/api/current.txt
index 3392d25..363e9d4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9410,8 +9410,8 @@
     method @NonNull public java.util.List<android.appwidget.AppWidgetProviderInfo> getInstalledProvidersForProfile(@Nullable android.os.UserHandle);
     method public static android.appwidget.AppWidgetManager getInstance(android.content.Context);
     method public boolean isRequestPinAppWidgetSupported();
-    method public void notifyAppWidgetViewDataChanged(int[], int);
-    method public void notifyAppWidgetViewDataChanged(int, int);
+    method @Deprecated public void notifyAppWidgetViewDataChanged(int[], int);
+    method @Deprecated public void notifyAppWidgetViewDataChanged(int, int);
     method public void partiallyUpdateAppWidget(int[], android.widget.RemoteViews);
     method public void partiallyUpdateAppWidget(int, android.widget.RemoteViews);
     method public boolean requestPinAppWidget(@NonNull android.content.ComponentName, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
@@ -9530,6 +9530,7 @@
     method @Nullable public CharSequence getDisplayName();
     method public int getId();
     method public int getSystemDataSyncFlags();
+    method @Nullable public String getTag();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
   }
@@ -9599,6 +9600,7 @@
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void attachSystemDataTransport(int, @NonNull java.io.InputStream, @NonNull java.io.OutputStream) throws android.companion.DeviceNotAssociatedException;
     method @Nullable public android.content.IntentSender buildAssociationCancellationIntent();
     method @Nullable public android.content.IntentSender buildPermissionTransferUserConsentIntent(int) throws android.companion.DeviceNotAssociatedException;
+    method public void clearAssociationTag(int);
     method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException;
     method public void disableSystemDataSyncForTypes(int, int);
     method @Deprecated public void disassociate(@NonNull String);
@@ -9608,6 +9610,7 @@
     method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
     method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
     method public void requestNotificationAccess(android.content.ComponentName);
+    method public void setAssociationTag(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
     method public void startSystemDataTransfer(int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,android.companion.CompanionException>) throws android.companion.DeviceNotAssociatedException;
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
@@ -11699,6 +11702,7 @@
     method @NonNull public void setResourceValue(@NonNull String, @IntRange(from=android.util.TypedValue.TYPE_FIRST_INT, to=android.util.TypedValue.TYPE_LAST_INT) int, int, @Nullable String);
     method @NonNull public void setResourceValue(@NonNull String, int, @NonNull String, @Nullable String);
     method @NonNull public void setResourceValue(@NonNull String, @NonNull android.os.ParcelFileDescriptor, @Nullable String);
+    method @NonNull public void setResourceValue(@NonNull String, @NonNull android.content.res.AssetFileDescriptor, @Nullable String);
     method public void setTargetOverlayable(@Nullable String);
   }
 
@@ -12826,7 +12830,7 @@
     field public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio.access";
     field public static final String FEATURE_TELEPHONY_SUBSCRIPTION = "android.hardware.telephony.subscription";
     field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
-    field public static final String FEATURE_THREADNETWORK = "android.hardware.threadnetwork";
+    field public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
     field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct";
@@ -14347,11 +14351,13 @@
     method public void execSQL(String, Object[]) throws android.database.SQLException;
     method public static String findEditTable(String);
     method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs();
+    method public long getLastChangedRowCount();
     method public long getLastInsertRowId();
     method public long getMaximumSize();
     method public long getPageSize();
     method public String getPath();
     method @Deprecated public java.util.Map<java.lang.String,java.lang.String> getSyncedTables();
+    method public long getTotalChangedRowCount();
     method public int getVersion();
     method public boolean inTransaction();
     method public long insert(String, String, android.content.ContentValues);
@@ -33662,6 +33668,7 @@
   public static class PerformanceHintManager.Session implements java.io.Closeable {
     method public void close();
     method public void reportActualWorkDuration(long);
+    method public void setPreferPowerEfficiency(boolean);
     method public void setThreads(@NonNull int[]);
     method public void updateTargetWorkDuration(long);
   }
@@ -33699,7 +33706,7 @@
     method public boolean isInteractive();
     method public boolean isLowPowerStandbyEnabled();
     method public boolean isPowerSaveMode();
-    method public boolean isRebootingUserspaceSupported();
+    method @Deprecated public boolean isRebootingUserspaceSupported();
     method @Deprecated public boolean isScreenOn();
     method public boolean isSustainedPerformanceModeSupported();
     method public boolean isWakeLockLevelSupported(int);
@@ -44999,6 +45006,7 @@
     field public static final int NR_STATE_RESTRICTED = 1; // 0x1
     field public static final int SERVICE_TYPE_DATA = 2; // 0x2
     field public static final int SERVICE_TYPE_EMERGENCY = 5; // 0x5
+    field public static final int SERVICE_TYPE_MMS = 6; // 0x6
     field public static final int SERVICE_TYPE_SMS = 3; // 0x3
     field public static final int SERVICE_TYPE_UNKNOWN = 0; // 0x0
     field public static final int SERVICE_TYPE_VIDEO = 4; // 0x4
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index cda9ae3..b1feb41 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -15,6 +15,7 @@
 
   @UiContext public class Activity extends android.view.ContextThemeWrapper implements android.content.ComponentCallbacks2 android.view.KeyEvent.Callback android.view.LayoutInflater.Factory2 android.view.View.OnCreateContextMenuListener android.view.Window.Callback {
     method public final boolean addDumpable(@NonNull android.util.Dumpable);
+    method public final boolean isResumed();
   }
 
   public class ActivityManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a02fd84..d480315 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -6276,11 +6276,13 @@
   }
 
   public final class GnssMeasurementRequest implements android.os.Parcelable {
+    method @NonNull public android.os.WorkSource getWorkSource();
     method public boolean isCorrelationVectorOutputsEnabled();
   }
 
   public static final class GnssMeasurementRequest.Builder {
     method @NonNull public android.location.GnssMeasurementRequest.Builder setCorrelationVectorOutputsEnabled(boolean);
+    method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.location.GnssMeasurementRequest.Builder setWorkSource(@Nullable android.os.WorkSource);
   }
 
   public final class GnssReflectingPlane implements android.os.Parcelable {
@@ -10748,7 +10750,7 @@
     field @RequiresPermission(android.Manifest.permission.MANAGE_LOW_POWER_STANDBY) public static final String ACTION_LOW_POWER_STANDBY_PORTS_CHANGED = "android.os.action.LOW_POWER_STANDBY_PORTS_CHANGED";
     field public static final int POWER_SAVE_MODE_TRIGGER_DYNAMIC = 1; // 0x1
     field public static final int POWER_SAVE_MODE_TRIGGER_PERCENTAGE = 0; // 0x0
-    field public static final String REBOOT_USERSPACE = "userspace";
+    field @Deprecated public static final String REBOOT_USERSPACE = "userspace";
     field public static final int SOUND_TRIGGER_MODE_ALL_DISABLED = 2; // 0x2
     field public static final int SOUND_TRIGGER_MODE_ALL_ENABLED = 0; // 0x0
     field public static final int SOUND_TRIGGER_MODE_CRITICAL_ONLY = 1; // 0x1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 833d9c6..3429c7c 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3285,6 +3285,9 @@
     field @Deprecated protected int mCapabilities;
   }
 
+  public static class MmTelFeature.MmTelCapabilities extends android.telephony.ims.feature.ImsFeature.Capabilities {
+  }
+
 }
 
 package android.text {
@@ -3864,15 +3867,21 @@
   public final class InputMethodManager {
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
     method public int getDisplayId();
+    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull android.os.UserHandle);
+    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(@NonNull String, boolean, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
     method public boolean hasActiveInputConnection(@Nullable android.view.View);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests();
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isCurrentRootView(@NonNull android.view.View);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
+    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public boolean isStylusHandwritingAvailableAsUser(@NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
     field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
   }
 
+  public final class InsertModeGesture extends android.view.inputmethod.CancellableHandwritingGesture implements android.os.Parcelable {
+  }
+
 }
 
 package android.view.inspector {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a5acf03..a366464 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2049,7 +2049,7 @@
      * indicator that the activity became active and ready to receive input. This sometimes could
      * also be a transit state toward another resting state. For instance, an activity may be
      * relaunched to {@link #onPause} due to configuration changes and the activity was visible,
-     * but wasn’t the top-most activity of an activity task. {@link #onResume} is guaranteed to be
+     * but wasn't the top-most activity of an activity task. {@link #onResume} is guaranteed to be
      * called before {@link #onPause} in this case which honors the activity lifecycle policy and
      * the activity eventually rests in {@link #onPause}.
      *
@@ -9063,6 +9063,7 @@
      * @hide
      */
     @UnsupportedAppUsage
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
     public final boolean isResumed() {
         return mResumed;
     }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a09d7dc..cc716ec 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -34,7 +34,6 @@
 import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
 import static android.window.ConfigurationHelper.isDifferentDisplay;
 import static android.window.ConfigurationHelper.shouldUpdateResources;
-import static android.window.ConfigurationHelper.shouldUpdateWindowMetricsBounds;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
@@ -6163,11 +6162,6 @@
     public static boolean shouldReportChange(@Nullable Configuration currentConfig,
             @NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets sizeBuckets,
             int handledConfigChanges, boolean alwaysReportChange) {
-        // Always report changes in window configuration bounds
-        if (shouldUpdateWindowMetricsBounds(currentConfig, newConfig)) {
-            return true;
-        }
-
         final int publicDiff = currentConfig.diffPublicOnly(newConfig);
         // Don't report the change if there's no public diff between current and new config.
         if (publicDiff == 0) {
diff --git a/core/java/android/app/IWindowToken.aidl b/core/java/android/app/IWindowToken.aidl
deleted file mode 100644
index 3627b0f..0000000
--- a/core/java/android/app/IWindowToken.aidl
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- ** Copyright 2020, 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 android.content.res.Configuration;
-import android.view.IWindow;
-
-/**
- * Callback to receive configuration changes from {@link com.android.server.WindowToken}.
- * WindowToken can be regarded to as a group of {@link android.view.IWindow} added from the same
- * visual context, such as {@link Activity} or one created with
- * {@link android.content.Context#createWindowContext(int)}. When WindowToken receives configuration
- * changes and/or when it is moved between displays, it will propagate the changes to client side
- * via this interface.
- * @see android.content.Context#createWindowContext(int)
- * {@hide}
- */
-oneway interface IWindowToken {
-    void onConfigurationChanged(in Configuration newConfig, int newDisplayId);
-
-    void onWindowTokenRemoved();
-}
diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java
index 0857c96..729e555 100644
--- a/core/java/android/app/LocaleConfig.java
+++ b/core/java/android/app/LocaleConfig.java
@@ -40,7 +40,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.LinkedHashSet;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Set;
@@ -195,8 +195,7 @@
         XmlUtils.beginDocument(parser, TAG_LOCALE_CONFIG);
         int outerDepth = parser.getDepth();
         AttributeSet attrs = Xml.asAttributeSet(parser);
-        // LinkedHashSet to preserve insertion order
-        Set<String> localeNames = new LinkedHashSet<>();
+        Set<String> localeNames = new HashSet<String>();
         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
             if (TAG_LOCALE.equals(parser.getName())) {
                 final TypedArray attributes = res.obtainAttributes(
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index d8cedb8..7ee1332 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -26,12 +26,14 @@
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
 import android.media.AudioAttributes;
+import android.media.RingtoneManager;
 import android.net.Uri;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
 import android.text.TextUtils;
+import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.Preconditions;
@@ -54,6 +56,7 @@
  * A representation of settings that apply to a collection of similarly themed notifications.
  */
 public final class NotificationChannel implements Parcelable {
+    private static final String TAG = "NotificationChannel";
 
     /**
      * The id of the default channel for an app. This id is reserved by the system. All
@@ -959,8 +962,11 @@
         setLockscreenVisibility(safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY));
 
         Uri sound = safeUri(parser, ATT_SOUND);
-        setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled) : sound,
-                safeAudioAttributes(parser));
+
+        final AudioAttributes audioAttributes = safeAudioAttributes(parser);
+        final int usage = audioAttributes.getUsage();
+        setSound(forRestore ? restoreSoundUri(context, sound, pkgInstalled, usage) : sound,
+                audioAttributes);
 
         enableLights(safeBool(parser, ATT_LIGHTS, false));
         setLightColor(safeInt(parser, ATT_LIGHT_COLOR, DEFAULT_LIGHT_COLOR));
@@ -1010,18 +1016,34 @@
         if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
             return uri;
         }
-
         return contentResolver.canonicalize(uri);
     }
 
     @Nullable
-    private Uri getUncanonicalizedSoundUri(ContentResolver contentResolver, @NonNull Uri uri) {
+    private Uri getUncanonicalizedSoundUri(
+            ContentResolver contentResolver, @NonNull Uri uri, int usage) {
         if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(uri)
                 || ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
                 || ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
             return uri;
         }
-        return contentResolver.uncanonicalize(uri);
+        int ringtoneType = 0;
+
+        // Consistent with UI(SoundPreferenceController.handlePreferenceTreeClick).
+        if (AudioAttributes.USAGE_ALARM == usage) {
+            ringtoneType = RingtoneManager.TYPE_ALARM;
+        } else if (AudioAttributes.USAGE_NOTIFICATION_RINGTONE == usage) {
+            ringtoneType = RingtoneManager.TYPE_RINGTONE;
+        } else {
+            ringtoneType = RingtoneManager.TYPE_NOTIFICATION;
+        }
+        try {
+            return RingtoneManager.getRingtoneUriForRestore(
+                    contentResolver, uri.toString(), ringtoneType);
+        } catch (Exception e) {
+            Log.e(TAG, "Failed to uncanonicalized sound uri for " + uri + " " + e);
+            return Settings.System.DEFAULT_NOTIFICATION_URI;
+        }
     }
 
     /**
@@ -1033,7 +1055,8 @@
      * @hide
      */
     @Nullable
-    public Uri restoreSoundUri(Context context, @Nullable Uri uri, boolean pkgInstalled) {
+    public Uri restoreSoundUri(
+            Context context, @Nullable Uri uri, boolean pkgInstalled, int usage) {
         if (uri == null || Uri.EMPTY.equals(uri)) {
             return null;
         }
@@ -1060,7 +1083,7 @@
             }
         }
         mSoundRestored = true;
-        return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri);
+        return getUncanonicalizedSoundUri(contentResolver, canonicalizedUri, usage);
     }
 
     /**
diff --git a/core/java/android/app/SharedPreferencesImpl.java b/core/java/android/app/SharedPreferencesImpl.java
index 1ebf565..a87187b 100644
--- a/core/java/android/app/SharedPreferencesImpl.java
+++ b/core/java/android/app/SharedPreferencesImpl.java
@@ -55,6 +55,11 @@
 import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
 
 final class SharedPreferencesImpl implements SharedPreferences {
     private static final String TAG = "SharedPreferencesImpl";
@@ -119,6 +124,10 @@
     private final ExponentiallyBucketedHistogram mSyncTimes = new ExponentiallyBucketedHistogram(16);
     private int mNumSync = 0;
 
+    private static final ThreadPoolExecutor sLoadExecutor = new ThreadPoolExecutor(0, 1, 10L,
+            TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
+            new SharedPreferencesThreadFactory());
+
     @UnsupportedAppUsage
     SharedPreferencesImpl(File file, int mode) {
         mFile = file;
@@ -135,11 +144,10 @@
         synchronized (mLock) {
             mLoaded = false;
         }
-        new Thread("SharedPreferencesImpl-load") {
-            public void run() {
-                loadFromDisk();
-            }
-        }.start();
+
+        sLoadExecutor.execute(() -> {
+            loadFromDisk();
+        });
     }
 
     private void loadFromDisk() {
@@ -874,4 +882,14 @@
         }
         mcr.setDiskWriteResult(false, false);
     }
+
+
+    private static final class SharedPreferencesThreadFactory implements ThreadFactory {
+        @Override
+        public Thread newThread(Runnable runnable) {
+            Thread thread = Executors.defaultThreadFactory().newThread(runnable);
+            thread.setName("SharedPreferences");
+            return thread;
+        }
+    }
 }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 8647dd2..e578499 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -237,8 +237,6 @@
 import android.view.contentcapture.IContentCaptureManager;
 import android.view.displayhash.DisplayHashManager;
 import android.view.inputmethod.InputMethodManager;
-import android.view.selectiontoolbar.ISelectionToolbarManager;
-import android.view.selectiontoolbar.SelectionToolbarManager;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textservice.TextServicesManager;
 import android.view.translation.ITranslationManager;
@@ -379,17 +377,6 @@
                 return new TextClassificationManager(ctx);
             }});
 
-        registerService(Context.SELECTION_TOOLBAR_SERVICE, SelectionToolbarManager.class,
-                new CachedServiceFetcher<SelectionToolbarManager>() {
-                    @Override
-                    public SelectionToolbarManager createService(ContextImpl ctx)
-                            throws ServiceNotFoundException {
-                        IBinder b = ServiceManager.getServiceOrThrow(
-                                Context.SELECTION_TOOLBAR_SERVICE);
-                        return new SelectionToolbarManager(ctx.getOuterContext(),
-                                ISelectionToolbarManager.Stub.asInterface(b));
-                    }});
-
         registerService(Context.FONT_SERVICE, FontManager.class,
                 new CachedServiceFetcher<FontManager>() {
             @Override
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index b0180c1..b0edc3d 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1206,12 +1206,14 @@
             return null;
         }
 
-        final ScreenshotHardwareBuffer screenshotBuffer =
-                syncScreenCapture.getBuffer();
+        final ScreenshotHardwareBuffer screenshotBuffer = syncScreenCapture.getBuffer();
+        if (screenshotBuffer == null) {
+            Log.e(LOG_TAG, "Failed to take screenshot for display=" + mDisplayId);
+            return null;
+        }
         Bitmap screenShot = screenshotBuffer.asBitmap();
         if (screenShot == null) {
-            Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display "
-                    + mDisplayId);
+            Log.e(LOG_TAG, "Failed to take screenshot for display=" + mDisplayId);
             return null;
         }
         Bitmap swBitmap;
@@ -1263,16 +1265,23 @@
                 ScreenCapture.createSyncCaptureListener();
         try {
             if (!mUiAutomationConnection.takeSurfaceControlScreenshot(sc, syncScreenCapture)) {
+                Log.e(LOG_TAG, "Failed to take screenshot for window=" + window);
                 return null;
             }
-
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "Error while taking screenshot!", re);
             return null;
         }
-        ScreenCapture.ScreenshotHardwareBuffer captureBuffer =
-                syncScreenCapture.getBuffer();
+        ScreenCapture.ScreenshotHardwareBuffer captureBuffer = syncScreenCapture.getBuffer();
+        if (captureBuffer == null) {
+            Log.e(LOG_TAG, "Failed to take screenshot for window=" + window);
+            return null;
+        }
         Bitmap screenShot = captureBuffer.asBitmap();
+        if (screenShot == null) {
+            Log.e(LOG_TAG, "Failed to take screenshot for window=" + window);
+            return null;
+        }
         Bitmap swBitmap;
         try (HardwareBuffer buffer = captureBuffer.getHardwareBuffer()) {
             swBitmap = screenShot.copy(Bitmap.Config.ARGB_8888, false);
diff --git a/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
index 270cb18..62ad8c0 100644
--- a/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
+++ b/core/java/android/app/admin/EnterprisePlatformSecurity_OWNERS
@@ -1,4 +1,3 @@
+file:EnterprisePlatform_OWNERS
 rubinxu@google.com
 pgrafov@google.com
-ayushsha@google.com
-alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/EnterprisePlatformTest_OWNERS b/core/java/android/app/admin/EnterprisePlatformTest_OWNERS
new file mode 100644
index 0000000..eb23a03
--- /dev/null
+++ b/core/java/android/app/admin/EnterprisePlatformTest_OWNERS
@@ -0,0 +1,5 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=1337891&template=1814288
+# Assign bugs to aep-automated-tests@google.com
+
+file:EnterprisePlatform_OWNERS
+scottjonathan@google.com
\ No newline at end of file
diff --git a/core/java/android/app/admin/EnterprisePlatform_OWNERS b/core/java/android/app/admin/EnterprisePlatform_OWNERS
index 6ce25cc..4d1ed590 100644
--- a/core/java/android/app/admin/EnterprisePlatform_OWNERS
+++ b/core/java/android/app/admin/EnterprisePlatform_OWNERS
@@ -1,5 +1,2 @@
-# Assign bugs to android-enterprise-triage@google.com
-file:WorkDeviceExperience_OWNERS
-file:Provisioning_OWNERS
-file:WorkProfile_OWNERS
-file:EnterprisePlatformSecurity_OWNERS
\ No newline at end of file
+sandness@google.com #{LAST_RESORT_SUGGESTION}
+scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/OWNERS b/core/java/android/app/admin/OWNERS
index 10a5f14..308f1d6 100644
--- a/core/java/android/app/admin/OWNERS
+++ b/core/java/android/app/admin/OWNERS
@@ -1,5 +1,7 @@
 # Bug component: 142675
+# Assign bugs to device-policy-manager-triage@google.com
 
-file:EnterprisePlatform_OWNERS
+file:WorkDeviceExperience_OWNERS
+file:EnterprisePlatformSecurity_OWNERS
 
 yamasani@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/Provisioning_OWNERS b/core/java/android/app/admin/Provisioning_OWNERS
index 2e5c2df..fa0a1f0 100644
--- a/core/java/android/app/admin/Provisioning_OWNERS
+++ b/core/java/android/app/admin/Provisioning_OWNERS
@@ -1,5 +1,4 @@
 # Assign bugs to android-enterprise-triage@google.com
 mdb.ae-provisioning-reviews@google.com
-petuska@google.com #{LAST_RESORT_SUGGESTION}
-nupursn@google.com #{LAST_RESORT_SUGGESTION}
-shreyacsingh@google.com #{LAST_RESORT_SUGGESTION}
+file:EnterprisePlatform_OWNERS
+petuska@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
diff --git a/core/java/android/app/admin/WorkDeviceExperience_OWNERS b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
index 2033343..10ac59a 100644
--- a/core/java/android/app/admin/WorkDeviceExperience_OWNERS
+++ b/core/java/android/app/admin/WorkDeviceExperience_OWNERS
@@ -1,7 +1,5 @@
 # Assign bugs to android-enterprise-triage@google.com
 work-device-experience+reviews@google.com
-scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
-eliselliott@google.com #{LAST_RESORT_SUGGESTION}
+file:EnterprisePlatform_OWNERS
 kholoudm@google.com #{LAST_RESORT_SUGGESTION}
 acjohnston@google.com #{LAST_RESORT_SUGGESTION}
-alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
diff --git a/core/java/android/app/admin/WorkProfile_OWNERS b/core/java/android/app/admin/WorkProfile_OWNERS
index 260b672..e9bcce2 100644
--- a/core/java/android/app/admin/WorkProfile_OWNERS
+++ b/core/java/android/app/admin/WorkProfile_OWNERS
@@ -1,5 +1,3 @@
 # Assign bugs to android-enterprise-triage@google.com
-liahav@google.com
-olit@google.com
-scottjonathan@google.com #{LAST_RESORT_SUGGESTION}
-alexkershaw@google.com #{LAST_RESORT_SUGGESTION}
\ No newline at end of file
+file:EnterprisePlatform_OWNERS
+liahav@google.com
\ No newline at end of file
diff --git a/core/java/android/app/assist/AssistStructure.java b/core/java/android/app/assist/AssistStructure.java
index b7ec7b5..d66fca8 100644
--- a/core/java/android/app/assist/AssistStructure.java
+++ b/core/java/android/app/assist/AssistStructure.java
@@ -22,6 +22,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.FillRequest;
+import android.text.Spanned;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -1557,6 +1558,10 @@
         /**
          * Returns any text associated with the node that is displayed to the user, or null
          * if there is none.
+         *
+         * <p> The text will be stripped of any spans that could potentially contain reference to
+         * the activity context, to avoid memory leak. If the text contained a span, a plain
+         * string version of the text will be returned.
          */
         @Nullable
         public CharSequence getText() {
@@ -1996,14 +2001,16 @@
         @Override
         public void setText(CharSequence text) {
             ViewNodeText t = getNodeText();
-            t.mText = TextUtils.trimNoCopySpans(text);
+            // Strip spans from the text to avoid memory leak
+            t.mText = TextUtils.trimToParcelableSize(stripAllSpansFromText(text));
             t.mTextSelectionStart = t.mTextSelectionEnd = -1;
         }
 
         @Override
         public void setText(CharSequence text, int selectionStart, int selectionEnd) {
             ViewNodeText t = getNodeText();
-            t.mText = TextUtils.trimNoCopySpans(text);
+            // Strip spans from the text to avoid memory leak
+            t.mText = stripAllSpansFromText(text);
             t.mTextSelectionStart = selectionStart;
             t.mTextSelectionEnd = selectionEnd;
         }
@@ -2222,6 +2229,13 @@
         public void setHtmlInfo(@NonNull HtmlInfo htmlInfo) {
             mNode.mHtmlInfo = htmlInfo;
         }
+
+        private CharSequence stripAllSpansFromText(CharSequence text) {
+            if (text instanceof Spanned) {
+                return text.toString();
+            }
+            return text;
+        }
     }
 
     private static final class HtmlInfoNode extends HtmlInfo implements Parcelable {
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index b159321..3927b40 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -781,7 +781,18 @@
      *
      * @param appWidgetIds  The AppWidget instances to notify of view data changes.
      * @param viewId        The collection view id.
+     * @deprecated The corresponding API
+     * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+     * deprecated. Moving forward please use
+     * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+     * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+     * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+     * {@link #updateAppWidget(int, RemoteViews)},
+     * {@link #updateAppWidget(ComponentName, RemoteViews)},
+     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+     * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
      */
+    @Deprecated
     public void notifyAppWidgetViewDataChanged(int[] appWidgetIds, int viewId) {
         if (mService == null) {
             return;
@@ -817,7 +828,18 @@
      *
      * @param appWidgetId  The AppWidget instance to notify of view data changes.
      * @param viewId       The collection view id.
+     * @deprecated The corresponding API
+     * {@link RemoteViews#setRemoteAdapter(int, Intent)} associated with this method has been
+     * deprecated. Moving forward please use
+     * {@link RemoteViews#setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)}
+     * instead to set {@link android.widget.RemoteViews.RemoteCollectionItems} for the remote
+     * adapter and update the widget views by calling {@link #updateAppWidget(int[], RemoteViews)},
+     * {@link #updateAppWidget(int, RemoteViews)},
+     * {@link #updateAppWidget(ComponentName, RemoteViews)},
+     * {@link #partiallyUpdateAppWidget(int[], RemoteViews)},
+     * or {@link #partiallyUpdateAppWidget(int, RemoteViews)}, whichever applicable.
      */
+    @Deprecated
     public void notifyAppWidgetViewDataChanged(int appWidgetId, int viewId) {
         if (mService == null) {
             return;
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 0958a806..7d62c79 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -57,6 +57,7 @@
     private final boolean mSelfManaged;
     private final boolean mNotifyOnDeviceNearby;
     private final int mSystemDataSyncFlags;
+    private final String mTag;
 
     /**
      * Indicates that the association has been revoked (removed), but we keep the association
@@ -78,10 +79,11 @@
      * @hide
      */
     public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
-            @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
-            @Nullable String deviceProfile, @Nullable AssociatedDevice associatedDevice,
-            boolean selfManaged, boolean notifyOnDeviceNearby, boolean revoked,
-            long timeApprovedMs, long lastTimeConnectedMs, int systemDataSyncFlags) {
+            @Nullable String tag, @Nullable MacAddress macAddress,
+            @Nullable CharSequence displayName, @Nullable String deviceProfile,
+            @Nullable AssociatedDevice associatedDevice, boolean selfManaged,
+            boolean notifyOnDeviceNearby, boolean revoked, long timeApprovedMs,
+            long lastTimeConnectedMs, int systemDataSyncFlags) {
         if (id <= 0) {
             throw new IllegalArgumentException("Association ID should be greater than 0");
         }
@@ -97,6 +99,7 @@
 
         mDeviceMacAddress = macAddress;
         mDisplayName = displayName;
+        mTag = tag;
         mDeviceProfile = deviceProfile;
         mAssociatedDevice = associatedDevice;
 
@@ -116,6 +119,14 @@
     }
 
     /**
+     * @return the tag of this association.
+     * @see CompanionDeviceManager#setAssociationTag(int, String)
+     */
+    public @Nullable String getTag() {
+        return mTag;
+    }
+
+    /**
      * @return the ID of the user who "owns" this association.
      * @hide
      */
@@ -287,6 +298,7 @@
                 + "mId=" + mId
                 + ", mUserId=" + mUserId
                 + ", mPackageName='" + mPackageName + '\''
+                + ", mTag='" + mTag + '\''
                 + ", mDeviceMacAddress=" + mDeviceMacAddress
                 + ", mDisplayName='" + mDisplayName + '\''
                 + ", mDeviceProfile='" + mDeviceProfile + '\''
@@ -315,6 +327,7 @@
                 && mTimeApprovedMs == that.mTimeApprovedMs
                 && mLastTimeConnectedMs == that.mLastTimeConnectedMs
                 && Objects.equals(mPackageName, that.mPackageName)
+                && Objects.equals(mTag, that.mTag)
                 && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress)
                 && Objects.equals(mDisplayName, that.mDisplayName)
                 && Objects.equals(mDeviceProfile, that.mDeviceProfile)
@@ -324,7 +337,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
+        return Objects.hash(mId, mUserId, mPackageName, mTag, mDeviceMacAddress, mDisplayName,
                 mDeviceProfile, mAssociatedDevice, mSelfManaged, mNotifyOnDeviceNearby, mRevoked,
                 mTimeApprovedMs, mLastTimeConnectedMs, mSystemDataSyncFlags);
     }
@@ -340,6 +353,7 @@
 
         dest.writeInt(mUserId);
         dest.writeString(mPackageName);
+        dest.writeString(mTag);
 
         dest.writeTypedObject(mDeviceMacAddress, 0);
         dest.writeCharSequence(mDisplayName);
@@ -359,6 +373,7 @@
 
         mUserId = in.readInt();
         mPackageName = in.readString();
+        mTag = in.readString();
 
         mDeviceMacAddress = in.readTypedObject(MacAddress.CREATOR);
         mDisplayName = in.readCharSequence();
@@ -413,9 +428,11 @@
         private boolean mRevoked;
         private long mLastTimeConnectedMs;
         private int mSystemDataSyncFlags;
+        private String mTag;
 
         private Builder(@NonNull AssociationInfo info) {
             mOriginalInfo = info;
+            mTag = info.mTag;
             mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
             mRevoked = info.mRevoked;
             mLastTimeConnectedMs = info.mLastTimeConnectedMs;
@@ -460,12 +477,21 @@
         }
 
         /** @hide */
+        @Override
+        @NonNull
+        public Builder setTag(String tag) {
+            mTag = tag;
+            return this;
+        }
+
+        /** @hide */
         @NonNull
         public AssociationInfo build() {
             return new AssociationInfo(
                     mOriginalInfo.mId,
                     mOriginalInfo.mUserId,
                     mOriginalInfo.mPackageName,
+                    mTag,
                     mOriginalInfo.mDeviceMacAddress,
                     mOriginalInfo.mDisplayName,
                     mOriginalInfo.mDeviceProfile,
@@ -508,5 +534,8 @@
         /** @hide */
         @NonNull
         Builder setSystemDataSyncFlags(int flags);
+
+        /** @hide */
+        Builder setTag(String tag);
     }
 }
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 69e1653..3aa2877 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -1372,6 +1372,50 @@
         }
     }
 
+    /**
+     * Sets the {@link AssociationInfo#getTag() tag} for this association.
+     *
+     * <p>The length of the tag must be at most 20 characters.
+     *
+     * <p>This allows to store useful information about the associated devices.
+     *
+     * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
+     *                          of the companion device recorded by CompanionDeviceManager
+     * @param tag the tag of this association
+     */
+    @UserHandleAware
+    public void setAssociationTag(int associationId, @NonNull String tag) {
+        Objects.requireNonNull(tag, "tag cannot be null");
+
+        if (tag.length() > 20) {
+            throw new IllegalArgumentException("Length of the tag must be at most 20 characters");
+        }
+
+        try {
+            mService.setAssociationTag(associationId, tag);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Clears the {@link AssociationInfo#getTag() tag} for this association.
+     *
+     * <p>The tag will be set to null for this association when calling this API.
+     *
+     * @param associationId The unique {@link AssociationInfo#getId ID} assigned to the Association
+     *                          of the companion device recorded by CompanionDeviceManager
+     * @see CompanionDeviceManager#setAssociationTag(int, String)
+     */
+    @UserHandleAware
+    public void clearAssociationTag(int associationId) {
+        try {
+            mService.clearAssociationTag(associationId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     private boolean checkFeaturePresent() {
         boolean featurePresent = mService != null;
         if (!featurePresent && DEBUG) {
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index 463dba2..a3b202a 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -115,4 +115,12 @@
 
     @EnforcePermission("MANAGE_COMPANION_DEVICES")
     void enableSecureTransport(boolean enabled);
+
+    void setAssociationTag(int associationId, String tag);
+
+    void clearAssociationTag(int associationId);
+
+    byte[] getBackupPayload(int userId);
+
+    void applyRestoredPayload(in byte[] payload, int userId);
 }
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index d0bb2b9..8f35ca2 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -517,7 +517,11 @@
     }
 
     /**
-     * The device ID for which permissions are checked.
+     * Gets the device ID for this attribution source. Attribution source can set the device ID
+     * using {@link Builder#setDeviceId(int)}, the default device ID is
+     * {@link Context#DEVICE_ID_DEFAULT}.
+     * <p>
+     * This device ID is used for permissions checking during attribution source validation.
      */
     public int getDeviceId() {
         return mAttributionSourceState.deviceId;
diff --git a/core/java/android/content/om/FabricatedOverlay.java b/core/java/android/content/om/FabricatedOverlay.java
index 7e787c9..c4547b8 100644
--- a/core/java/android/content/om/FabricatedOverlay.java
+++ b/core/java/android/content/om/FabricatedOverlay.java
@@ -20,6 +20,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.res.AssetFileDescriptor;
 import android.os.FabricatedOverlayInternal;
 import android.os.FabricatedOverlayInternalEntry;
 import android.os.ParcelFileDescriptor;
@@ -269,7 +270,7 @@
          * @param configuration The string representation of the config this overlay is enabled for
          * @return the builder itself
          * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String,
-                       ParcelFileDescriptor, String)} instead.
+                ParcelFileDescriptor, String)} instead.
          * @hide
          */
         @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead")
@@ -285,6 +286,30 @@
         }
 
         /**
+         * Sets the value of the fabricated overlay for the file descriptor type.
+         *
+         * @param resourceName name of the target resource to overlay (in the form
+         *     [package]:type/entry)
+         * @param value the file descriptor whose contents are the value of the frro
+         * @param configuration The string representation of the config this overlay is enabled for
+         * @return the builder itself
+         * @deprecated Framework should use {@link FabricatedOverlay#setResourceValue(String,
+                ParcelFileDescriptor, String)} instead.
+         * @hide
+         */
+        @Deprecated(since = "Please use FabricatedOverlay#setResourceValue instead")
+        @NonNull
+        public Builder setResourceValue(
+                @NonNull String resourceName,
+                @NonNull AssetFileDescriptor value,
+                @Nullable String configuration) {
+            ensureValidResourceName(resourceName);
+            mEntries.add(
+                    generateFabricatedOverlayInternalEntry(resourceName, value, configuration));
+            return this;
+        }
+
+        /**
          * Builds an immutable fabricated overlay.
          *
          * @return the fabricated overlay
@@ -421,6 +446,21 @@
         entry.resourceName = resourceName;
         entry.binaryData = Objects.requireNonNull(parcelFileDescriptor);
         entry.configuration = configuration;
+        entry.binaryDataOffset = 0;
+        entry.binaryDataSize = parcelFileDescriptor.getStatSize();
+        return entry;
+    }
+
+    @NonNull
+    private static FabricatedOverlayInternalEntry generateFabricatedOverlayInternalEntry(
+            @NonNull String resourceName, @NonNull AssetFileDescriptor assetFileDescriptor,
+            @Nullable String configuration) {
+        final FabricatedOverlayInternalEntry entry = new FabricatedOverlayInternalEntry();
+        entry.resourceName = resourceName;
+        entry.binaryData = Objects.requireNonNull(assetFileDescriptor.getParcelFileDescriptor());
+        entry.binaryDataOffset = assetFileDescriptor.getStartOffset();
+        entry.binaryDataSize = assetFileDescriptor.getLength();
+        entry.configuration = configuration;
         return entry;
     }
 
@@ -495,4 +535,23 @@
         mOverlay.entries.add(
                 generateFabricatedOverlayInternalEntry(resourceName, value, configuration));
     }
+
+    /**
+     * Sets the resource value in the fabricated overlay for the file descriptor type with the
+     * configuration.
+     *
+     * @param resourceName name of the target resource to overlay (in the form
+     *     [package]:type/entry)
+     * @param value the file descriptor whose contents are the value of the frro
+     * @param configuration The string representation of the config this overlay is enabled for
+     */
+    @NonNull
+    public void setResourceValue(
+            @NonNull String resourceName,
+            @NonNull AssetFileDescriptor value,
+            @Nullable String configuration) {
+        ensureValidResourceName(resourceName);
+        mOverlay.entries.add(
+                generateFabricatedOverlayInternalEntry(resourceName, value, configuration));
+    }
 }
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 33d37bb..9f14c97 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3708,12 +3708,12 @@
             "android.hardware.telephony.subscription";
 
     /**
-     * Feature for {@link #getSystemAvailableFeatures} and
-     * {@link #hasSystemFeature}: The device is capable of communicating with
-     * other devices via Thread network.
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
+     * The device is capable of communicating with other devices via
+     * <a href="https://www.threadgroup.org">Thread</a> networking protocol.
      */
     @SdkConstant(SdkConstantType.FEATURE)
-    public static final String FEATURE_THREADNETWORK = "android.hardware.threadnetwork";
+    public static final String FEATURE_THREAD_NETWORK = "android.hardware.thread_network";
 
     /**
      * Feature for {@link #getSystemAvailableFeatures} and
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 508eeed..048289f 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1456,8 +1456,8 @@
 
     private static AssetManager newConfiguredAssetManager() {
         AssetManager assetManager = new AssetManager();
-        assetManager.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
+        assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                Build.VERSION.RESOURCES_SDK_INT);
         return assetManager;
     }
 
@@ -9011,8 +9011,8 @@
             }
 
             AssetManager assets = new AssetManager();
-            assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
             assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
 
             mCachedAssetManager = assets;
@@ -9086,8 +9086,8 @@
 
         private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
             final AssetManager assets = new AssetManager();
-            assets.setConfiguration(0, 0, null, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    0, 0, 0, Build.VERSION.RESOURCES_SDK_INT);
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                    Build.VERSION.RESOURCES_SDK_INT);
             assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
             return assets;
         }
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 7e30157..ea21d51 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -125,6 +125,9 @@
                 },
                 {
                     "exclude-annotation":"org.junit.Ignore"
+                },
+                {
+                    "exclude-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest"
                 }
             ]
         },
@@ -162,6 +165,14 @@
         },
         {
             "name":"CtsInstallHostTestCases"
+        },
+        {
+            "name": "CtsPackageManagerTestCases",
+            "options": [
+                {
+                    "include-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest"
+                }
+            ]
         }
     ]
 }
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 23b9d0b..b225de4 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1480,13 +1480,9 @@
             int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
             int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
             int majorVersion) {
-        if (locale != null) {
-            setConfiguration(mcc, mnc, null, new String[]{locale}, orientation, touchscreen,
-                    density, keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
-                    smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
-                    colorMode, grammaticalGender, majorVersion);
-        } else {
-            setConfiguration(mcc, mnc, null, null, orientation, touchscreen, density,
+        synchronized (this) {
+            ensureValidLocked();
+            nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density,
                     keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
                     smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
                     colorMode, grammaticalGender, majorVersion);
@@ -1494,25 +1490,6 @@
     }
 
     /**
-     * Change the configuration used when retrieving resources.  Not for use by
-     * applications.
-     * @hide
-     */
-    public void setConfiguration(int mcc, int mnc, String defaultLocale, String[] locales,
-            int orientation, int touchscreen, int density, int keyboard, int keyboardHidden,
-            int navigation, int screenWidth, int screenHeight, int smallestScreenWidthDp,
-            int screenWidthDp, int screenHeightDp, int screenLayout, int uiMode, int colorMode,
-            int grammaticalGender, int majorVersion) {
-        synchronized (this) {
-            ensureValidLocked();
-            nativeSetConfiguration(mObject, mcc, mnc, defaultLocale, locales, orientation,
-                    touchscreen, density, keyboard, keyboardHidden, navigation, screenWidth,
-                    screenHeight, smallestScreenWidthDp, screenWidthDp, screenHeightDp,
-                    screenLayout, uiMode, colorMode, grammaticalGender, majorVersion);
-        }
-    }
-
-    /**
      * @hide
      */
     @UnsupportedAppUsage
@@ -1595,11 +1572,10 @@
     private static native void nativeSetApkAssets(long ptr, @NonNull ApkAssets[] apkAssets,
             boolean invalidateCaches);
     private static native void nativeSetConfiguration(long ptr, int mcc, int mnc,
-            @Nullable String defaultLocale, @NonNull String[] locales, int orientation,
-            int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
-            int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
-            int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
-            int majorVersion);
+            @Nullable String locale, int orientation, int touchscreen, int density, int keyboard,
+            int keyboardHidden, int navigation, int screenWidth, int screenHeight,
+            int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout,
+            int uiMode, int colorMode, int grammaticalGender, int majorVersion);
     private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
             long ptr, boolean includeOverlays, boolean includeLoaders);
 
diff --git a/core/java/android/content/res/Element.java b/core/java/android/content/res/Element.java
index a86c0c9..e931fe8 100644
--- a/core/java/android/content/res/Element.java
+++ b/core/java/android/content/res/Element.java
@@ -42,6 +42,8 @@
     public static final int MAX_ATTR_LEN_PATH = 4000;
     public static final int MAX_ATTR_LEN_DATA_VALUE = 4000;
 
+    private static final String BAD_COMPONENT_NAME_CHARS = ";,[](){}:?-%^*|/\\";
+
     private static final String TAG = "PackageParsing";
     protected static final String TAG_ACTION = "action";
     protected static final String TAG_ACTIVITY = "activity";
@@ -128,6 +130,7 @@
     protected static final String TAG_ATTR_VALUE = "value";
     protected static final String TAG_ATTR_VERSION_NAME = "versionName";
     protected static final String TAG_ATTR_WRITE_PERMISSION = "writePermission";
+    protected static final String TAG_ATTR_ZYGOTE_PRELOAD_NAME = "zygotePreloadName";
 
     // The length of mTagCounters corresponds to the number of tags defined in getCounterIdx. If new
     // tags are added then the size here should be increased to match.
@@ -374,6 +377,7 @@
             case TAG_ATTR_TASK_AFFINITY:
             case TAG_ATTR_WRITE_PERMISSION:
             case TAG_ATTR_VERSION_NAME:
+            case TAG_ATTR_ZYGOTE_PRELOAD_NAME:
                 return MAX_ATTR_LEN_NAME;
             case TAG_ATTR_PATH:
             case TAG_ATTR_PATH_ADVANCED_PATTERN:
@@ -488,6 +492,7 @@
             case R.styleable.AndroidManifestApplication_requiredAccountType:
             case R.styleable.AndroidManifestApplication_restrictedAccountType:
             case R.styleable.AndroidManifestApplication_taskAffinity:
+            case R.styleable.AndroidManifestApplication_zygotePreloadName:
                 return MAX_ATTR_LEN_NAME;
             default:
                 return DEFAULT_MAX_STRING_ATTR_LENGTH;
@@ -738,6 +743,7 @@
                 switch (name) {
                     case TAG_ATTR_BACKUP_AGENT:
                     case TAG_ATTR_NAME:
+                    case TAG_ATTR_ZYGOTE_PRELOAD_NAME:
                         return true;
                     default:
                         return false;
@@ -766,7 +772,8 @@
                 return index == R.styleable.AndroidManifestActivityAlias_targetActivity;
             case TAG_APPLICATION:
                 return index == R.styleable.AndroidManifestApplication_backupAgent
-                        || index == R.styleable.AndroidManifestApplication_name;
+                        || index == R.styleable.AndroidManifestApplication_name
+                        || index == R.styleable.AndroidManifestApplication_zygotePreloadName;
             case TAG_INSTRUMENTATION:
                 return index ==  R.styleable.AndroidManifestInstrumentation_name;
             case TAG_PROVIDER:
@@ -785,33 +792,13 @@
     }
 
     void validateComponentName(CharSequence name) {
-        int i = 0;
-        if (name.charAt(0) == '.') {
-            i = 1;
-        }
         boolean isStart = true;
-        for (; i < name.length(); i++) {
-            if (name.charAt(i) == '.') {
-                if (isStart) {
-                    break;
-                }
-                isStart = true;
-            } else {
-                if (isStart) {
-                    if (Character.isJavaIdentifierStart(name.charAt(i))) {
-                        isStart = false;
-                    } else {
-                        break;
-                    }
-                } else if (!Character.isJavaIdentifierPart(name.charAt(i))) {
-                    break;
-                }
+        for (int i = 0; i < name.length(); i++) {
+            if (BAD_COMPONENT_NAME_CHARS.indexOf(name.charAt(i)) >= 0) {
+                Slog.e(TAG, name + " is not a valid Java class name");
+                throw new SecurityException(name + " is not a valid Java class name");
             }
         }
-        if ((i < name.length()) || (name.charAt(name.length() - 1) == '.')) {
-            Slog.e(TAG, name + " is not a valid Java class name");
-            throw new SecurityException(name + " is not a valid Java class name");
-        }
     }
 
     void validateStrAttr(String attrName, String attrValue) {
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index a937832..395fef2 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -425,12 +425,14 @@
                     mConfiguration.setLocales(locales);
                 }
 
-                String[] selectedLocales = null;
-                String defaultLocale = null;
                 if ((configChanges & ActivityInfo.CONFIG_LOCALE) != 0) {
                     if (locales.size() > 1) {
                         String[] availableLocales;
-                        if (ResourcesManager.getInstance().getLocaleList().isEmpty()) {
+
+                        LocaleList localeList = ResourcesManager.getInstance().getLocaleList();
+                        if (!localeList.isEmpty()) {
+                            availableLocales = localeList.toLanguageTags().split(",");
+                        } else {
                             // The LocaleList has changed. We must query the AssetManager's
                             // available Locales and figure out the best matching Locale in the new
                             // LocaleList.
@@ -442,30 +444,14 @@
                                     availableLocales = null;
                                 }
                             }
-                            if (availableLocales != null) {
-                                final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
-                                        availableLocales);
-                                if (bestLocale != null) {
-                                    selectedLocales = new String[]{
-                                            adjustLanguageTag(bestLocale.toLanguageTag())};
-                                    if (!bestLocale.equals(locales.get(0))) {
-                                        mConfiguration.setLocales(
-                                                new LocaleList(bestLocale, locales));
-                                    }
-                                }
-                            }
-                        } else {
-                            selectedLocales = locales.getIntersection(
-                                    ResourcesManager.getInstance().getLocaleList());
-                            defaultLocale = ResourcesManager.getInstance()
-                                    .getLocaleList().get(0).toLanguageTag();
                         }
-                    }
-                }
-                if (selectedLocales == null) {
-                    selectedLocales = new String[locales.size()];
-                    for (int i = 0; i < locales.size(); i++) {
-                        selectedLocales[i] = adjustLanguageTag(locales.get(i).toLanguageTag());
+                        if (availableLocales != null) {
+                            final Locale bestLocale = locales.getFirstMatchWithEnglishSupported(
+                                    availableLocales);
+                            if (bestLocale != null && bestLocale != locales.get(0)) {
+                                mConfiguration.setLocales(new LocaleList(bestLocale, locales));
+                            }
+                        }
                     }
                 }
 
@@ -502,8 +488,7 @@
                 }
 
                 mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
-                        defaultLocale,
-                        selectedLocales,
+                        adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
                         mConfiguration.orientation,
                         mConfiguration.touchscreen,
                         mConfiguration.densityDpi, mConfiguration.keyboard,
diff --git a/core/java/android/credentials/CreateCredentialRequest.java b/core/java/android/credentials/CreateCredentialRequest.java
index fc3dc79..946b5f3 100644
--- a/core/java/android/credentials/CreateCredentialRequest.java
+++ b/core/java/android/credentials/CreateCredentialRequest.java
@@ -261,7 +261,10 @@
 
         /**
          * @param type the type of the credential to be stored
-         * @param credentialData the full credential creation request data
+         * @param credentialData the full credential creation request data, which must at minimum
+         * contain the required fields observed at the
+         * {@link androidx.credentials.CreateCredentialRequest} Bundle conversion static methods,
+         * because they are required for properly displaying the system credential selector UI
          * @param candidateQueryData the partial request data that will be sent to the provider
          *                           during the initial creation candidate query stage
          */
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 706e75e..f2980f4 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -1875,7 +1875,7 @@
      * statement
      * @hide
      */
-    long getLastChangedRowsCount() {
+    long getLastChangedRowCount() {
         try {
             return nativeChanges(mConnectionPtr);
         } finally {
@@ -1887,7 +1887,7 @@
      * Return the total number of database changes made on the current connection.
      * @hide
      */
-    long getTotalChangedRowsCount() {
+    long getTotalChangedRowCount() {
         try {
             return nativeTotalChanges(mConnectionPtr);
         } finally {
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index a3f8383..5b80e6a 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -956,7 +956,7 @@
      * Open the database according to the flags {@link #OPEN_READWRITE}
      * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
      *
-     * <p>Sets the locale of the database to the  the system's current locale.
+     * <p>Sets the locale of the database to the system's current locale.
      * Call {@link #setLocale} if you would like something else.</p>
      *
      * @param path to database file to open and/or create
@@ -1002,7 +1002,7 @@
      * Open the database according to the flags {@link #OPEN_READWRITE}
      * {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
      *
-     * <p>Sets the locale of the database to the  the system's current locale.
+     * <p>Sets the locale of the database to the system's current locale.
      * Call {@link #setLocale} if you would like something else.</p>
      *
      * <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
@@ -1163,7 +1163,7 @@
      * Create a memory backed SQLite database.  Its contents will be destroyed
      * when the database is closed.
      *
-     * <p>Sets the locale of the database to the  the system's current locale.
+     * <p>Sets the locale of the database to the system's current locale.
      * Call {@link #setLocale} if you would like something else.</p>
      *
      * @param factory an optional factory class that is called to instantiate a
@@ -1182,7 +1182,7 @@
      * Create a memory backed SQLite database.  Its contents will be destroyed
      * when the database is closed.
      *
-     * <p>Sets the locale of the database to the  the system's current locale.
+     * <p>Sets the locale of the database to the system's current locale.
      * Call {@link #setLocale} if you would like something else.</p>
      * @param openParams configuration parameters that are used for opening SQLiteDatabase
      * @return a SQLiteDatabase instance
@@ -2208,10 +2208,9 @@
      *
      * @return The number of rows changed by the most recent sql statement
      * @throws IllegalStateException if there is no current transaction.
-     * @hide
      */
-    public long getLastChangedRowsCount() {
-        return getThreadSession().getLastChangedRowsCount();
+    public long getLastChangedRowCount() {
+        return getThreadSession().getLastChangedRowCount();
     }
 
     /**
@@ -2223,9 +2222,9 @@
      * <code><pre>
      *    database.beginTransaction();
      *    try {
-     *        long initialValue = database.getTotalChangedRowsCount();
+     *        long initialValue = database.getTotalChangedRowCount();
      *        // Execute SQL statements
-     *        long changedRows = database.getTotalChangedRowsCount() - initialValue;
+     *        long changedRows = database.getTotalChangedRowCount() - initialValue;
      *        // changedRows counts the total number of rows updated in the transaction.
      *    } finally {
      *        database.endTransaction();
@@ -2236,10 +2235,9 @@
      *
      * @return The number of rows changed on the current connection.
      * @throws IllegalStateException if there is no current transaction.
-     * @hide
      */
-    public long getTotalChangedRowsCount() {
-        return getThreadSession().getTotalChangedRowsCount();
+    public long getTotalChangedRowCount() {
+        return getThreadSession().getTotalChangedRowCount();
     }
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index ef1a9cb..7d9f02d 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -998,9 +998,9 @@
      * this connection.
      * @hide
      */
-    long getLastChangedRowsCount() {
+    long getLastChangedRowCount() {
         throwIfNoTransaction();
-        return mConnection.getLastChangedRowsCount();
+        return mConnection.getLastChangedRowCount();
     }
 
     /**
@@ -1008,9 +1008,9 @@
      * it was created.
      * @hide
      */
-    long getTotalChangedRowsCount() {
+    long getTotalChangedRowCount() {
         throwIfNoTransaction();
-        return mConnection.getTotalChangedRowsCount();
+        return mConnection.getTotalChangedRowCount();
     }
 
     /**
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 889a43c..5ff0e7a 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -22,6 +22,7 @@
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.graphics.GraphicBuffer;
+import android.os.BadParcelableException;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -399,11 +400,14 @@
     public static final @android.annotation.NonNull Parcelable.Creator<HardwareBuffer> CREATOR =
             new Parcelable.Creator<HardwareBuffer>() {
         public HardwareBuffer createFromParcel(Parcel in) {
+            if (in == null) {
+                throw new NullPointerException("null passed to createFromParcel");
+            }
             long nativeObject = nReadHardwareBufferFromParcel(in);
             if (nativeObject != 0) {
                 return new HardwareBuffer(nativeObject);
             }
-            return null;
+            throw new BadParcelableException("Failed to read hardware buffer");
         }
 
         public HardwareBuffer[] newArray(int size) {
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index c2fe080..c80124c 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -31,6 +31,7 @@
 import android.content.pm.PackageManager;
 import android.graphics.Point;
 import android.hardware.CameraExtensionSessionStats;
+import android.hardware.CameraIdRemapping;
 import android.hardware.CameraStatus;
 import android.hardware.ICameraService;
 import android.hardware.ICameraServiceListener;
@@ -1730,6 +1731,17 @@
     }
 
     /**
+     * Remaps Camera Ids in the CameraService.
+     *
+     * @hide
+    */
+    @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA)
+    public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
+            throws CameraAccessException, SecurityException, IllegalArgumentException {
+        CameraManagerGlobal.get().remapCameraIds(cameraIdRemapping);
+    }
+
+    /**
      * Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for
      * currently active session. Validation is done downstream.
      *
@@ -1802,6 +1814,13 @@
 
         private final Object mLock = new Object();
 
+        /**
+         * The active CameraIdRemapping. This will be used to refresh the cameraIdRemapping state
+         * in the CameraService every time we connect to it, including when the CameraService
+         * Binder dies and we reconnect to it.
+         */
+        @Nullable private CameraIdRemapping mActiveCameraIdRemapping;
+
         // Access only through getCameraService to deal with binder death
         private ICameraService mCameraService;
         private boolean mHasOpenCloseListenerPermission = false;
@@ -1944,6 +1963,41 @@
             } catch (RemoteException e) {
                 // Camera service died in all probability
             }
+
+            if (mActiveCameraIdRemapping != null) {
+                try {
+                    cameraService.remapCameraIds(mActiveCameraIdRemapping);
+                } catch (ServiceSpecificException e) {
+                    // Unexpected failure, ignore and continue.
+                    Log.e(TAG, "Unable to remap camera Ids in the camera service");
+                } catch (RemoteException e) {
+                    // Camera service died in all probability
+                }
+            }
+        }
+
+        /** Updates the cameraIdRemapping state in the CameraService. */
+        public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping)
+                throws CameraAccessException, SecurityException {
+            synchronized (mLock) {
+                ICameraService cameraService = getCameraService();
+                if (cameraService == null) {
+                    throw new CameraAccessException(
+                            CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+
+                try {
+                    cameraService.remapCameraIds(cameraIdRemapping);
+                    mActiveCameraIdRemapping = cameraIdRemapping;
+                } catch (ServiceSpecificException e) {
+                    throwAsPublicException(e);
+                } catch (RemoteException e) {
+                    throw new CameraAccessException(
+                            CameraAccessException.CAMERA_DISCONNECTED,
+                            "Camera service is currently unavailable.");
+                }
+            }
         }
 
         private String[] extractCameraIdListLocked() {
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index c872516..5940819 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -4194,9 +4194,8 @@
      * <p>This control allows Camera extension clients to configure the strength of the applied
      * extension effect. Strength equal to 0 means that the extension must not apply any
      * post-processing and return a regular captured frame. Strength equal to 100 is the
-     * default level of post-processing applied when the control is not supported or not set
-     * by the client. Values between 0 and 100 will have different effect depending on the
-     * extension type as described below:</p>
+     * maximum level of post-processing. Values between 0 and 100 will have different effect
+     * depending on the extension type as described below:</p>
      * <ul>
      * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_BOKEH BOKEH} -
      * the strength is expected to control the amount of blur.</li>
@@ -4211,7 +4210,9 @@
      * {@link android.hardware.camera2.CameraExtensionCharacteristics#getAvailableCaptureRequestKeys }.
      * The control is only defined and available to clients sending capture requests via
      * {@link android.hardware.camera2.CameraExtensionSession }.
-     * The default value is 100.</p>
+     * If the client doesn't specify the extension strength value, then a default value will
+     * be set by the extension. Clients can retrieve the default value by checking the
+     * corresponding capture result.</p>
      * <p><b>Range of valid values:</b><br>
      * 0 - 100</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 57f7bca..905f98d 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -5694,9 +5694,8 @@
      * <p>This control allows Camera extension clients to configure the strength of the applied
      * extension effect. Strength equal to 0 means that the extension must not apply any
      * post-processing and return a regular captured frame. Strength equal to 100 is the
-     * default level of post-processing applied when the control is not supported or not set
-     * by the client. Values between 0 and 100 will have different effect depending on the
-     * extension type as described below:</p>
+     * maximum level of post-processing. Values between 0 and 100 will have different effect
+     * depending on the extension type as described below:</p>
      * <ul>
      * <li>{@link android.hardware.camera2.CameraExtensionCharacteristics#EXTENSION_BOKEH BOKEH} -
      * the strength is expected to control the amount of blur.</li>
@@ -5711,7 +5710,9 @@
      * {@link android.hardware.camera2.CameraExtensionCharacteristics#getAvailableCaptureRequestKeys }.
      * The control is only defined and available to clients sending capture requests via
      * {@link android.hardware.camera2.CameraExtensionSession }.
-     * The default value is 100.</p>
+     * If the client doesn't specify the extension strength value, then a default value will
+     * be set by the extension. Clients can retrieve the default value by checking the
+     * corresponding capture result.</p>
      * <p><b>Range of valid values:</b><br>
      * 0 - 100</p>
      * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
diff --git a/core/java/android/hardware/display/VirtualDisplayConfig.java b/core/java/android/hardware/display/VirtualDisplayConfig.java
index a62d74e..22e3938 100644
--- a/core/java/android/hardware/display/VirtualDisplayConfig.java
+++ b/core/java/android/hardware/display/VirtualDisplayConfig.java
@@ -39,7 +39,8 @@
  * Holds configuration used to create {@link VirtualDisplay} instances.
  *
  * @see DisplayManager#createVirtualDisplay(VirtualDisplayConfig, Handler, VirtualDisplay.Callback)
- * @see MediaProjection#createVirtualDisplay
+ * @see MediaProjection#createVirtualDisplay(String, int, int, int, int, Surface,
+ * VirtualDisplay.Callback, Handler)
  */
 public final class VirtualDisplayConfig implements Parcelable {
 
diff --git a/core/java/android/hardware/usb/UsbConfiguration.java b/core/java/android/hardware/usb/UsbConfiguration.java
index 66269cb..b25f47b 100644
--- a/core/java/android/hardware/usb/UsbConfiguration.java
+++ b/core/java/android/hardware/usb/UsbConfiguration.java
@@ -172,7 +172,8 @@
             String name = in.readString();
             int attributes = in.readInt();
             int maxPower = in.readInt();
-            Parcelable[] interfaces = in.readParcelableArray(UsbInterface.class.getClassLoader());
+            Parcelable[] interfaces = in.readParcelableArray(
+                    UsbInterface.class.getClassLoader(), UsbInterface.class);
             UsbConfiguration configuration = new UsbConfiguration(id, name, attributes, maxPower);
             configuration.setInterfaces(interfaces);
             return configuration;
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index c111138..36199e5 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -642,6 +642,7 @@
                 try {
                     sTagService = sService.getNfcTagInterface();
                 } catch (RemoteException e) {
+                    sTagService = null;
                     Log.e(TAG, "could not retrieve NFC Tag service");
                     throw new UnsupportedOperationException();
                 }
@@ -650,12 +651,14 @@
                 try {
                     sNfcFCardEmulationService = sService.getNfcFCardEmulationInterface();
                 } catch (RemoteException e) {
+                    sNfcFCardEmulationService = null;
                     Log.e(TAG, "could not retrieve NFC-F card emulation service");
                     throw new UnsupportedOperationException();
                 }
                 try {
                     sCardEmulationService = sService.getNfcCardEmulationInterface();
                 } catch (RemoteException e) {
+                    sCardEmulationService = null;
                     Log.e(TAG, "could not retrieve card emulation service");
                     throw new UnsupportedOperationException();
                 }
@@ -839,30 +842,54 @@
         // assigning to sService is not thread-safe, but this is best-effort code
         // and on a well-behaved system should never happen
         sService = service;
-        try {
-            sTagService = service.getNfcTagInterface();
-        } catch (RemoteException ee) {
-            Log.e(TAG, "could not retrieve NFC tag service during service recovery");
-            // nothing more can be done now, sService is still stale, we'll hit
-            // this recovery path again later
-            return;
+        if (sHasNfcFeature) {
+            try {
+                sTagService = service.getNfcTagInterface();
+            } catch (RemoteException ee) {
+                sTagService = null;
+                Log.e(TAG, "could not retrieve NFC tag service during service recovery");
+                // nothing more can be done now, sService is still stale, we'll hit
+                // this recovery path again later
+                return;
+            }
         }
 
-        try {
-            sCardEmulationService = service.getNfcCardEmulationInterface();
-        } catch (RemoteException ee) {
-            Log.e(TAG, "could not retrieve NFC card emulation service during service recovery");
-        }
+        if (sHasCeFeature) {
+            try {
+                sCardEmulationService = service.getNfcCardEmulationInterface();
+            } catch (RemoteException ee) {
+                sCardEmulationService = null;
+                Log.e(TAG,
+                        "could not retrieve NFC card emulation service during service recovery");
+            }
 
-        try {
-            sNfcFCardEmulationService = service.getNfcFCardEmulationInterface();
-        } catch (RemoteException ee) {
-            Log.e(TAG, "could not retrieve NFC-F card emulation service during service recovery");
+            try {
+                sNfcFCardEmulationService = service.getNfcFCardEmulationInterface();
+            } catch (RemoteException ee) {
+                sNfcFCardEmulationService = null;
+                Log.e(TAG,
+                        "could not retrieve NFC-F card emulation service during service recovery");
+            }
         }
 
         return;
     }
 
+    private boolean isCardEmulationEnabled() {
+        if (sHasCeFeature) {
+            return (sCardEmulationService != null || sNfcFCardEmulationService != null);
+        }
+        return false;
+    }
+
+    private boolean isTagReadingEnabled() {
+        if (sHasNfcFeature) {
+            return sTagService != null;
+        }
+        return false;
+    }
+
+
     /**
      * Return true if this NFC Adapter has any features enabled.
      *
@@ -876,8 +903,9 @@
      * @return true if this NFC Adapter has any features enabled
      */
     public boolean isEnabled() {
+        boolean serviceState = false;
         try {
-            return sService.getState() == STATE_ON;
+            serviceState = sService.getState() == STATE_ON;
         } catch (RemoteException e) {
             attemptDeadServiceRecovery(e);
             // Try one more time
@@ -886,12 +914,12 @@
                 return false;
             }
             try {
-                return sService.getState() == STATE_ON;
+                serviceState = sService.getState() == STATE_ON;
             } catch (RemoteException ee) {
                 Log.e(TAG, "Failed to recover NFC Service.");
             }
-            return false;
         }
+        return serviceState && (isTagReadingEnabled() || isCardEmulationEnabled());
     }
 
     /**
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 538ed42..a07735e 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -178,23 +178,6 @@
     }
 
     /**
-     * Query to determine if the Game Mode has enabled ANGLE.
-     */
-    private boolean isAngleEnabledByGameMode(Context context, String packageName) {
-        try {
-            final boolean gameModeEnabledAngle =
-                    (mGameManager != null) && mGameManager.isAngleEnabled(packageName);
-            Log.v(TAG, "ANGLE GameManagerService for " + packageName + ": " + gameModeEnabledAngle);
-            return gameModeEnabledAngle;
-        } catch (SecurityException e) {
-            Log.e(TAG, "Caught exception while querying GameManagerService if ANGLE is enabled "
-                    + "for package: " + packageName);
-        }
-
-        return false;
-    }
-
-    /**
      * Query to determine the ANGLE driver choice.
      */
     private String queryAngleChoice(Context context, Bundle coreSettings,
@@ -422,8 +405,7 @@
      * 2) The per-application switch (i.e. Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS and
      *    Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES; which corresponds to the
      *    “angle_gl_driver_selection_pkgs” and “angle_gl_driver_selection_values” settings); if it
-     *    forces a choice;
-     * 3) Use ANGLE if isAngleEnabledByGameMode() returns true;
+     *    forces a choice.
      */
     private String queryAngleChoiceInternal(Context context, Bundle bundle,
                                                        String packageName) {
@@ -457,10 +439,6 @@
         Log.v(TAG, "  angle_gl_driver_selection_pkgs=" + optInPackages);
         Log.v(TAG, "  angle_gl_driver_selection_values=" + optInValues);
 
-        final String gameModeChoice = isAngleEnabledByGameMode(context, packageName)
-                                      ? ANGLE_GL_DRIVER_CHOICE_ANGLE
-                                      : ANGLE_GL_DRIVER_CHOICE_DEFAULT;
-
         // Make sure we have good settings to use
         if (optInPackages.size() == 0 || optInPackages.size() != optInValues.size()) {
             Log.v(TAG,
@@ -469,7 +447,7 @@
                             + optInPackages.size() + ", "
                         + "number of values: "
                             + optInValues.size());
-            return gameModeChoice;
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
 
         // See if this application is listed in the per-application settings list
@@ -477,7 +455,7 @@
 
         if (pkgIndex < 0) {
             Log.v(TAG, packageName + " is not listed in per-application setting");
-            return gameModeChoice;
+            return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
         }
         mAngleOptInIndex = pkgIndex;
 
@@ -491,11 +469,9 @@
             return ANGLE_GL_DRIVER_CHOICE_ANGLE;
         } else if (optInValue.equals(ANGLE_GL_DRIVER_CHOICE_NATIVE)) {
             return ANGLE_GL_DRIVER_CHOICE_NATIVE;
-        } else {
-            // The user either chose default or an invalid value; go with the default driver or what
-            // the game mode indicates
-            return gameModeChoice;
         }
+        // The user either chose default or an invalid value; go with the default driver.
+        return ANGLE_GL_DRIVER_CHOICE_DEFAULT;
     }
 
     /**
diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java
index 2584f04..22d6fcd 100644
--- a/core/java/android/os/Handler.java
+++ b/core/java/android/os/Handler.java
@@ -182,7 +182,7 @@
      *
      * Asynchronous messages represent interrupts or events that do not require global ordering
      * with respect to synchronous messages.  Asynchronous messages are not subject to
-     * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
+     * the synchronization barriers introduced by {@link MessageQueue#postSyncBarrier()}.
      *
      * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
      * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
@@ -203,7 +203,7 @@
      *
      * Asynchronous messages represent interrupts or events that do not require global ordering
      * with respect to synchronous messages.  Asynchronous messages are not subject to
-     * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.
+     * the synchronization barriers introduced by {@link MessageQueue#postSyncBarrier()}.
      *
      * @param callback The callback interface in which to handle messages, or null.
      * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
@@ -751,7 +751,7 @@
         MessageQueue queue = mQueue;
         if (queue == null) {
             RuntimeException e = new RuntimeException(
-                this + " sendMessageAtTime() called with no mQueue");
+                    this + " sendMessageAtFrontOfQueue() called with no mQueue");
             Log.w("Looper", e.getMessage(), e);
             return false;
         }
diff --git a/core/java/android/os/IHintSession.aidl b/core/java/android/os/IHintSession.aidl
index 0d1dde1..6b43e73 100644
--- a/core/java/android/os/IHintSession.aidl
+++ b/core/java/android/os/IHintSession.aidl
@@ -23,4 +23,5 @@
     void reportActualWorkDuration(in long[] actualDurationNanos, in long[] timeStampNanos);
     void close();
     void sendHint(int hint);
+    void setMode(int mode, boolean enabled);
 }
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 82cdd28..b74bb33 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -30,7 +30,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
-import java.util.List;
 import java.util.Locale;
 
 /**
@@ -152,25 +151,6 @@
     }
 
     /**
-     * Find the intersection between this LocaleList and another
-     * @return a String array of the Locales in both LocaleLists
-     * {@hide}
-     */
-    @NonNull
-    public String[] getIntersection(@NonNull LocaleList other) {
-        List<String> intersection = new ArrayList<>();
-        for (Locale l1 : mList) {
-            for (Locale l2 : other.mList) {
-                if (matchesLanguageAndScript(l2, l1)) {
-                    intersection.add(l1.toLanguageTag());
-                    break;
-                }
-            }
-        }
-        return intersection.toArray(new String[0]);
-    }
-
-    /**
      * Creates a new {@link LocaleList}.
      *
      * If two or more same locales are passed, the repeated locales will be dropped.
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index 69889c5..6ef1dc0 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -83,3 +83,6 @@
 # DDM Protocol
 per-file DdmSyncState.java = sanglardf@google.com, rpaquay@google.com
 per-file DdmSyncStageUpdater.java = sanglardf@google.com, rpaquay@google.com
+
+# PerformanceHintManager
+per-file PerformanceHintManager.java = file:/ADPF_OWNERS
diff --git a/core/java/android/os/PerformanceHintManager.java b/core/java/android/os/PerformanceHintManager.java
index 977ef60..cbc9213 100644
--- a/core/java/android/os/PerformanceHintManager.java
+++ b/core/java/android/os/PerformanceHintManager.java
@@ -51,19 +51,33 @@
     }
 
     /**
+     * Get preferred update rate information for this device.
+     *
+     * @return the preferred update rate supported by device software
+     */
+    public long getPreferredUpdateRateNanos() {
+        return nativeGetPreferredUpdateRateNanos(mNativeManagerPtr);
+    }
+
+    /**
      * Creates a {@link Session} for the given set of threads and sets their initial target work
      * duration.
      *
      * @param tids The list of threads to be associated with this session. They must be part of
-     *     this process' thread group.
+     *     this process' thread group
      * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new
-     *     session.
+     *     session
      * @return the new session if it is supported on this device, null if hint session is not
-     *     supported on this device.
+     *     supported on this device or the tid doesn't belong to the application
+     * @throws IllegalArgumentException if the thread id list is empty, or
+     *                                  initialTargetWorkDurationNanos is non-positive
      */
     @Nullable
     public Session createHintSession(@NonNull int[] tids, long initialTargetWorkDurationNanos) {
-        Preconditions.checkNotNull(tids, "tids cannot be null");
+        Objects.requireNonNull(tids, "tids cannot be null");
+        if (tids.length == 0) {
+            throw new IllegalArgumentException("thread id list can't be empty.");
+        }
         Preconditions.checkArgumentPositive(initialTargetWorkDurationNanos,
                 "the hint target duration should be positive.");
         long nativeSessionPtr = nativeCreateSession(mNativeManagerPtr, tids,
@@ -73,35 +87,22 @@
     }
 
     /**
-     * Get preferred update rate information for this device.
-     *
-     * @return the preferred update rate supported by device software.
-     */
-    public long getPreferredUpdateRateNanos() {
-        return nativeGetPreferredUpdateRateNanos(mNativeManagerPtr);
-    }
-
-    /**
      * A Session represents a group of threads with an inter-related workload such that hints for
      * their performance should be considered as a unit. The threads in a given session should be
-     * long-life and not created or destroyed dynamically.
+     * long-lived and not created or destroyed dynamically.
      *
-     * <p>Each session is expected to have a periodic workload with a target duration for each
-     * cycle. The cycle duration is likely greater than the target work duration to allow other
-     * parts of the pipeline to run within the available budget. For example, a renderer thread may
-     * work at 60hz in order to produce frames at the display's frame but have a target work
-     * duration of only 6ms.</p>
+     * The work duration API can be used with periodic workloads to dynamically adjust thread
+     * performance and keep the work on schedule while optimizing the available power budget.
+     * When using the work duration API, the starting target duration should be specified
+     * while creating the session, but can later be adjusted with
+     * {@link #updateTargetWorkDuration(long)}. While using the work duration API, the client is be
+     * expected to call {@link #reportActualWorkDuration(long)} each cycle to report the actual
+     * time taken to complete to the system.
      *
-     * <p>Any call in this class will change its internal data, so you must do your own thread
-     * safety to protect from racing.</p>
+     * Any call in this class will change its internal data, so you must do your own thread
+     * safety to protect from racing.
      *
-     * <p>Note that the target work duration can be {@link #updateTargetWorkDuration(long) updated}
-     * if workloads change.</p>
-     *
-     * <p>After each cycle of work, the client is expected to
-     * {@link #reportActualWorkDuration(long) report} the actual time taken to complete.</p>
-     *
-     * <p>All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.</p>
+     * All timings should be in {@link SystemClock#elapsedRealtimeNanos()}.
      */
     public static class Session implements Closeable {
         private long mNativeSessionPtr;
@@ -181,9 +182,9 @@
         /**
          * Reports the actual duration for the last cycle of work.
          *
-         * <p>The system will attempt to adjust the core placement of the threads within the thread
+         * The system will attempt to adjust the core placement of the threads within the thread
          * group and/or the frequency of the core on which they are run to bring the actual duration
-         * close to the target duration.</p>
+         * close to the target duration.
          *
          * @param actualDurationNanos how long the thread group took to complete its last task in
          *     nanoseconds
@@ -197,7 +198,7 @@
         /**
          * Ends the current hint session.
          *
-         * <p>Once called, you should not call anything else on this object.</p>
+         * Once called, you should not call anything else on this object.
          */
         public void close() {
             if (mNativeSessionPtr != 0) {
@@ -209,7 +210,7 @@
         /**
          * Sends performance hints to inform the hint session of changes in the workload.
          *
-         * @param hint The hint to send to the session.
+         * @param hint The hint to send to the session
          *
          * @hide
          */
@@ -225,16 +226,26 @@
         }
 
         /**
+         * This tells the session that these threads can be
+         * safely scheduled to prefer power efficiency over performance.
+         *
+         * @param enabled The flag that sets whether this session uses power-efficient scheduling.
+         */
+        public void setPreferPowerEfficiency(boolean enabled) {
+            nativeSetPreferPowerEfficiency(mNativeSessionPtr, enabled);
+        }
+
+        /**
          * Set a list of threads to the performance hint session. This operation will replace
          * the current list of threads with the given list of threads.
          * Note that this is not an oneway method.
          *
          * @param tids The list of threads to be associated with this session. They must be
-         *     part of this app's thread group.
+         *     part of this app's thread group
          *
-         * @throws IllegalStateException if the hint session is not in the foreground.
-         * @throws IllegalArgumentException if the thread id list is empty.
-         * @throws SecurityException if any thread id doesn't belong to the application.
+         * @throws IllegalStateException if the hint session is not in the foreground
+         * @throws IllegalArgumentException if the thread id list is empty
+         * @throws SecurityException if any thread id doesn't belong to the application
          */
         public void setThreads(@NonNull int[] tids) {
             if (mNativeSessionPtr == 0) {
@@ -270,4 +281,6 @@
     private static native void nativeCloseSession(long nativeSessionPtr);
     private static native void nativeSendHint(long nativeSessionPtr, int hint);
     private static native void nativeSetThreads(long nativeSessionPtr, int[] tids);
+    private static native void nativeSetPreferPowerEfficiency(long nativeSessionPtr,
+            boolean enabled);
 }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index c6b9d20..d676509 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -33,7 +33,6 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.service.dreams.Sandman;
-import android.sysprop.InitProperties;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -868,6 +867,8 @@
 
     /**
      * The 'reason' value used for rebooting userspace.
+     *
+     * @deprecated userspace reboot is not supported
      * @hide
      */
     @SystemApi
@@ -1824,16 +1825,18 @@
      * <p>This method exists solely for the sake of re-using same logic between {@code PowerManager}
      * and {@code PowerManagerService}.
      *
+     * @deprecated TODO(b/292469129): remove this method.
      * @hide
      */
     public static boolean isRebootingUserspaceSupportedImpl() {
-        return InitProperties.is_userspace_reboot_supported().orElse(false);
+        return false;
     }
 
     /**
      * Returns {@code true} if this device supports rebooting userspace.
+     *
+     * @deprecated userspace reboot is deprecated, this method always returns {@code false}.
      */
-    // TODO(b/138605180): add link to documentation once it's ready.
     public boolean isRebootingUserspaceSupported() {
         return isRebootingUserspaceSupportedImpl();
     }
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 180735b..81d4e3a 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -1462,7 +1462,7 @@
 
         if (Build.IS_USER || DISABLE || SystemProperties.getBoolean(DISABLE_PROPERTY, false)) {
             // Detect nothing extra
-        } else if (Build.IS_USERDEBUG) {
+        } else if (Build.IS_USERDEBUG || Build.IS_ENG) {
             // Detect everything in bundled apps
             if (isBundledSystemApp(ai)) {
                 builder.detectAll();
@@ -1470,14 +1470,9 @@
                 if (SystemProperties.getBoolean(VISUAL_PROPERTY, false)) {
                     builder.penaltyFlashScreen();
                 }
-            }
-        } else if (Build.IS_ENG) {
-            // Detect everything in bundled apps
-            if (isBundledSystemApp(ai)) {
-                builder.detectAll();
-                builder.penaltyDropBox();
-                builder.penaltyLog();
-                builder.penaltyFlashScreen();
+                if (Build.IS_ENG) {
+                    builder.penaltyLog();
+                }
             }
         }
 
diff --git a/core/java/android/os/TEST_MAPPING b/core/java/android/os/TEST_MAPPING
index dae9b5e..60622f18 100644
--- a/core/java/android/os/TEST_MAPPING
+++ b/core/java/android/os/TEST_MAPPING
@@ -99,13 +99,10 @@
         "BatteryStats[^/]*\\.java",
         "BatteryUsageStats[^/]*\\.java",
         "PowerComponents\\.java",
+        "PowerMonitor[^/]*\\.java",
         "[^/]*BatteryConsumer[^/]*\\.java"
       ],
-      "name": "FrameworksServicesTests",
-      "options": [
-        { "include-filter": "com.android.server.power.stats" },
-        { "exclude-filter": "com.android.server.power.stats.BatteryStatsTests" }
-      ]
+      "name": "PowerStatsTests"
     },
     {
       "file_patterns": [
diff --git a/core/java/android/os/UserHandle.java b/core/java/android/os/UserHandle.java
index 0527ed6..cac7f3b 100644
--- a/core/java/android/os/UserHandle.java
+++ b/core/java/android/os/UserHandle.java
@@ -125,7 +125,8 @@
     public static final int MIN_SECONDARY_USER_ID = 10;
 
     /** @hide */
-    public static final int MAX_SECONDARY_USER_ID = Integer.MAX_VALUE / UserHandle.PER_USER_RANGE;
+    public static final int MAX_SECONDARY_USER_ID =
+            Integer.MAX_VALUE / UserHandle.PER_USER_RANGE - 1;
 
     /**
      * (Arbitrary) user handle cache size.
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 2abf02e..2e62e03 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -11836,6 +11836,13 @@
                 "accessibility_display_magnification_edge_haptic_enabled";
 
         /**
+         * If 1, DND default allowed packages have been updated
+         *
+         *  @hide
+         */
+        public static final String DND_CONFIGS_MIGRATED = "dnd_settings_migrated";
+
+        /**
          * These entries are considered common between the personal and the managed profile,
          * since the managed profile doesn't get to change them.
          */
@@ -17096,6 +17103,12 @@
                 ArrayMap<String, Integer> readableKeysWithMaxTargetSdk) {
             getPublicSettingsForClass(Global.class, allKeys, readableKeys,
                     readableKeysWithMaxTargetSdk);
+            // Add Global.Wearable keys on watches.
+            if (ActivityThread.currentApplication().getApplicationContext().getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+                getPublicSettingsForClass(Global.Wearable.class, allKeys, readableKeys,
+                        readableKeysWithMaxTargetSdk);
+            }
         }
 
         /**
@@ -18280,7 +18293,7 @@
          * Settings migrated from Wear OS settings provider.
          * @hide
          */
-        public static class Wearable {
+        public static final class Wearable extends NameValueTable {
             /**
              * Whether the user has any pay tokens on their watch.
              * @hide
@@ -18603,6 +18616,7 @@
              * What OS does paired device has.
              * @hide
              */
+            @Readable
             public static final String PAIRED_DEVICE_OS_TYPE = "paired_device_os_type";
 
             // Possible values of PAIRED_DEVICE_OS_TYPE
@@ -19175,6 +19189,14 @@
              * @hide
              */
             public static final String WEAR_LAUNCHER_UI_MODE = "wear_launcher_ui_mode";
+
+            /** Whether Wear Power Anomaly Service is enabled.
+             *
+             * (0 = false, 1 = true)
+             * @hide
+             */
+            public static final String WEAR_POWER_ANOMALY_SERVICE_ENABLED =
+                    "wear_power_anomaly_service_enabled";
         }
     }
 
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 59b945c..cf3707b 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4905,6 +4905,16 @@
          */
         public static final String COLUMN_SATELLITE_ENABLED = "satellite_enabled";
 
+        /**
+         * TelephonyProvider column name for satellite attach enabled for carrier. The value of this
+         * column is set based on user settings.
+         * By default, it's disabled.
+         *
+         * @hide
+         */
+        public static final String COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER =
+                "satellite_attach_enabled_for_carrier";
+
         /** All columns in {@link SimInfo} table. */
         private static final List<String> ALL_COLUMNS = List.of(
                 COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
@@ -4974,7 +4984,8 @@
                 COLUMN_USAGE_SETTING,
                 COLUMN_TP_MESSAGE_REF,
                 COLUMN_USER_HANDLE,
-                COLUMN_SATELLITE_ENABLED
+                COLUMN_SATELLITE_ENABLED,
+                COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER
         );
 
         /**
diff --git a/core/java/android/service/gatekeeper/OWNERS b/core/java/android/service/gatekeeper/OWNERS
index 2ca52cd..7c4f285 100644
--- a/core/java/android/service/gatekeeper/OWNERS
+++ b/core/java/android/service/gatekeeper/OWNERS
@@ -1,3 +1,2 @@
-swillden@google.com
-jdanis@google.com
-jbires@google.com
+include platform/system/gatekeeper:/OWNERS
+include /services/core/java/com/android/server/locksettings/OWNERS
diff --git a/core/java/android/service/rotationresolver/OWNERS b/core/java/android/service/rotationresolver/OWNERS
index e381d17..5b57fc7 100644
--- a/core/java/android/service/rotationresolver/OWNERS
+++ b/core/java/android/service/rotationresolver/OWNERS
@@ -2,7 +2,6 @@
 
 asalo@google.com
 augale@google.com
-bquezada@google.com
 eejiang@google.com
 payamp@google.com
 siddikap@google.com
diff --git a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
deleted file mode 100644
index ad73a53..0000000
--- a/core/java/android/service/selectiontoolbar/DefaultSelectionToolbarRenderService.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.selectiontoolbar;
-
-import static android.view.selectiontoolbar.SelectionToolbarManager.ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR;
-import static android.view.selectiontoolbar.SelectionToolbarManager.NO_TOOLBAR_ID;
-
-import android.util.Pair;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.view.selectiontoolbar.ShowInfo;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.UUID;
-
-/**
- * The default implementation of {@link SelectionToolbarRenderService}.
- *
- * <p><b>NOTE:<b/> The requests are handled on the service main thread.
- *
- *  @hide
- */
-// TODO(b/214122495): fix class not found then move to system service folder
-public final class DefaultSelectionToolbarRenderService extends SelectionToolbarRenderService {
-
-    private static final String TAG = "DefaultSelectionToolbarRenderService";
-
-    // TODO(b/215497659): handle remove if the client process dies.
-    // Only show one toolbar, dismiss the old ones and remove from cache
-    private final SparseArray<Pair<Long, RemoteSelectionToolbar>> mToolbarCache =
-            new SparseArray<>();
-
-    /**
-     * Only allow one package to create one toolbar.
-     */
-    private boolean canShowToolbar(int uid, ShowInfo showInfo) {
-        if (showInfo.getWidgetToken() != NO_TOOLBAR_ID) {
-            return true;
-        }
-        return mToolbarCache.indexOfKey(uid) < 0;
-    }
-
-    @Override
-    public void onShow(int callingUid, ShowInfo showInfo,
-            SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper) {
-        if (!canShowToolbar(callingUid, showInfo)) {
-            Slog.e(TAG, "Do not allow multiple toolbar for the app.");
-            callbackWrapper.onError(ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR);
-            return;
-        }
-        long widgetToken = showInfo.getWidgetToken() == NO_TOOLBAR_ID
-                ? UUID.randomUUID().getMostSignificantBits()
-                : showInfo.getWidgetToken();
-
-        if (mToolbarCache.indexOfKey(callingUid) < 0) {
-            RemoteSelectionToolbar toolbar = new RemoteSelectionToolbar(this,
-                    widgetToken, showInfo,
-                    callbackWrapper, this::transferTouch);
-            mToolbarCache.put(callingUid, new Pair<>(widgetToken, toolbar));
-        }
-        Slog.v(TAG, "onShow() for " + widgetToken);
-        Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.get(callingUid);
-        if (toolbarPair.first == widgetToken) {
-            toolbarPair.second.show(showInfo);
-        } else {
-            Slog.w(TAG, "onShow() for unknown " + widgetToken);
-        }
-    }
-
-    @Override
-    public void onHide(long widgetToken) {
-        RemoteSelectionToolbar toolbar = getRemoteSelectionToolbarByTokenLocked(widgetToken);
-        if (toolbar != null) {
-            Slog.v(TAG, "onHide() for " + widgetToken);
-            toolbar.hide(widgetToken);
-        }
-    }
-
-    @Override
-    public void onDismiss(long widgetToken) {
-        RemoteSelectionToolbar toolbar = getRemoteSelectionToolbarByTokenLocked(widgetToken);
-        if (toolbar != null) {
-            Slog.v(TAG, "onDismiss() for " + widgetToken);
-            toolbar.dismiss(widgetToken);
-            removeRemoteSelectionToolbarByTokenLocked(widgetToken);
-        }
-    }
-
-    @Override
-    public void onToolbarShowTimeout(int callingUid) {
-        Slog.w(TAG, "onToolbarShowTimeout for callingUid = " + callingUid);
-        Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.get(callingUid);
-        if (toolbarPair != null) {
-            RemoteSelectionToolbar remoteToolbar = toolbarPair.second;
-            remoteToolbar.dismiss(toolbarPair.first);
-            remoteToolbar.onToolbarShowTimeout();
-            mToolbarCache.remove(callingUid);
-        }
-    }
-
-    private RemoteSelectionToolbar getRemoteSelectionToolbarByTokenLocked(long widgetToken) {
-        for (int i = 0; i < mToolbarCache.size(); i++) {
-            Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.valueAt(i);
-            if (toolbarPair.first == widgetToken) {
-                return toolbarPair.second;
-            }
-        }
-        return null;
-    }
-
-    private void removeRemoteSelectionToolbarByTokenLocked(long widgetToken) {
-        for (int i = 0; i < mToolbarCache.size(); i++) {
-            Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.valueAt(i);
-            if (toolbarPair.first == widgetToken) {
-                mToolbarCache.remove(mToolbarCache.keyAt(i));
-                return;
-            }
-        }
-    }
-
-    @Override
-    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        int size = mToolbarCache.size();
-        pw.print("number selectionToolbar: "); pw.println(size);
-        String pfx = "  ";
-        for (int i = 0; i < size; i++) {
-            pw.print("#"); pw.println(i);
-            int callingUid = mToolbarCache.keyAt(i);
-            pw.print(pfx); pw.print("callingUid: "); pw.println(callingUid);
-            Pair<Long, RemoteSelectionToolbar> toolbarPair = mToolbarCache.valueAt(i);
-            RemoteSelectionToolbar selectionToolbar = toolbarPair.second;
-            pw.print(pfx); pw.print("selectionToolbar: ");
-            selectionToolbar.dump(pfx, pw);
-            pw.println();
-        }
-    }
-}
-
diff --git a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java b/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
deleted file mode 100644
index adc9251..0000000
--- a/core/java/android/service/selectiontoolbar/FloatingToolbarRoot.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.selectiontoolbar;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.widget.LinearLayout;
-
-import java.io.PrintWriter;
-
-/**
- * This class is the root view for the selection toolbar. It is responsible for
- * detecting the click on the item and to also transfer input focus to the application.
- *
- * @hide
- */
-@SuppressLint("ViewConstructor")
-public class FloatingToolbarRoot extends LinearLayout {
-
-    private static final boolean DEBUG = false;
-    private static final String TAG = "FloatingToolbarRoot";
-
-    private final IBinder mTargetInputToken;
-    private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener;
-    private final Rect mContentRect = new Rect();
-
-    private int mLastDownX = -1;
-    private int mLastDownY = -1;
-
-    public FloatingToolbarRoot(Context context, IBinder targetInputToken,
-            SelectionToolbarRenderService.TransferTouchListener transferTouchListener) {
-        super(context);
-        mTargetInputToken = targetInputToken;
-        mTransferTouchListener = transferTouchListener;
-        setFocusable(false);
-    }
-
-    /**
-     * Sets the Rect that shows the selection toolbar content.
-     */
-    public void setContentRect(Rect contentRect) {
-        mContentRect.set(contentRect);
-    }
-
-    @Override
-    @SuppressLint("ClickableViewAccessibility")
-    public boolean dispatchTouchEvent(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
-            mLastDownX = (int) event.getX();
-            mLastDownY = (int) event.getY();
-            if (DEBUG) {
-                Log.d(TAG, "downX=" + mLastDownX + " downY=" + mLastDownY);
-            }
-            // TODO(b/215497659): Check FLAG_WINDOW_IS_PARTIALLY_OBSCURED
-            if (!mContentRect.contains(mLastDownX, mLastDownY)) {
-                if (DEBUG) {
-                    Log.d(TAG, "Transfer touch focus to application.");
-                }
-                mTransferTouchListener.onTransferTouch(getViewRootImpl().getInputToken(),
-                        mTargetInputToken);
-            }
-        }
-        return super.dispatchTouchEvent(event);
-    }
-
-    void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.println("FloatingToolbarRoot:");
-        pw.print(prefix + "  "); pw.print("last down X: "); pw.println(mLastDownX);
-        pw.print(prefix + "  "); pw.print("last down Y: "); pw.println(mLastDownY);
-    }
-}
diff --git a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl b/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl
deleted file mode 100644
index 79281b8..0000000
--- a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderService.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.selectiontoolbar;
-
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ShowInfo;
-
-/**
- * The service to render the selection toolbar menus.
- *
- * @hide
- */
-oneway interface ISelectionToolbarRenderService {
-    void onConnected(in IBinder callback);
-    void onShow(int callingUid, in ShowInfo showInfo, in ISelectionToolbarCallback callback);
-    void onHide(long widgetToken);
-    void onDismiss(int callingUid, long widgetToken);
-}
diff --git a/core/java/android/service/selectiontoolbar/OWNERS b/core/java/android/service/selectiontoolbar/OWNERS
deleted file mode 100644
index 5500b92..0000000
--- a/core/java/android/service/selectiontoolbar/OWNERS
+++ /dev/null
@@ -1,10 +0,0 @@
-# Bug component: 709498
-
-augale@google.com
-joannechung@google.com
-licha@google.com
-lpeter@google.com
-svetoslavganov@google.com
-toki@google.com
-tonymak@google.com
-tymtsai@google.com
\ No newline at end of file
diff --git a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java b/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
deleted file mode 100644
index 59e3a5e..0000000
--- a/core/java/android/service/selectiontoolbar/RemoteSelectionToolbar.java
+++ /dev/null
@@ -1,1398 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.selectiontoolbar;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.AnimatedVectorDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.IBinder;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.Size;
-import android.view.ContextThemeWrapper;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-import android.view.animation.Animation;
-import android.view.animation.AnimationSet;
-import android.view.animation.AnimationUtils;
-import android.view.animation.Interpolator;
-import android.view.animation.Transformation;
-import android.view.selectiontoolbar.ShowInfo;
-import android.view.selectiontoolbar.ToolbarMenuItem;
-import android.view.selectiontoolbar.WidgetInfo;
-import android.widget.ArrayAdapter;
-import android.widget.ImageButton;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.TextView;
-
-import com.android.internal.R;
-import com.android.internal.util.Preconditions;
-import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
-
-import java.io.PrintWriter;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * This class is responsible for rendering/animation of the selection toolbar in the remote
- * system process. It holds 2 panels (i.e. main panel and overflow panel) and an overflow
- * button to transition between panels.
- *
- *  @hide
- */
-// TODO(b/215497659): share code with LocalFloatingToolbarPopup
-final class RemoteSelectionToolbar {
-    private static final String TAG = "RemoteSelectionToolbar";
-
-    /* Minimum and maximum number of items allowed in the overflow. */
-    private static final int MIN_OVERFLOW_SIZE = 2;
-    private static final int MAX_OVERFLOW_SIZE = 4;
-
-    private final Context mContext;
-
-    /* Margins between the popup window and its content. */
-    private final int mMarginHorizontal;
-    private final int mMarginVertical;
-
-    /* View components */
-    private final ViewGroup mContentContainer;  // holds all contents.
-    private final ViewGroup mMainPanel;  // holds menu items that are initially displayed.
-    // holds menu items hidden in the overflow.
-    private final OverflowPanel mOverflowPanel;
-    private final ImageButton mOverflowButton;  // opens/closes the overflow.
-    /* overflow button drawables. */
-    private final Drawable mArrow;
-    private final Drawable mOverflow;
-    private final AnimatedVectorDrawable mToArrow;
-    private final AnimatedVectorDrawable mToOverflow;
-
-    private final OverflowPanelViewHelper mOverflowPanelViewHelper;
-
-    /* Animation interpolators. */
-    private final Interpolator mLogAccelerateInterpolator;
-    private final Interpolator mFastOutSlowInInterpolator;
-    private final Interpolator mLinearOutSlowInInterpolator;
-    private final Interpolator mFastOutLinearInInterpolator;
-
-    /* Animations. */
-    private final AnimatorSet mShowAnimation;
-    private final AnimatorSet mDismissAnimation;
-    private final AnimatorSet mHideAnimation;
-    private final AnimationSet mOpenOverflowAnimation;
-    private final AnimationSet mCloseOverflowAnimation;
-    private final Animation.AnimationListener mOverflowAnimationListener;
-
-    private final Rect mViewPortOnScreen = new Rect();  // portion of screen we can draw in.
-
-    private final int mLineHeight;
-    private final int mIconTextSpacing;
-
-    private final long mSelectionToolbarToken;
-    private IBinder mHostInputToken;
-    private final SelectionToolbarRenderService.RemoteCallbackWrapper mCallbackWrapper;
-    private final SelectionToolbarRenderService.TransferTouchListener mTransferTouchListener;
-    private int mPopupWidth;
-    private int mPopupHeight;
-    // Coordinates to show the toolbar relative to the specified view port
-    private final Point mRelativeCoordsForToolbar = new Point();
-    private List<ToolbarMenuItem> mMenuItems;
-    private SurfaceControlViewHost mSurfaceControlViewHost;
-    private SurfaceControlViewHost.SurfacePackage mSurfacePackage;
-
-    /**
-     * @see OverflowPanelViewHelper#preparePopupContent().
-     */
-    private final Runnable mPreparePopupContentRTLHelper = new Runnable() {
-        @Override
-        public void run() {
-            setPanelsStatesAtRestingPosition();
-            mContentContainer.setAlpha(1);
-        }
-    };
-
-    private boolean mDismissed = true; // tracks whether this popup is dismissed or dismissing.
-    private boolean mHidden; // tracks whether this popup is hidden or hiding.
-
-    /* Calculated sizes for panels and overflow button. */
-    private final Size mOverflowButtonSize;
-    private Size mOverflowPanelSize;  // Should be null when there is no overflow.
-    private Size mMainPanelSize;
-
-    /* Menu items and click listeners */
-    private final View.OnClickListener mMenuItemButtonOnClickListener;
-
-    private boolean mOpenOverflowUpwards;  // Whether the overflow opens upwards or downwards.
-    private boolean mIsOverflowOpen;
-
-    private int mTransitionDurationScale;  // Used to scale the toolbar transition duration.
-
-    private final Rect mPreviousContentRect = new Rect();
-
-    private final Rect mTempContentRect = new Rect();
-    private final Rect mTempContentRectForRoot = new Rect();
-    private final int[] mTempCoords = new int[2];
-
-    RemoteSelectionToolbar(Context context, long selectionToolbarToken, ShowInfo showInfo,
-            SelectionToolbarRenderService.RemoteCallbackWrapper callbackWrapper,
-            SelectionToolbarRenderService.TransferTouchListener transferTouchListener) {
-        mContext = applyDefaultTheme(context, showInfo.isIsLightTheme());
-        mSelectionToolbarToken = selectionToolbarToken;
-        mCallbackWrapper = callbackWrapper;
-        mTransferTouchListener = transferTouchListener;
-        mHostInputToken = showInfo.getHostInputToken();
-        mContentContainer = createContentContainer(mContext);
-        mMarginHorizontal = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
-        mMarginVertical = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.floating_toolbar_vertical_margin);
-        mLineHeight = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.floating_toolbar_height);
-        mIconTextSpacing = mContext.getResources()
-                .getDimensionPixelSize(R.dimen.floating_toolbar_icon_text_spacing);
-
-        // Interpolators
-        mLogAccelerateInterpolator = new LogAccelerateInterpolator();
-        mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(
-                mContext, android.R.interpolator.fast_out_slow_in);
-        mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(
-                mContext, android.R.interpolator.linear_out_slow_in);
-        mFastOutLinearInInterpolator = AnimationUtils.loadInterpolator(
-                mContext, android.R.interpolator.fast_out_linear_in);
-
-        // Drawables. Needed for views.
-        mArrow = mContext.getResources()
-                .getDrawable(R.drawable.ft_avd_tooverflow, mContext.getTheme());
-        mArrow.setAutoMirrored(true);
-        mOverflow = mContext.getResources()
-                .getDrawable(R.drawable.ft_avd_toarrow, mContext.getTheme());
-        mOverflow.setAutoMirrored(true);
-        mToArrow = (AnimatedVectorDrawable) mContext.getResources()
-                .getDrawable(R.drawable.ft_avd_toarrow_animation, mContext.getTheme());
-        mToArrow.setAutoMirrored(true);
-        mToOverflow = (AnimatedVectorDrawable) mContext.getResources()
-                .getDrawable(R.drawable.ft_avd_tooverflow_animation, mContext.getTheme());
-        mToOverflow.setAutoMirrored(true);
-
-        // Views
-        mOverflowButton = createOverflowButton();
-        mOverflowButtonSize = measure(mOverflowButton);
-        mMainPanel = createMainPanel();
-        mOverflowPanelViewHelper = new OverflowPanelViewHelper(mContext, mIconTextSpacing);
-        mOverflowPanel = createOverflowPanel();
-
-        // Animation. Need views.
-        mOverflowAnimationListener = createOverflowAnimationListener();
-        mOpenOverflowAnimation = new AnimationSet(true);
-        mOpenOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
-        mCloseOverflowAnimation = new AnimationSet(true);
-        mCloseOverflowAnimation.setAnimationListener(mOverflowAnimationListener);
-        mShowAnimation = createEnterAnimation(mContentContainer,
-                new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        updateFloatingToolbarRootContentRect();
-                    }
-                });
-        mDismissAnimation = createExitAnimation(
-                mContentContainer,
-                150,  // startDelay
-                new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        // TODO(b/215497659): should dismiss window after animation
-                        mContentContainer.removeAllViews();
-                        mSurfaceControlViewHost.release();
-                        mSurfaceControlViewHost = null;
-                        mSurfacePackage = null;
-                    }
-                });
-        mHideAnimation = createExitAnimation(
-                mContentContainer,
-                0,  // startDelay
-                null); // TODO(b/215497659): should handle hide after animation
-        mMenuItemButtonOnClickListener = v -> {
-            Object tag = v.getTag();
-            if (!(tag instanceof ToolbarMenuItem)) {
-                return;
-            }
-            mCallbackWrapper.onMenuItemClicked((ToolbarMenuItem) tag);
-        };
-    }
-
-    private void updateFloatingToolbarRootContentRect() {
-        if (mSurfaceControlViewHost == null) {
-            return;
-        }
-        final FloatingToolbarRoot root = (FloatingToolbarRoot) mSurfaceControlViewHost.getView();
-        mContentContainer.getLocationOnScreen(mTempCoords);
-        int contentLeft = mTempCoords[0];
-        int contentTop = mTempCoords[1];
-        mTempContentRectForRoot.set(contentLeft, contentTop,
-                contentLeft + mContentContainer.getWidth(),
-                contentTop + mContentContainer.getHeight());
-        root.setContentRect(mTempContentRectForRoot);
-    }
-
-    private WidgetInfo createWidgetInfo() {
-        mTempContentRect.set(mRelativeCoordsForToolbar.x, mRelativeCoordsForToolbar.y,
-                mRelativeCoordsForToolbar.x + mPopupWidth,
-                mRelativeCoordsForToolbar.y + mPopupHeight);
-        return new WidgetInfo(mSelectionToolbarToken, mTempContentRect, getSurfacePackage());
-    }
-
-    private SurfaceControlViewHost.SurfacePackage getSurfacePackage() {
-        if (mSurfaceControlViewHost == null) {
-            final FloatingToolbarRoot contentHolder = new FloatingToolbarRoot(mContext,
-                    mHostInputToken, mTransferTouchListener);
-            contentHolder.addView(mContentContainer);
-            mSurfaceControlViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(),
-                    mHostInputToken, "RemoteSelectionToolbar");
-            mSurfaceControlViewHost.setView(contentHolder, mPopupWidth, mPopupHeight);
-        }
-        if (mSurfacePackage == null) {
-            mSurfacePackage = mSurfaceControlViewHost.getSurfacePackage();
-        }
-        return mSurfacePackage;
-    }
-
-    private void layoutMenuItems(
-            List<ToolbarMenuItem> menuItems,
-            int suggestedWidth) {
-        cancelOverflowAnimations();
-        clearPanels();
-
-        menuItems = layoutMainPanelItems(menuItems, getAdjustedToolbarWidth(suggestedWidth));
-        if (!menuItems.isEmpty()) {
-            // Add remaining items to the overflow.
-            layoutOverflowPanelItems(menuItems);
-        }
-        updatePopupSize();
-    }
-
-    public void onToolbarShowTimeout() {
-        mCallbackWrapper.onToolbarShowTimeout();
-    }
-
-    /**
-     * Show the specified selection toolbar.
-     */
-    public void show(ShowInfo showInfo) {
-        debugLog("show() for " + showInfo);
-
-        mMenuItems = showInfo.getMenuItems();
-        mViewPortOnScreen.set(showInfo.getViewPortOnScreen());
-
-        debugLog("show(): layoutRequired=" + showInfo.isLayoutRequired());
-        if (showInfo.isLayoutRequired()) {
-            layoutMenuItems(mMenuItems, showInfo.getSuggestedWidth());
-        }
-        Rect contentRect = showInfo.getContentRect();
-        if (!isShowing()) {
-            show(contentRect);
-        } else if (!mPreviousContentRect.equals(contentRect)) {
-            updateCoordinates(contentRect);
-        }
-        mPreviousContentRect.set(contentRect);
-    }
-
-    private void show(Rect contentRectOnScreen) {
-        Objects.requireNonNull(contentRectOnScreen);
-
-        mHidden = false;
-        mDismissed = false;
-        cancelDismissAndHideAnimations();
-        cancelOverflowAnimations();
-        refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
-        preparePopupContent();
-        mCallbackWrapper.onShown(createWidgetInfo());
-        // TODO(b/215681595): Use Choreographer to coordinate for show between different thread
-        mShowAnimation.start();
-    }
-
-    /**
-     * Dismiss the specified selection toolbar.
-     */
-    public void dismiss(long floatingToolbarToken) {
-        debugLog("dismiss for " + floatingToolbarToken);
-        if (mDismissed) {
-            return;
-        }
-        mHidden = false;
-        mDismissed = true;
-
-        mHideAnimation.cancel();
-        mDismissAnimation.start();
-    }
-
-    /**
-     * Hide the specified selection toolbar.
-     */
-    public void hide(long floatingToolbarToken) {
-        debugLog("hide for " + floatingToolbarToken);
-        if (!isShowing()) {
-            return;
-        }
-
-        mHidden = true;
-        mHideAnimation.start();
-    }
-
-    public boolean isShowing() {
-        return !mDismissed && !mHidden;
-    }
-
-    private void updateCoordinates(Rect contentRectOnScreen) {
-        Objects.requireNonNull(contentRectOnScreen);
-
-        if (!isShowing()) {
-            return;
-        }
-        cancelOverflowAnimations();
-        refreshCoordinatesAndOverflowDirection(contentRectOnScreen);
-        preparePopupContent();
-        WidgetInfo widgetInfo = createWidgetInfo();
-        mSurfaceControlViewHost.relayout(mPopupWidth, mPopupHeight);
-        mCallbackWrapper.onWidgetUpdated(widgetInfo);
-    }
-
-    private void refreshCoordinatesAndOverflowDirection(Rect contentRectOnScreen) {
-        // Initialize x ensuring that the toolbar isn't rendered behind the nav bar in
-        // landscape.
-        final int x = Math.min(
-                contentRectOnScreen.centerX() - mPopupWidth / 2,
-                mViewPortOnScreen.right - mPopupWidth);
-
-        final int y;
-
-        final int availableHeightAboveContent =
-                contentRectOnScreen.top - mViewPortOnScreen.top;
-        final int availableHeightBelowContent =
-                mViewPortOnScreen.bottom - contentRectOnScreen.bottom;
-
-        final int margin = 2 * mMarginVertical;
-        final int toolbarHeightWithVerticalMargin = mLineHeight + margin;
-
-        if (!hasOverflow()) {
-            if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin) {
-                // There is enough space at the top of the content.
-                y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
-            } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin) {
-                // There is enough space at the bottom of the content.
-                y = contentRectOnScreen.bottom;
-            } else if (availableHeightBelowContent >= mLineHeight) {
-                // Just enough space to fit the toolbar with no vertical margins.
-                y = contentRectOnScreen.bottom - mMarginVertical;
-            } else {
-                // Not enough space. Prefer to position as high as possible.
-                y = Math.max(
-                        mViewPortOnScreen.top,
-                        contentRectOnScreen.top - toolbarHeightWithVerticalMargin);
-            }
-        } else {
-            // Has an overflow.
-            final int minimumOverflowHeightWithMargin =
-                    calculateOverflowHeight(MIN_OVERFLOW_SIZE) + margin;
-            final int availableHeightThroughContentDown =
-                    mViewPortOnScreen.bottom - contentRectOnScreen.top
-                            + toolbarHeightWithVerticalMargin;
-            final int availableHeightThroughContentUp =
-                    contentRectOnScreen.bottom - mViewPortOnScreen.top
-                            + toolbarHeightWithVerticalMargin;
-
-            if (availableHeightAboveContent >= minimumOverflowHeightWithMargin) {
-                // There is enough space at the top of the content rect for the overflow.
-                // Position above and open upwards.
-                updateOverflowHeight(availableHeightAboveContent - margin);
-                y = contentRectOnScreen.top - mPopupHeight;
-                mOpenOverflowUpwards = true;
-            } else if (availableHeightAboveContent >= toolbarHeightWithVerticalMargin
-                    && availableHeightThroughContentDown >= minimumOverflowHeightWithMargin) {
-                // There is enough space at the top of the content rect for the main panel
-                // but not the overflow.
-                // Position above but open downwards.
-                updateOverflowHeight(availableHeightThroughContentDown - margin);
-                y = contentRectOnScreen.top - toolbarHeightWithVerticalMargin;
-                mOpenOverflowUpwards = false;
-            } else if (availableHeightBelowContent >= minimumOverflowHeightWithMargin) {
-                // There is enough space at the bottom of the content rect for the overflow.
-                // Position below and open downwards.
-                updateOverflowHeight(availableHeightBelowContent - margin);
-                y = contentRectOnScreen.bottom;
-                mOpenOverflowUpwards = false;
-            } else if (availableHeightBelowContent >= toolbarHeightWithVerticalMargin
-                    && mViewPortOnScreen.height() >= minimumOverflowHeightWithMargin) {
-                // There is enough space at the bottom of the content rect for the main panel
-                // but not the overflow.
-                // Position below but open upwards.
-                updateOverflowHeight(availableHeightThroughContentUp - margin);
-                y = contentRectOnScreen.bottom + toolbarHeightWithVerticalMargin
-                        - mPopupHeight;
-                mOpenOverflowUpwards = true;
-            } else {
-                // Not enough space.
-                // Position at the top of the view port and open downwards.
-                updateOverflowHeight(mViewPortOnScreen.height() - margin);
-                y = mViewPortOnScreen.top;
-                mOpenOverflowUpwards = false;
-            }
-        }
-        mRelativeCoordsForToolbar.set(x, y);
-    }
-
-    private void cancelDismissAndHideAnimations() {
-        mDismissAnimation.cancel();
-        mHideAnimation.cancel();
-    }
-
-    private void cancelOverflowAnimations() {
-        mContentContainer.clearAnimation();
-        mMainPanel.animate().cancel();
-        mOverflowPanel.animate().cancel();
-        mToArrow.stop();
-        mToOverflow.stop();
-    }
-
-    private void openOverflow() {
-        final int targetWidth = mOverflowPanelSize.getWidth();
-        final int targetHeight = mOverflowPanelSize.getHeight();
-        final int startWidth = mContentContainer.getWidth();
-        final int startHeight = mContentContainer.getHeight();
-        final float startY = mContentContainer.getY();
-        final float left = mContentContainer.getX();
-        final float right = left + mContentContainer.getWidth();
-        Animation widthAnimation = new Animation() {
-            @Override
-            protected void applyTransformation(float interpolatedTime, Transformation t) {
-                int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
-                setWidth(mContentContainer, startWidth + deltaWidth);
-                if (isInRTLMode()) {
-                    mContentContainer.setX(left);
-
-                    // Lock the panels in place.
-                    mMainPanel.setX(0);
-                    mOverflowPanel.setX(0);
-                } else {
-                    mContentContainer.setX(right - mContentContainer.getWidth());
-
-                    // Offset the panels' positions so they look like they're locked in place
-                    // on the screen.
-                    mMainPanel.setX(mContentContainer.getWidth() - startWidth);
-                    mOverflowPanel.setX(mContentContainer.getWidth() - targetWidth);
-                }
-            }
-        };
-        Animation heightAnimation = new Animation() {
-            @Override
-            protected void applyTransformation(float interpolatedTime, Transformation t) {
-                int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
-                setHeight(mContentContainer, startHeight + deltaHeight);
-                if (mOpenOverflowUpwards) {
-                    mContentContainer.setY(
-                            startY - (mContentContainer.getHeight() - startHeight));
-                    positionContentYCoordinatesIfOpeningOverflowUpwards();
-                }
-            }
-        };
-        final float overflowButtonStartX = mOverflowButton.getX();
-        final float overflowButtonTargetX =
-                isInRTLMode() ? overflowButtonStartX + targetWidth - mOverflowButton.getWidth()
-                        : overflowButtonStartX - targetWidth + mOverflowButton.getWidth();
-        Animation overflowButtonAnimation = new Animation() {
-            @Override
-            protected void applyTransformation(float interpolatedTime, Transformation t) {
-                float overflowButtonX = overflowButtonStartX
-                        + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
-                float deltaContainerWidth =
-                        isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth;
-                float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
-                mOverflowButton.setX(actualOverflowButtonX);
-                updateFloatingToolbarRootContentRect();
-            }
-        };
-        widthAnimation.setInterpolator(mLogAccelerateInterpolator);
-        widthAnimation.setDuration(getAnimationDuration());
-        heightAnimation.setInterpolator(mFastOutSlowInInterpolator);
-        heightAnimation.setDuration(getAnimationDuration());
-        overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
-        overflowButtonAnimation.setDuration(getAnimationDuration());
-        mOpenOverflowAnimation.getAnimations().clear();
-        mOpenOverflowAnimation.addAnimation(widthAnimation);
-        mOpenOverflowAnimation.addAnimation(heightAnimation);
-        mOpenOverflowAnimation.addAnimation(overflowButtonAnimation);
-        mContentContainer.startAnimation(mOpenOverflowAnimation);
-        mIsOverflowOpen = true;
-        mMainPanel.animate()
-                .alpha(0).withLayer()
-                .setInterpolator(mLinearOutSlowInInterpolator)
-                .setDuration(250)
-                .start();
-        mOverflowPanel.setAlpha(1); // fadeIn in 0ms.
-    }
-
-    private void closeOverflow() {
-        final int targetWidth = mMainPanelSize.getWidth();
-        final int startWidth = mContentContainer.getWidth();
-        final float left = mContentContainer.getX();
-        final float right = left + mContentContainer.getWidth();
-        Animation widthAnimation = new Animation() {
-            @Override
-            protected void applyTransformation(float interpolatedTime, Transformation t) {
-                int deltaWidth = (int) (interpolatedTime * (targetWidth - startWidth));
-                setWidth(mContentContainer, startWidth + deltaWidth);
-                if (isInRTLMode()) {
-                    mContentContainer.setX(left);
-
-                    // Lock the panels in place.
-                    mMainPanel.setX(0);
-                    mOverflowPanel.setX(0);
-                } else {
-                    mContentContainer.setX(right - mContentContainer.getWidth());
-
-                    // Offset the panels' positions so they look like they're locked in place
-                    // on the screen.
-                    mMainPanel.setX(mContentContainer.getWidth() - targetWidth);
-                    mOverflowPanel.setX(mContentContainer.getWidth() - startWidth);
-                }
-            }
-        };
-        final int targetHeight = mMainPanelSize.getHeight();
-        final int startHeight = mContentContainer.getHeight();
-        final float bottom = mContentContainer.getY() + mContentContainer.getHeight();
-        Animation heightAnimation = new Animation() {
-            @Override
-            protected void applyTransformation(float interpolatedTime, Transformation t) {
-                int deltaHeight = (int) (interpolatedTime * (targetHeight - startHeight));
-                setHeight(mContentContainer, startHeight + deltaHeight);
-                if (mOpenOverflowUpwards) {
-                    mContentContainer.setY(bottom - mContentContainer.getHeight());
-                    positionContentYCoordinatesIfOpeningOverflowUpwards();
-                }
-            }
-        };
-        final float overflowButtonStartX = mOverflowButton.getX();
-        final float overflowButtonTargetX =
-                isInRTLMode() ? overflowButtonStartX - startWidth + mOverflowButton.getWidth()
-                        : overflowButtonStartX + startWidth - mOverflowButton.getWidth();
-        Animation overflowButtonAnimation = new Animation() {
-            @Override
-            protected void applyTransformation(float interpolatedTime, Transformation t) {
-                float overflowButtonX = overflowButtonStartX
-                        + interpolatedTime * (overflowButtonTargetX - overflowButtonStartX);
-                float deltaContainerWidth =
-                        isInRTLMode() ? 0 : mContentContainer.getWidth() - startWidth;
-                float actualOverflowButtonX = overflowButtonX + deltaContainerWidth;
-                mOverflowButton.setX(actualOverflowButtonX);
-                updateFloatingToolbarRootContentRect();
-            }
-        };
-        widthAnimation.setInterpolator(mFastOutSlowInInterpolator);
-        widthAnimation.setDuration(getAnimationDuration());
-        heightAnimation.setInterpolator(mLogAccelerateInterpolator);
-        heightAnimation.setDuration(getAnimationDuration());
-        overflowButtonAnimation.setInterpolator(mFastOutSlowInInterpolator);
-        overflowButtonAnimation.setDuration(getAnimationDuration());
-        mCloseOverflowAnimation.getAnimations().clear();
-        mCloseOverflowAnimation.addAnimation(widthAnimation);
-        mCloseOverflowAnimation.addAnimation(heightAnimation);
-        mCloseOverflowAnimation.addAnimation(overflowButtonAnimation);
-        mContentContainer.startAnimation(mCloseOverflowAnimation);
-        mIsOverflowOpen = false;
-        mMainPanel.animate()
-                .alpha(1).withLayer()
-                .setInterpolator(mFastOutLinearInInterpolator)
-                .setDuration(100)
-                .start();
-        mOverflowPanel.animate()
-                .alpha(0).withLayer()
-                .setInterpolator(mLinearOutSlowInInterpolator)
-                .setDuration(150)
-                .start();
-    }
-
-    /**
-     * Defines the position of the floating toolbar popup panels when transition animation has
-     * stopped.
-     */
-    private void setPanelsStatesAtRestingPosition() {
-        mOverflowButton.setEnabled(true);
-        mOverflowPanel.awakenScrollBars();
-
-        if (mIsOverflowOpen) {
-            // Set open state.
-            final Size containerSize = mOverflowPanelSize;
-            setSize(mContentContainer, containerSize);
-            mMainPanel.setAlpha(0);
-            mMainPanel.setVisibility(View.INVISIBLE);
-            mOverflowPanel.setAlpha(1);
-            mOverflowPanel.setVisibility(View.VISIBLE);
-            mOverflowButton.setImageDrawable(mArrow);
-            mOverflowButton.setContentDescription(mContext.getString(
-                    R.string.floating_toolbar_close_overflow_description));
-
-            // Update x-coordinates depending on RTL state.
-            if (isInRTLMode()) {
-                mContentContainer.setX(mMarginHorizontal);  // align left
-                mMainPanel.setX(0);  // align left
-                mOverflowButton.setX(// align right
-                        containerSize.getWidth() - mOverflowButtonSize.getWidth());
-                mOverflowPanel.setX(0);  // align left
-            } else {
-                mContentContainer.setX(// align right
-                        mPopupWidth - containerSize.getWidth() - mMarginHorizontal);
-                mMainPanel.setX(-mContentContainer.getX());  // align right
-                mOverflowButton.setX(0);  // align left
-                mOverflowPanel.setX(0);  // align left
-            }
-
-            // Update y-coordinates depending on overflow's open direction.
-            if (mOpenOverflowUpwards) {
-                mContentContainer.setY(mMarginVertical);  // align top
-                mMainPanel.setY(// align bottom
-                        containerSize.getHeight() - mContentContainer.getHeight());
-                mOverflowButton.setY(// align bottom
-                        containerSize.getHeight() - mOverflowButtonSize.getHeight());
-                mOverflowPanel.setY(0);  // align top
-            } else {
-                // opens downwards.
-                mContentContainer.setY(mMarginVertical);  // align top
-                mMainPanel.setY(0);  // align top
-                mOverflowButton.setY(0);  // align top
-                mOverflowPanel.setY(mOverflowButtonSize.getHeight());  // align bottom
-            }
-        } else {
-            // Overflow not open. Set closed state.
-            final Size containerSize = mMainPanelSize;
-            setSize(mContentContainer, containerSize);
-            mMainPanel.setAlpha(1);
-            mMainPanel.setVisibility(View.VISIBLE);
-            mOverflowPanel.setAlpha(0);
-            mOverflowPanel.setVisibility(View.INVISIBLE);
-            mOverflowButton.setImageDrawable(mOverflow);
-            mOverflowButton.setContentDescription(mContext.getString(
-                    R.string.floating_toolbar_open_overflow_description));
-
-            if (hasOverflow()) {
-                // Update x-coordinates depending on RTL state.
-                if (isInRTLMode()) {
-                    mContentContainer.setX(mMarginHorizontal);  // align left
-                    mMainPanel.setX(0);  // align left
-                    mOverflowButton.setX(0);  // align left
-                    mOverflowPanel.setX(0);  // align left
-                } else {
-                    mContentContainer.setX(// align right
-                            mPopupWidth - containerSize.getWidth() - mMarginHorizontal);
-                    mMainPanel.setX(0);  // align left
-                    mOverflowButton.setX(// align right
-                            containerSize.getWidth() - mOverflowButtonSize.getWidth());
-                    mOverflowPanel.setX(// align right
-                            containerSize.getWidth() - mOverflowPanelSize.getWidth());
-                }
-
-                // Update y-coordinates depending on overflow's open direction.
-                if (mOpenOverflowUpwards) {
-                    mContentContainer.setY(// align bottom
-                            mMarginVertical + mOverflowPanelSize.getHeight()
-                                    - containerSize.getHeight());
-                    mMainPanel.setY(0);  // align top
-                    mOverflowButton.setY(0);  // align top
-                    mOverflowPanel.setY(// align bottom
-                            containerSize.getHeight() - mOverflowPanelSize.getHeight());
-                } else {
-                    // opens downwards.
-                    mContentContainer.setY(mMarginVertical);  // align top
-                    mMainPanel.setY(0);  // align top
-                    mOverflowButton.setY(0);  // align top
-                    mOverflowPanel.setY(mOverflowButtonSize.getHeight());  // align bottom
-                }
-            } else {
-                // No overflow.
-                mContentContainer.setX(mMarginHorizontal);  // align left
-                mContentContainer.setY(mMarginVertical);  // align top
-                mMainPanel.setX(0);  // align left
-                mMainPanel.setY(0);  // align top
-            }
-        }
-    }
-
-    private void updateOverflowHeight(int suggestedHeight) {
-        if (hasOverflow()) {
-            final int maxItemSize =
-                    (suggestedHeight - mOverflowButtonSize.getHeight()) / mLineHeight;
-            final int newHeight = calculateOverflowHeight(maxItemSize);
-            if (mOverflowPanelSize.getHeight() != newHeight) {
-                mOverflowPanelSize = new Size(mOverflowPanelSize.getWidth(), newHeight);
-            }
-            setSize(mOverflowPanel, mOverflowPanelSize);
-            if (mIsOverflowOpen) {
-                setSize(mContentContainer, mOverflowPanelSize);
-                if (mOpenOverflowUpwards) {
-                    final int deltaHeight = mOverflowPanelSize.getHeight() - newHeight;
-                    mContentContainer.setY(mContentContainer.getY() + deltaHeight);
-                    mOverflowButton.setY(mOverflowButton.getY() - deltaHeight);
-                }
-            } else {
-                setSize(mContentContainer, mMainPanelSize);
-            }
-            updatePopupSize();
-        }
-    }
-
-    private void updatePopupSize() {
-        int width = 0;
-        int height = 0;
-        if (mMainPanelSize != null) {
-            width = Math.max(width, mMainPanelSize.getWidth());
-            height = Math.max(height, mMainPanelSize.getHeight());
-        }
-        if (mOverflowPanelSize != null) {
-            width = Math.max(width, mOverflowPanelSize.getWidth());
-            height = Math.max(height, mOverflowPanelSize.getHeight());
-        }
-
-        mPopupWidth = width + mMarginHorizontal * 2;
-        mPopupHeight = height + mMarginVertical * 2;
-        maybeComputeTransitionDurationScale();
-    }
-
-    private int getAdjustedToolbarWidth(int suggestedWidth) {
-        int width = suggestedWidth;
-        int maximumWidth = mViewPortOnScreen.width() - 2 * mContext.getResources()
-                .getDimensionPixelSize(R.dimen.floating_toolbar_horizontal_margin);
-        if (width <= 0) {
-            width = mContext.getResources()
-                    .getDimensionPixelSize(R.dimen.floating_toolbar_preferred_width);
-        }
-        return Math.min(width, maximumWidth);
-    }
-
-    private boolean isInRTLMode() {
-        return mContext.getApplicationInfo().hasRtlSupport()
-                && mContext.getResources().getConfiguration().getLayoutDirection()
-                == View.LAYOUT_DIRECTION_RTL;
-    }
-
-    private boolean hasOverflow() {
-        return mOverflowPanelSize != null;
-    }
-
-    /**
-     * Fits as many menu items in the main panel and returns a list of the menu items that
-     * were not fit in.
-     *
-     * @return The menu items that are not included in this main panel.
-     */
-    private List<ToolbarMenuItem> layoutMainPanelItems(List<ToolbarMenuItem> menuItems,
-            int toolbarWidth) {
-        final LinkedList<ToolbarMenuItem> remainingMenuItems = new LinkedList<>();
-        // add the overflow menu items to the end of the remainingMenuItems list.
-        final LinkedList<ToolbarMenuItem> overflowMenuItems = new LinkedList();
-        for (ToolbarMenuItem menuItem : menuItems) {
-            if (menuItem.getItemId() != android.R.id.textAssist
-                    && menuItem.getPriority() == ToolbarMenuItem.PRIORITY_OVERFLOW) {
-                overflowMenuItems.add(menuItem);
-            } else {
-                remainingMenuItems.add(menuItem);
-            }
-        }
-        remainingMenuItems.addAll(overflowMenuItems);
-
-        mMainPanel.removeAllViews();
-        mMainPanel.setPaddingRelative(0, 0, 0, 0);
-
-        int availableWidth = toolbarWidth;
-        boolean isFirstItem = true;
-        while (!remainingMenuItems.isEmpty()) {
-            ToolbarMenuItem menuItem = remainingMenuItems.peek();
-            // if this is the first item, regardless of requiresOverflow(), it should be
-            // displayed on the main panel. Otherwise all items including this one will be
-            // overflow items, and should be displayed in overflow panel.
-            if (!isFirstItem && menuItem.getPriority() == ToolbarMenuItem.PRIORITY_OVERFLOW) {
-                break;
-            }
-            final boolean showIcon = isFirstItem && menuItem.getItemId() == R.id.textAssist;
-            final View menuItemButton = createMenuItemButton(
-                    mContext, menuItem, mIconTextSpacing, showIcon);
-            if (!showIcon && menuItemButton instanceof LinearLayout) {
-                ((LinearLayout) menuItemButton).setGravity(Gravity.CENTER);
-            }
-            // Adding additional start padding for the first button to even out button spacing.
-            if (isFirstItem) {
-                menuItemButton.setPaddingRelative(
-                        (int) (1.5 * menuItemButton.getPaddingStart()),
-                        menuItemButton.getPaddingTop(),
-                        menuItemButton.getPaddingEnd(),
-                        menuItemButton.getPaddingBottom());
-            }
-            // Adding additional end padding for the last button to even out button spacing.
-            boolean isLastItem = remainingMenuItems.size() == 1;
-            if (isLastItem) {
-                menuItemButton.setPaddingRelative(
-                        menuItemButton.getPaddingStart(),
-                        menuItemButton.getPaddingTop(),
-                        (int) (1.5 * menuItemButton.getPaddingEnd()),
-                        menuItemButton.getPaddingBottom());
-            }
-            menuItemButton.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-            final int menuItemButtonWidth = Math.min(
-                    menuItemButton.getMeasuredWidth(), toolbarWidth);
-            // Check if we can fit an item while reserving space for the overflowButton.
-            final boolean canFitWithOverflow =
-                    menuItemButtonWidth <= availableWidth - mOverflowButtonSize.getWidth();
-            final boolean canFitNoOverflow =
-                    isLastItem && menuItemButtonWidth <= availableWidth;
-            if (canFitWithOverflow || canFitNoOverflow) {
-                menuItemButton.setTag(menuItem);
-                menuItemButton.setOnClickListener(mMenuItemButtonOnClickListener);
-                // Set tooltips for main panel items, but not overflow items (b/35726766).
-                menuItemButton.setTooltipText(menuItem.getTooltipText());
-                mMainPanel.addView(menuItemButton);
-                final ViewGroup.LayoutParams params = menuItemButton.getLayoutParams();
-                params.width = menuItemButtonWidth;
-                menuItemButton.setLayoutParams(params);
-                availableWidth -= menuItemButtonWidth;
-                remainingMenuItems.pop();
-            } else {
-                break;
-            }
-            isFirstItem = false;
-        }
-        if (!remainingMenuItems.isEmpty()) {
-            // Reserve space for overflowButton.
-            mMainPanel.setPaddingRelative(0, 0, mOverflowButtonSize.getWidth(), 0);
-        }
-        mMainPanelSize = measure(mMainPanel);
-
-        return remainingMenuItems;
-    }
-
-    private void layoutOverflowPanelItems(List<ToolbarMenuItem> menuItems) {
-        ArrayAdapter<ToolbarMenuItem> overflowPanelAdapter =
-                (ArrayAdapter<ToolbarMenuItem>) mOverflowPanel.getAdapter();
-        overflowPanelAdapter.clear();
-        final int size = menuItems.size();
-        for (int i = 0; i < size; i++) {
-            overflowPanelAdapter.add(menuItems.get(i));
-        }
-        mOverflowPanel.setAdapter(overflowPanelAdapter);
-        if (mOpenOverflowUpwards) {
-            mOverflowPanel.setY(0);
-        } else {
-            mOverflowPanel.setY(mOverflowButtonSize.getHeight());
-        }
-        int width = Math.max(getOverflowWidth(), mOverflowButtonSize.getWidth());
-        int height = calculateOverflowHeight(MAX_OVERFLOW_SIZE);
-        mOverflowPanelSize = new Size(width, height);
-        setSize(mOverflowPanel, mOverflowPanelSize);
-    }
-
-    /**
-     * Resets the content container and appropriately position it's panels.
-     */
-    private void preparePopupContent() {
-        mContentContainer.removeAllViews();
-        // Add views in the specified order so they stack up as expected.
-        // Order: overflowPanel, mainPanel, overflowButton.
-        if (hasOverflow()) {
-            mContentContainer.addView(mOverflowPanel);
-        }
-        mContentContainer.addView(mMainPanel);
-        if (hasOverflow()) {
-            mContentContainer.addView(mOverflowButton);
-        }
-        setPanelsStatesAtRestingPosition();
-
-        // The positioning of contents in RTL is wrong when the view is first rendered.
-        // Hide the view and post a runnable to recalculate positions and render the view.
-        // TODO: Investigate why this happens and fix.
-        if (isInRTLMode()) {
-            mContentContainer.setAlpha(0);
-            mContentContainer.post(mPreparePopupContentRTLHelper);
-        }
-    }
-
-    /**
-     * Clears out the panels and their container. Resets their calculated sizes.
-     */
-    private void clearPanels() {
-        mIsOverflowOpen = false;
-        mMainPanelSize = null;
-        mMainPanel.removeAllViews();
-        mOverflowPanelSize = null;
-        ArrayAdapter<ToolbarMenuItem> overflowPanelAdapter =
-                (ArrayAdapter<ToolbarMenuItem>) mOverflowPanel.getAdapter();
-        overflowPanelAdapter.clear();
-        mOverflowPanel.setAdapter(overflowPanelAdapter);
-        mContentContainer.removeAllViews();
-    }
-
-    private void positionContentYCoordinatesIfOpeningOverflowUpwards() {
-        if (mOpenOverflowUpwards) {
-            mMainPanel.setY(mContentContainer.getHeight() - mMainPanelSize.getHeight());
-            mOverflowButton.setY(mContentContainer.getHeight() - mOverflowButton.getHeight());
-            mOverflowPanel.setY(mContentContainer.getHeight() - mOverflowPanelSize.getHeight());
-        }
-    }
-
-    private int getOverflowWidth() {
-        int overflowWidth = 0;
-        final int count = mOverflowPanel.getAdapter().getCount();
-        for (int i = 0; i < count; i++) {
-            ToolbarMenuItem menuItem = (ToolbarMenuItem) mOverflowPanel.getAdapter().getItem(i);
-            overflowWidth =
-                    Math.max(mOverflowPanelViewHelper.calculateWidth(menuItem), overflowWidth);
-        }
-        return overflowWidth;
-    }
-
-    private int calculateOverflowHeight(int maxItemSize) {
-        // Maximum of 4 items, minimum of 2 if the overflow has to scroll.
-        int actualSize = Math.min(
-                MAX_OVERFLOW_SIZE,
-                Math.min(
-                        Math.max(MIN_OVERFLOW_SIZE, maxItemSize),
-                        mOverflowPanel.getCount()));
-        int extension = 0;
-        if (actualSize < mOverflowPanel.getCount()) {
-            // The overflow will require scrolling to get to all the items.
-            // Extend the height so that part of the hidden items is displayed.
-            extension = (int) (mLineHeight * 0.5f);
-        }
-        return actualSize * mLineHeight
-                + mOverflowButtonSize.getHeight()
-                + extension;
-    }
-
-    /**
-     * NOTE: Use only in android.view.animation.* animations. Do not use in android.animation.*
-     * animations. See comment about this in the code.
-     */
-    private int getAnimationDuration() {
-        if (mTransitionDurationScale < 150) {
-            // For smaller transition, decrease the time.
-            return 200;
-        } else if (mTransitionDurationScale > 300) {
-            // For bigger transition, increase the time.
-            return 300;
-        }
-
-        // Scale the animation duration with getDurationScale(). This allows
-        // android.view.animation.* animations to scale just like android.animation.* animations
-        // when  animator duration scale is adjusted in "Developer Options".
-        // For this reason, do not use this method for android.animation.* animations.
-        return (int) (250 * ValueAnimator.getDurationScale());
-    }
-
-    private void maybeComputeTransitionDurationScale() {
-        if (mMainPanelSize != null && mOverflowPanelSize != null) {
-            int w = mMainPanelSize.getWidth() - mOverflowPanelSize.getWidth();
-            int h = mOverflowPanelSize.getHeight() - mMainPanelSize.getHeight();
-            mTransitionDurationScale = (int) (Math.sqrt(w * w + h * h)
-                    / mContentContainer.getContext().getResources().getDisplayMetrics().density);
-        }
-    }
-
-    private ViewGroup createMainPanel() {
-        return new LinearLayout(mContext) {
-            @Override
-            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-                if (isOverflowAnimating()) {
-                    // Update widthMeasureSpec to make sure that this view is not clipped
-                    // as we offset its coordinates with respect to its parent.
-                    widthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                            mMainPanelSize.getWidth(),
-                            MeasureSpec.EXACTLY);
-                }
-                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-            }
-
-            @Override
-            public boolean onInterceptTouchEvent(MotionEvent ev) {
-                // Intercept the touch event while the overflow is animating.
-                return isOverflowAnimating();
-            }
-        };
-    }
-
-    private ImageButton createOverflowButton() {
-        final ImageButton overflowButton = (ImageButton) LayoutInflater.from(mContext)
-                .inflate(R.layout.floating_popup_overflow_button, null);
-        overflowButton.setImageDrawable(mOverflow);
-        overflowButton.setOnClickListener(v -> {
-            if (isShowing()) {
-                preparePopupContent();
-                WidgetInfo widgetInfo = createWidgetInfo();
-                mSurfaceControlViewHost.relayout(mPopupWidth, mPopupHeight);
-                mCallbackWrapper.onWidgetUpdated(widgetInfo);
-            }
-            if (mIsOverflowOpen) {
-                overflowButton.setImageDrawable(mToOverflow);
-                mToOverflow.start();
-                closeOverflow();
-            } else {
-                overflowButton.setImageDrawable(mToArrow);
-                mToArrow.start();
-                openOverflow();
-            }
-        });
-        return overflowButton;
-    }
-
-    private OverflowPanel createOverflowPanel() {
-        final OverflowPanel overflowPanel = new OverflowPanel(this);
-        overflowPanel.setLayoutParams(new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
-        overflowPanel.setDivider(null);
-        overflowPanel.setDividerHeight(0);
-
-        final ArrayAdapter adapter =
-                new ArrayAdapter<ToolbarMenuItem>(mContext, 0) {
-                    @Override
-                    public View getView(int position, View convertView, ViewGroup parent) {
-                        return mOverflowPanelViewHelper.getView(
-                                getItem(position), mOverflowPanelSize.getWidth(), convertView);
-                    }
-                };
-        overflowPanel.setAdapter(adapter);
-        overflowPanel.setOnItemClickListener((parent, view, position, id) -> {
-            ToolbarMenuItem menuItem =
-                    (ToolbarMenuItem) overflowPanel.getAdapter().getItem(position);
-            mCallbackWrapper.onMenuItemClicked(menuItem);
-        });
-        return overflowPanel;
-    }
-
-    private boolean isOverflowAnimating() {
-        final boolean overflowOpening = mOpenOverflowAnimation.hasStarted()
-                && !mOpenOverflowAnimation.hasEnded();
-        final boolean overflowClosing = mCloseOverflowAnimation.hasStarted()
-                && !mCloseOverflowAnimation.hasEnded();
-        return overflowOpening || overflowClosing;
-    }
-
-    private Animation.AnimationListener createOverflowAnimationListener() {
-        return new Animation.AnimationListener() {
-            @Override
-            public void onAnimationStart(Animation animation) {
-                // Disable the overflow button while it's animating.
-                // It will be re-enabled when the animation stops.
-                mOverflowButton.setEnabled(false);
-                // Ensure both panels have visibility turned on when the overflow animation
-                // starts.
-                mMainPanel.setVisibility(View.VISIBLE);
-                mOverflowPanel.setVisibility(View.VISIBLE);
-            }
-
-            @Override
-            public void onAnimationEnd(Animation animation) {
-                // Posting this because it seems like this is called before the animation
-                // actually ends.
-                mContentContainer.post(() -> {
-                    setPanelsStatesAtRestingPosition();
-                });
-            }
-
-            @Override
-            public void onAnimationRepeat(Animation animation) {
-            }
-        };
-    }
-
-    private static Size measure(View view) {
-        Preconditions.checkState(view.getParent() == null);
-        view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-        return new Size(view.getMeasuredWidth(), view.getMeasuredHeight());
-    }
-
-    private static void setSize(View view, int width, int height) {
-        view.setMinimumWidth(width);
-        view.setMinimumHeight(height);
-        ViewGroup.LayoutParams params = view.getLayoutParams();
-        params = (params == null) ? new ViewGroup.LayoutParams(0, 0) : params;
-        params.width = width;
-        params.height = height;
-        view.setLayoutParams(params);
-    }
-
-    private static void setSize(View view, Size size) {
-        setSize(view, size.getWidth(), size.getHeight());
-    }
-
-    private static void setWidth(View view, int width) {
-        ViewGroup.LayoutParams params = view.getLayoutParams();
-        setSize(view, width, params.height);
-    }
-
-    private static void setHeight(View view, int height) {
-        ViewGroup.LayoutParams params = view.getLayoutParams();
-        setSize(view, params.width, height);
-    }
-
-    /**
-     * A custom ListView for the overflow panel.
-     */
-    private static final class OverflowPanel extends ListView {
-
-        private final RemoteSelectionToolbar mPopup;
-
-        OverflowPanel(RemoteSelectionToolbar popup) {
-            super(Objects.requireNonNull(popup).mContext);
-            this.mPopup = popup;
-            setScrollBarDefaultDelayBeforeFade(ViewConfiguration.getScrollDefaultDelay() * 3);
-            setScrollIndicators(View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_BOTTOM);
-        }
-
-        @Override
-        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-            // Update heightMeasureSpec to make sure that this view is not clipped
-            // as we offset it's coordinates with respect to its parent.
-            int height = mPopup.mOverflowPanelSize.getHeight()
-                    - mPopup.mOverflowButtonSize.getHeight();
-            heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
-            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-        }
-
-        @Override
-        public boolean dispatchTouchEvent(MotionEvent ev) {
-            if (mPopup.isOverflowAnimating()) {
-                // Eat the touch event.
-                return true;
-            }
-            return super.dispatchTouchEvent(ev);
-        }
-
-        @Override
-        protected boolean awakenScrollBars() {
-            return super.awakenScrollBars();
-        }
-    }
-
-    /**
-     * A custom interpolator used for various floating toolbar animations.
-     */
-    private static final class LogAccelerateInterpolator implements Interpolator {
-
-        private static final int BASE = 100;
-        private static final float LOGS_SCALE = 1f / computeLog(1, BASE);
-
-        private static float computeLog(float t, int base) {
-            return (float) (1 - Math.pow(base, -t));
-        }
-
-        @Override
-        public float getInterpolation(float t) {
-            return 1 - computeLog(1 - t, BASE) * LOGS_SCALE;
-        }
-    }
-
-    /**
-     * A helper for generating views for the overflow panel.
-     */
-    private static final class OverflowPanelViewHelper {
-        private final Context mContext;
-        private final View mCalculator;
-        private final int mIconTextSpacing;
-        private final int mSidePadding;
-
-        OverflowPanelViewHelper(Context context, int iconTextSpacing) {
-            mContext = Objects.requireNonNull(context);
-            mIconTextSpacing = iconTextSpacing;
-            mSidePadding = context.getResources()
-                    .getDimensionPixelSize(R.dimen.floating_toolbar_overflow_side_padding);
-            mCalculator = createMenuButton(null);
-        }
-
-        public View getView(ToolbarMenuItem menuItem, int minimumWidth, View convertView) {
-            Objects.requireNonNull(menuItem);
-            if (convertView != null) {
-                updateMenuItemButton(
-                        convertView, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
-            } else {
-                convertView = createMenuButton(menuItem);
-            }
-            convertView.setMinimumWidth(minimumWidth);
-            return convertView;
-        }
-
-        public int calculateWidth(ToolbarMenuItem menuItem) {
-            updateMenuItemButton(
-                    mCalculator, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
-            mCalculator.measure(
-                    View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
-            return mCalculator.getMeasuredWidth();
-        }
-
-        private View createMenuButton(ToolbarMenuItem menuItem) {
-            View button = createMenuItemButton(
-                    mContext, menuItem, mIconTextSpacing, shouldShowIcon(menuItem));
-            button.setPadding(mSidePadding, 0, mSidePadding, 0);
-            return button;
-        }
-
-        private boolean shouldShowIcon(ToolbarMenuItem menuItem) {
-            if (menuItem != null) {
-                return menuItem.getGroupId() == android.R.id.textAssist;
-            }
-            return  false;
-        }
-    }
-
-    /**
-     * Creates and returns a menu button for the specified menu item.
-     */
-    private static View createMenuItemButton(
-            Context context, ToolbarMenuItem menuItem, int iconTextSpacing, boolean showIcon) {
-        final View menuItemButton = LayoutInflater.from(context)
-                .inflate(R.layout.floating_popup_menu_button, null);
-        if (menuItem != null) {
-            updateMenuItemButton(menuItemButton, menuItem, iconTextSpacing, showIcon);
-        }
-        return menuItemButton;
-    }
-
-    /**
-     * Updates the specified menu item button with the specified menu item data.
-     */
-    private static void updateMenuItemButton(View menuItemButton, ToolbarMenuItem menuItem,
-            int iconTextSpacing, boolean showIcon) {
-        final TextView buttonText = menuItemButton.findViewById(
-                R.id.floating_toolbar_menu_item_text);
-        buttonText.setEllipsize(null);
-        if (TextUtils.isEmpty(menuItem.getTitle())) {
-            buttonText.setVisibility(View.GONE);
-        } else {
-            buttonText.setVisibility(View.VISIBLE);
-            buttonText.setText(menuItem.getTitle());
-        }
-        final ImageView buttonIcon = menuItemButton.findViewById(
-                R.id.floating_toolbar_menu_item_image);
-        if (menuItem.getIcon() == null || !showIcon) {
-            buttonIcon.setVisibility(View.GONE);
-            buttonText.setPaddingRelative(0, 0, 0, 0);
-        } else {
-            buttonIcon.setVisibility(View.VISIBLE);
-            buttonIcon.setImageDrawable(
-                    menuItem.getIcon().loadDrawable(menuItemButton.getContext()));
-            buttonText.setPaddingRelative(iconTextSpacing, 0, 0, 0);
-        }
-        final CharSequence contentDescription = menuItem.getContentDescription();
-        if (TextUtils.isEmpty(contentDescription)) {
-            menuItemButton.setContentDescription(menuItem.getTitle());
-        } else {
-            menuItemButton.setContentDescription(contentDescription);
-        }
-    }
-
-    private static ViewGroup createContentContainer(Context context) {
-        ViewGroup contentContainer = (ViewGroup) LayoutInflater.from(context)
-                .inflate(R.layout.floating_popup_container, null);
-        contentContainer.setLayoutParams(new ViewGroup.LayoutParams(
-                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
-        contentContainer.setTag(FloatingToolbar.FLOATING_TOOLBAR_TAG);
-        contentContainer.setClipToOutline(true);
-        return contentContainer;
-    }
-
-    /**
-     * Creates an "appear" animation for the specified view.
-     *
-     * @param view  The view to animate
-     */
-    private static AnimatorSet createEnterAnimation(View view, Animator.AnimatorListener listener) {
-        AnimatorSet animation = new AnimatorSet();
-        animation.playTogether(
-                ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1).setDuration(150));
-        animation.addListener(listener);
-        return animation;
-    }
-
-    /**
-     * Creates a "disappear" animation for the specified view.
-     *
-     * @param view  The view to animate
-     * @param startDelay  The start delay of the animation
-     * @param listener  The animation listener
-     */
-    private static AnimatorSet createExitAnimation(
-            View view, int startDelay, Animator.AnimatorListener listener) {
-        AnimatorSet animation =  new AnimatorSet();
-        animation.playTogether(
-                ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0).setDuration(100));
-        animation.setStartDelay(startDelay);
-        if (listener != null) {
-            animation.addListener(listener);
-        }
-        return animation;
-    }
-
-    /**
-     * Returns a re-themed context with controlled look and feel for views.
-     */
-    private static Context applyDefaultTheme(Context originalContext, boolean isLightTheme) {
-        int themeId =
-                isLightTheme ? R.style.Theme_DeviceDefault_Light : R.style.Theme_DeviceDefault;
-        return new ContextThemeWrapper(originalContext, themeId);
-    }
-
-    private static void debugLog(String message) {
-        if (Log.isLoggable(FloatingToolbar.FLOATING_TOOLBAR_TAG, Log.DEBUG)) {
-            Log.v(TAG, message);
-        }
-    }
-
-    void dump(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("toolbar token: "); pw.println(mSelectionToolbarToken);
-        pw.print(prefix); pw.print("dismissed: "); pw.println(mDismissed);
-        pw.print(prefix); pw.print("hidden: "); pw.println(mHidden);
-        pw.print(prefix); pw.print("popup width: "); pw.println(mPopupWidth);
-        pw.print(prefix); pw.print("popup height: "); pw.println(mPopupHeight);
-        pw.print(prefix); pw.print("relative coords: "); pw.println(mRelativeCoordsForToolbar);
-        pw.print(prefix); pw.print("main panel size: "); pw.println(mMainPanelSize);
-        boolean hasOverflow = hasOverflow();
-        pw.print(prefix); pw.print("has overflow: "); pw.println(hasOverflow);
-        if (hasOverflow) {
-            pw.print(prefix); pw.print("overflow open: "); pw.println(mIsOverflowOpen);
-            pw.print(prefix); pw.print("overflow size: "); pw.println(mOverflowPanelSize);
-        }
-        if (mSurfaceControlViewHost != null) {
-            FloatingToolbarRoot root = (FloatingToolbarRoot) mSurfaceControlViewHost.getView();
-            root.dump(prefix, pw);
-        }
-        if (mMenuItems != null) {
-            int menuItemSize = mMenuItems.size();
-            pw.print(prefix); pw.print("number menu items: "); pw.println(menuItemSize);
-            for (int i = 0; i < menuItemSize; i++) {
-                pw.print(prefix); pw.print("#"); pw.println(i);
-                pw.print(prefix + "  "); pw.println(mMenuItems.get(i));
-            }
-        }
-    }
-}
diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java
deleted file mode 100644
index a10b6a8..0000000
--- a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderCallback.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.selectiontoolbar;
-
-import android.view.selectiontoolbar.ToolbarMenuItem;
-import android.view.selectiontoolbar.WidgetInfo;
-
-/**
- * The callback that the render service uses to communicate with the host of the selection toolbar
- * container.
- *
- * @hide
- */
-public interface SelectionToolbarRenderCallback {
-    /**
-     * The selection toolbar is shown.
-     */
-    void onShown(WidgetInfo widgetInfo);
-    /**
-     * The selection toolbar has changed.
-     */
-    void onWidgetUpdated(WidgetInfo info);
-    /**
-     * The menu item on the selection toolbar has been clicked.
-     */
-    void onMenuItemClicked(ToolbarMenuItem item);
-    /**
-     * The toolbar doesn't be dismissed after showing on a given timeout.
-     */
-    void onToolbarShowTimeout();
-    /**
-     * The error occurred when operating on the selection toolbar.
-     */
-    void onError(int errorCode);
-}
diff --git a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java b/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java
deleted file mode 100644
index f33feae..0000000
--- a/core/java/android/service/selectiontoolbar/SelectionToolbarRenderService.java
+++ /dev/null
@@ -1,257 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.service.selectiontoolbar;
-
-import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-
-import android.annotation.CallSuper;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.Service;
-import android.content.Intent;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ShowInfo;
-import android.view.selectiontoolbar.ToolbarMenuItem;
-import android.view.selectiontoolbar.WidgetInfo;
-
-/**
- * Service for rendering selection toolbar.
- *
- * @hide
- */
-public abstract class SelectionToolbarRenderService extends Service {
-
-    private static final String TAG = "SelectionToolbarRenderService";
-
-    // TODO(b/215497659): read from DeviceConfig
-    // The timeout to clean the cache if the client forgot to call dismiss()
-    private static final int CACHE_CLEAN_AFTER_SHOW_TIMEOUT_IN_MS = 10 * 60 * 1000; // 10 minutes
-
-    /**
-     * The {@link Intent} that must be declared as handled by the service.
-     *
-     * <p>To be supported, the service must also require the
-     * {@link android.Manifest.permission#BIND_SELECTION_TOOLBAR_RENDER_SERVICE} permission so
-     * that other applications can not abuse it.
-     */
-    public static final String SERVICE_INTERFACE =
-            "android.service.selectiontoolbar.SelectionToolbarRenderService";
-
-    private Handler mHandler;
-    private ISelectionToolbarRenderServiceCallback mServiceCallback;
-
-    private final SparseArray<Pair<RemoteCallbackWrapper, CleanCacheRunnable>> mCache =
-            new SparseArray<>();
-
-    /**
-     * Binder to receive calls from system server.
-     */
-    private final ISelectionToolbarRenderService mInterface =
-            new ISelectionToolbarRenderService.Stub() {
-
-        @Override
-        public void onShow(int callingUid, ShowInfo showInfo, ISelectionToolbarCallback callback) {
-            if (mCache.indexOfKey(callingUid) < 0) {
-                mCache.put(callingUid, new Pair<>(new RemoteCallbackWrapper(callback),
-                        new CleanCacheRunnable(callingUid)));
-            }
-            Pair<RemoteCallbackWrapper, CleanCacheRunnable> toolbarPair = mCache.get(callingUid);
-            CleanCacheRunnable cleanRunnable = toolbarPair.second;
-            mHandler.removeCallbacks(cleanRunnable);
-            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onShow,
-                    SelectionToolbarRenderService.this, callingUid, showInfo,
-                    toolbarPair.first));
-            mHandler.postDelayed(cleanRunnable, CACHE_CLEAN_AFTER_SHOW_TIMEOUT_IN_MS);
-        }
-
-        @Override
-        public void onHide(long widgetToken) {
-            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onHide,
-                    SelectionToolbarRenderService.this, widgetToken));
-        }
-
-        @Override
-        public void onDismiss(int callingUid, long widgetToken) {
-            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::onDismiss,
-                    SelectionToolbarRenderService.this, widgetToken));
-            Pair<RemoteCallbackWrapper, CleanCacheRunnable> toolbarPair = mCache.get(callingUid);
-            if (toolbarPair != null) {
-                mHandler.removeCallbacks(toolbarPair.second);
-                mCache.remove(callingUid);
-            }
-        }
-
-        @Override
-        public void onConnected(IBinder callback) {
-            mHandler.sendMessage(obtainMessage(SelectionToolbarRenderService::handleOnConnected,
-                    SelectionToolbarRenderService.this, callback));
-        }
-    };
-
-    @CallSuper
-    @Override
-    public void onCreate() {
-        super.onCreate();
-        mHandler = new Handler(Looper.getMainLooper(), null, true);
-    }
-
-    @Override
-    @Nullable
-    public final IBinder onBind(@NonNull Intent intent) {
-        if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return mInterface.asBinder();
-        }
-        Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
-        return null;
-    }
-
-    private void handleOnConnected(@NonNull IBinder callback) {
-        mServiceCallback = ISelectionToolbarRenderServiceCallback.Stub.asInterface(callback);
-    }
-
-    protected void transferTouch(@NonNull IBinder source, @NonNull IBinder target) {
-        final ISelectionToolbarRenderServiceCallback callback = mServiceCallback;
-        if (callback == null) {
-            Log.e(TAG, "transferTouch(): no server callback");
-            return;
-        }
-        try {
-            callback.transferTouch(source, target);
-        } catch (RemoteException e) {
-            // no-op
-        }
-    }
-
-    /**
-     * Called when showing the selection toolbar.
-     */
-    public abstract void onShow(int callingUid, ShowInfo showInfo,
-            RemoteCallbackWrapper callbackWrapper);
-
-    /**
-     * Called when hiding the selection toolbar.
-     */
-    public abstract void onHide(long widgetToken);
-
-
-    /**
-     * Called when dismissing the selection toolbar.
-     */
-    public abstract void onDismiss(long widgetToken);
-
-    /**
-     * Called when showing the selection toolbar for a specific timeout. This avoids the client
-     * forgot to call dismiss to clean the state.
-     */
-    public void onToolbarShowTimeout(int callingUid) {
-        // no-op
-    }
-
-    /**
-     * Callback to notify the client toolbar events.
-     */
-    public static final class RemoteCallbackWrapper implements SelectionToolbarRenderCallback {
-
-        private final ISelectionToolbarCallback mRemoteCallback;
-
-        RemoteCallbackWrapper(ISelectionToolbarCallback remoteCallback) {
-            // TODO(b/215497659): handle if the binder dies.
-            mRemoteCallback = remoteCallback;
-        }
-
-        @Override
-        public void onShown(WidgetInfo widgetInfo) {
-            try {
-                mRemoteCallback.onShown(widgetInfo);
-            } catch (RemoteException e) {
-                // no-op
-            }
-        }
-
-        @Override
-        public void onToolbarShowTimeout() {
-            try {
-                mRemoteCallback.onToolbarShowTimeout();
-            } catch (RemoteException e) {
-                // no-op
-            }
-        }
-
-        @Override
-        public void onWidgetUpdated(WidgetInfo widgetInfo) {
-            try {
-                mRemoteCallback.onWidgetUpdated(widgetInfo);
-            } catch (RemoteException e) {
-                // no-op
-            }
-        }
-
-        @Override
-        public void onMenuItemClicked(ToolbarMenuItem item) {
-            try {
-                mRemoteCallback.onMenuItemClicked(item);
-            } catch (RemoteException e) {
-                // no-op
-            }
-        }
-
-        @Override
-        public void onError(int errorCode) {
-            try {
-                mRemoteCallback.onError(errorCode);
-            } catch (RemoteException e) {
-                // no-op
-            }
-        }
-    }
-
-    private class CleanCacheRunnable implements Runnable {
-
-        int mCleanUid;
-
-        CleanCacheRunnable(int cleanUid) {
-            mCleanUid = cleanUid;
-        }
-
-        @Override
-        public void run() {
-            Pair<RemoteCallbackWrapper, CleanCacheRunnable> toolbarPair = mCache.get(mCleanUid);
-            if (toolbarPair != null) {
-                Log.w(TAG, "CleanCacheRunnable: remove " + mCleanUid + " from cache.");
-                mCache.remove(mCleanUid);
-                onToolbarShowTimeout(mCleanUid);
-            }
-        }
-    }
-
-    /**
-     * A listener to notify the service to the transfer touch focus.
-     */
-    public interface TransferTouchListener {
-        /**
-         * Notify the service to transfer the touch focus.
-         */
-        void onTransferTouch(IBinder source, IBinder target);
-    }
-}
diff --git a/core/java/android/text/OWNERS b/core/java/android/text/OWNERS
index a6be687..0935ffd9 100644
--- a/core/java/android/text/OWNERS
+++ b/core/java/android/text/OWNERS
@@ -1,5 +1,6 @@
 set noparent
 
+grantapher@google.com
 halilibo@google.com
 haoyuchang@google.com
 justinghan@google.com
diff --git a/core/java/android/text/TEST_MAPPING b/core/java/android/text/TEST_MAPPING
new file mode 100644
index 0000000..0fe974a
--- /dev/null
+++ b/core/java/android/text/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTextTestCases",
+      "options": [
+          {
+              "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+          },
+          {
+              "exclude-annotation": "androidx.test.filters.LargeTest"
+          }
+      ]
+    }
+  ]
+}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4b96d74..0b2b6ce 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -375,6 +375,14 @@
     public static final int FLAG_REAR = 1 << 13;
 
     /**
+     * Display flag: Indicates that the orientation of this display is not fixed and is coupled to
+     * the orientation of its content.
+     *
+     * @hide
+     */
+    public static final int FLAG_ROTATES_WITH_CONTENT = 1 << 14;
+
+    /**
      * Display flag: Indicates that the contents of the display should not be scaled
      * to fit the physical screen dimensions.  Used for development only to emulate
      * devices with smaller physicals screens while preserving density.
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index c501f5a..751cd21 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -203,6 +203,7 @@
                             candidateView.getHandwritingDelegatorCallback().run();
                             mState.mHasPreparedHandwritingDelegation = true;
                         } else {
+                            mState.mPendingConnectedView = new WeakReference<>(candidateView);
                             requestFocusWithoutReveal(candidateView);
                         }
                     }
@@ -264,8 +265,9 @@
                 mShowHoverIconForConnectedView = false;
                 return;
             }
-            if (mState != null && mState.mShouldInitHandwriting) {
-                tryStartHandwriting();
+            if (mState != null && mState.mPendingConnectedView != null
+                    && mState.mPendingConnectedView.get() == view) {
+                startHandwriting(view);
             }
         }
     }
@@ -290,40 +292,6 @@
         }
     }
 
-    /**
-     * Try to initiate handwriting. For this method to successfully send startHandwriting signal,
-     * the following 3 conditions should meet:
-     *   a) The stylus movement exceeds the touchSlop.
-     *   b) A View has built InputConnection with IME.
-     *   c) The stylus event lands into the connected View's boundary.
-     * This method will immediately fail without any side effect if condition a or b is not met.
-     * However, if both condition a and b are met but the condition c is not met, it will reset the
-     * internal states. And HandwritingInitiator won't attempt to call startHandwriting until the
-     * next ACTION_DOWN.
-     */
-    private void tryStartHandwriting() {
-        if (!mState.mExceedHandwritingSlop) {
-            return;
-        }
-        final View connectedView = getConnectedView();
-        if (connectedView == null) {
-            return;
-        }
-
-        if (!connectedView.isAutoHandwritingEnabled()) {
-            clearConnectedView();
-            return;
-        }
-
-        final Rect handwritingArea = getViewHandwritingArea(connectedView);
-        if (isInHandwritingArea(
-                handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) {
-            startHandwriting(connectedView);
-        } else {
-            mState.mShouldInitHandwriting = false;
-        }
-    }
-
     /** Starts a stylus handwriting session for the view. */
     @VisibleForTesting
     public void startHandwriting(@NonNull View view) {
@@ -626,6 +594,7 @@
         private boolean mHasInitiatedHandwriting;
 
         private boolean mHasPreparedHandwritingDelegation;
+
         /**
          * Whether the current ongoing stylus MotionEvent sequence already exceeds the
          * handwriting slop.
@@ -634,6 +603,12 @@
          */
         private boolean mExceedHandwritingSlop;
 
+        /**
+         * A view which has requested focus and is pending input connection creation. When an input
+         * connection is created for the view, a handwriting session should be started for the view.
+         */
+        private WeakReference<View> mPendingConnectedView = null;
+
         /** The pointer id of the stylus pointer that is being tracked. */
         private final int mStylusPointerId;
         /** The time stamp when the stylus pointer goes down. */
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index f81dc5a..c35b690 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -37,6 +37,7 @@
 import android.os.Parcelable;
 import android.os.Vibrator;
 import android.os.VibratorManager;
+import android.text.TextUtils;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -483,10 +484,12 @@
         mSources = sources;
         mKeyboardType = keyboardType;
         mKeyCharacterMap = keyCharacterMap;
-        if (keyboardLanguageTag != null) {
-            mKeyboardLanguageTag = ULocale
+        if (!TextUtils.isEmpty(keyboardLanguageTag)) {
+            String langTag;
+            langTag = ULocale
                     .createCanonical(ULocale.forLanguageTag(keyboardLanguageTag))
                     .toLanguageTag();
+            mKeyboardLanguageTag = TextUtils.equals(langTag, "und") ? null : langTag;
         } else {
             mKeyboardLanguageTag = null;
         }
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index c11f497..e673676 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -47,6 +47,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.gui.DropInputMode;
+import android.gui.StalledTransactionInfo;
 import android.hardware.DataSpace;
 import android.hardware.HardwareBuffer;
 import android.hardware.OverlayProperties;
@@ -292,6 +293,7 @@
             long nativeObject, long nativeTpc, TrustedPresentationThresholds thresholds);
     private static native void nativeClearTrustedPresentationCallback(long transactionObj,
             long nativeObject);
+    private static native StalledTransactionInfo nativeGetStalledTransactionInfo(int pid);
 
     /**
      * Transforms that can be applied to buffers as they are displayed to a window.
@@ -4363,4 +4365,11 @@
         callback.accept(fence);
     }
 
+    /**
+     * @hide
+     */
+    public static StalledTransactionInfo getStalledTransactionInfo(int pid) {
+        return nativeGetStalledTransactionInfo(pid);
+    }
+
 }
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 2db2132..ad46f2b 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -815,7 +815,7 @@
 
         int syncResult = syncAndDrawFrame(frameInfo);
         if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
-            Log.w("OpenGLRenderer", "Surface lost, forcing relayout");
+            Log.w("HWUI", "Surface lost, forcing relayout");
             // We lost our surface. For a relayout next frame which should give us a new
             // surface from WindowManager, which hopefully will work.
             attachInfo.mViewRootImpl.mForceNextWindowRelayout = true;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e0e8a6b..92509c9 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -16212,7 +16212,27 @@
         if (fg != null && isVisible != fg.isVisible()) {
             fg.setVisible(isVisible, false);
         }
+        notifyAutofillManagerViewVisibilityChanged(isVisible);
+        if (isVisible != oldVisible) {
+            if (isAccessibilityPane()) {
+                notifyViewAccessibilityStateChangedIfNeeded(isVisible
+                        ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
+                        : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
+            }
 
+            notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
+
+            if (!getSystemGestureExclusionRects().isEmpty()) {
+                postUpdate(this::updateSystemGestureExclusionRects);
+            }
+
+            if (!collectPreferKeepClearRects().isEmpty()) {
+                postUpdate(this::updateKeepClearRects);
+            }
+        }
+    }
+
+    private void notifyAutofillManagerViewVisibilityChanged(boolean isVisible) {
         if (isAutofillable()) {
             AutofillManager afm = getAutofillManager();
 
@@ -16236,24 +16256,6 @@
                 }
             }
         }
-
-        if (isVisible != oldVisible) {
-            if (isAccessibilityPane()) {
-                notifyViewAccessibilityStateChangedIfNeeded(isVisible
-                        ? AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_APPEARED
-                        : AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED);
-            }
-
-            notifyAppearedOrDisappearedForContentCaptureIfNeeded(isVisible);
-
-            if (!getSystemGestureExclusionRects().isEmpty()) {
-                postUpdate(this::updateSystemGestureExclusionRects);
-            }
-
-            if (!collectPreferKeepClearRects().isEmpty()) {
-                postUpdate(this::updateKeepClearRects);
-            }
-        }
     }
 
     /**
@@ -22128,6 +22130,8 @@
                     // Invoking onVisibilityAggregated directly here since the subtree
                     // will also receive detached from window
                     onVisibilityAggregated(false);
+                } else {
+                    notifyAutofillManagerViewVisibilityChanged(false);
                 }
             }
         }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 21ee0e7..3aa610a 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1810,6 +1810,9 @@
                 // Request to update light center.
                 mAttachInfo.mNeedsUpdateLightCenter = true;
             }
+            if ((changes & WindowManager.LayoutParams.COLOR_MODE_CHANGED) != 0) {
+                invalidate();
+            }
             if (mWindowAttributes.packageName == null) {
                 mWindowAttributes.packageName = mBasePackageName;
             }
@@ -3792,6 +3795,11 @@
         boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
         boolean cancelAndRedraw = cancelDueToPreDrawListener
                  || (cancelDraw && mDrewOnceForSync);
+        if (cancelAndRedraw) {
+            Log.d(mTag, "Cancelling draw."
+                    + " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener
+                    + " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync));
+        }
         if (!cancelAndRedraw) {
             // A sync was already requested before the WMS requested sync. This means we need to
             // sync the buffer, regardless if WMS wants to sync the buffer.
@@ -5561,6 +5569,7 @@
         if (desiredRatio != mDesiredHdrSdrRatio) {
             mDesiredHdrSdrRatio = desiredRatio;
             updateRenderHdrSdrRatio();
+            invalidate();
 
             if (mDesiredHdrSdrRatio < 1.01f) {
                 mDisplay.unregisterHdrSdrRatioChangedListener(mHdrSdrRatioChangedListener);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 3554483..5d1a81f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -913,7 +913,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION =
             "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
 
@@ -983,7 +982,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Make this public API.
     String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS =
             "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
 
@@ -1018,7 +1016,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
 
     /**
@@ -1056,7 +1053,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION =
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION";
 
@@ -1102,7 +1098,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH =
             "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
 
@@ -1151,7 +1146,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE =
             "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
 
@@ -1189,7 +1183,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
 
@@ -1233,7 +1226,6 @@
      * &lt;/application&gt;
      * </pre>
      */
-    // TODO(b/263984287): Add CTS tests.
     String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
             "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
 
@@ -1300,6 +1292,102 @@
             "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
 
     /**
+     * Application level
+     * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+     * tag that (when set to false) informs the system the app has opted out of the
+     * user-facing aspect ratio compatibility override.
+     *
+     * <p>The compatibility override enables device users to set the app's aspect
+     * ratio or force the app to fill the display regardless of the aspect
+     * ratio or orientation specified in the app manifest.
+     *
+     * <p>The aspect ratio compatibility override is exposed to users in device
+     * settings. A menu in device settings lists all apps that don't opt out of
+     * the compatibility override. Users select apps from the menu and set the
+     * app aspect ratio on a per-app basis. Typically, the menu is available
+     * only on large screen devices.
+     *
+     * <p>When users apply the aspect ratio override, the minimum aspect ratio
+     * specified in the app manifest is overridden. If users choose a
+     * full-screen aspect ratio, the orientation of the activity is forced to
+     * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER};
+     * see {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE} to
+     * disable the full-screen option only.
+     *
+     * <p>The user override is intended to improve the app experience on devices
+     * that have the ignore orientation request display setting enabled by OEMs
+     * (enables compatibility mode for fixed orientation on Android 12 (API
+     * level 31) or higher; see
+     * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
+     * Large screen app compatibility</a>
+     * for more details).
+     *
+     * <p>To opt out of the user aspect ratio compatibility override, add this property
+     * to your app manifest and set the value to {@code false}. Your app will be excluded
+     * from the list of apps in device settings, and users will not be able to override
+     * the app's aspect ratio.
+     *
+     * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE"
+     *     android:value="false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     * @hide
+     */
+    // TODO(b/294227289): Make this public API
+    String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE =
+            "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE";
+
+    /**
+     * Application level
+     * {@link android.content.pm.PackageManager.Property PackageManager.Property}
+     * tag that (when set to false) informs the system the app has opted out of the
+     * full-screen option of the aspect ratio compatibility override. (For
+     * background information about the aspect ratio compatibility override, see
+     * {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE}.)
+     *
+     * <p>When users apply the aspect ratio compatibility override, the orientation
+     * of the activity is forced to {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_USER}.
+     *
+     * <p>The user override is intended to improve the app experience on devices
+     * that have the ignore orientation request display setting enabled by OEMs
+     * (enables compatibility mode for fixed orientation on Android 12 (API
+     * level 31) or higher; see
+     * <a href="https://developer.android.com/guide/topics/large-screens/large-screen-app-compatibility">
+     * Large screen app compatibility</a>
+     * for more details).
+     *
+     * <p>To opt out of the full-screen option of the user aspect ratio compatibility
+     * override, add this property to your app manifest and set the value to {@code false}.
+     * Your app will have full-screen option removed from the list of user aspect ratio
+     * override options in device settings, and users will not be able to apply
+     * full-screen override to your app.
+     *
+     * <p><b>Note:</b> If {@link #PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE} is
+     * {@code false}, this property has no effect.
+     *
+     * <p>Not setting this property at all, or setting this property to {@code true} has no effect.
+     *
+     * <p><b>Syntax:</b>
+     * <pre>
+     * &lt;application&gt;
+     *   &lt;property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE"
+     *     android:value="false"/&gt;
+     * &lt;/application&gt;
+     * </pre>
+     * @hide
+     */
+    // TODO(b/294227289): Make this public API
+    String PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE =
+            "android.window.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE";
+
+    /**
      * @hide
      */
     public static final String PARCEL_KEY_SHORTCUTS_ARRAY = "shortcuts_array";
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index f14ec37..60f46e6 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -831,8 +831,17 @@
                     //   A11yService -> App -> SurfaceFlinger -> A11yService
                     ScreenCapture.ScreenCaptureListener listener =
                             new ScreenCapture.ScreenCaptureListener(
-                                    screenshot -> sendWindowScreenshotSuccess(screenshot,
-                                            interactionId));
+                                    (screenshot, status) -> {
+                                        if (status != 0) {
+                                            sendTakeScreenshotOfWindowError(
+                                                    AccessibilityService
+                                                            .ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+                                                    interactionId);
+                                        } else {
+                                            sendWindowScreenshotSuccess(screenshot,
+                                                    interactionId);
+                                        }
+                                    });
                     connection.takeScreenshotOfWindow(accessibilityWindowId, interactionId,
                             listener, this);
                     mMainHandler.postDelayed(() -> {
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 7f8f611..c877873 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -1516,7 +1516,7 @@
      * @see #startStylusHandwriting(View)
      */
     public boolean isStylusHandwritingAvailable() {
-        return isStylusHandwritingAvailableAsUser(UserHandle.myUserId());
+        return isStylusHandwritingAvailableAsUser(UserHandle.of(UserHandle.myUserId()));
     }
 
     /**
@@ -1527,14 +1527,17 @@
      * called and Stylus touch should continue as normal touch input.</p>
      *
      * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
-     * {@code userId} is different from the user id of the current process.</p>
+     * {@code user} is different from the user of the current process.</p>
      *
      * @see #startStylusHandwriting(View)
-     * @param userId user ID to query.
+     * @param user UserHandle to query.
      * @hide
      */
+    @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
-    public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+    @TestApi
+    @SuppressLint("UserHandle")
+    public boolean isStylusHandwritingAvailableAsUser(@NonNull UserHandle user) {
         final Context fallbackContext = ActivityThread.currentApplication();
         if (fallbackContext == null) {
             return false;
@@ -1551,7 +1554,7 @@
                     }
                 };
             }
-            isAvailable = mStylusHandwritingAvailableCache.query(userId);
+            isAvailable = mStylusHandwritingAvailableCache.query(user.getIdentifier());
         }
         return isAvailable;
     }
@@ -1643,16 +1646,19 @@
      * Returns the list of enabled input methods for the specified user.
      *
      * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
-     * {@code userId} is different from the user id of the current process.</p>
+     * {@code user} is different from the user of the current process.</p>
      *
-     * @param userId user ID to query
+     * @param user UserHandle to query
      * @return {@link List} of {@link InputMethodInfo}.
-     * @see #getEnabledInputMethodSubtypeListAsUser(String, boolean, int)
+     * @see #getEnabledInputMethodSubtypeListAsUser(String, boolean, UserHandle)
      * @hide
      */
+    @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
-    public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
-        return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(userId);
+    @TestApi
+    @SuppressLint("UserHandle")
+    public List<InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull UserHandle user) {
+        return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(user.getIdentifier());
     }
 
     /**
@@ -1681,7 +1687,7 @@
      *
      * @param imeId IME ID to be queried about.
      * @param allowsImplicitlyEnabledSubtypes {@code true} to include implicitly enabled subtypes.
-     * @param userId user ID to be queried about.
+     * @param user UserHandle to be queried about.
      *               {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required if this is
      *               different from the calling process user ID.
      * @return {@link List} of {@link InputMethodSubtype}.
@@ -1690,10 +1696,14 @@
      */
     @NonNull
     @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+    @TestApi
+    @SuppressLint("UserHandle")
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
-            @NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
+            @NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes,
+            @NonNull UserHandle user) {
         return IInputMethodManagerGlobalInvoker.getEnabledInputMethodSubtypeList(
-                Objects.requireNonNull(imeId), allowsImplicitlyEnabledSubtypes, userId);
+                Objects.requireNonNull(imeId), allowsImplicitlyEnabledSubtypes,
+                user.getIdentifier());
     }
 
     /**
@@ -2106,7 +2116,7 @@
         // Re-dispatch if there is a context mismatch.
         final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
         if (fallbackImm != null) {
-            return fallbackImm.showSoftInput(view, flags, resultReceiver);
+            return fallbackImm.showSoftInput(view, statsToken, flags, resultReceiver, reason);
         }
 
         checkFocus();
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index 987ac2e..f67a61b 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -160,6 +160,8 @@
 
     @NonNull
     private final AtomicReference<InputConnection> mInputConnectionRef;
+    @NonNull
+    private final AtomicBoolean mDeactivateRequested = new AtomicBoolean(false);
 
     @NonNull
     private final Looper mLooper;
@@ -211,10 +213,6 @@
         return mInputConnectionRef.get() == null;
     }
 
-    private boolean isActive() {
-        return mParentInputMethodManager.isActive() && !isFinished();
-    }
-
     private View getServedView() {
         return mServedView.get();
     }
@@ -349,25 +347,15 @@
      */
     @Dispatching(cancellable = false)
     public void deactivate() {
-        if (isFinished()) {
+        if (mDeactivateRequested.getAndSet(true)) {
             // This is a small performance optimization.  Still only the 1st call of
-            // reportFinish() will take effect.
+            // deactivate() will take effect.
             return;
         }
         dispatch(() -> {
-            // Note that we do not need to worry about race condition here, because 1) mFinished is
-            // updated only inside this block, and 2) the code here is running on a Handler hence we
-            // assume multiple closeConnection() tasks will not be handled at the same time.
-            if (isFinished()) {
-                return;
-            }
             Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#closeConnection");
             try {
                 InputConnection ic = getInputConnection();
-                // Note we do NOT check isActive() here, because this is safe
-                // for an IME to call at any time, and we need to allow it
-                // through to clean up our state after the IME has switched to
-                // another client.
                 if (ic == null) {
                     return;
                 }
@@ -429,7 +417,7 @@
     public String toString() {
         return "RemoteInputConnectionImpl{"
                 + "connection=" + getInputConnection()
-                + " mParentInputMethodManager.isActive()=" + mParentInputMethodManager.isActive()
+                + " mDeactivateRequested=" + mDeactivateRequested.get()
                 + " mServedView=" + mServedView.get()
                 + "}";
     }
@@ -464,7 +452,7 @@
     public void dispatchReportFullscreenMode(boolean enabled) {
         dispatch(() -> {
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 return;
             }
             ic.reportFullscreenMode(enabled);
@@ -480,7 +468,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
                 return null;
             }
@@ -502,7 +490,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
                 return null;
             }
@@ -524,7 +512,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getSelectedText on inactive InputConnection");
                 return null;
             }
@@ -546,7 +534,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getSurroundingText on inactive InputConnection");
                 return null;
             }
@@ -574,7 +562,7 @@
                 return 0;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
                 return 0;
             }
@@ -591,7 +579,7 @@
                 return null;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "getExtractedText on inactive InputConnection");
                 return null;
             }
@@ -608,7 +596,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitText on inactive InputConnection");
                 return;
             }
@@ -625,7 +613,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitText on inactive InputConnection");
                 return;
             }
@@ -641,7 +629,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitCompletion on inactive InputConnection");
                 return;
             }
@@ -657,7 +645,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitCorrection on inactive InputConnection");
                 return;
             }
@@ -677,7 +665,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setSelection on inactive InputConnection");
                 return;
             }
@@ -693,7 +681,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performEditorAction on inactive InputConnection");
                 return;
             }
@@ -709,7 +697,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performContextMenuAction on inactive InputConnection");
                 return;
             }
@@ -725,7 +713,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setComposingRegion on inactive InputConnection");
                 return;
             }
@@ -746,7 +734,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setComposingRegion on inactive InputConnection");
                 return;
             }
@@ -763,7 +751,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setComposingText on inactive InputConnection");
                 return;
             }
@@ -780,7 +768,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setComposingText on inactive InputConnection");
                 return;
             }
@@ -809,11 +797,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            // Note we do NOT check isActive() here, because this is safe
-            // for an IME to call at any time, and we need to allow it
-            // through to clean up our state after the IME has switched to
-            // another client.
-            if (ic == null) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "finishComposingTextFromImm on inactive InputConnection");
                 return;
             }
@@ -837,11 +821,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            // Note we do NOT check isActive() here, because this is safe
-            // for an IME to call at any time, and we need to allow it
-            // through to clean up our state after the IME has switched to
-            // another client.
-            if (ic == null) {
+            if (ic == null && mDeactivateRequested.get()) {
                 Log.w(TAG, "finishComposingText on inactive InputConnection");
                 return;
             }
@@ -857,7 +837,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "sendKeyEvent on inactive InputConnection");
                 return;
             }
@@ -873,7 +853,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "clearMetaKeyStates on inactive InputConnection");
                 return;
             }
@@ -890,7 +870,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "deleteSurroundingText on inactive InputConnection");
                 return;
             }
@@ -907,7 +887,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection");
                 return;
             }
@@ -927,7 +907,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "beginBatchEdit on inactive InputConnection");
                 return;
             }
@@ -943,7 +923,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "endBatchEdit on inactive InputConnection");
                 return;
             }
@@ -959,7 +939,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performSpellCheck on inactive InputConnection");
                 return;
             }
@@ -976,7 +956,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performPrivateCommand on inactive InputConnection");
                 return;
             }
@@ -1014,7 +994,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "performHandwritingGesture on inactive InputConnection");
                 if (resultReceiver != null) {
                     resultReceiver.send(
@@ -1054,7 +1034,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "previewHandwritingGesture on inactive InputConnection");
                 return; // cancelled
             }
@@ -1102,7 +1082,7 @@
             @InputConnection.CursorUpdateMode int cursorUpdateMode,
             @InputConnection.CursorUpdateFilter int cursorUpdateFilter, int imeDisplayId) {
         final InputConnection ic = getInputConnection();
-        if (ic == null || !isActive()) {
+        if (ic == null || mDeactivateRequested.get()) {
             Log.w(TAG, "requestCursorUpdates on inactive InputConnection");
             return false;
         }
@@ -1139,7 +1119,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "requestTextBoundsInfo on inactive InputConnection");
                 resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null);
                 return;
@@ -1168,7 +1148,7 @@
                 return false;  // cancelled
             }
             final InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "commitContent on inactive InputConnection");
                 return false;
             }
@@ -1193,7 +1173,7 @@
                 return;  // cancelled
             }
             InputConnection ic = getInputConnection();
-            if (ic == null || !isActive()) {
+            if (ic == null || mDeactivateRequested.get()) {
                 Log.w(TAG, "setImeConsumesInput on inactive InputConnection");
                 return;
             }
@@ -1217,7 +1197,7 @@
                         return; // cancelled
                     }
                     InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
+                    if (ic == null || mDeactivateRequested.get()) {
                         Log.w(TAG, "replaceText on inactive InputConnection");
                         return;
                     }
@@ -1236,7 +1216,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "commitText on inactive InputConnection");
                     return;
                 }
@@ -1256,7 +1236,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "setSelection on inactive InputConnection");
                     return;
                 }
@@ -1273,7 +1253,7 @@
                     return null;  // cancelled
                 }
                 final InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "getSurroundingText on inactive InputConnection");
                     return null;
                 }
@@ -1301,7 +1281,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "deleteSurroundingText on inactive InputConnection");
                     return;
                 }
@@ -1317,7 +1297,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "sendKeyEvent on inactive InputConnection");
                     return;
                 }
@@ -1333,7 +1313,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "performEditorAction on inactive InputConnection");
                     return;
                 }
@@ -1349,7 +1329,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "performContextMenuAction on inactive InputConnection");
                     return;
                 }
@@ -1366,7 +1346,7 @@
                     return 0;  // cancelled
                 }
                 final InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
                     return 0;
                 }
@@ -1382,7 +1362,7 @@
                     return;  // cancelled
                 }
                 InputConnection ic = getInputConnection();
-                if (ic == null || !isActive()) {
+                if (ic == null || mDeactivateRequested.get()) {
                     Log.w(TAG, "clearMetaKeyStates on inactive InputConnection");
                     return;
                 }
diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
deleted file mode 100644
index aaeb120..0000000
--- a/core/java/android/view/selectiontoolbar/ISelectionToolbarCallback.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.view.selectiontoolbar.ToolbarMenuItem;
-import android.view.selectiontoolbar.WidgetInfo;
-
-/**
- * Binder interface to notify the selection toolbar events from one process to the other.
- * @hide
- */
-oneway interface ISelectionToolbarCallback {
-    void onShown(in WidgetInfo info);
-    void onWidgetUpdated(in WidgetInfo info);
-    void onToolbarShowTimeout();
-    void onMenuItemClicked(in ToolbarMenuItem item);
-    void onError(int errorCode);
-}
diff --git a/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl b/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl
deleted file mode 100644
index 4a647ad..0000000
--- a/core/java/android/view/selectiontoolbar/ISelectionToolbarManager.aidl
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ShowInfo;
-
-/**
- * Mediator between apps and selection toolbar service implementation.
- *
- * @hide
- */
-oneway interface ISelectionToolbarManager {
-    void showToolbar(in ShowInfo showInfo, in ISelectionToolbarCallback callback, int userId);
-    void hideToolbar(long widgetToken, int userId);
-    void dismissToolbar(long widgetToken, int userId);
-}
\ No newline at end of file
diff --git a/core/java/android/view/selectiontoolbar/OWNERS b/core/java/android/view/selectiontoolbar/OWNERS
deleted file mode 100644
index 5500b92..0000000
--- a/core/java/android/view/selectiontoolbar/OWNERS
+++ /dev/null
@@ -1,10 +0,0 @@
-# Bug component: 709498
-
-augale@google.com
-joannechung@google.com
-licha@google.com
-lpeter@google.com
-svetoslavganov@google.com
-toki@google.com
-tonymak@google.com
-tymtsai@google.com
\ No newline at end of file
diff --git a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java b/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
deleted file mode 100644
index 6de0316..0000000
--- a/core/java/android/view/selectiontoolbar/SelectionToolbarManager.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.annotation.SystemService;
-import android.content.Context;
-import android.os.RemoteException;
-import android.provider.DeviceConfig;
-
-import java.util.Objects;
-
-/**
- * The {@link SelectionToolbarManager} class provides ways for apps to control the
- * selection toolbar.
- *
- * @hide
- */
-@SystemService(Context.SELECTION_TOOLBAR_SERVICE)
-public final class SelectionToolbarManager {
-
-    private static final String TAG = "SelectionToolbar";
-
-    /**
-     * The tag which uses for enabling debug log dump. To enable it, we can use command "adb shell
-     * setprop log.tag.UiTranslation DEBUG".
-     */
-    public static final String LOG_TAG = "SelectionToolbar";
-
-    /**
-     * Whether system selection toolbar is enabled.
-     */
-    private static final String REMOTE_SELECTION_TOOLBAR_ENABLED =
-            "remote_selection_toolbar_enabled";
-
-    /**
-     * Used to mark a toolbar that has no toolbar token id.
-     */
-    public static final long NO_TOOLBAR_ID = 0;
-
-    /**
-     * The error code that do not allow to create multiple toolbar.
-     */
-    public static final int ERROR_DO_NOT_ALLOW_MULTIPLE_TOOL_BAR = 1;
-
-    @NonNull
-    private final Context mContext;
-    private final ISelectionToolbarManager mService;
-
-    public SelectionToolbarManager(@NonNull Context context,
-            @NonNull ISelectionToolbarManager service) {
-        mContext = Objects.requireNonNull(context);
-        mService = service;
-    }
-
-    /**
-     * Request to show selection toolbar for a given View.
-     */
-    public void showToolbar(@NonNull ShowInfo showInfo,
-            @NonNull ISelectionToolbarCallback callback) {
-        try {
-            Objects.requireNonNull(showInfo);
-            Objects.requireNonNull(callback);
-            mService.showToolbar(showInfo, callback, mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Request to hide selection toolbar.
-     */
-    public void hideToolbar(long widgetToken) {
-        try {
-            mService.hideToolbar(widgetToken, mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Dismiss to dismiss selection toolbar.
-     */
-    public void dismissToolbar(long widgetToken) {
-        try {
-            mService.dismissToolbar(widgetToken, mContext.getUserId());
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    private boolean isRemoteSelectionToolbarEnabled() {
-        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SELECTION_TOOLBAR,
-                REMOTE_SELECTION_TOOLBAR_ENABLED, false);
-    }
-
-    /**
-     * Returns {@code true} if remote render selection toolbar enabled, otherwise
-     * returns {@code false}.
-     */
-    public static boolean isRemoteSelectionToolbarEnabled(Context context) {
-        SelectionToolbarManager manager = context.getSystemService(SelectionToolbarManager.class);
-        if (manager != null) {
-            return manager.isRemoteSelectionToolbarEnabled();
-        }
-        return false;
-    }
-}
diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.aidl b/core/java/android/view/selectiontoolbar/ShowInfo.aidl
deleted file mode 100644
index dce9c15..0000000
--- a/core/java/android/view/selectiontoolbar/ShowInfo.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-/**
- * @hide
- */
-parcelable ShowInfo;
diff --git a/core/java/android/view/selectiontoolbar/ShowInfo.java b/core/java/android/view/selectiontoolbar/ShowInfo.java
deleted file mode 100644
index 28b4480..0000000
--- a/core/java/android/view/selectiontoolbar/ShowInfo.java
+++ /dev/null
@@ -1,361 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.os.Parcelable;
-
-import com.android.internal.util.DataClass;
-
-import java.util.List;
-
-
-/**
- * The class holds menu information for render service to render the selection toolbar.
- *
- * @hide
- */
-@DataClass(genToString = true, genEqualsHashCode = true)
-public final class ShowInfo implements Parcelable {
-
-    /**
-     * The token that is used to identify the selection toolbar. This is initially set to 0
-     * until a selection toolbar has been created for the showToolbar request.
-     */
-    private final long mWidgetToken;
-
-    /**
-     * If the toolbar menu items need to be re-layout.
-     */
-    private final boolean mLayoutRequired;
-
-    /**
-     * The menu items to be rendered in the selection toolbar.
-     */
-    @NonNull
-    private final List<ToolbarMenuItem> mMenuItems;
-
-    /**
-     * A rect specifying where the selection toolbar on the screen.
-     */
-    @NonNull
-    private final Rect mContentRect;
-
-    /**
-     * A recommended maximum suggested width of the selection toolbar.
-     */
-    private final int mSuggestedWidth;
-
-    /**
-     * The portion of the screen that is available to the selection toolbar.
-     */
-    @NonNull
-    private final Rect mViewPortOnScreen;
-
-    /**
-     * The host application's input token, this allows the remote render service to transfer
-     * the touch focus to the host application.
-     */
-    @NonNull
-    private final IBinder mHostInputToken;
-
-    /**
-     * If the host application uses light theme.
-     */
-    private final boolean mIsLightTheme;
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    /**
-     * Creates a new ShowInfo.
-     *
-     * @param widgetToken
-     *   The token that is used to identify the selection toolbar. This is initially set to 0
-     *   until a selection toolbar has been created for the showToolbar request.
-     * @param layoutRequired
-     *   If the toolbar menu items need to be re-layout.
-     * @param menuItems
-     *   The menu items to be rendered in the selection toolbar.
-     * @param contentRect
-     *   A rect specifying where the selection toolbar on the screen.
-     * @param suggestedWidth
-     *   A recommended maximum suggested width of the selection toolbar.
-     * @param viewPortOnScreen
-     *   The portion of the screen that is available to the selection toolbar.
-     * @param hostInputToken
-     *   The host application's input token, this allows the remote render service to transfer
-     *   the touch focus to the host application.
-     * @param isLightTheme
-     *   If the host application uses light theme.
-     */
-    @DataClass.Generated.Member
-    public ShowInfo(
-            long widgetToken,
-            boolean layoutRequired,
-            @NonNull List<ToolbarMenuItem> menuItems,
-            @NonNull Rect contentRect,
-            int suggestedWidth,
-            @NonNull Rect viewPortOnScreen,
-            @NonNull IBinder hostInputToken,
-            boolean isLightTheme) {
-        this.mWidgetToken = widgetToken;
-        this.mLayoutRequired = layoutRequired;
-        this.mMenuItems = menuItems;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mMenuItems);
-        this.mContentRect = contentRect;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mContentRect);
-        this.mSuggestedWidth = suggestedWidth;
-        this.mViewPortOnScreen = viewPortOnScreen;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mViewPortOnScreen);
-        this.mHostInputToken = hostInputToken;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mHostInputToken);
-        this.mIsLightTheme = isLightTheme;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    /**
-     * The token that is used to identify the selection toolbar. This is initially set to 0
-     * until a selection toolbar has been created for the showToolbar request.
-     */
-    @DataClass.Generated.Member
-    public long getWidgetToken() {
-        return mWidgetToken;
-    }
-
-    /**
-     * If the toolbar menu items need to be re-layout.
-     */
-    @DataClass.Generated.Member
-    public boolean isLayoutRequired() {
-        return mLayoutRequired;
-    }
-
-    /**
-     * The menu items to be rendered in the selection toolbar.
-     */
-    @DataClass.Generated.Member
-    public @NonNull List<ToolbarMenuItem> getMenuItems() {
-        return mMenuItems;
-    }
-
-    /**
-     * A rect specifying where the selection toolbar on the screen.
-     */
-    @DataClass.Generated.Member
-    public @NonNull Rect getContentRect() {
-        return mContentRect;
-    }
-
-    /**
-     * A recommended maximum suggested width of the selection toolbar.
-     */
-    @DataClass.Generated.Member
-    public int getSuggestedWidth() {
-        return mSuggestedWidth;
-    }
-
-    /**
-     * The portion of the screen that is available to the selection toolbar.
-     */
-    @DataClass.Generated.Member
-    public @NonNull Rect getViewPortOnScreen() {
-        return mViewPortOnScreen;
-    }
-
-    /**
-     * The host application's input token, this allows the remote render service to transfer
-     * the touch focus to the host application.
-     */
-    @DataClass.Generated.Member
-    public @NonNull IBinder getHostInputToken() {
-        return mHostInputToken;
-    }
-
-    /**
-     * If the host application uses light theme.
-     */
-    @DataClass.Generated.Member
-    public boolean isIsLightTheme() {
-        return mIsLightTheme;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public String toString() {
-        // You can override field toString logic by defining methods like:
-        // String fieldNameToString() { ... }
-
-        return "ShowInfo { " +
-                "widgetToken = " + mWidgetToken + ", " +
-                "layoutRequired = " + mLayoutRequired + ", " +
-                "menuItems = " + mMenuItems + ", " +
-                "contentRect = " + mContentRect + ", " +
-                "suggestedWidth = " + mSuggestedWidth + ", " +
-                "viewPortOnScreen = " + mViewPortOnScreen + ", " +
-                "hostInputToken = " + mHostInputToken + ", " +
-                "isLightTheme = " + mIsLightTheme +
-        " }";
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public boolean equals(@android.annotation.Nullable Object o) {
-        // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(ShowInfo other) { ... }
-        // boolean fieldNameEquals(FieldType otherValue) { ... }
-
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        @SuppressWarnings("unchecked")
-        ShowInfo that = (ShowInfo) o;
-        //noinspection PointlessBooleanExpression
-        return true
-                && mWidgetToken == that.mWidgetToken
-                && mLayoutRequired == that.mLayoutRequired
-                && java.util.Objects.equals(mMenuItems, that.mMenuItems)
-                && java.util.Objects.equals(mContentRect, that.mContentRect)
-                && mSuggestedWidth == that.mSuggestedWidth
-                && java.util.Objects.equals(mViewPortOnScreen, that.mViewPortOnScreen)
-                && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
-                && mIsLightTheme == that.mIsLightTheme;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int hashCode() {
-        // You can override field hashCode logic by defining methods like:
-        // int fieldNameHashCode() { ... }
-
-        int _hash = 1;
-        _hash = 31 * _hash + Long.hashCode(mWidgetToken);
-        _hash = 31 * _hash + Boolean.hashCode(mLayoutRequired);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mMenuItems);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mContentRect);
-        _hash = 31 * _hash + mSuggestedWidth;
-        _hash = 31 * _hash + java.util.Objects.hashCode(mViewPortOnScreen);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mHostInputToken);
-        _hash = 31 * _hash + Boolean.hashCode(mIsLightTheme);
-        return _hash;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        int flg = 0;
-        if (mLayoutRequired) flg |= 0x2;
-        if (mIsLightTheme) flg |= 0x80;
-        dest.writeInt(flg);
-        dest.writeLong(mWidgetToken);
-        dest.writeParcelableList(mMenuItems, flags);
-        dest.writeTypedObject(mContentRect, flags);
-        dest.writeInt(mSuggestedWidth);
-        dest.writeTypedObject(mViewPortOnScreen, flags);
-        dest.writeStrongBinder(mHostInputToken);
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int describeContents() { return 0; }
-
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    @DataClass.Generated.Member
-    /* package-private */ ShowInfo(@NonNull android.os.Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        int flg = in.readInt();
-        boolean layoutRequired = (flg & 0x2) != 0;
-        boolean isLightTheme = (flg & 0x80) != 0;
-        long widgetToken = in.readLong();
-        List<ToolbarMenuItem> menuItems = new java.util.ArrayList<>();
-        in.readParcelableList(menuItems, ToolbarMenuItem.class.getClassLoader(), android.view.selectiontoolbar.ToolbarMenuItem.class);
-        Rect contentRect = (Rect) in.readTypedObject(Rect.CREATOR);
-        int suggestedWidth = in.readInt();
-        Rect viewPortOnScreen = (Rect) in.readTypedObject(Rect.CREATOR);
-        IBinder hostInputToken = (IBinder) in.readStrongBinder();
-
-        this.mWidgetToken = widgetToken;
-        this.mLayoutRequired = layoutRequired;
-        this.mMenuItems = menuItems;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mMenuItems);
-        this.mContentRect = contentRect;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mContentRect);
-        this.mSuggestedWidth = suggestedWidth;
-        this.mViewPortOnScreen = viewPortOnScreen;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mViewPortOnScreen);
-        this.mHostInputToken = hostInputToken;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mHostInputToken);
-        this.mIsLightTheme = isLightTheme;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<ShowInfo> CREATOR
-            = new Parcelable.Creator<ShowInfo>() {
-        @Override
-        public ShowInfo[] newArray(int size) {
-            return new ShowInfo[size];
-        }
-
-        @Override
-        public ShowInfo createFromParcel(@NonNull android.os.Parcel in) {
-            return new ShowInfo(in);
-        }
-    };
-
-    @DataClass.Generated(
-            time = 1645108384245L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ShowInfo.java",
-            inputSignatures = "private final  long mWidgetToken\nprivate final  boolean mLayoutRequired\nprivate final @android.annotation.NonNull java.util.List<android.view.selectiontoolbar.ToolbarMenuItem> mMenuItems\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final  int mSuggestedWidth\nprivate final @android.annotation.NonNull android.graphics.Rect mViewPortOnScreen\nprivate final @android.annotation.NonNull android.os.IBinder mHostInputToken\nprivate final  boolean mIsLightTheme\nclass ShowInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
-}
diff --git a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.aidl b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.aidl
deleted file mode 100644
index 711a85a..0000000
--- a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.aidl
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-/**
- * @hide
- */
-parcelable ToolbarMenuItem;
diff --git a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java b/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
deleted file mode 100644
index 89347c6..0000000
--- a/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
+++ /dev/null
@@ -1,543 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.drawable.Icon;
-import android.os.Parcelable;
-import android.view.MenuItem;
-
-import com.android.internal.util.DataClass;
-
-/**
- * The menu item that is used to show the selection toolbar.
- *
- * @hide
- */
-@DataClass(genBuilder = true, genToString = true, genEqualsHashCode = true)
-public final class ToolbarMenuItem implements Parcelable {
-
-    /**
-     * The priority of menu item is unknown.
-     */
-    public static final int PRIORITY_UNKNOWN = 0;
-
-    /**
-     * The priority of menu item is shown in primary selection toolbar.
-     */
-    public static final int PRIORITY_PRIMARY = 1;
-
-    /**
-     * The priority of menu item is shown in overflow selection toolbar.
-     */
-    public static final int PRIORITY_OVERFLOW = 2;
-
-    /**
-     * The id of the menu item.
-     *
-     * @see MenuItem#getItemId()
-     */
-    private final int mItemId;
-
-    /**
-     * The title of the menu item.
-     *
-     * @see MenuItem#getTitle()
-     */
-    @NonNull
-    private final CharSequence mTitle;
-
-    /**
-     * The content description of the menu item.
-     *
-     * @see MenuItem#getContentDescription()
-     */
-    @Nullable
-    private final CharSequence mContentDescription;
-
-    /**
-     * The group id of the menu item.
-     *
-     * @see MenuItem#getGroupId()
-     */
-    private final int mGroupId;
-
-    /**
-     * The icon id of the menu item.
-     *
-     * @see MenuItem#getIcon()
-     */
-    @Nullable
-    private final Icon mIcon;
-
-    /**
-     * The tooltip text of the menu item.
-     *
-     * @see MenuItem#getTooltipText()
-     */
-    @Nullable
-    private final CharSequence mTooltipText;
-
-    /**
-     * The priority of the menu item used to display the order of the menu item.
-     */
-    private final int mPriority;
-
-    /**
-     * Returns the priority from a given {@link MenuItem}.
-     */
-    public static int getPriorityFromMenuItem(MenuItem menuItem) {
-        if (menuItem.requiresActionButton()) {
-            return PRIORITY_PRIMARY;
-        } else if (menuItem.requiresOverflow()) {
-            return PRIORITY_OVERFLOW;
-        }
-        return PRIORITY_UNKNOWN;
-    }
-
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    @android.annotation.IntDef(prefix = "PRIORITY_", value = {
-        PRIORITY_UNKNOWN,
-        PRIORITY_PRIMARY,
-        PRIORITY_OVERFLOW
-    })
-    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
-    @DataClass.Generated.Member
-    public @interface Priority {}
-
-    @DataClass.Generated.Member
-    public static String priorityToString(@Priority int value) {
-        switch (value) {
-            case PRIORITY_UNKNOWN:
-                    return "PRIORITY_UNKNOWN";
-            case PRIORITY_PRIMARY:
-                    return "PRIORITY_PRIMARY";
-            case PRIORITY_OVERFLOW:
-                    return "PRIORITY_OVERFLOW";
-            default: return Integer.toHexString(value);
-        }
-    }
-
-    @DataClass.Generated.Member
-    /* package-private */ ToolbarMenuItem(
-            int itemId,
-            @NonNull CharSequence title,
-            @Nullable CharSequence contentDescription,
-            int groupId,
-            @Nullable Icon icon,
-            @Nullable CharSequence tooltipText,
-            int priority) {
-        this.mItemId = itemId;
-        this.mTitle = title;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTitle);
-        this.mContentDescription = contentDescription;
-        this.mGroupId = groupId;
-        this.mIcon = icon;
-        this.mTooltipText = tooltipText;
-        this.mPriority = priority;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    /**
-     * The id of the menu item.
-     *
-     * @see MenuItem#getItemId()
-     */
-    @DataClass.Generated.Member
-    public int getItemId() {
-        return mItemId;
-    }
-
-    /**
-     * The title of the menu item.
-     *
-     * @see MenuItem#getTitle()
-     */
-    @DataClass.Generated.Member
-    public @NonNull CharSequence getTitle() {
-        return mTitle;
-    }
-
-    /**
-     * The content description of the menu item.
-     *
-     * @see MenuItem#getContentDescription()
-     */
-    @DataClass.Generated.Member
-    public @Nullable CharSequence getContentDescription() {
-        return mContentDescription;
-    }
-
-    /**
-     * The group id of the menu item.
-     *
-     * @see MenuItem#getGroupId()
-     */
-    @DataClass.Generated.Member
-    public int getGroupId() {
-        return mGroupId;
-    }
-
-    /**
-     * The icon id of the menu item.
-     *
-     * @see MenuItem#getIcon()
-     */
-    @DataClass.Generated.Member
-    public @Nullable Icon getIcon() {
-        return mIcon;
-    }
-
-    /**
-     * The tooltip text of the menu item.
-     *
-     * @see MenuItem#getTooltipText()
-     */
-    @DataClass.Generated.Member
-    public @Nullable CharSequence getTooltipText() {
-        return mTooltipText;
-    }
-
-    /**
-     * The priority of the menu item used to display the order of the menu item.
-     */
-    @DataClass.Generated.Member
-    public int getPriority() {
-        return mPriority;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public String toString() {
-        // You can override field toString logic by defining methods like:
-        // String fieldNameToString() { ... }
-
-        return "ToolbarMenuItem { " +
-                "itemId = " + mItemId + ", " +
-                "title = " + mTitle + ", " +
-                "contentDescription = " + mContentDescription + ", " +
-                "groupId = " + mGroupId + ", " +
-                "icon = " + mIcon + ", " +
-                "tooltipText = " + mTooltipText + ", " +
-                "priority = " + mPriority +
-        " }";
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public boolean equals(@Nullable Object o) {
-        // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(ToolbarMenuItem other) { ... }
-        // boolean fieldNameEquals(FieldType otherValue) { ... }
-
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        @SuppressWarnings("unchecked")
-        ToolbarMenuItem that = (ToolbarMenuItem) o;
-        //noinspection PointlessBooleanExpression
-        return true
-                && mItemId == that.mItemId
-                && java.util.Objects.equals(mTitle, that.mTitle)
-                && java.util.Objects.equals(mContentDescription, that.mContentDescription)
-                && mGroupId == that.mGroupId
-                && java.util.Objects.equals(mIcon, that.mIcon)
-                && java.util.Objects.equals(mTooltipText, that.mTooltipText)
-                && mPriority == that.mPriority;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int hashCode() {
-        // You can override field hashCode logic by defining methods like:
-        // int fieldNameHashCode() { ... }
-
-        int _hash = 1;
-        _hash = 31 * _hash + mItemId;
-        _hash = 31 * _hash + java.util.Objects.hashCode(mTitle);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mContentDescription);
-        _hash = 31 * _hash + mGroupId;
-        _hash = 31 * _hash + java.util.Objects.hashCode(mIcon);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mTooltipText);
-        _hash = 31 * _hash + mPriority;
-        return _hash;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        byte flg = 0;
-        if (mContentDescription != null) flg |= 0x4;
-        if (mIcon != null) flg |= 0x10;
-        if (mTooltipText != null) flg |= 0x20;
-        dest.writeByte(flg);
-        dest.writeInt(mItemId);
-        dest.writeCharSequence(mTitle);
-        if (mContentDescription != null) dest.writeCharSequence(mContentDescription);
-        dest.writeInt(mGroupId);
-        if (mIcon != null) dest.writeTypedObject(mIcon, flags);
-        if (mTooltipText != null) dest.writeCharSequence(mTooltipText);
-        dest.writeInt(mPriority);
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int describeContents() { return 0; }
-
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    @DataClass.Generated.Member
-    /* package-private */ ToolbarMenuItem(@NonNull android.os.Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        byte flg = in.readByte();
-        int itemId = in.readInt();
-        CharSequence title = (CharSequence) in.readCharSequence();
-        CharSequence contentDescription = (flg & 0x4) == 0 ? null : (CharSequence) in.readCharSequence();
-        int groupId = in.readInt();
-        Icon icon = (flg & 0x10) == 0 ? null : (Icon) in.readTypedObject(Icon.CREATOR);
-        CharSequence tooltipText = (flg & 0x20) == 0 ? null : (CharSequence) in.readCharSequence();
-        int priority = in.readInt();
-
-        this.mItemId = itemId;
-        this.mTitle = title;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTitle);
-        this.mContentDescription = contentDescription;
-        this.mGroupId = groupId;
-        this.mIcon = icon;
-        this.mTooltipText = tooltipText;
-        this.mPriority = priority;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<ToolbarMenuItem> CREATOR
-            = new Parcelable.Creator<ToolbarMenuItem>() {
-        @Override
-        public ToolbarMenuItem[] newArray(int size) {
-            return new ToolbarMenuItem[size];
-        }
-
-        @Override
-        public ToolbarMenuItem createFromParcel(@NonNull android.os.Parcel in) {
-            return new ToolbarMenuItem(in);
-        }
-    };
-
-    /**
-     * A builder for {@link ToolbarMenuItem}
-     */
-    @SuppressWarnings("WeakerAccess")
-    @DataClass.Generated.Member
-    public static final class Builder {
-
-        private int mItemId;
-        private @NonNull CharSequence mTitle;
-        private @Nullable CharSequence mContentDescription;
-        private int mGroupId;
-        private @Nullable Icon mIcon;
-        private @Nullable CharSequence mTooltipText;
-        private int mPriority;
-
-        private long mBuilderFieldsSet = 0L;
-
-        /**
-         * Creates a new Builder.
-         *
-         * @param itemId
-         *   The id of the menu item.
-         * @param title
-         *   The title of the menu item.
-         * @param contentDescription
-         *   The content description of the menu item.
-         * @param groupId
-         *   The group id of the menu item.
-         * @param icon
-         *   The icon id of the menu item.
-         * @param tooltipText
-         *   The tooltip text of the menu item.
-         * @param priority
-         *   The priority of the menu item used to display the order of the menu item.
-         */
-        public Builder(
-                int itemId,
-                @NonNull CharSequence title,
-                @Nullable CharSequence contentDescription,
-                int groupId,
-                @Nullable Icon icon,
-                @Nullable CharSequence tooltipText,
-                int priority) {
-            mItemId = itemId;
-            mTitle = title;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mTitle);
-            mContentDescription = contentDescription;
-            mGroupId = groupId;
-            mIcon = icon;
-            mTooltipText = tooltipText;
-            mPriority = priority;
-        }
-
-        /**
-         * The id of the menu item.
-         *
-         * @see MenuItem#getItemId()
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setItemId(int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x1;
-            mItemId = value;
-            return this;
-        }
-
-        /**
-         * The title of the menu item.
-         *
-         * @see MenuItem#getTitle()
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setTitle(@NonNull CharSequence value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x2;
-            mTitle = value;
-            return this;
-        }
-
-        /**
-         * The content description of the menu item.
-         *
-         * @see MenuItem#getContentDescription()
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setContentDescription(@NonNull CharSequence value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x4;
-            mContentDescription = value;
-            return this;
-        }
-
-        /**
-         * The group id of the menu item.
-         *
-         * @see MenuItem#getGroupId()
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setGroupId(int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x8;
-            mGroupId = value;
-            return this;
-        }
-
-        /**
-         * The icon id of the menu item.
-         *
-         * @see MenuItem#getIcon()
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setIcon(@NonNull Icon value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x10;
-            mIcon = value;
-            return this;
-        }
-
-        /**
-         * The tooltip text of the menu item.
-         *
-         * @see MenuItem#getTooltipText()
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setTooltipText(@NonNull CharSequence value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x20;
-            mTooltipText = value;
-            return this;
-        }
-
-        /**
-         * The priority of the menu item used to display the order of the menu item.
-         */
-        @DataClass.Generated.Member
-        public @NonNull Builder setPriority(int value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x40;
-            mPriority = value;
-            return this;
-        }
-
-        /** Builds the instance. This builder should not be touched after calling this! */
-        public @NonNull ToolbarMenuItem build() {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x80; // Mark builder used
-
-            ToolbarMenuItem o = new ToolbarMenuItem(
-                    mItemId,
-                    mTitle,
-                    mContentDescription,
-                    mGroupId,
-                    mIcon,
-                    mTooltipText,
-                    mPriority);
-            return o;
-        }
-
-        private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x80) != 0) {
-                throw new IllegalStateException(
-                        "This Builder should not be reused. Use a new Builder instance instead");
-            }
-        }
-    }
-
-    @DataClass.Generated(
-            time = 1643200806234L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/ToolbarMenuItem.java",
-            inputSignatures = "public static final  int PRIORITY_UNKNOWN\npublic static final  int PRIORITY_PRIMARY\npublic static final  int PRIORITY_OVERFLOW\nprivate final  int mItemId\nprivate final @android.annotation.NonNull java.lang.CharSequence mTitle\nprivate final @android.annotation.Nullable java.lang.CharSequence mContentDescription\nprivate final  int mGroupId\nprivate final @android.annotation.Nullable android.graphics.drawable.Icon mIcon\nprivate final @android.annotation.Nullable java.lang.CharSequence mTooltipText\nprivate final  int mPriority\npublic static  int getPriorityFromMenuItem(android.view.MenuItem)\nclass ToolbarMenuItem extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genBuilder=true, genToString=true, genEqualsHashCode=true)")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
-}
diff --git a/core/java/android/view/selectiontoolbar/WidgetInfo.java b/core/java/android/view/selectiontoolbar/WidgetInfo.java
deleted file mode 100644
index 5d0fd47..0000000
--- a/core/java/android/view/selectiontoolbar/WidgetInfo.java
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.graphics.Rect;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.view.SurfaceControlViewHost;
-
-import com.android.internal.util.DataClass;
-
-/**
- * The class holds the rendered content and the related information from the render service to
- * be used to show on the selection toolbar.
- *
- * @hide
- */
-@DataClass(genToString = true, genEqualsHashCode = true)
-public final class WidgetInfo implements Parcelable {
-
-    /**
-     * The token that is used to identify the selection toolbar.
-     */
-    private final long mWidgetToken;
-
-    /**
-     * A Rect that defines the size and positioning of the remote view with respect to
-     * its host window.
-     */
-    @NonNull
-    private final Rect mContentRect;
-
-    /**
-     * The SurfacePackage pointing to the remote view.
-     */
-    @NonNull
-    private final SurfaceControlViewHost.SurfacePackage mSurfacePackage;
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/selectiontoolbar/WidgetInfo.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    /**
-     * Creates a new WidgetInfo.
-     *
-     * @param widgetToken
-     *   The token that is used to identify the selection toolbar.
-     * @param contentRect
-     *   A Rect that defines the size and positioning of the remote view with respect to
-     *   its host window.
-     * @param surfacePackage
-     *   The SurfacePackage pointing to the remote view.
-     */
-    @DataClass.Generated.Member
-    public WidgetInfo(
-            long widgetToken,
-            @NonNull Rect contentRect,
-            @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) {
-        this.mWidgetToken = widgetToken;
-        this.mContentRect = contentRect;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mContentRect);
-        this.mSurfacePackage = surfacePackage;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mSurfacePackage);
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    /**
-     * The token that is used to identify the selection toolbar.
-     */
-    @DataClass.Generated.Member
-    public long getWidgetToken() {
-        return mWidgetToken;
-    }
-
-    /**
-     * A Rect that defines the size and positioning of the remote view with respect to
-     * its host window.
-     */
-    @DataClass.Generated.Member
-    public @NonNull Rect getContentRect() {
-        return mContentRect;
-    }
-
-    /**
-     * The SurfacePackage pointing to the remote view.
-     */
-    @DataClass.Generated.Member
-    public @NonNull SurfaceControlViewHost.SurfacePackage getSurfacePackage() {
-        return mSurfacePackage;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public String toString() {
-        // You can override field toString logic by defining methods like:
-        // String fieldNameToString() { ... }
-
-        return "WidgetInfo { " +
-                "widgetToken = " + mWidgetToken + ", " +
-                "contentRect = " + mContentRect + ", " +
-                "surfacePackage = " + mSurfacePackage +
-        " }";
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public boolean equals(@android.annotation.Nullable Object o) {
-        // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(WidgetInfo other) { ... }
-        // boolean fieldNameEquals(FieldType otherValue) { ... }
-
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        @SuppressWarnings("unchecked")
-        WidgetInfo that = (WidgetInfo) o;
-        //noinspection PointlessBooleanExpression
-        return true
-                && mWidgetToken == that.mWidgetToken
-                && java.util.Objects.equals(mContentRect, that.mContentRect)
-                && java.util.Objects.equals(mSurfacePackage, that.mSurfacePackage);
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int hashCode() {
-        // You can override field hashCode logic by defining methods like:
-        // int fieldNameHashCode() { ... }
-
-        int _hash = 1;
-        _hash = 31 * _hash + Long.hashCode(mWidgetToken);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mContentRect);
-        _hash = 31 * _hash + java.util.Objects.hashCode(mSurfacePackage);
-        return _hash;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        dest.writeLong(mWidgetToken);
-        dest.writeTypedObject(mContentRect, flags);
-        dest.writeTypedObject(mSurfacePackage, flags);
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int describeContents() { return 0; }
-
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    @DataClass.Generated.Member
-    /* package-private */ WidgetInfo(@NonNull Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        long widgetToken = in.readLong();
-        Rect contentRect = (Rect) in.readTypedObject(Rect.CREATOR);
-        SurfaceControlViewHost.SurfacePackage surfacePackage = (SurfaceControlViewHost.SurfacePackage) in.readTypedObject(SurfaceControlViewHost.SurfacePackage.CREATOR);
-
-        this.mWidgetToken = widgetToken;
-        this.mContentRect = contentRect;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mContentRect);
-        this.mSurfacePackage = surfacePackage;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mSurfacePackage);
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<WidgetInfo> CREATOR
-            = new Parcelable.Creator<WidgetInfo>() {
-        @Override
-        public WidgetInfo[] newArray(int size) {
-            return new WidgetInfo[size];
-        }
-
-        @Override
-        public WidgetInfo createFromParcel(@NonNull Parcel in) {
-            return new WidgetInfo(in);
-        }
-    };
-
-    @DataClass.Generated(
-            time = 1643281495056L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/view/selectiontoolbar/WidgetInfo.java",
-            inputSignatures = "private final  long mWidgetToken\nprivate final @android.annotation.NonNull android.graphics.Rect mContentRect\nprivate final @android.annotation.NonNull android.view.SurfaceControlViewHost.SurfacePackage mSurfacePackage\nclass WidgetInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
-}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 1d6778b..55b2251 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -1498,6 +1498,11 @@
      * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
      */
     private int consumeFlingInStretch(int unconsumed) {
+        int scrollX = getScrollX();
+        if (scrollX < 0 || scrollX > getScrollRange()) {
+            // We've overscrolled, so don't stretch
+            return unconsumed;
+        }
         if (unconsumed > 0 && mEdgeGlowLeft != null && mEdgeGlowLeft.getDistance() != 0f) {
             int size = getWidth();
             float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index b0e5f777..7a96fd2 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -337,7 +337,7 @@
      *
      * @hide
      */
-    private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 2000;
+    private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 5000;
 
     /**
      * Application that hosts the remote views.
@@ -4823,7 +4823,7 @@
     public static boolean isAdapterConversionEnabled() {
         return AppGlobals.getIntCoreSetting(
                 SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
-                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) == 1;
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) != 0;
     }
 
     /**
diff --git a/core/java/android/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java
index d4f4d19..a250a86 100644
--- a/core/java/android/widget/RemoteViewsService.java
+++ b/core/java/android/widget/RemoteViewsService.java
@@ -47,7 +47,7 @@
      *
      * @hide
      */
-    private static final int MAX_NUM_ENTRY = 25;
+    private static final int MAX_NUM_ENTRY = 10;
 
     /**
      * An interface for an adapter between a remote collection view (ListView, GridView, etc) and
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index eeb6b43..d330ebf 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1566,6 +1566,11 @@
      * @return The unconsumed delta after the EdgeEffects have had an opportunity to consume.
      */
     private int consumeFlingInStretch(int unconsumed) {
+        int scrollY = getScrollY();
+        if (scrollY < 0 || scrollY > getScrollRange()) {
+            // We've overscrolled, so don't stretch
+            return unconsumed;
+        }
         if (unconsumed > 0 && mEdgeGlowTop != null && mEdgeGlowTop.getDistance() != 0f) {
             int size = getHeight();
             float deltaDistance = -unconsumed * FLING_DESTRETCH_FACTOR / size;
diff --git a/core/java/android/widget/TEST_MAPPING b/core/java/android/widget/TEST_MAPPING
index 107cac2..bc71bee 100644
--- a/core/java/android/widget/TEST_MAPPING
+++ b/core/java/android/widget/TEST_MAPPING
@@ -45,6 +45,17 @@
           "exclude-annotation": "android.platform.test.annotations.AppModeFull"
         }
       ]
+    },
+    {
+      "name": "CtsTextTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
     }
   ]
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 56349d1..afe7559 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -13202,9 +13202,8 @@
         if (mTextOperationUser == null) {
             return super.isStylusHandwritingAvailable();
         }
-        final int userId = mTextOperationUser.getIdentifier();
         final InputMethodManager imm = getInputMethodManager();
-        return imm.isStylusHandwritingAvailableAsUser(userId);
+        return imm.isStylusHandwritingAvailableAsUser(mTextOperationUser);
     }
 
     @Nullable
@@ -14996,7 +14995,9 @@
     }
 
     boolean canShare() {
-        if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) {
+        if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()
+                || !getContext().getResources().getBoolean(
+                com.android.internal.R.bool.config_textShareSupported)) {
             return false;
         }
         return canCopy();
diff --git a/core/java/android/window/ConfigurationHelper.java b/core/java/android/window/ConfigurationHelper.java
index 269ce08..e32adcf 100644
--- a/core/java/android/window/ConfigurationHelper.java
+++ b/core/java/android/window/ConfigurationHelper.java
@@ -106,7 +106,7 @@
      * @see WindowManager#getCurrentWindowMetrics()
      * @see WindowManager#getMaximumWindowMetrics()
      */
-    public static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
+    private static boolean shouldUpdateWindowMetricsBounds(@NonNull Configuration currentConfig,
             @NonNull Configuration newConfig) {
         final Rect currentBounds = currentConfig.windowConfiguration.getBounds();
         final Rect newBounds = newConfig.windowConfiguration.getBounds();
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 534c9de..5ba2f6c 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -80,13 +80,8 @@
      * Finishes a transition. This must be called for all created transitions.
      * @param transitionToken Which transition to finish
      * @param t Changes to make before finishing but in the same SF Transaction. Can be null.
-     * @param callback Called when t is finished applying.
-     * @return An ID for the sync operation (see {@link #applySyncTransaction}. This will be
-     *         negative if no sync transaction was attached (null t or callback)
      */
-    int finishTransition(in IBinder transitionToken,
-            in @nullable WindowContainerTransaction t,
-            in IWindowContainerTransactionCallback callback);
+    void finishTransition(in IBinder transitionToken, in @nullable WindowContainerTransaction t);
 
     /** @return An interface enabling the management of task organizers. */
     ITaskOrganizerController getTaskOrganizerController();
diff --git a/core/java/android/window/ScreenCapture.java b/core/java/android/window/ScreenCapture.java
index 0cc9c64..95e9e86 100644
--- a/core/java/android/window/ScreenCapture.java
+++ b/core/java/android/window/ScreenCapture.java
@@ -23,6 +23,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
+import android.os.Build;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -33,7 +34,7 @@
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Consumer;
+import java.util.function.ObjIntConsumer;
 
 /**
  * Handles display and layer captures for the system.
@@ -42,14 +43,14 @@
  */
 public class ScreenCapture {
     private static final String TAG = "ScreenCapture";
-    private static final int SCREENSHOT_WAIT_TIME_S = 1;
+    private static final int SCREENSHOT_WAIT_TIME_S = 4 * Build.HW_TIMEOUT_MULTIPLIER;
 
     private static native int nativeCaptureDisplay(DisplayCaptureArgs captureArgs,
             long captureListener);
     private static native int nativeCaptureLayers(LayerCaptureArgs captureArgs,
             long captureListener);
     private static native long nativeCreateScreenCaptureListener(
-            Consumer<ScreenshotHardwareBuffer> consumer);
+            ObjIntConsumer<ScreenshotHardwareBuffer> consumer);
     private static native void nativeWriteListenerToParcel(long nativeObject, Parcel out);
     private static native long nativeReadListenerFromParcel(Parcel in);
     private static native long getNativeListenerFinalizer();
@@ -695,7 +696,7 @@
         /**
          * @param consumer The callback invoked when the screen capture is complete.
          */
-        public ScreenCaptureListener(Consumer<ScreenshotHardwareBuffer> consumer) {
+        public ScreenCaptureListener(ObjIntConsumer<ScreenshotHardwareBuffer> consumer) {
             mNativeObject = nativeCreateScreenCaptureListener(consumer);
             sRegistry.registerNativeAllocation(this, mNativeObject);
         }
@@ -748,8 +749,13 @@
     public static SynchronousScreenCaptureListener createSyncCaptureListener() {
         ScreenshotHardwareBuffer[] bufferRef = new ScreenshotHardwareBuffer[1];
         CountDownLatch latch = new CountDownLatch(1);
-        Consumer<ScreenshotHardwareBuffer> consumer = buffer -> {
-            bufferRef[0] = buffer;
+        ObjIntConsumer<ScreenshotHardwareBuffer> consumer = (buffer, status) -> {
+            if (status != 0) {
+                bufferRef[0] = null;
+                Log.e(TAG, "Failed to generate screen capture. Error code: " + status);
+            } else {
+                bufferRef[0] = buffer;
+            }
             latch.countDown();
         };
 
@@ -758,12 +764,15 @@
             // it references, the underlying JNI listener holds a weak reference to the consumer.
             // This property exists to ensure the consumer stays alive during the listener's
             // lifetime.
-            private Consumer<ScreenshotHardwareBuffer> mConsumer = consumer;
+            private ObjIntConsumer<ScreenshotHardwareBuffer> mConsumer = consumer;
 
             @Override
             public ScreenshotHardwareBuffer getBuffer() {
                 try {
-                    latch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS);
+                    if (!latch.await(SCREENSHOT_WAIT_TIME_S, TimeUnit.SECONDS)) {
+                        Log.e(TAG, "Timed out waiting for screenshot results");
+                        return null;
+                    }
                     return bufferRef[0];
                 } catch (Exception e) {
                     Log.e(TAG, "Failed to wait for screen capture result", e);
@@ -779,7 +788,7 @@
      * {@link #captureDisplay(DisplayCaptureArgs, ScreenCaptureListener)}
      */
     public abstract static class SynchronousScreenCaptureListener extends ScreenCaptureListener {
-        SynchronousScreenCaptureListener(Consumer<ScreenshotHardwareBuffer> consumer) {
+        SynchronousScreenCaptureListener(ObjIntConsumer<ScreenshotHardwareBuffer> consumer) {
             super(consumer);
         }
 
@@ -787,6 +796,7 @@
          * Get the {@link ScreenshotHardwareBuffer} synchronously. This can be null if the
          * screenshot failed or if there was no callback in {@link #SCREENSHOT_WAIT_TIME_S} seconds.
          */
+        @Nullable
         public abstract ScreenshotHardwareBuffer getBuffer();
     }
 }
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 695d01e..2dc2cbc 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -115,19 +115,15 @@
      * Finishes a running transition.
      * @param transitionToken The transition to finish. Can't be null.
      * @param t A set of window operations to apply before finishing.
-     * @param callback A sync callback (if provided). See {@link #applySyncTransaction}.
-     * @return An ID for the sync operation if performed. See {@link #applySyncTransaction}.
      *
      * @hide
      */
     @SuppressLint("ExecutorRegistration")
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
-    public int finishTransition(@NonNull IBinder transitionToken,
-            @Nullable WindowContainerTransaction t,
-            @Nullable WindowContainerTransactionCallback callback) {
+    public void finishTransition(@NonNull IBinder transitionToken,
+            @Nullable WindowContainerTransaction t) {
         try {
-            return getWindowOrganizerController().finishTransition(transitionToken, t,
-                    callback != null ? callback.mInterface : null);
+            getWindowOrganizerController().finishTransition(transitionToken, t);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java
index 6c11797..c1d1b27 100644
--- a/core/java/android/window/WindowTokenClient.java
+++ b/core/java/android/window/WindowTokenClient.java
@@ -23,12 +23,12 @@
 import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.app.ActivityThread;
-import android.app.IWindowToken;
 import android.app.ResourcesManager;
 import android.content.Context;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.inputmethodservice.AbstractInputMethodService;
+import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Debug;
@@ -37,6 +37,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.lang.ref.WeakReference;
@@ -52,7 +53,7 @@
  *
  * @hide
  */
-public class WindowTokenClient extends IWindowToken.Stub {
+public class WindowTokenClient extends Binder {
     private static final String TAG = WindowTokenClient.class.getSimpleName();
 
     /**
@@ -95,8 +96,8 @@
      * @param newConfig the updated {@link Configuration}
      * @param newDisplayId the updated {@link android.view.Display} ID
      */
+    @VisibleForTesting
     @MainThread
-    @Override
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId) {
         onConfigurationChanged(newConfig, newDisplayId, true /* shouldReportConfigChange */);
     }
@@ -104,6 +105,7 @@
     /**
      * Posts an {@link #onConfigurationChanged} to the main thread.
      */
+    @VisibleForTesting
     public void postOnConfigurationChanged(@NonNull Configuration newConfig, int newDisplayId) {
         mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig,
                 newDisplayId, true /* shouldReportConfigChange */).recycleOnUse());
@@ -119,14 +121,13 @@
      * <p>
      * Note that this method must be executed on the main thread if
      * {@code shouldReportConfigChange} is {@code true}, which is usually from
-     * {@link IWindowToken#onConfigurationChanged(Configuration, int)}
+     * {@link #onConfigurationChanged(Configuration, int)}
      * directly, while this method could be run on any thread if it is used to initialize
      * Context's {@code Configuration} via {@link WindowTokenClientController#attachToDisplayArea}
      * or {@link WindowTokenClientController#attachToDisplayContent}.
      *
      * @param shouldReportConfigChange {@code true} to indicate that the {@code Configuration}
      *                                 should be dispatched to listeners.
-     *
      */
     @AnyThread
     public void onConfigurationChanged(Configuration newConfig, int newDisplayId,
@@ -192,8 +193,11 @@
         }
     }
 
+    /**
+     * Called when the attached window is removed from the display.
+     */
+    @VisibleForTesting
     @MainThread
-    @Override
     public void onWindowTokenRemoved() {
         final Context context = mContextRef.get();
         if (context != null) {
diff --git a/core/java/android/window/WindowTokenClientController.java b/core/java/android/window/WindowTokenClientController.java
index 7a84123..10f6d5e 100644
--- a/core/java/android/window/WindowTokenClientController.java
+++ b/core/java/android/window/WindowTokenClientController.java
@@ -160,7 +160,7 @@
     /** Detaches a {@link WindowTokenClient} from associated WindowContainer if there's one. */
     public void detachIfNeeded(@NonNull WindowTokenClient client) {
         synchronized (mLock) {
-            if (mWindowTokenClientMap.remove(client.asBinder()) == null) {
+            if (mWindowTokenClientMap.remove(client) == null) {
                 return;
             }
         }
@@ -174,7 +174,7 @@
     private void onWindowContextTokenAttached(@NonNull WindowTokenClient client,
             @NonNull WindowContextInfo info, boolean shouldReportConfigChange) {
         synchronized (mLock) {
-            mWindowTokenClientMap.put(client.asBinder(), client);
+            mWindowTokenClientMap.put(client, client);
         }
         if (shouldReportConfigChange) {
             // Should trigger an #onConfigurationChanged callback to the WindowContext. Post the
diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
index f19f6c7..2efe445 100644
--- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
+++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java
@@ -128,7 +128,7 @@
             DialogStatus.SHOWN,
     })
     /** Denotes the user shortcut type. */
-    private @interface DialogStatus {
+    @interface DialogStatus {
         int NOT_SHOWN = 0;
         int SHOWN  = 1;
     }
@@ -333,12 +333,35 @@
         // Avoid non-a11y users accidentally turning shortcut on without reading this carefully.
         // Put "don't turn on" as the primary action.
         final AlertDialog alertDialog = mFrameworkObjectProvider.getAlertDialogBuilder(
-                // Use SystemUI context so we pick up any theme set in a vendor overlay
-                mFrameworkObjectProvider.getSystemUiContext())
+                        // Use SystemUI context so we pick up any theme set in a vendor overlay
+                        mFrameworkObjectProvider.getSystemUiContext())
                 .setTitle(getShortcutWarningTitle(targets))
                 .setMessage(getShortcutWarningMessage(targets))
                 .setCancelable(false)
-                .setNegativeButton(R.string.accessibility_shortcut_on, null)
+                .setNegativeButton(R.string.accessibility_shortcut_on,
+                        (DialogInterface d, int which) -> {
+                            String targetServices = Settings.Secure.getStringForUser(
+                                    mContext.getContentResolver(),
+                                    Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, userId);
+                            String defaultService = mContext.getString(
+                                    R.string.config_defaultAccessibilityService);
+                            // If the targetServices is null, means the user enables a
+                            // shortcut for the default service by triggering the volume keys
+                            // shortcut in the SUW instead of intentionally configuring the
+                            // shortcut on UI.
+                            if (targetServices == null && !TextUtils.isEmpty(defaultService)) {
+                                // The defaultService in the string resource could be a shorten
+                                // form like com.google.android.marvin.talkback/.TalkBackService.
+                                // Converts it to the componentName for consistency before saving
+                                // to the Settings.
+                                final ComponentName configDefaultService =
+                                        ComponentName.unflattenFromString(defaultService);
+                                Settings.Secure.putStringForUser(mContext.getContentResolver(),
+                                        Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE,
+                                        configDefaultService.flattenToString(),
+                                        userId);
+                            }
+                        })
                 .setPositiveButton(R.string.accessibility_shortcut_off,
                         (DialogInterface d, int which) -> {
                             Settings.Secure.putStringForUser(mContext.getContentResolver(),
diff --git a/core/java/com/android/internal/app/AssistUtils.java b/core/java/com/android/internal/app/AssistUtils.java
index 0ea8014..4261a0f 100644
--- a/core/java/com/android/internal/app/AssistUtils.java
+++ b/core/java/com/android/internal/app/AssistUtils.java
@@ -234,6 +234,23 @@
     }
 
     /**
+     * Allows subscription to {@link android.service.voice.VisualQueryDetectionService} service
+     * status.
+     *
+     * @param listener to receive visual service start/stop events.
+     */
+    public void subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener
+            listener) {
+        try {
+            if (mVoiceInteractionManagerService != null) {
+                mVoiceInteractionManagerService.subscribeVisualQueryRecognitionStatus(listener);
+            }
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to register visual query detection start listener", e);
+        }
+    }
+
+    /**
      * Enables visual detection service.
      *
      * @param listener to receive visual attention gained/lost events.
diff --git a/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl b/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
new file mode 100644
index 0000000..cc49a75
--- /dev/null
+++ b/core/java/com/android/internal/app/IVisualQueryRecognitionStatusListener.aidl
@@ -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.internal.app;
+
+
+ oneway interface IVisualQueryRecognitionStatusListener {
+    /**
+     * Called when {@link VisualQueryDetectionService#onStartDetection} is scheduled from the system
+     * server via {@link VoiceInteractionManagerService#StartPerceiving}.
+     */
+    void onStartPerceiving();
+
+    /**
+     * Called when {@link VisualQueryDetectionService#onStopDetection} is scheduled from the system
+     * server via {@link VoiceInteractionManagerService#StopPerceiving}.
+     */
+    void onStopPerceiving();
+ }
\ No newline at end of file
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 24d5afc..314ed69 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -40,6 +40,7 @@
 import com.android.internal.app.IVoiceInteractionSoundTriggerSession;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
 
 interface IVoiceInteractionManagerService {
     void showSession(in Bundle sessionArgs, int flags, String attributionTag);
@@ -325,6 +326,9 @@
     void shutdownHotwordDetectionService();
 
     @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
+    void subscribeVisualQueryRecognitionStatus(in IVisualQueryRecognitionStatusListener listener);
+
+    @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
     void enableVisualQueryDetection(in IVisualQueryDetectionAttentionListener Listener);
 
     @EnforcePermission("ACCESS_VOICE_INTERACTION_SERVICE")
diff --git a/core/java/com/android/internal/app/LocaleStore.java b/core/java/com/android/internal/app/LocaleStore.java
index 43d263b..b3b0603 100644
--- a/core/java/com/android/internal/app/LocaleStore.java
+++ b/core/java/com/android/internal/app/LocaleStore.java
@@ -390,12 +390,17 @@
     public static Set<LocaleInfo> transformImeLanguageTagToLocaleInfo(
             List<InputMethodSubtype> list) {
         Set<LocaleInfo> imeLocales = new HashSet<>();
+        Set<String> languageTagSet = new HashSet<>();
         for (InputMethodSubtype subtype : list) {
-            Locale locale = Locale.forLanguageTag(subtype.getLanguageTag());
-            LocaleInfo cacheInfo  = getLocaleInfo(locale, sLocaleCache);
-            LocaleInfo localeInfo = new LocaleInfo(cacheInfo);
-            localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE;
-            imeLocales.add(localeInfo);
+            String languageTag = subtype.getLanguageTag();
+            if (!languageTagSet.contains(languageTag)) {
+                languageTagSet.add(languageTag);
+                Locale locale = Locale.forLanguageTag(languageTag);
+                LocaleInfo cacheInfo = getLocaleInfo(locale, sLocaleCache);
+                LocaleInfo localeInfo = new LocaleInfo(cacheInfo);
+                localeInfo.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE;
+                imeLocales.add(localeInfo);
+            }
         }
         return imeLocales;
     }
diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java
deleted file mode 100644
index f34aabb..0000000
--- a/core/java/com/android/internal/app/NetInitiatedActivity.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2007 Google Inc.
- *
- * 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.app;
-
-import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
-
-import android.app.AlertDialog;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.location.LocationManagerInternal;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-
-import com.android.internal.R;
-import com.android.internal.location.GpsNetInitiatedHandler;
-import com.android.server.LocalServices;
-
-/**
- * This activity is shown to the user for them to accept or deny network-initiated
- * requests. It uses the alert dialog style. It will be launched from a notification.
- */
-public class NetInitiatedActivity extends AlertActivity implements DialogInterface.OnClickListener {
-
-    private static final String TAG = "NetInitiatedActivity";
-
-    private static final boolean DEBUG = true;
-
-    private static final int POSITIVE_BUTTON = AlertDialog.BUTTON_POSITIVE;
-    private static final int NEGATIVE_BUTTON = AlertDialog.BUTTON_NEGATIVE;
-
-    private static final int GPS_NO_RESPONSE_TIME_OUT = 1;
-    // Received ID from intent, -1 when no notification is in progress
-    private int notificationId = -1;
-    private int timeout = -1;
-    private int default_response = -1;
-    private int default_response_timeout = 6;
-
-    private final Handler mHandler = new Handler() {
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case GPS_NO_RESPONSE_TIME_OUT: {
-                    if (notificationId != -1) {
-                        sendUserResponse(default_response);
-                    }
-                    finish();
-                }
-                break;
-                default:
-            }
-        }
-    };
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
-
-        // Set up the "dialog"
-        final Intent intent = getIntent();
-        final AlertController.AlertParams p = mAlertParams;
-        Context context = getApplicationContext();
-        p.mTitle = intent.getStringExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_TITLE);
-        p.mMessage = intent.getStringExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_MESSAGE);
-        p.mPositiveButtonText = String.format(context.getString(R.string.gpsVerifYes));
-        p.mPositiveButtonListener = this;
-        p.mNegativeButtonText = String.format(context.getString(R.string.gpsVerifNo));
-        p.mNegativeButtonListener = this;
-
-        notificationId = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_NOTIF_ID, -1);
-        timeout = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_TIMEOUT, default_response_timeout);
-        default_response = intent.getIntExtra(GpsNetInitiatedHandler.NI_INTENT_KEY_DEFAULT_RESPONSE, GpsNetInitiatedHandler.GPS_NI_RESPONSE_ACCEPT);
-        if (DEBUG) Log.d(TAG, "onCreate() : notificationId: " + notificationId + " timeout: " + timeout + " default_response:" + default_response);
-
-        mHandler.sendMessageDelayed(mHandler.obtainMessage(GPS_NO_RESPONSE_TIME_OUT), (timeout * 1000));
-        setupAlert();
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        if (DEBUG) Log.d(TAG, "onResume");
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-        if (DEBUG) Log.d(TAG, "onPause");
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    public void onClick(DialogInterface dialog, int which) {
-        if (which == POSITIVE_BUTTON) {
-            sendUserResponse(GpsNetInitiatedHandler.GPS_NI_RESPONSE_ACCEPT);
-        }
-        if (which == NEGATIVE_BUTTON) {
-            sendUserResponse(GpsNetInitiatedHandler.GPS_NI_RESPONSE_DENY);
-        }
-
-        // No matter what, finish the activity
-        finish();
-        notificationId = -1;
-    }
-
-    // Respond to NI Handler under GnssLocationProvider, 1 = accept, 2 = deny
-    private void sendUserResponse(int response) {
-        if (DEBUG) Log.d(TAG, "sendUserResponse, response: " + response);
-        LocationManagerInternal lm = LocalServices.getService(LocationManagerInternal.class);
-        lm.sendNiResponse(notificationId, response);
-    }
-}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 2445daf..ac15f11 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -40,6 +40,7 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PROTECTED;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
 import android.annotation.UiThread;
@@ -68,6 +69,7 @@
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
 import android.graphics.Insets;
+import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -93,6 +95,7 @@
 import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
 import android.widget.AbsListView;
 import android.widget.AdapterView;
 import android.widget.Button;
@@ -488,6 +491,14 @@
             rdl.setOnApplyWindowInsetsListener(this::onApplyWindowInsets);
 
             mResolverDrawerLayout = rdl;
+
+            for (int i = 0, size = mMultiProfilePagerAdapter.getCount(); i < size; i++) {
+                View view = mMultiProfilePagerAdapter.getItem(i).rootView.findViewById(
+                        R.id.resolver_list);
+                if (view != null) {
+                    view.setAccessibilityDelegate(new AppListAccessibilityDelegate(rdl));
+                }
+            }
         }
 
         mProfileView = findViewById(R.id.profile_button);
@@ -2607,4 +2618,41 @@
         }
         return resolveInfo.userHandle;
     }
+
+    /**
+     * An a11y delegate that expands resolver drawer when gesture navigation reaches a partially
+     * invisible target in the list.
+     */
+    private static class AppListAccessibilityDelegate extends View.AccessibilityDelegate {
+        private final ResolverDrawerLayout mDrawer;
+        @Nullable
+        private final View mBottomBar;
+        private final Rect mRect = new Rect();
+
+        private AppListAccessibilityDelegate(ResolverDrawerLayout drawer) {
+            mDrawer = drawer;
+            mBottomBar = mDrawer.findViewById(R.id.button_bar_container);
+        }
+
+        @Override
+        public boolean onRequestSendAccessibilityEvent(@androidx.annotation.NonNull ViewGroup host,
+                @NonNull View child,
+                @NonNull AccessibilityEvent event) {
+            boolean result = super.onRequestSendAccessibilityEvent(host, child, event);
+            if (result && event.getEventType() == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED
+                    && mDrawer.isCollapsed()) {
+                child.getBoundsOnScreen(mRect);
+                int childTop = mRect.top;
+                int childBottom = mRect.bottom;
+                mDrawer.getBoundsOnScreen(mRect, true);
+                int bottomBarHeight = mBottomBar == null ? 0 : mBottomBar.getHeight();
+                int drawerTop = mRect.top;
+                int drawerBottom = mRect.bottom - bottomBarHeight;
+                if (drawerTop > childTop || childBottom > drawerBottom) {
+                    mDrawer.setCollapsed(false);
+                }
+            }
+            return result;
+        }
+    }
 }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index a2c4b23..0a69ea8 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -534,7 +534,14 @@
     /**
      * (boolean) Whether to enable the adapter conversion in RemoteViews
      */
-    public static final String REMOTEVIEWS_ADAPTER_CONVERSION = "remoteviews_adapter_conversion";
+    public static final String REMOTEVIEWS_ADAPTER_CONVERSION =
+            "CursorControlFeature__remoteviews_adapter_conversion";
+
+    /**
+     * The key name used in app core settings for {@link #REMOTEVIEWS_ADAPTER_CONVERSION}
+     */
+    public static final String KEY_REMOTEVIEWS_ADAPTER_CONVERSION =
+            "systemui__remoteviews_adapter_conversion";
 
     /**
      * Default value for whether the adapter conversion is enabled or not. This is set for
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 10336bd..9233050 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -70,10 +70,6 @@
         public static final Flag OTP_REDACTION =
                 devFlag("persist.sysui.notification.otp_redaction");
 
-        /** Gating the removal of sorting-notifications-by-interruptiveness. */
-        public static final Flag NO_SORT_BY_INTERRUPTIVENESS =
-                releasedFlag("persist.sysui.notification.no_sort_by_interruptiveness");
-
         /** Gating the logging of DND state change events. */
         public static final Flag LOG_DND_STATE_EVENTS =
                 releasedFlag("persist.sysui.notification.log_dnd_state_events");
@@ -86,7 +82,7 @@
         public static final Flag RANKING_UPDATE_ASHMEM = devFlag(
                 "persist.sysui.notification.ranking_update_ashmem");
 
-        public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = devFlag(
+        public static final Flag PROPAGATE_CHANNEL_UPDATES_TO_CONVERSATIONS = releasedFlag(
                 "persist.sysui.notification.propagate_channel_updates_to_conversations");
     }
 
diff --git a/core/java/com/android/internal/os/TEST_MAPPING b/core/java/com/android/internal/os/TEST_MAPPING
index 60b160a..d552e0b 100644
--- a/core/java/com/android/internal/os/TEST_MAPPING
+++ b/core/java/com/android/internal/os/TEST_MAPPING
@@ -38,11 +38,18 @@
       ],
       "name": "FrameworksServicesTests",
       "options": [
-        { "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
-        { "include-filter": "com.android.server.power.stats.BatteryStatsTests" }
+        { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }
       ]
     },
     {
+      "file_patterns": [
+        "Battery[^/]*\\.java",
+        "Kernel[^/]*\\.java",
+        "[^/]*Power[^/]*\\.java"
+      ],
+      "name": "PowerStatsTests"
+    },
+    {
       "name": "FrameworksCoreTests",
       "options": [
         {
diff --git a/core/java/com/android/internal/power/TEST_MAPPING b/core/java/com/android/internal/power/TEST_MAPPING
index c6cab18..1946f5c 100644
--- a/core/java/com/android/internal/power/TEST_MAPPING
+++ b/core/java/com/android/internal/power/TEST_MAPPING
@@ -8,11 +8,7 @@
       ]
     },
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        { "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
-        { "include-filter": "com.android.server.power.stats.BatteryStatsTests" }
-      ]
+      "name": "PowerStatsTests"
     }
   ]
 }
diff --git a/core/java/com/android/internal/util/NotificationMessagingUtil.java b/core/java/com/android/internal/util/NotificationMessagingUtil.java
index d3cc0e7..10856b3 100644
--- a/core/java/com/android/internal/util/NotificationMessagingUtil.java
+++ b/core/java/com/android/internal/util/NotificationMessagingUtil.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.util;
 
+import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
@@ -39,10 +40,12 @@
 
     private static final String DEFAULT_SMS_APP_SETTING = Settings.Secure.SMS_DEFAULT_APPLICATION;
     private final Context mContext;
-    private SparseArray<String> mDefaultSmsApp = new SparseArray<>();
+    private final SparseArray<String> mDefaultSmsApp = new SparseArray<>();
+    private final Object mStateLock;
 
-    public NotificationMessagingUtil(Context context) {
+    public NotificationMessagingUtil(Context context, @Nullable Object stateLock) {
         mContext = context;
+        mStateLock = stateLock != null ? stateLock : new Object();
         mContext.getContentResolver().registerContentObserver(
                 Settings.Secure.getUriFor(DEFAULT_SMS_APP_SETTING), false, mSmsContentObserver);
     }
@@ -63,16 +66,20 @@
     private boolean isDefaultMessagingApp(StatusBarNotification sbn) {
         final int userId = sbn.getUserId();
         if (userId == UserHandle.USER_NULL || userId == UserHandle.USER_ALL) return false;
-        if (mDefaultSmsApp.get(userId) == null) {
-            cacheDefaultSmsApp(userId);
+        synchronized (mStateLock) {
+            if (mDefaultSmsApp.get(userId) == null) {
+                cacheDefaultSmsApp(userId);
+            }
+            return Objects.equals(mDefaultSmsApp.get(userId), sbn.getPackageName());
         }
-        return Objects.equals(mDefaultSmsApp.get(userId), sbn.getPackageName());
     }
 
     private void cacheDefaultSmsApp(int userId) {
-        mDefaultSmsApp.put(userId, Settings.Secure.getStringForUser(
-                mContext.getContentResolver(),
-                Settings.Secure.SMS_DEFAULT_APPLICATION, userId));
+        String smsApp = Settings.Secure.getStringForUser(mContext.getContentResolver(),
+                Settings.Secure.SMS_DEFAULT_APPLICATION, userId);
+        synchronized (mStateLock) {
+            mDefaultSmsApp.put(userId, smsApp);
+        }
     }
 
     private final ContentObserver mSmsContentObserver = new ContentObserver(
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 635adca..7dda91d 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -383,7 +383,11 @@
         updateContentEndPaddings();
     }
 
-    @RemotableViewMethod
+    /**
+     * Set conversation data
+     * @param extras Bundle contains conversation data
+     */
+    @RemotableViewMethod(asyncImpl = "setDataAsync")
     public void setData(Bundle extras) {
         Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
         List<Notification.MessagingStyle.Message> newMessages
@@ -393,8 +397,7 @@
                 = Notification.MessagingStyle.Message.getMessagesFromBundleArray(histMessages);
 
         // mUser now set (would be nice to avoid the side effect but WHATEVER)
-        setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, android.app.Person.class));
-
+        final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
         // Append remote input history to newMessages (again, side effect is lame but WHATEVS)
         RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
                 extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
@@ -402,11 +405,30 @@
 
         boolean showSpinner =
                 extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
-        // bind it, baby
-        bind(newMessages, newHistoricMessages, showSpinner);
-
         int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
-        setUnreadCount(unreadCount);
+
+        // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
+        // if they exist
+        final List<MessagingMessage> newMessagingMessages =
+                createMessages(newMessages, false /* isHistoric */);
+        final List<MessagingMessage> newHistoricMessagingMessages =
+                createMessages(newHistoricMessages, true /* isHistoric */);
+        // bind it, baby
+        bindViews(user, showSpinner, unreadCount,
+                newMessagingMessages,
+                newHistoricMessagingMessages);
+    }
+
+    /**
+     * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+     * This should be called on a background thread, and returns a Runnable which is then must be
+     * called on the main thread to complete the operation and set text.
+     * @param extras Bundle contains conversation data
+     * @hide
+     */
+    @NonNull
+    public Runnable setDataAsync(Bundle extras) {
+        return () -> setData(extras);
     }
 
     @Override
@@ -436,15 +458,17 @@
         }
     }
 
-    private void bind(List<Notification.MessagingStyle.Message> newMessages,
-            List<Notification.MessagingStyle.Message> newHistoricMessages,
-            boolean showSpinner) {
-        // convert MessagingStyle.Message to MessagingMessage, re-using ones from a previous binding
-        // if they exist
-        List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
-                true /* isHistoric */);
-        List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
 
+    private void bindViews(Person user,
+            boolean showSpinner, int unreadCount, List<MessagingMessage> newMessagingMessages,
+            List<MessagingMessage> newHistoricMessagingMessages) {
+        setUser(user);
+        setUnreadCount(unreadCount);
+        bind(showSpinner, newMessagingMessages, newHistoricMessagingMessages);
+    }
+
+    private void bind(boolean showSpinner, List<MessagingMessage> messages,
+            List<MessagingMessage> historicMessages) {
         // Copy our groups, before they get clobbered
         ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
 
diff --git a/core/java/com/android/internal/widget/ImageFloatingTextView.java b/core/java/com/android/internal/widget/ImageFloatingTextView.java
index de10bd2..0704cb8 100644
--- a/core/java/com/android/internal/widget/ImageFloatingTextView.java
+++ b/core/java/com/android/internal/widget/ImageFloatingTextView.java
@@ -63,6 +63,8 @@
     public ImageFloatingTextView(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST);
+        setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY);
     }
 
     @Override
@@ -83,8 +85,8 @@
                 .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier())
                 .setIncludePad(getIncludeFontPadding())
                 .setUseLineSpacingFromFallbacks(true)
-                .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
-                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL_FAST);
+                .setBreakStrategy(getBreakStrategy())
+                .setHyphenationFrequency(getHyphenationFrequency());
         int maxLines;
         if (mMaxLinesForHeight > 0) {
             maxLines = mMaxLinesForHeight;
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index 9d142f6..8345c5c 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -156,7 +156,11 @@
         mConversationTitle = conversationTitle;
     }
 
-    @RemotableViewMethod
+    /**
+     * Set Messaging data
+     * @param extras Bundle contains messaging data
+     */
+    @RemotableViewMethod(asyncImpl = "setDataAsync")
     public void setData(Bundle extras) {
         Parcelable[] messages = extras.getParcelableArray(Notification.EXTRA_MESSAGES);
         List<Notification.MessagingStyle.Message> newMessages
@@ -168,9 +172,28 @@
         RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
                 extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS, android.app.RemoteInputHistoryItem.class);
         addRemoteInputHistoryToMessages(newMessages, history);
+
+        final Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON, Person.class);
         boolean showSpinner =
                 extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
-        bind(newMessages, newHistoricMessages, showSpinner);
+
+        final List<MessagingMessage> historicMessagingMessages = createMessages(newHistoricMessages,
+                true /* isHistoric */);
+        final List<MessagingMessage> newMessagingMessages =
+                createMessages(newMessages, false /* isHistoric */);
+        bindViews(user, showSpinner, historicMessagingMessages, newMessagingMessages);
+    }
+
+    /**
+     * RemotableViewMethod's asyncImpl of {@link #setData(Bundle)}.
+     * This should be called on a background thread, and returns a Runnable which is then must be
+     * called on the main thread to complete the operation and set text.
+     * @param extras Bundle contains messaging data
+     * @hide
+     */
+    @NonNull
+    public Runnable setDataAsync(Bundle extras) {
+        return () -> setData(extras);
     }
 
     @Override
@@ -195,14 +218,15 @@
         }
     }
 
-    private void bind(List<Notification.MessagingStyle.Message> newMessages,
-            List<Notification.MessagingStyle.Message> newHistoricMessages,
-            boolean showSpinner) {
+    private void bindViews(Person user, boolean showSpinner,
+            List<MessagingMessage> historicMessagingMessages,
+            List<MessagingMessage> newMessagingMessages) {
+        setUser(user);
+        bind(showSpinner, historicMessagingMessages, newMessagingMessages);
+    }
 
-        List<MessagingMessage> historicMessages = createMessages(newHistoricMessages,
-                true /* isHistoric */);
-        List<MessagingMessage> messages = createMessages(newMessages, false /* isHistoric */);
-
+    private void bind(boolean showSpinner, List<MessagingMessage> historicMessages,
+            List<MessagingMessage> messages) {
         ArrayList<MessagingGroup> oldGroups = new ArrayList<>(mGroups);
         addMessagesToGroups(historicMessages, messages, showSpinner);
 
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
index f7af67b..e9449eb 100644
--- a/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
+++ b/core/java/com/android/internal/widget/floatingtoolbar/FloatingToolbarPopup.java
@@ -21,7 +21,6 @@
 import android.graphics.Rect;
 import android.view.MenuItem;
 import android.view.View;
-import android.view.selectiontoolbar.SelectionToolbarManager;
 import android.widget.PopupWindow;
 
 import java.util.List;
@@ -89,14 +88,10 @@
             @Nullable PopupWindow.OnDismissListener onDismiss);
 
     /**
-     * Returns {@link RemoteFloatingToolbarPopup} implementation if the system selection toolbar
-     * enabled, otherwise returns {@link LocalFloatingToolbarPopup} implementation.
+     * Returns {@link LocalFloatingToolbarPopup} implementation.
      */
     static FloatingToolbarPopup createInstance(Context context, View parent) {
-        boolean enabled = SelectionToolbarManager.isRemoteSelectionToolbarEnabled(context);
-        return enabled
-                ? new RemoteFloatingToolbarPopup(context, parent)
-                : new LocalFloatingToolbarPopup(context, parent);
+        return new LocalFloatingToolbarPopup(context, parent);
     }
 
 }
diff --git a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java b/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
deleted file mode 100644
index 8787c39..0000000
--- a/core/java/com/android/internal/widget/floatingtoolbar/RemoteFloatingToolbarPopup.java
+++ /dev/null
@@ -1,572 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.widget.floatingtoolbar;
-
-import static android.view.selectiontoolbar.SelectionToolbarManager.NO_TOOLBAR_ID;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UiThread;
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.PixelFormat;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.MenuItem;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.SelectionToolbarManager;
-import android.view.selectiontoolbar.ShowInfo;
-import android.view.selectiontoolbar.ToolbarMenuItem;
-import android.view.selectiontoolbar.WidgetInfo;
-import android.widget.LinearLayout;
-import android.widget.PopupWindow;
-
-import com.android.internal.R;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * A popup window used by the floating toolbar to render menu items in the remote system process.
- *
- * It holds 2 panels (i.e. main panel and overflow panel) and an overflow button
- * to transition between panels.
- */
-public final class RemoteFloatingToolbarPopup implements FloatingToolbarPopup {
-
-    private static final boolean DEBUG =
-            Log.isLoggable(FloatingToolbar.FLOATING_TOOLBAR_TAG, Log.VERBOSE);
-
-    private static final int TOOLBAR_STATE_SHOWN = 1;
-    private static final int TOOLBAR_STATE_HIDDEN = 2;
-    private static final int TOOLBAR_STATE_DISMISSED = 3;
-
-    @IntDef(prefix = {"TOOLBAR_STATE_"}, value = {
-            TOOLBAR_STATE_SHOWN,
-            TOOLBAR_STATE_HIDDEN,
-            TOOLBAR_STATE_DISMISSED
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface ToolbarState {
-    }
-
-    @NonNull
-    private final SelectionToolbarManager mSelectionToolbarManager;
-    // Parent for the popup window.
-    @NonNull
-    private final View mParent;
-    // A popup window used for showing menu items rendered by the remote system process
-    @NonNull
-    private final PopupWindow mPopupWindow;
-    // The callback to handle remote rendered selection toolbar.
-    @NonNull
-    private final SelectionToolbarCallbackImpl mSelectionToolbarCallback;
-
-    // tracks this popup state.
-    private @ToolbarState int mState;
-
-    // The token of the current showing floating toolbar.
-    private long mFloatingToolbarToken;
-    private final Rect mPreviousContentRect = new Rect();
-    private List<MenuItem> mMenuItems;
-    private MenuItem.OnMenuItemClickListener mMenuItemClickListener;
-    private int mSuggestedWidth;
-    private final Rect mScreenViewPort = new Rect();
-    private boolean mWidthChanged = true;
-    private final boolean mIsLightTheme;
-
-    private final int[] mCoordsOnScreen = new int[2];
-    private final int[] mCoordsOnWindow = new int[2];
-
-    public RemoteFloatingToolbarPopup(Context context, View parent) {
-        mParent = Objects.requireNonNull(parent);
-        mPopupWindow = createPopupWindow(context);
-        mSelectionToolbarManager = context.getSystemService(SelectionToolbarManager.class);
-        mSelectionToolbarCallback = new SelectionToolbarCallbackImpl(this);
-        mIsLightTheme = isLightTheme(context);
-        mFloatingToolbarToken = NO_TOOLBAR_ID;
-    }
-
-    private boolean isLightTheme(Context context) {
-        TypedArray a = context.obtainStyledAttributes(new int[]{R.attr.isLightTheme});
-        boolean isLightTheme = a.getBoolean(0, true);
-        a.recycle();
-        return isLightTheme;
-    }
-
-    @UiThread
-    @Override
-    public void show(List<MenuItem> menuItems,
-            MenuItem.OnMenuItemClickListener menuItemClickListener, Rect contentRect) {
-        Objects.requireNonNull(menuItems);
-        Objects.requireNonNull(menuItemClickListener);
-        if (isShowing() && Objects.equals(menuItems, mMenuItems)
-                && Objects.equals(contentRect, mPreviousContentRect)) {
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "Ignore duplicate show() for the same content.");
-            }
-            return;
-        }
-
-        boolean isLayoutRequired = mMenuItems == null
-                || !MenuItemRepr.reprEquals(menuItems, mMenuItems)
-                || mWidthChanged;
-        if (isLayoutRequired) {
-            mSelectionToolbarManager.dismissToolbar(mFloatingToolbarToken);
-            doDismissPopupWindow();
-        }
-        mMenuItemClickListener = menuItemClickListener;
-        mMenuItems = menuItems;
-
-        mParent.getWindowVisibleDisplayFrame(mScreenViewPort);
-        final int suggestWidth = mSuggestedWidth > 0
-                ? mSuggestedWidth
-                : mParent.getResources().getDimensionPixelSize(
-                        R.dimen.floating_toolbar_preferred_width);
-        final ShowInfo showInfo = new ShowInfo(
-                mFloatingToolbarToken, isLayoutRequired,
-                getToolbarMenuItems(mMenuItems),
-                contentRect,
-                suggestWidth,
-                mScreenViewPort,
-                mParent.getViewRootImpl().getInputToken(), mIsLightTheme);
-        if (DEBUG) {
-            Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                    "RemoteFloatingToolbarPopup.show() for " + showInfo);
-        }
-        mSelectionToolbarManager.showToolbar(showInfo, mSelectionToolbarCallback);
-        mPreviousContentRect.set(contentRect);
-    }
-
-    @UiThread
-    @Override
-    public void dismiss() {
-        if (mState == TOOLBAR_STATE_DISMISSED) {
-            Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                    "The floating toolbar already dismissed.");
-            return;
-        }
-        if (DEBUG) {
-            Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                    "RemoteFloatingToolbarPopup.dismiss().");
-        }
-        mSelectionToolbarManager.dismissToolbar(mFloatingToolbarToken);
-        doDismissPopupWindow();
-    }
-
-    @UiThread
-    @Override
-    public void hide() {
-        if (mState == TOOLBAR_STATE_DISMISSED || mState == TOOLBAR_STATE_HIDDEN) {
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "The floating toolbar already dismissed or hidden.");
-            }
-            return;
-        }
-        if (DEBUG) {
-            Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                    "RemoteFloatingToolbarPopup.hide().");
-        }
-        mSelectionToolbarManager.hideToolbar(mFloatingToolbarToken);
-        mState = TOOLBAR_STATE_HIDDEN;
-        mPopupWindow.dismiss();
-    }
-
-    @UiThread
-    @Override
-    public void setSuggestedWidth(int suggestedWidth) {
-        int difference = Math.abs(suggestedWidth - mSuggestedWidth);
-        mWidthChanged = difference > (mSuggestedWidth * 0.2);
-        mSuggestedWidth = suggestedWidth;
-    }
-
-    @Override
-    public void setWidthChanged(boolean widthChanged) {
-        mWidthChanged = widthChanged;
-    }
-
-    @UiThread
-    @Override
-    public boolean isHidden() {
-        return mState == TOOLBAR_STATE_HIDDEN;
-    }
-
-    @UiThread
-    @Override
-    public boolean isShowing() {
-        return mState == TOOLBAR_STATE_SHOWN;
-    }
-
-    @UiThread
-    @Override
-    public boolean setOutsideTouchable(boolean outsideTouchable,
-            @Nullable PopupWindow.OnDismissListener onDismiss) {
-        if (mState == TOOLBAR_STATE_DISMISSED) {
-            return false;
-        }
-        boolean ret = false;
-        if (mPopupWindow.isOutsideTouchable() ^ outsideTouchable) {
-            mPopupWindow.setOutsideTouchable(outsideTouchable);
-            mPopupWindow.setFocusable(!outsideTouchable);
-            mPopupWindow.update();
-            ret = true;
-        }
-        mPopupWindow.setOnDismissListener(onDismiss);
-        return ret;
-    }
-
-    private void updatePopupWindowContent(WidgetInfo widgetInfo) {
-        if (DEBUG) {
-            Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, "updatePopupWindowContent.");
-        }
-        ViewGroup contentContainer = (ViewGroup) mPopupWindow.getContentView();
-        contentContainer.removeAllViews();
-        SurfaceView surfaceView = new SurfaceView(mParent.getContext());
-        surfaceView.setZOrderOnTop(true);
-        surfaceView.getHolder().setFormat(PixelFormat.TRANSPARENT);
-        surfaceView.setChildSurfacePackage(widgetInfo.getSurfacePackage());
-        contentContainer.addView(surfaceView);
-    }
-
-    private MenuItem getMenuItemByToolbarMenuItem(ToolbarMenuItem toolbarMenuItem) {
-        for (MenuItem item : mMenuItems) {
-            if (toolbarMenuItem.getItemId() == item.getItemId()) {
-                return item;
-            }
-        }
-        return null;
-    }
-
-    private Point getCoordinatesInWindow(int x, int y) {
-        // We later specify the location of PopupWindow relative to the attached window.
-        // The idea here is that 1) we can get the location of a View in both window coordinates
-        // and screen coordinates, where the offset between them should be equal to the window
-        // origin, and 2) we can use an arbitrary for this calculation while calculating the
-        // location of the rootview is supposed to be least expensive.
-        // TODO: Consider to use PopupWindow.setIsLaidOutInScreen(true) so that we can avoid
-        // the following calculation.
-        mParent.getRootView().getLocationOnScreen(mCoordsOnScreen);
-        mParent.getRootView().getLocationInWindow(mCoordsOnWindow);
-        int windowLeftOnScreen = mCoordsOnScreen[0] - mCoordsOnWindow[0];
-        int windowTopOnScreen = mCoordsOnScreen[1] - mCoordsOnWindow[1];
-        return new Point(Math.max(0, x - windowLeftOnScreen), Math.max(0, y - windowTopOnScreen));
-    }
-
-    private static List<ToolbarMenuItem> getToolbarMenuItems(List<MenuItem> menuItems) {
-        final List<ToolbarMenuItem> list = new ArrayList<>(menuItems.size());
-        for (MenuItem menuItem : menuItems) {
-            // TODO: use ToolbarMenuItem.Builder(MenuItem) instead
-            ToolbarMenuItem toolbarMenuItem = new ToolbarMenuItem.Builder(menuItem.getItemId(),
-                    menuItem.getTitle(), menuItem.getContentDescription(), menuItem.getGroupId(),
-                    convertDrawableToIcon(menuItem.getIcon()),
-                    menuItem.getTooltipText(),
-                    ToolbarMenuItem.getPriorityFromMenuItem(menuItem)).build();
-            list.add(toolbarMenuItem);
-        }
-        return list;
-    }
-
-    private static Icon convertDrawableToIcon(Drawable drawable) {
-        if (drawable == null) {
-            return null;
-        }
-        if (drawable instanceof BitmapDrawable) {
-            final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
-            if (bitmapDrawable.getBitmap() != null) {
-                return Icon.createWithBitmap(bitmapDrawable.getBitmap());
-            }
-        }
-        final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
-                drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
-        final Canvas canvas = new Canvas(bitmap);
-        drawable.setBounds(0, 0, canvas.getWidth(),  canvas.getHeight());
-        drawable.draw(canvas);
-        return Icon.createWithBitmap(bitmap);
-    }
-
-    private static PopupWindow createPopupWindow(Context content) {
-        ViewGroup popupContentHolder = new LinearLayout(content);
-        PopupWindow popupWindow = new PopupWindow(popupContentHolder);
-        popupWindow.setClippingEnabled(false);
-        popupWindow.setWindowLayoutType(
-                WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL);
-        popupWindow.setAnimationStyle(0);
-        popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
-        return popupWindow;
-    }
-
-    private void doDismissPopupWindow() {
-        if (DEBUG) {
-            Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG, "RemoteFloatingToolbarPopup.doDismiss().");
-        }
-        mState = TOOLBAR_STATE_DISMISSED;
-        mMenuItems = null;
-        mMenuItemClickListener = null;
-        mFloatingToolbarToken = 0;
-        mSuggestedWidth = 0;
-        mWidthChanged = true;
-        resetCoords();
-        mPreviousContentRect.setEmpty();
-        mScreenViewPort.setEmpty();
-        mPopupWindow.dismiss();
-    }
-
-    private void resetCoords() {
-        mCoordsOnScreen[0] = 0;
-        mCoordsOnScreen[1] = 0;
-        mCoordsOnWindow[0] = 0;
-        mCoordsOnWindow[1] = 0;
-    }
-
-    private void runOnUiThread(Runnable runnable) {
-        mParent.post(runnable);
-    }
-
-    private void onShow(WidgetInfo info) {
-        runOnUiThread(() -> {
-            mFloatingToolbarToken = info.getWidgetToken();
-            mState = TOOLBAR_STATE_SHOWN;
-            updatePopupWindowContent(info);
-            Rect contentRect = info.getContentRect();
-            mPopupWindow.setWidth(contentRect.width());
-            mPopupWindow.setHeight(contentRect.height());
-            final Point coords = getCoordinatesInWindow(contentRect.left, contentRect.top);
-            mPopupWindow.showAtLocation(mParent, Gravity.NO_GRAVITY, coords.x, coords.y);
-        });
-    }
-
-    private void onWidgetUpdated(WidgetInfo info) {
-        runOnUiThread(() -> {
-            if (!isShowing()) {
-                Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "onWidgetUpdated(): The widget isn't showing.");
-                return;
-            }
-            updatePopupWindowContent(info);
-            Rect contentRect = info.getContentRect();
-            Point coords = getCoordinatesInWindow(contentRect.left, contentRect.top);
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "PopupWindow x= " + coords.x + " y= " + coords.y + " w="
-                                + contentRect.width() + " h=" + contentRect.height());
-            }
-            mPopupWindow.update(coords.x, coords.y, contentRect.width(), contentRect.height());
-        });
-    }
-
-    private void onToolbarShowTimeout() {
-        runOnUiThread(() -> {
-            if (mState == TOOLBAR_STATE_DISMISSED) {
-                return;
-            }
-            doDismissPopupWindow();
-        });
-    }
-
-    private void onMenuItemClicked(ToolbarMenuItem toolbarMenuItem) {
-        runOnUiThread(() -> {
-            if (mMenuItems == null || mMenuItemClickListener == null) {
-                return;
-            }
-            MenuItem item = getMenuItemByToolbarMenuItem(toolbarMenuItem);
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "SelectionToolbarCallbackImpl onMenuItemClicked. toolbarMenuItem="
-                                + toolbarMenuItem + " item=" + item);
-            }
-            // TODO: handle the menu item like clipboard
-            if (item != null) {
-                mMenuItemClickListener.onMenuItemClick(item);
-            } else {
-                Log.e(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "onMenuItemClicked: cannot find menu item.");
-            }
-        });
-    }
-
-    private static class SelectionToolbarCallbackImpl extends ISelectionToolbarCallback.Stub {
-
-        private final WeakReference<RemoteFloatingToolbarPopup> mRemotePopup;
-
-        SelectionToolbarCallbackImpl(RemoteFloatingToolbarPopup popup) {
-            mRemotePopup = new WeakReference<>(popup);
-        }
-
-        @Override
-        public void onShown(WidgetInfo info) {
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "SelectionToolbarCallbackImpl onShown: " + info);
-            }
-            final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get();
-            if (remoteFloatingToolbarPopup != null) {
-                remoteFloatingToolbarPopup.onShow(info);
-            } else {
-                Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "Lost remoteFloatingToolbarPopup reference for onShown.");
-            }
-        }
-
-        @Override
-        public void onWidgetUpdated(WidgetInfo info) {
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "SelectionToolbarCallbackImpl onWidgetUpdated: info = " + info);
-            }
-            final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get();
-            if (remoteFloatingToolbarPopup != null) {
-                remoteFloatingToolbarPopup.onWidgetUpdated(info);
-            } else {
-                Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "Lost remoteFloatingToolbarPopup reference for onWidgetUpdated.");
-            }
-        }
-
-        @Override
-        public void onToolbarShowTimeout() {
-            final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get();
-            if (remoteFloatingToolbarPopup != null) {
-                remoteFloatingToolbarPopup.onToolbarShowTimeout();
-            } else {
-                Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "Lost remoteFloatingToolbarPopup reference for onToolbarShowTimeout.");
-            }
-        }
-
-        @Override
-        public void onMenuItemClicked(ToolbarMenuItem toolbarMenuItem) {
-            final RemoteFloatingToolbarPopup remoteFloatingToolbarPopup = mRemotePopup.get();
-            if (remoteFloatingToolbarPopup != null) {
-                remoteFloatingToolbarPopup.onMenuItemClicked(toolbarMenuItem);
-            } else {
-                Log.w(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "Lost remoteFloatingToolbarPopup reference for onMenuItemClicked.");
-            }
-        }
-
-        @Override
-        public void onError(int errorCode) {
-            if (DEBUG) {
-                Log.v(FloatingToolbar.FLOATING_TOOLBAR_TAG,
-                        "SelectionToolbarCallbackImpl onError: " + errorCode);
-            }
-        }
-    }
-
-    /**
-     * Represents the identity of a MenuItem that is rendered in a FloatingToolbarPopup.
-     */
-    static final class MenuItemRepr {
-
-        public final int mItemId;
-        public final int mGroupId;
-        @Nullable
-        public final String mTitle;
-        @Nullable private final Drawable mIcon;
-
-        private MenuItemRepr(
-                int itemId, int groupId, @Nullable CharSequence title,
-                @Nullable Drawable icon) {
-            mItemId = itemId;
-            mGroupId = groupId;
-            mTitle = (title == null) ? null : title.toString();
-            mIcon = icon;
-        }
-
-        /**
-         * Creates an instance of MenuItemRepr for the specified menu item.
-         */
-        public static MenuItemRepr of(MenuItem menuItem) {
-            return new MenuItemRepr(
-                    menuItem.getItemId(),
-                    menuItem.getGroupId(),
-                    menuItem.getTitle(),
-                    menuItem.getIcon());
-        }
-
-        /**
-         * Returns this object's hashcode.
-         */
-        @Override
-        public int hashCode() {
-            return Objects.hash(mItemId, mGroupId, mTitle, mIcon);
-        }
-
-        /**
-         * Returns true if this object is the same as the specified object.
-         */
-        @Override
-        public boolean equals(Object o) {
-            if (o == this) {
-                return true;
-            }
-            if (!(o instanceof LocalFloatingToolbarPopup.MenuItemRepr)) {
-                return false;
-            }
-            final MenuItemRepr other = (MenuItemRepr) o;
-            return mItemId == other.mItemId
-                    && mGroupId == other.mGroupId
-                    && TextUtils.equals(mTitle, other.mTitle)
-                    // Many Drawables (icons) do not implement equals(). Using equals() here instead
-                    // of reference comparisons in case a Drawable subclass implements equals().
-                    && Objects.equals(mIcon, other.mIcon);
-        }
-
-        /**
-         * Returns true if the two menu item collections are the same based on MenuItemRepr.
-         */
-        public static boolean reprEquals(
-                Collection<MenuItem> menuItems1, Collection<MenuItem> menuItems2) {
-            if (menuItems1.size() != menuItems2.size()) {
-                return false;
-            }
-
-            final Iterator<MenuItem> menuItems2Iter = menuItems2.iterator();
-            for (MenuItem menuItem1 : menuItems1) {
-                final MenuItem menuItem2 = menuItems2Iter.next();
-                if (!MenuItemRepr.of(menuItem1).equals(
-                        MenuItemRepr.of(menuItem2))) {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-}
diff --git a/core/java/com/android/server/backup/CompanionBackupHelper.java b/core/java/com/android/server/backup/CompanionBackupHelper.java
new file mode 100644
index 0000000..ef247c2
--- /dev/null
+++ b/core/java/com/android/server/backup/CompanionBackupHelper.java
@@ -0,0 +1,76 @@
+/*
+ * 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.backup;
+
+import android.annotation.UserIdInt;
+import android.app.backup.BlobBackupHelper;
+import android.companion.ICompanionDeviceManager;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+/**
+ * CDM backup and restore helper.
+ */
+public class CompanionBackupHelper extends BlobBackupHelper {
+
+    private static final String TAG = "CompanionBackupHelper";
+
+    // current schema of the backup state blob
+    private static final int BLOB_VERSION = 1;
+
+    // key under which the CDM data blob is committed to back up
+    private static final String KEY_COMPANION = "companion";
+
+    @UserIdInt
+    private final int mUserId;
+
+    public CompanionBackupHelper(int userId) {
+        super(BLOB_VERSION, KEY_COMPANION);
+
+        mUserId = userId;
+    }
+
+    @Override
+    protected byte[] getBackupPayload(String key) {
+        byte[] payload = null;
+        if (KEY_COMPANION.equals(key)) {
+            try {
+                ICompanionDeviceManager cdm = ICompanionDeviceManager.Stub.asInterface(
+                        ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
+                payload = cdm.getBackupPayload(mUserId);
+            } catch (Exception e) {
+                Slog.e(TAG, "Error getting backup from CompanionDeviceManager.", e);
+            }
+        }
+        return payload;
+    }
+
+    @Override
+    protected void applyRestoredPayload(String key, byte[] payload) {
+        Slog.i(TAG, "Got companion backup data.");
+        if (KEY_COMPANION.equals(key)) {
+            try {
+                ICompanionDeviceManager cdm = ICompanionDeviceManager.Stub.asInterface(
+                        ServiceManager.getService(Context.COMPANION_DEVICE_SERVICE));
+                cdm.applyRestoredPayload(payload, mUserId);
+            } catch (Exception e) {
+                Slog.e(TAG, "Error applying restored payload to CompanionDeviceManager.", e);
+            }
+        }
+    }
+}
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index dd43527..3aca751 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -107,3 +107,6 @@
 
 # SQLite
 per-file android_database_SQLite* = file:/SQLITE_OWNERS
+
+# PerformanceHintManager
+per-file android_os_PerformanceHintManager.cpp = file:/ADPF_OWNERS
diff --git a/core/jni/android_hardware_HardwareBuffer.cpp b/core/jni/android_hardware_HardwareBuffer.cpp
index 5fcc46e..2ea2158d 100644
--- a/core/jni/android_hardware_HardwareBuffer.cpp
+++ b/core/jni/android_hardware_HardwareBuffer.cpp
@@ -203,10 +203,10 @@
     Parcel* parcel = parcelForJavaObject(env, in);
     if (parcel) {
         sp<GraphicBuffer> buffer = new GraphicBuffer();
-        parcel->read(*buffer);
-        return reinterpret_cast<jlong>(new GraphicBufferWrapper(buffer));
+        if (parcel->read(*buffer) == STATUS_OK) {
+            return reinterpret_cast<jlong>(new GraphicBufferWrapper(buffer));
+        }
     }
-
     return NULL;
 }
 
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 4cf17b7..1998548 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -523,13 +523,14 @@
     }
 
     jclass clazz = env->FindClass(kClassPathName);
-    const char* zechars = regId.string();
-    jstring zestring = env->NewStringUTF(zechars);
+    const char *regIdString = regId.string();
+    jstring regIdJString = env->NewStringUTF(regIdString);
 
     env->CallStaticVoidMethod(clazz, gAudioPolicyEventHandlerMethods.postDynPolicyEventFromNative,
-            event, zestring, val);
+                              event, regIdJString, val);
 
-    env->ReleaseStringUTFChars(zestring, zechars);
+    const char *regIdJChars = env->GetStringUTFChars(regIdJString, NULL);
+    env->ReleaseStringUTFChars(regIdJString, regIdJChars);
     env->DeleteLocalRef(clazz);
 }
 
diff --git a/core/jni/android_os_PerformanceHintManager.cpp b/core/jni/android_os_PerformanceHintManager.cpp
index ffe844d..27c4cd4 100644
--- a/core/jni/android_os_PerformanceHintManager.cpp
+++ b/core/jni/android_os_PerformanceHintManager.cpp
@@ -34,26 +34,28 @@
 struct APerformanceHintSession;
 
 typedef APerformanceHintManager* (*APH_getManager)();
+typedef int64_t (*APH_getPreferredUpdateRateNanos)(APerformanceHintManager* manager);
 typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
                                                       size_t, int64_t);
-typedef int64_t (*APH_getPreferredUpdateRateNanos)(APerformanceHintManager* manager);
 typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
 typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
 typedef void (*APH_closeSession)(APerformanceHintSession* session);
 typedef void (*APH_sendHint)(APerformanceHintSession*, int32_t);
 typedef int (*APH_setThreads)(APerformanceHintSession*, const pid_t*, size_t);
 typedef void (*APH_getThreadIds)(APerformanceHintSession*, int32_t* const, size_t* const);
+typedef void (*APH_setPreferPowerEfficiency)(APerformanceHintSession*, bool);
 
 bool gAPerformanceHintBindingInitialized = false;
 APH_getManager gAPH_getManagerFn = nullptr;
-APH_createSession gAPH_createSessionFn = nullptr;
 APH_getPreferredUpdateRateNanos gAPH_getPreferredUpdateRateNanosFn = nullptr;
+APH_createSession gAPH_createSessionFn = nullptr;
 APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
 APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
 APH_closeSession gAPH_closeSessionFn = nullptr;
 APH_sendHint gAPH_sendHintFn = nullptr;
 APH_setThreads gAPH_setThreadsFn = nullptr;
 APH_getThreadIds gAPH_getThreadIdsFn = nullptr;
+APH_setPreferPowerEfficiency gAPH_setPreferPowerEfficiencyFn = nullptr;
 
 void ensureAPerformanceHintBindingInitialized() {
     if (gAPerformanceHintBindingInitialized) return;
@@ -65,10 +67,6 @@
     LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
                         "Failed to find required symbol APerformanceHint_getManager!");
 
-    gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
-    LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_createSession!");
-
     gAPH_getPreferredUpdateRateNanosFn =
             (APH_getPreferredUpdateRateNanos)dlsym(handle_,
                                                    "APerformanceHint_getPreferredUpdateRateNanos");
@@ -76,6 +74,10 @@
                         "Failed to find required symbol "
                         "APerformanceHint_getPreferredUpdateRateNanos!");
 
+    gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
+    LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
+                        "Failed to find required symbol APerformanceHint_createSession!");
+
     gAPH_updateTargetWorkDurationFn =
             (APH_updateTargetWorkDuration)dlsym(handle_,
                                                 "APerformanceHint_updateTargetWorkDuration");
@@ -96,8 +98,7 @@
 
     gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
     LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
-                        "Failed to find required symbol "
-                        "APerformanceHint_sendHint!");
+                        "Failed to find required symbol APerformanceHint_sendHint!");
 
     gAPH_setThreadsFn = (APH_setThreads)dlsym(handle_, "APerformanceHint_setThreads");
     LOG_ALWAYS_FATAL_IF(gAPH_setThreadsFn == nullptr,
@@ -107,6 +108,13 @@
     LOG_ALWAYS_FATAL_IF(gAPH_getThreadIdsFn == nullptr,
                         "Failed to find required symbol APerformanceHint_getThreadIds!");
 
+    gAPH_setPreferPowerEfficiencyFn =
+            (APH_setPreferPowerEfficiency)dlsym(handle_,
+                                                "APerformanceHint_setPreferPowerEfficiency");
+    LOG_ALWAYS_FATAL_IF(gAPH_setPreferPowerEfficiencyFn == nullptr,
+                        "Failed to find required symbol"
+                        "APerformanceHint_setPreferPowerEfficiency!");
+
     gAPerformanceHintBindingInitialized = true;
 }
 
@@ -223,6 +231,13 @@
     return jintArr;
 }
 
+static void nativeSetPreferPowerEfficiency(JNIEnv* env, jclass clazz, jlong nativeSessionPtr,
+                                           jboolean enabled) {
+    ensureAPerformanceHintBindingInitialized();
+    gAPH_setPreferPowerEfficiencyFn(reinterpret_cast<APerformanceHintSession*>(nativeSessionPtr),
+                                    enabled);
+}
+
 static const JNINativeMethod gPerformanceHintMethods[] = {
         {"nativeAcquireManager", "()J", (void*)nativeAcquireManager},
         {"nativeGetPreferredUpdateRateNanos", "(J)J", (void*)nativeGetPreferredUpdateRateNanos},
@@ -233,6 +248,7 @@
         {"nativeSendHint", "(JI)V", (void*)nativeSendHint},
         {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
         {"nativeGetThreadIds", "(J)[I", (void*)nativeGetThreadIds},
+        {"nativeSetPreferPowerEfficiency", "(JZ)V", (void*)nativeSetPreferPowerEfficiency},
 };
 
 int register_android_os_PerformanceHintManager(JNIEnv* env) {
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 1afae29..979c9e3 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -347,23 +347,14 @@
 }
 
 static void NativeSetConfiguration(JNIEnv* env, jclass /*clazz*/, jlong ptr, jint mcc, jint mnc,
-                                   jstring default_locale, jobjectArray locales, jint orientation,
-                                   jint touchscreen, jint density, jint keyboard,
-                                   jint keyboard_hidden, jint navigation, jint screen_width,
-                                   jint screen_height, jint smallest_screen_width_dp,
-                                   jint screen_width_dp, jint screen_height_dp, jint screen_layout,
-                                   jint ui_mode, jint color_mode, jint grammatical_gender,
-                                   jint major_version) {
+                                   jstring locale, jint orientation, jint touchscreen, jint density,
+                                   jint keyboard, jint keyboard_hidden, jint navigation,
+                                   jint screen_width, jint screen_height,
+                                   jint smallest_screen_width_dp, jint screen_width_dp,
+                                   jint screen_height_dp, jint screen_layout, jint ui_mode,
+                                   jint color_mode, jint grammatical_gender, jint major_version) {
   ATRACE_NAME("AssetManager::SetConfiguration");
 
-  const jsize locale_count = (locales == NULL) ? 0 : env->GetArrayLength(locales);
-
-  // Constants duplicated from Java class android.content.res.Configuration.
-  static const jint kScreenLayoutRoundMask = 0x300;
-  static const jint kScreenLayoutRoundShift = 8;
-
-  std::vector<ResTable_config> configs;
-
   ResTable_config configuration;
   memset(&configuration, 0, sizeof(configuration));
   configuration.mcc = static_cast<uint16_t>(mcc);
@@ -384,37 +375,25 @@
   configuration.colorMode = static_cast<uint8_t>(color_mode);
   configuration.grammaticalInflection = static_cast<uint8_t>(grammatical_gender);
   configuration.sdkVersion = static_cast<uint16_t>(major_version);
+
+  if (locale != nullptr) {
+    ScopedUtfChars locale_utf8(env, locale);
+    CHECK(locale_utf8.c_str() != nullptr);
+    configuration.setBcp47Locale(locale_utf8.c_str());
+  }
+
+  // Constants duplicated from Java class android.content.res.Configuration.
+  static const jint kScreenLayoutRoundMask = 0x300;
+  static const jint kScreenLayoutRoundShift = 8;
+
   // In Java, we use a 32bit integer for screenLayout, while we only use an 8bit integer
   // in C++. We must extract the round qualifier out of the Java screenLayout and put it
   // into screenLayout2.
   configuration.screenLayout2 =
-          static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
-
-  if (locale_count > 0) {
-    configs.resize(locale_count, configuration);
-    for (int i = 0; i < locale_count; i++) {
-      jstring locale = (jstring)(env->GetObjectArrayElement(locales, i));
-      ScopedUtfChars locale_utf8(env, locale);
-      CHECK(locale_utf8.c_str() != nullptr);
-      configs[i].setBcp47Locale(locale_utf8.c_str());
-    }
-  } else {
-    configs.push_back(configuration);
-  }
-
-  uint32_t default_locale_int = 0;
-  if (default_locale != nullptr) {
-    ResTable_config config;
-    static_assert(std::is_same_v<decltype(config.locale), decltype(default_locale_int)>);
-    ScopedUtfChars locale_utf8(env, default_locale);
-    CHECK(locale_utf8.c_str() != nullptr);
-    config.setBcp47Locale(locale_utf8.c_str());
-    default_locale_int = config.locale;
-  }
+      static_cast<uint8_t>((screen_layout & kScreenLayoutRoundMask) >> kScreenLayoutRoundShift);
 
   auto assetmanager = LockAndStartAssetManager(ptr);
-  assetmanager->SetConfigurations(configs);
-  assetmanager->SetDefaultLocale(default_locale_int);
+  assetmanager->SetConfiguration(configuration);
 }
 
 static jobject NativeGetAssignedPackageIdentifiers(JNIEnv* env, jclass /*clazz*/, jlong ptr,
@@ -1519,97 +1498,94 @@
 
 // JNI registration.
 static const JNINativeMethod gAssetManagerMethods[] = {
-        // AssetManager setup methods.
-        {"nativeCreate", "()J", (void*)NativeCreate},
-        {"nativeDestroy", "(J)V", (void*)NativeDestroy},
-        {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
-        {"nativeSetConfiguration", "(JIILjava/lang/String;[Ljava/lang/String;IIIIIIIIIIIIIIII)V",
-         (void*)NativeSetConfiguration},
-        {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
-         (void*)NativeGetAssignedPackageIdentifiers},
+    // AssetManager setup methods.
+    {"nativeCreate", "()J", (void*)NativeCreate},
+    {"nativeDestroy", "(J)V", (void*)NativeDestroy},
+    {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
+    {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIIII)V",
+     (void*)NativeSetConfiguration},
+    {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
+     (void*)NativeGetAssignedPackageIdentifiers},
 
-        // AssetManager file methods.
-        {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
-        {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
-        {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
-        {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
-         (void*)NativeOpenAssetFd},
-        {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
-        {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
-         (void*)NativeOpenNonAssetFd},
-        {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
-        {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
+    // AssetManager file methods.
+    {"nativeContainsAllocatedTable", "(J)Z", (void*)ContainsAllocatedTable},
+    {"nativeList", "(JLjava/lang/String;)[Ljava/lang/String;", (void*)NativeList},
+    {"nativeOpenAsset", "(JLjava/lang/String;I)J", (void*)NativeOpenAsset},
+    {"nativeOpenAssetFd", "(JLjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+     (void*)NativeOpenAssetFd},
+    {"nativeOpenNonAsset", "(JILjava/lang/String;I)J", (void*)NativeOpenNonAsset},
+    {"nativeOpenNonAssetFd", "(JILjava/lang/String;[J)Landroid/os/ParcelFileDescriptor;",
+     (void*)NativeOpenNonAssetFd},
+    {"nativeOpenXmlAsset", "(JILjava/lang/String;)J", (void*)NativeOpenXmlAsset},
+    {"nativeOpenXmlAssetFd", "(JILjava/io/FileDescriptor;)J", (void*)NativeOpenXmlAssetFd},
 
-        // AssetManager resource methods.
-        {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I",
-         (void*)NativeGetResourceValue},
-        {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
-         (void*)NativeGetResourceBagValue},
-        {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
-        {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
-         (void*)NativeGetResourceStringArray},
-        {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
-        {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
-        {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
-        {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
-        {"nativeGetParentThemeIdentifier", "(JI)I", (void*)NativeGetParentThemeIdentifier},
+    // AssetManager resource methods.
+    {"nativeGetResourceValue", "(JISLandroid/util/TypedValue;Z)I", (void*)NativeGetResourceValue},
+    {"nativeGetResourceBagValue", "(JIILandroid/util/TypedValue;)I",
+     (void*)NativeGetResourceBagValue},
+    {"nativeGetStyleAttributes", "(JI)[I", (void*)NativeGetStyleAttributes},
+    {"nativeGetResourceStringArray", "(JI)[Ljava/lang/String;",
+     (void*)NativeGetResourceStringArray},
+    {"nativeGetResourceStringArrayInfo", "(JI)[I", (void*)NativeGetResourceStringArrayInfo},
+    {"nativeGetResourceIntArray", "(JI)[I", (void*)NativeGetResourceIntArray},
+    {"nativeGetResourceArraySize", "(JI)I", (void*)NativeGetResourceArraySize},
+    {"nativeGetResourceArray", "(JI[I)I", (void*)NativeGetResourceArray},
+    {"nativeGetParentThemeIdentifier", "(JI)I",
+     (void*)NativeGetParentThemeIdentifier},
 
-        // AssetManager resource name/ID methods.
-        {"nativeGetResourceIdentifier",
-         "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
-         (void*)NativeGetResourceIdentifier},
-        {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
-        {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;",
-         (void*)NativeGetResourcePackageName},
-        {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
-        {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
-        {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
-         (void*)NativeSetResourceResolutionLoggingEnabled},
-        {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
-         (void*)NativeGetLastResourceResolution},
-        {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
-        {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
-         (void*)NativeGetSizeConfigurations},
-        {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
-         (void*)NativeGetSizeAndUiModeConfigurations},
+    // AssetManager resource name/ID methods.
+    {"nativeGetResourceIdentifier", "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;)I",
+     (void*)NativeGetResourceIdentifier},
+    {"nativeGetResourceName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceName},
+    {"nativeGetResourcePackageName", "(JI)Ljava/lang/String;", (void*)NativeGetResourcePackageName},
+    {"nativeGetResourceTypeName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceTypeName},
+    {"nativeGetResourceEntryName", "(JI)Ljava/lang/String;", (void*)NativeGetResourceEntryName},
+    {"nativeSetResourceResolutionLoggingEnabled", "(JZ)V",
+     (void*) NativeSetResourceResolutionLoggingEnabled},
+    {"nativeGetLastResourceResolution", "(J)Ljava/lang/String;",
+     (void*) NativeGetLastResourceResolution},
+    {"nativeGetLocales", "(JZ)[Ljava/lang/String;", (void*)NativeGetLocales},
+    {"nativeGetSizeConfigurations", "(J)[Landroid/content/res/Configuration;",
+     (void*)NativeGetSizeConfigurations},
+    {"nativeGetSizeAndUiModeConfigurations", "(J)[Landroid/content/res/Configuration;",
+     (void*)NativeGetSizeAndUiModeConfigurations},
 
-        // Style attribute related methods.
-        {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
-        {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
-        {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
-        {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
+    // Style attribute related methods.
+    {"nativeAttributeResolutionStack", "(JJIII)[I", (void*)NativeAttributeResolutionStack},
+    {"nativeApplyStyle", "(JJIIJ[IJJ)V", (void*)NativeApplyStyle},
+    {"nativeResolveAttrs", "(JJII[I[I[I[I)Z", (void*)NativeResolveAttrs},
+    {"nativeRetrieveAttributes", "(JJ[I[I[I)Z", (void*)NativeRetrieveAttributes},
 
-        // Theme related methods.
-        {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
-        {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
-        {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
-        {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
+    // Theme related methods.
+    {"nativeThemeCreate", "(J)J", (void*)NativeThemeCreate},
+    {"nativeGetThemeFreeFunction", "()J", (void*)NativeGetThemeFreeFunction},
+    {"nativeThemeApplyStyle", "(JJIZ)V", (void*)NativeThemeApplyStyle},
+    {"nativeThemeRebase", "(JJ[I[ZI)V", (void*)NativeThemeRebase},
 
-        {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
-        {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
-         (void*)NativeThemeGetAttributeValue},
-        {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
-        {"nativeThemeGetChangingConfigurations", "(J)I",
-         (void*)NativeThemeGetChangingConfigurations},
+    {"nativeThemeCopy", "(JJJJ)V", (void*)NativeThemeCopy},
+    {"nativeThemeGetAttributeValue", "(JJILandroid/util/TypedValue;Z)I",
+     (void*)NativeThemeGetAttributeValue},
+    {"nativeThemeDump", "(JJILjava/lang/String;Ljava/lang/String;)V", (void*)NativeThemeDump},
+    {"nativeThemeGetChangingConfigurations", "(J)I", (void*)NativeThemeGetChangingConfigurations},
 
-        // AssetInputStream methods.
-        {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
-        {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
-        {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
-        {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
-        {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
-        {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
+    // AssetInputStream methods.
+    {"nativeAssetDestroy", "(J)V", (void*)NativeAssetDestroy},
+    {"nativeAssetReadChar", "(J)I", (void*)NativeAssetReadChar},
+    {"nativeAssetRead", "(J[BII)I", (void*)NativeAssetRead},
+    {"nativeAssetSeek", "(JJI)J", (void*)NativeAssetSeek},
+    {"nativeAssetGetLength", "(J)J", (void*)NativeAssetGetLength},
+    {"nativeAssetGetRemainingLength", "(J)J", (void*)NativeAssetGetRemainingLength},
 
-        // System/idmap related methods.
-        {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
-         (void*)NativeGetOverlayableMap},
-        {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
-         (void*)NativeGetOverlayablesToString},
+    // System/idmap related methods.
+    {"nativeGetOverlayableMap", "(JLjava/lang/String;)Ljava/util/Map;",
+     (void*)NativeGetOverlayableMap},
+    {"nativeGetOverlayablesToString", "(JLjava/lang/String;)Ljava/lang/String;",
+     (void*)NativeGetOverlayablesToString},
 
-        // Global management/debug methods.
-        {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
-        {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
-        {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
+    // Global management/debug methods.
+    {"getGlobalAssetCount", "()I", (void*)NativeGetGlobalAssetCount},
+    {"getAssetAllocations", "()Ljava/lang/String;", (void*)NativeGetAssetAllocations},
+    {"getGlobalAssetManagerCount", "()I", (void*)NativeGetGlobalAssetManagerCount},
 };
 
 int register_android_content_AssetManager(JNIEnv* env) {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4249253..dbe0338 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -257,6 +257,14 @@
     jmethodID onTrustedPresentationChanged;
 } gTrustedPresentationCallbackClassInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctor;
+    jfieldID layerName;
+    jfieldID bufferId;
+    jfieldID frameNumber;
+} gStalledTransactionInfoClassInfo;
+
 constexpr ui::Dataspace pickDataspaceFromColorMode(const ui::ColorMode colorMode) {
     switch (colorMode) {
         case ui::ColorMode::DISPLAY_P3:
@@ -2032,6 +2040,29 @@
     return static_cast<jlong>(reinterpret_cast<uintptr_t>(&destroyNativeTpc));
 }
 
+static jobject nativeGetStalledTransactionInfo(JNIEnv* env, jclass clazz, jint pid) {
+    std::optional<gui::StalledTransactionInfo> stalledTransactionInfo =
+            SurfaceComposerClient::getStalledTransactionInfo(pid);
+    if (!stalledTransactionInfo) {
+        return nullptr;
+    }
+
+    jobject jStalledTransactionInfo = env->NewObject(gStalledTransactionInfoClassInfo.clazz,
+                                                     gStalledTransactionInfoClassInfo.ctor);
+    if (!jStalledTransactionInfo) {
+        jniThrowException(env, "java/lang/OutOfMemoryError", nullptr);
+        return nullptr;
+    }
+
+    env->SetObjectField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.layerName,
+                        env->NewStringUTF(String8{stalledTransactionInfo->layerName}));
+    env->SetLongField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.bufferId,
+                      static_cast<jlong>(stalledTransactionInfo->bufferId));
+    env->SetLongField(jStalledTransactionInfo, gStalledTransactionInfoClassInfo.frameNumber,
+                      static_cast<jlong>(stalledTransactionInfo->frameNumber));
+    return jStalledTransactionInfo;
+}
+
 // ----------------------------------------------------------------------------
 
 SurfaceControl* android_view_SurfaceControl_getNativeSurfaceControl(JNIEnv* env,
@@ -2281,6 +2312,8 @@
     {"nativeCreateTpc", "(Landroid/view/SurfaceControl$TrustedPresentationCallback;)J",
             (void*)nativeCreateTpc},
     {"getNativeTrustedPresentationCallbackFinalizer", "()J", (void*)getNativeTrustedPresentationCallbackFinalizer },
+    {"nativeGetStalledTransactionInfo", "(I)Landroid/gui/StalledTransactionInfo;",
+            (void*) nativeGetStalledTransactionInfo },
         // clang-format on
 };
 
@@ -2524,6 +2557,18 @@
     gTrustedPresentationCallbackClassInfo.onTrustedPresentationChanged =
             GetMethodIDOrDie(env, trustedPresentationCallbackClazz, "onTrustedPresentationChanged",
                              "(Z)V");
+
+    jclass stalledTransactionInfoClazz = FindClassOrDie(env, "android/gui/StalledTransactionInfo");
+    gStalledTransactionInfoClassInfo.clazz = MakeGlobalRefOrDie(env, stalledTransactionInfoClazz);
+    gStalledTransactionInfoClassInfo.ctor =
+            GetMethodIDOrDie(env, stalledTransactionInfoClazz, "<init>", "()V");
+    gStalledTransactionInfoClassInfo.layerName =
+            GetFieldIDOrDie(env, stalledTransactionInfoClazz, "layerName", "Ljava/lang/String;");
+    gStalledTransactionInfoClassInfo.bufferId =
+            GetFieldIDOrDie(env, stalledTransactionInfoClazz, "bufferId", "J");
+    gStalledTransactionInfoClassInfo.frameNumber =
+            GetFieldIDOrDie(env, stalledTransactionInfoClazz, "frameNumber", "J");
+
     return err;
 }
 
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index e729750..bdf7eaa 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -102,7 +102,8 @@
         }
 
         if (!captureResults.fenceResult.ok() || captureResults.buffer == nullptr) {
-            env->CallVoidMethod(consumer.get(), gConsumerClassInfo.accept, nullptr);
+            env->CallVoidMethod(consumer.get(), gConsumerClassInfo.accept, nullptr,
+                                fenceStatus(captureResults.fenceResult));
             checkAndClearException(env, "accept");
             return binder::Status::ok();
         }
@@ -117,7 +118,8 @@
                                             captureResults.capturedSecureLayers,
                                             captureResults.capturedHdrLayers);
         checkAndClearException(env, "builder");
-        env->CallVoidMethod(consumer.get(), gConsumerClassInfo.accept, screenshotHardwareBuffer);
+        env->CallVoidMethod(consumer.get(), gConsumerClassInfo.accept, screenshotHardwareBuffer,
+                            fenceStatus(captureResults.fenceResult));
         checkAndClearException(env, "accept");
         env->DeleteLocalRef(jhardwareBuffer);
         env->DeleteLocalRef(screenshotHardwareBuffer);
@@ -285,7 +287,7 @@
             (void*)nativeCaptureDisplay },
     {"nativeCaptureLayers",  "(Landroid/window/ScreenCapture$LayerCaptureArgs;J)I",
             (void*)nativeCaptureLayers },
-    {"nativeCreateScreenCaptureListener", "(Ljava/util/function/Consumer;)J",
+    {"nativeCreateScreenCaptureListener", "(Ljava/util/function/ObjIntConsumer;)J",
             (void*)nativeCreateScreenCaptureListener },
     {"nativeWriteListenerToParcel", "(JLandroid/os/Parcel;)V", (void*)nativeWriteListenerToParcel },
     {"nativeReadListenerFromParcel", "(Landroid/os/Parcel;)J",
@@ -333,8 +335,8 @@
     gLayerCaptureArgsClassInfo.childrenOnly =
             GetFieldIDOrDie(env, layerCaptureArgsClazz, "mChildrenOnly", "Z");
 
-    jclass consumer = FindClassOrDie(env, "java/util/function/Consumer");
-    gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V");
+    jclass consumer = FindClassOrDie(env, "java/util/function/ObjIntConsumer");
+    gConsumerClassInfo.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;I)V");
 
     jclass screenshotGraphicsBufferClazz =
             FindClassOrDie(env, "android/window/ScreenCapture$ScreenshotHardwareBuffer");
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index da308b2..149e57a 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -130,6 +130,7 @@
 static install_status_t
 copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntry, const char* fileName)
 {
+    static const size_t kPageSize = getpagesize();
     void** args = reinterpret_cast<void**>(arg);
     jstring* javaNativeLibPath = (jstring*) args[0];
     jboolean extractNativeLibs = *(jboolean*) args[1];
@@ -162,9 +163,9 @@
             return INSTALL_FAILED_INVALID_APK;
         }
 
-        if (offset % PAGE_SIZE != 0) {
-            ALOGE("Library '%s' is not page-aligned - will not be able to open it directly from"
-                " apk.\n", fileName);
+        if (offset % kPageSize != 0) {
+            ALOGE("Library '%s' is not PAGE(%zu)-aligned - will not be able to open it directly "
+                  "from apk.\n", fileName, kPageSize);
             return INSTALL_FAILED_INVALID_APK;
         }
 
diff --git a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
index bba1760..d4f6e18 100644
--- a/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
+++ b/core/jni/com_android_internal_content_om_OverlayManagerImpl.cpp
@@ -44,6 +44,8 @@
     jfieldID stringData;
     jfieldID binaryData;
     jfieldID configuration;
+    jfieldID binaryDataOffset;
+    jfieldID binaryDataSize;
 } gFabricatedOverlayInternalEntryOffsets;
 
 static struct parcel_file_descriptor_offsets_t {
@@ -281,10 +283,17 @@
         auto binary_data =
                 getNullableFileDescriptor(env, entry,
                                           gFabricatedOverlayInternalEntryOffsets.binaryData);
+
+        const auto data_offset =
+                env->GetLongField(entry, gFabricatedOverlayInternalEntryOffsets.binaryDataOffset);
+        const auto data_size =
+                env->GetLongField(entry, gFabricatedOverlayInternalEntryOffsets.binaryDataSize);
         entries_params.push_back(
                 FabricatedOverlayEntryParameters{resourceName.c_str(), (DataType)dataType,
                                                  (DataValue)data,
                                                  string_data.value_or(std::string()), binary_data,
+                                                 static_cast<off64_t>(data_offset),
+                                                 static_cast<size_t>(data_size),
                                                  configuration.value_or(std::string())});
         ALOGV("resourceName = %s, dataType = 0x%08x, data = 0x%08x, dataString = %s,"
               " binaryData = %d, configuration = %s",
@@ -440,6 +449,12 @@
     gFabricatedOverlayInternalEntryOffsets.configuration =
             GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject,
                             "configuration", "Ljava/lang/String;");
+    gFabricatedOverlayInternalEntryOffsets.binaryDataOffset =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject,
+                            "binaryDataOffset", "J");
+    gFabricatedOverlayInternalEntryOffsets.binaryDataSize =
+            GetFieldIDOrDie(env, gFabricatedOverlayInternalEntryOffsets.classObject,
+                            "binaryDataSize", "J");
 
     jclass parcelFileDescriptorClass =
             android::FindClassOrDie(env, "android/os/ParcelFileDescriptor");
diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto
index a950a79..34ccb48 100644
--- a/core/proto/android/server/windowmanagertransitiontrace.proto
+++ b/core/proto/android/server/windowmanagertransitiontrace.proto
@@ -56,6 +56,7 @@
   repeated Target targets = 8;
   optional int32 flags = 9;
   optional int64 abort_time_ns = 10;
+  optional int64 starting_window_remove_time_ns = 11;
 }
 
 message Target {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 86cf80e..67710f6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -821,7 +821,6 @@
     <protected-broadcast android:name="android.intent.action.PROFILE_REMOVED" />
     <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_SENT_ACTION" />
     <protected-broadcast android:name="com.android.internal.telephony.cat.SMS_DELIVERY_ACTION" />
-    <protected-broadcast android:name="com.android.internal.telephony.data.ACTION_RETRY" />
     <protected-broadcast android:name="android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED" />
     <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW" />
     <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 71630cb..72333fb 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4288,6 +4288,9 @@
     <!-- Colon separated list of package names that should be granted DND access -->
     <string name="config_defaultDndAccessPackages" translatable="false">com.android.camera2</string>
 
+    <!-- Colon separated list of package names that should be removed from DND access packages -->
+    <string name="config_defaultDndDeniedPackages" translatable="false"></string>
+
     <!-- User restrictions set on the SYSTEM user when it is first created.
          Note: Also update appropriate overlay files. -->
     <string-array translatable="false" name="config_defaultFirstUserRestrictions">
@@ -5369,6 +5372,12 @@
     <!-- Default value for performant auth feature. -->
     <bool name="config_performantAuthDefault">false</bool>
 
+    <!-- Threshold for false rejection rate (FRR) of biometric authentication. Applies for both
+         fingerprint and face. If a dual-modality device only enrolled a single biometric and
+         experiences high FRR (above threshold), system notification will be sent to encourage user
+         to enroll the other eligible biometric. -->
+    <fraction name="config_biometricNotificationFrrThreshold">30%</fraction>
+
     <!-- The component name for the default profile supervisor, which can be set as a profile owner
     even after user setup is complete. The defined component should be used for supervision purposes
     only. The component must be part of a system app. -->
@@ -6589,6 +6598,10 @@
          for the non-customized ones. -->
     <string name="config_hapticFeedbackCustomizationFile" />
 
+    <!-- Enables or disables the "Share" action item shown in the context menu that appears upon
+        long-pressing on selected text. Enabled by default. -->
+    <bool name="config_textShareSupported">true</bool>
+
     <!-- Whether or not ActivityManager PSS profiling is disabled. -->
     <bool name="config_am_disablePssProfiling">false</bool>
 </resources>
diff --git a/core/res/res/values/config_device_idle.xml b/core/res/res/values/config_device_idle.xml
index 764dbbe..98a5ff9 100644
--- a/core/res/res/values/config_device_idle.xml
+++ b/core/res/res/values/config_device_idle.xml
@@ -120,7 +120,7 @@
     <!-- Default for DeviceIdleController.Constants.USE_WINDOW_ALARMS -->
     <bool name="device_idle_use_window_alarms">true</bool>
 
-    <!-- Default for DeviceIdleController.Constants.USE_BODY_SENSOR -->
-    <bool name="device_idle_use_body_sensor">false</bool>
+    <!-- Default for DeviceIdleController.Constants.USE_MODE_MANAGER -->
+    <bool name="device_idle_use_mode_manager">false</bool>
 </resources>
 
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 18abe70..bda194a 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -160,6 +160,7 @@
             3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}
             4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}
             5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}
+            6 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_MMS}
          Example of a config string: "10011:2,3"
 
          The PLMNs not configured in this array will be ignored and will not be used for satellite
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index a11eaa9..c5aa8b0 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1786,10 +1786,6 @@
     <string name="biometric_dialog_default_title">Verify it\u2019s you</string>
     <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face). [CHAR LIMIT=70] -->
     <string name="biometric_dialog_default_subtitle">Use your biometric to continue</string>
-    <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with fingerprint. [CHAR LIMIT=70] -->
-    <string name="biometric_dialog_fingerprint_subtitle">Use your fingerprint to continue</string>
-    <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with face. [CHAR LIMIT=70] -->
-    <string name="biometric_dialog_face_subtitle">Use your face to continue</string>
     <!-- Subtitle shown on the system-provided biometric dialog, asking the user to authenticate with a biometric (e.g. fingerprint or face) or their screen lock credential (i.e. PIN, pattern, or password). [CHAR LIMIT=90] -->
     <string name="biometric_or_screen_lock_dialog_default_subtitle">Use your biometric or screen lock to continue</string>
 
@@ -1841,6 +1837,8 @@
     <string name="fingerprint_error_not_match">Fingerprint not recognized</string>
     <!-- Message shown when UDFPS fails to match -->
     <string name="fingerprint_udfps_error_not_match">Fingerprint not recognized</string>
+    <!-- Message shown to inform the user a face cannot be recognized and fingerprint should instead be used.[CHAR LIMIT=50] -->
+    <string name="fingerprint_dialog_use_fingerprint_instead">Can\u2019t recognize face. Use fingerprint instead.</string>
 
     <!-- Accessibility message announced when a fingerprint has been authenticated [CHAR LIMIT=NONE] -->
     <string name="fingerprint_authenticated">Fingerprint authenticated</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5806e93..0a0dc36 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2585,8 +2585,6 @@
   <java-symbol type="string" name="biometric_or_screen_lock_app_setting_name" />
   <java-symbol type="string" name="biometric_dialog_default_title" />
   <java-symbol type="string" name="biometric_dialog_default_subtitle" />
-  <java-symbol type="string" name="biometric_dialog_face_subtitle" />
-  <java-symbol type="string" name="biometric_dialog_fingerprint_subtitle" />
   <java-symbol type="string" name="biometric_or_screen_lock_dialog_default_subtitle" />
   <java-symbol type="string" name="biometric_error_hw_unavailable" />
   <java-symbol type="string" name="biometric_error_user_canceled" />
@@ -2596,6 +2594,9 @@
   <java-symbol type="string" name="biometric_error_device_not_secured" />
   <java-symbol type="string" name="biometric_error_generic" />
 
+  <!-- Biometric FRR config -->
+  <java-symbol type="fraction" name="config_biometricNotificationFrrThreshold" />
+
   <!-- Device credential strings for BiometricManager -->
   <java-symbol type="string" name="screen_lock_app_setting_name" />
   <java-symbol type="string" name="screen_lock_dialog_default_subtitle" />
@@ -2609,6 +2610,7 @@
   <java-symbol type="string" name="fingerprint_error_vendor_unknown" />
   <java-symbol type="string" name="fingerprint_error_not_match" />
   <java-symbol type="string" name="fingerprint_udfps_error_not_match" />
+  <java-symbol type="string" name="fingerprint_dialog_use_fingerprint_instead" />
   <java-symbol type="string" name="fingerprint_acquired_partial" />
   <java-symbol type="string" name="fingerprint_acquired_insufficient" />
   <java-symbol type="string" name="fingerprint_acquired_imager_dirty" />
@@ -3051,6 +3053,7 @@
   <java-symbol type="id" name="addToDictionaryButton" />
   <java-symbol type="id" name="deleteButton" />
   <!-- TextView -->
+  <java-symbol type="bool" name="config_textShareSupported" />
   <java-symbol type="string" name="failed_to_copy_to_clipboard" />
 
   <java-symbol type="id" name="notification_material_reply_container" />
@@ -3383,6 +3386,8 @@
 
   <!-- Colon separated list of package names that should be granted DND access -->
   <java-symbol type="string" name="config_defaultDndAccessPackages" />
+  <!-- Colon separated list of package names that should be removed from DND access packages -->
+  <java-symbol type="string" name="config_defaultDndDeniedPackages" />
 
   <!-- For NetworkPolicyManagerService -->
   <java-symbol type="string" name="config_networkOverLimitComponent" />
@@ -4522,7 +4527,7 @@
   <java-symbol type="integer" name="device_idle_notification_allowlist_duration_ms" />
   <java-symbol type="bool" name="device_idle_wait_for_unlock" />
   <java-symbol type="bool" name="device_idle_use_window_alarms" />
-  <java-symbol type="bool" name="device_idle_use_body_sensor" />
+  <java-symbol type="bool" name="device_idle_use_mode_manager" />
 
   <!-- Binder heavy hitter watcher configs -->
   <java-symbol type="bool" name="config_defaultBinderHeavyHitterWatcherEnabled" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index c7aaeb0..c14da29 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -91,7 +91,6 @@
     java_resources: [":ApkVerityTestCertDer"],
 
     data: [
-        ":BstatsTestApp",
         ":BinderDeathRecipientHelperApp1",
         ":BinderDeathRecipientHelperApp2",
         ":com.android.cts.helpers.aosp",
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 129de64..31755ef 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -231,6 +231,28 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.widget.HorizontalScrollViewActivity"
+                android:label="HorizontalScrollViewActivity"
+                android:screenOrientation="portrait"
+                android:exported="true"
+                android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.widget.ScrollViewActivity"
+                android:label="ScrollViewActivity"
+                android:screenOrientation="portrait"
+                android:exported="true"
+                android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+            </intent-filter>
+        </activity>
+
         <activity android:name="android.widget.DatePickerActivity"
                 android:label="DatePickerActivity"
                 android:screenOrientation="portrait"
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index 3e4c47b..05b309b 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -20,7 +20,6 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="FrameworksCoreTests.apk" />
-        <option name="test-file-name" value="BstatsTestApp.apk" />
         <option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" />
         <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" />
     </target_preparer>
diff --git a/core/tests/coretests/BstatsTestApp/OWNERS b/core/tests/coretests/BstatsTestApp/OWNERS
deleted file mode 100644
index 4068e2b..0000000
--- a/core/tests/coretests/BstatsTestApp/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /BATTERY_STATS_OWNERS
diff --git a/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
new file mode 100644
index 0000000..866e1a9
--- /dev/null
+++ b/core/tests/coretests/res/layout/activity_horizontal_scroll_view.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2015 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
+  -->
+
+<HorizontalScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/horizontal_scroll_view">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+
+        <View
+            android:background="#F00"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#880"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#0F0"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#088"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#00F"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#808"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#F00"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#880"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#0F0"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#088"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#00F"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#808"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#F00"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#880"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#0F0"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#088"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#00F"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#808"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+    </LinearLayout>
+</HorizontalScrollView>
diff --git a/core/tests/coretests/res/layout/activity_scroll_view.xml b/core/tests/coretests/res/layout/activity_scroll_view.xml
new file mode 100644
index 0000000..61fabf8
--- /dev/null
+++ b/core/tests/coretests/res/layout/activity_scroll_view.xml
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2015 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
+  -->
+
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:id="@+id/scroll_view">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <View
+            android:background="#F00"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#880"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#0F0"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#088"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#00F"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#808"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#F00"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#880"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#0F0"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#088"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#00F"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#808"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#F00"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#880"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#0F0"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#088"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#00F"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+        <View
+            android:background="#808"
+            android:layout_width="100dp"
+            android:layout_height="100dp" />
+
+    </LinearLayout>
+</ScrollView>
diff --git a/core/tests/coretests/src/android/app/NotificationChannelTest.java b/core/tests/coretests/src/android/app/NotificationChannelTest.java
index 647bfe8..d8305f0 100644
--- a/core/tests/coretests/src/android/app/NotificationChannelTest.java
+++ b/core/tests/coretests/src/android/app/NotificationChannelTest.java
@@ -16,19 +16,52 @@
 
 package android.app;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.TestCase.assertEquals;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.AttributionSource;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.pm.ApplicationInfo;
+import android.database.MatrixCursor;
+import android.media.AudioAttributes;
 import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Bundle;
 import android.os.Parcel;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.provider.MediaStore.Audio.AudioColumns;
+import android.test.mock.MockContentResolver;
+import android.util.Xml;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.modules.utils.TypedXmlPullParser;
+import com.android.modules.utils.TypedXmlSerializer;
+
 import com.google.common.base.Strings;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.lang.reflect.Field;
 
 @RunWith(AndroidJUnit4.class)
@@ -36,6 +69,88 @@
 public class NotificationChannelTest {
     private final String CLASS = "android.app.NotificationChannel";
 
+    Context mContext;
+    ContentProvider mContentProvider;
+    IContentProvider mIContentProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = mock(Context.class);
+        when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
+        MockContentResolver mContentResolver = new MockContentResolver(mContext);
+        when(mContext.getContentResolver()).thenReturn(mContentResolver);
+        mContentProvider = mock(ContentProvider.class);
+        mIContentProvider = mock(IContentProvider.class);
+        when(mContentProvider.getIContentProvider()).thenReturn(mIContentProvider);
+        doAnswer(
+                invocation -> {
+                        AttributionSource attributionSource = invocation.getArgument(0);
+                        Uri uri = invocation.getArgument(1);
+                        RemoteCallback cb = invocation.getArgument(2);
+                        IContentProvider mock = (IContentProvider) (invocation.getMock());
+                        AsyncTask.SERIAL_EXECUTOR.execute(
+                                () -> {
+                                final Bundle bundle = new Bundle();
+                                try {
+                                        bundle.putParcelable(
+                                                ContentResolver.REMOTE_CALLBACK_RESULT,
+                                                mock.canonicalize(attributionSource, uri));
+                                } catch (RemoteException e) {
+                                        /* consume */
+                                }
+                                cb.sendResult(bundle);
+                                });
+                        return null;
+                })
+            .when(mIContentProvider)
+            .canonicalizeAsync(any(), any(), any());
+        doAnswer(
+                invocation -> {
+                        AttributionSource attributionSource = invocation.getArgument(0);
+                        Uri uri = invocation.getArgument(1);
+                        RemoteCallback cb = invocation.getArgument(2);
+                        IContentProvider mock = (IContentProvider) (invocation.getMock());
+                        AsyncTask.SERIAL_EXECUTOR.execute(
+                                () -> {
+                                final Bundle bundle = new Bundle();
+                                try {
+                                        bundle.putParcelable(
+                                                ContentResolver.REMOTE_CALLBACK_RESULT,
+                                                mock.uncanonicalize(attributionSource, uri));
+                                } catch (RemoteException e) {
+                                        /* consume */
+                                }
+                                cb.sendResult(bundle);
+                                });
+                        return null;
+                })
+            .when(mIContentProvider)
+            .uncanonicalizeAsync(any(), any(), any());
+        doAnswer(
+                invocation -> {
+                        Uri uri = invocation.getArgument(0);
+                        RemoteCallback cb = invocation.getArgument(1);
+                        IContentProvider mock = (IContentProvider) (invocation.getMock());
+                        AsyncTask.SERIAL_EXECUTOR.execute(
+                                () -> {
+                                final Bundle bundle = new Bundle();
+                                try {
+                                        bundle.putString(
+                                                ContentResolver.REMOTE_CALLBACK_RESULT,
+                                                mock.getType(uri));
+                                } catch (RemoteException e) {
+                                        /* consume */
+                                }
+                                cb.sendResult(bundle);
+                                });
+                        return null;
+                })
+            .when(mIContentProvider)
+            .getTypeAsync(any(), any());
+
+        mContentResolver.addProvider("media", mContentProvider);
+    }
+
     @Test
     public void testLongStringFields() {
         NotificationChannel channel = new NotificationChannel("id", "name", 3);
@@ -103,4 +218,139 @@
         assertEquals(NotificationChannel.MAX_TEXT_LENGTH,
                 fromParcel.getSound().toString().length());
     }
+
+    @Test
+    public void testRestoreSoundUri_customLookup() throws Exception {
+        Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
+        Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
+        Uri uriAfterRestoredUncanonicalized = Uri.parse("content://media/100");
+        Uri uriAfterRestoredCanonicalized = Uri.parse("content://media/100?title=Song&canonical=1");
+
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
+        cursor.addRow(new Object[] {100L});
+
+        when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
+                .thenReturn(uriToBeRestoredCanonicalized);
+
+        // Mock the failure of regular uncanonicalize.
+        when(mIContentProvider.uncanonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+                .thenReturn(null);
+
+        // Mock the custom lookup in RingtoneManager.getRingtoneUriForRestore.
+        when(mIContentProvider.query(any(), any(), any(), any(), any())).thenReturn(cursor);
+
+        // Mock the canonicalize in RingtoneManager.getRingtoneUriForRestore.
+        when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
+                .thenReturn(uriAfterRestoredCanonicalized);
+
+        assertThat(
+                        channel.restoreSoundUri(
+                                mContext,
+                                uriToBeRestoredUncanonicalized,
+                                true,
+                                AudioAttributes.USAGE_NOTIFICATION))
+                .isEqualTo(uriAfterRestoredCanonicalized);
+    }
+
+    @Test
+    public void testWriteXmlForBackup_customLookup_notificationUsage() throws Exception {
+        testWriteXmlForBackup_customLookup(
+                AudioAttributes.USAGE_NOTIFICATION, AudioColumns.IS_NOTIFICATION);
+    }
+
+    @Test
+    public void testWriteXmlForBackup_customLookup_alarmUsage() throws Exception {
+        testWriteXmlForBackup_customLookup(AudioAttributes.USAGE_ALARM, AudioColumns.IS_ALARM);
+    }
+
+    @Test
+    public void testWriteXmlForBackup_customLookup_ringtoneUsage() throws Exception {
+        testWriteXmlForBackup_customLookup(
+                AudioAttributes.USAGE_NOTIFICATION_RINGTONE, AudioColumns.IS_RINGTONE);
+    }
+
+    @Test
+    public void testWriteXmlForBackup_customLookup_unknownUsage() throws Exception {
+        testWriteXmlForBackup_customLookup(
+                AudioAttributes.USAGE_UNKNOWN, AudioColumns.IS_NOTIFICATION);
+    }
+
+    private void testWriteXmlForBackup_customLookup(int usage, String customQuerySelection)
+            throws Exception {
+        Uri uriToBeRestoredUncanonicalized = Uri.parse("content://media/1");
+        Uri uriToBeRestoredCanonicalized = Uri.parse("content://media/1?title=Song&canonical=1");
+        Uri uriAfterRestoredUncanonicalized = Uri.parse("content://media/100");
+        Uri uriAfterRestoredCanonicalized = Uri.parse("content://media/100?title=Song&canonical=1");
+
+        AudioAttributes mAudioAttributes =
+                new AudioAttributes.Builder()
+                        .setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
+                        .setUsage(usage)
+                        .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
+                        .build();
+
+        NotificationChannel channel = new NotificationChannel("id", "name", 3);
+        channel.setSound(uriToBeRestoredCanonicalized, mAudioAttributes);
+
+        TypedXmlSerializer serializer = Xml.newFastSerializer();
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
+        serializer.startDocument(null, true);
+
+        // mock the canonicalize in writeXmlForBackup -> getSoundForBackup
+        when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredUncanonicalized)))
+                .thenReturn(uriToBeRestoredCanonicalized);
+        when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+                .thenReturn(uriToBeRestoredCanonicalized);
+
+        channel.writeXmlForBackup(serializer, mContext);
+        serializer.endDocument();
+        serializer.flush();
+
+        TypedXmlPullParser parser = Xml.newFastPullParser();
+        byte[] byteArray = baos.toByteArray();
+        parser.setInput(new BufferedInputStream(new ByteArrayInputStream(byteArray)), null);
+        parser.nextTag();
+
+        NotificationChannel targetChannel = new NotificationChannel("id", "name", 3);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {"_id"});
+        cursor.addRow(new Object[] {100L});
+
+        when(mIContentProvider.canonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+                .thenReturn(uriToBeRestoredCanonicalized);
+
+        // Mock the failure of regular uncanonicalize.
+        when(mIContentProvider.uncanonicalize(any(), eq(uriToBeRestoredCanonicalized)))
+                .thenReturn(null);
+
+        Bundle expectedBundle =
+                ContentResolver.createSqlQueryBundle(
+                        customQuerySelection + "=1 AND title=?", new String[] {"Song"}, null);
+
+        // Mock the custom lookup in RingtoneManager.getRingtoneUriForRestore.
+        when(mIContentProvider.query(
+                        any(),
+                        any(),
+                        any(),
+                        // any(),
+                        argThat(
+                                queryBundle -> {
+                                    return queryBundle != null
+                                            && expectedBundle
+                                                    .toString()
+                                                    .equals(queryBundle.toString());
+                                }),
+                        any()))
+                .thenReturn(cursor);
+
+        // Mock the canonicalize in RingtoneManager.getRingtoneUriForRestore.
+        when(mIContentProvider.canonicalize(any(), eq(uriAfterRestoredUncanonicalized)))
+                .thenReturn(uriAfterRestoredCanonicalized);
+
+        targetChannel.populateFromXmlForRestore(parser, true, mContext);
+        assertThat(targetChannel.getSound()).isEqualTo(uriAfterRestoredCanonicalized);
+    }
 }
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index c904d96..91c4dde 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -472,8 +472,10 @@
         final Rect bounds = activity.getWindowManager().getCurrentWindowMetrics().getBounds();
         assertEquals(activityConfigPortrait.windowConfiguration.getBounds(), bounds);
 
-        // Ensure changes in window configuration bounds are reported
-        assertEquals(numOfConfig + 1, activity.mNumOfConfigChanges);
+        // Ensure that Activity#onConfigurationChanged() not be called because the changes in
+        // WindowConfiguration shouldn't be reported, and we only apply the latest Configuration
+        // update in transaction.
+        assertEquals(numOfConfig, activity.mNumOfConfigChanges);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
index fc72f61..4ee987b 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteDatabaseTest.java
@@ -180,8 +180,8 @@
                     assertFalse(r);
                     s.reset();
                     assertEquals(i + 1, mDatabase.getLastInsertRowId());
-                    assertEquals(1, mDatabase.getLastChangedRowsCount());
-                    assertEquals(i + 2, mDatabase.getTotalChangedRowsCount());
+                    assertEquals(1, mDatabase.getLastChangedRowCount());
+                    assertEquals(i + 2, mDatabase.getTotalChangedRowCount());
                 }
             }
             mDatabase.setTransactionSuccessful();
@@ -205,8 +205,8 @@
                     assertFalse(r);
                     s.reset();
                     assertEquals(size + i + 1, mDatabase.getLastInsertRowId());
-                    assertEquals(1, mDatabase.getLastChangedRowsCount());
-                    assertEquals(size + i + 2, mDatabase.getTotalChangedRowsCount());
+                    assertEquals(1, mDatabase.getLastChangedRowCount());
+                    assertEquals(size + i + 2, mDatabase.getTotalChangedRowCount());
                 }
             }
             mDatabase.setTransactionSuccessful();
@@ -214,4 +214,21 @@
             mDatabase.endTransaction();
         }
     }
+
+    @Test
+    public void testAutomaticCountersOutsideTransactions() {
+        try {
+            mDatabase.getLastChangedRowCount();
+            fail("getLastChangedRowCount() succeeded outside a transaction");
+        } catch (IllegalStateException e) {
+            // This exception is expected.
+        }
+
+        try {
+            mDatabase.getTotalChangedRowCount();
+            fail("getTotalChangedRowCount() succeeded outside a transaction");
+        } catch (IllegalStateException e) {
+            // This exception is expected.
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS
index f2d6ff8..8b333f3 100644
--- a/core/tests/coretests/src/android/os/OWNERS
+++ b/core/tests/coretests/src/android/os/OWNERS
@@ -5,4 +5,7 @@
 per-file *Vibrat*.java = file:/services/core/java/com/android/server/vibrator/OWNERS
 
 # Power
-per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
\ No newline at end of file
+per-file PowerManager*.java = michaelwr@google.com, santoscordon@google.com
+
+# PerformanceHintManager
+per-file PerformanceHintManagerTest.java = file:/ADPF_OWNERS
diff --git a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
index 2c03fdc..20ba427 100644
--- a/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
+++ b/core/tests/coretests/src/android/os/PerformanceHintManagerTest.java
@@ -69,6 +69,24 @@
     }
 
     @Test
+    public void testCreateHintSession_noTids() {
+        assertThrows(NullPointerException.class, () -> {
+            mPerformanceHintManager.createHintSession(
+                    null, DEFAULT_TARGET_NS);
+        });
+        assertThrows(IllegalArgumentException.class, () -> {
+            mPerformanceHintManager.createHintSession(
+                    new int[]{}, DEFAULT_TARGET_NS);
+        });
+    }
+
+    @Test
+    public void testCreateHintSession_invalidTids() {
+        assertNull(mPerformanceHintManager.createHintSession(
+                new int[]{-1}, DEFAULT_TARGET_NS));
+    }
+
+    @Test
     public void testGetPreferredUpdateRateNanos() {
         if (createSession() != null) {
             assertTrue(mPerformanceHintManager.getPreferredUpdateRateNanos() > 0);
@@ -155,4 +173,13 @@
             session.setThreads(new int[]{-1});
         });
     }
+
+    @Test
+    public void testSetPreferPowerEfficiency() {
+        Session s = createSession();
+        assumeNotNull(s);
+        s.setPreferPowerEfficiency(false);
+        s.setPreferPowerEfficiency(true);
+        s.setPreferPowerEfficiency(true);
+    }
 }
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index 8028b14..f1eef75 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -221,8 +221,10 @@
 
     @Test
     public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() {
+        // The stylus down point is between mTestView1 and  mTestView2, but it is within the
+        // extended handwriting area of both views. It is closer to mTestView1.
         final int x1 = sHwArea1.right + HW_BOUNDS_OFFSETS_RIGHT_PX / 2;
-        final int y1 = sHwArea1.bottom + HW_BOUNDS_OFFSETS_BOTTOM_PX / 2;
+        final int y1 = sHwArea1.bottom + (sHwArea2.top - sHwArea1.bottom) / 3;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent1);
 
@@ -231,10 +233,14 @@
         MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
-        // InputConnection is created after stylus movement.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        // First create InputConnection for mTestView2 and verify that handwriting is not started.
+        mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+        verify(mHandwritingInitiator, never()).startHandwriting(mTestView2);
 
-        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
+        // Next create InputConnection for mTextView1. Handwriting is started for this view since
+        // the stylus down point is closest to this view.
+        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        verify(mHandwritingInitiator).startHandwriting(mTestView1);
     }
 
     @Test
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java
new file mode 100644
index 0000000..2101354
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * An activity for testing the TextView widget.
+ */
+public class HorizontalScrollViewActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_horizontal_scroll_view);
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
new file mode 100644
index 0000000..86f26e5
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.widget;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.PollingCheck;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+@Presubmit
+public class HorizontalScrollViewFunctionalTest {
+    private HorizontalScrollViewActivity mActivity;
+    private HorizontalScrollView mHorizontalScrollView;
+    @Rule
+    public ActivityTestRule<HorizontalScrollViewActivity> mActivityRule = new ActivityTestRule<>(
+            HorizontalScrollViewActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = mActivityRule.getActivity();
+        mHorizontalScrollView = mActivity.findViewById(R.id.horizontal_scroll_view);
+    }
+
+    @Test
+    public void testScrollAfterFlingTop() {
+        mHorizontalScrollView.scrollTo(100, 0);
+        mHorizontalScrollView.fling(-10000);
+        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0);
+        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f);
+        assertEquals(0, mHorizontalScrollView.getScrollX());
+    }
+
+    @Test
+    public void testScrollAfterFlingBottom() {
+        int childWidth = mHorizontalScrollView.getChildAt(0).getWidth();
+        int maxScroll = childWidth - mHorizontalScrollView.getWidth();
+        mHorizontalScrollView.scrollTo(maxScroll - 100, 0);
+        mHorizontalScrollView.fling(10000);
+        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0);
+        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f);
+        assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
+    }
+}
+
diff --git a/core/tests/coretests/src/android/widget/ScrollViewActivity.java b/core/tests/coretests/src/android/widget/ScrollViewActivity.java
new file mode 100644
index 0000000..899d631
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/ScrollViewActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import com.android.frameworks.coretests.R;
+
+/**
+ * An activity for testing the TextView widget.
+ */
+public class ScrollViewActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_scroll_view);
+    }
+}
diff --git a/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
new file mode 100644
index 0000000..a49bb6a
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/ScrollViewFunctionalTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.widget;
+
+import static org.junit.Assert.assertEquals;
+
+import android.platform.test.annotations.Presubmit;
+import android.util.PollingCheck;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+@Presubmit
+public class ScrollViewFunctionalTest {
+    private ScrollViewActivity mActivity;
+    private ScrollView mScrollView;
+    @Rule
+    public ActivityTestRule<ScrollViewActivity> mActivityRule = new ActivityTestRule<>(
+            ScrollViewActivity.class);
+
+    @Before
+    public void setUp() throws Exception {
+        mActivity = mActivityRule.getActivity();
+        mScrollView = mActivity.findViewById(R.id.scroll_view);
+    }
+
+    @Test
+    public void testScrollAfterFlingTop() {
+        mScrollView.scrollTo(0, 100);
+        mScrollView.fling(-10000);
+        PollingCheck.waitFor(() -> mScrollView.mEdgeGlowTop.getDistance() > 0);
+        PollingCheck.waitFor(() -> mScrollView.mEdgeGlowTop.getDistance() == 0f);
+        assertEquals(0, mScrollView.getScrollY());
+    }
+
+    @Test
+    public void testScrollAfterFlingBottom() {
+        int childHeight = mScrollView.getChildAt(0).getHeight();
+        int maxScroll = childHeight - mScrollView.getHeight();
+        mScrollView.scrollTo(0, maxScroll - 100);
+        mScrollView.fling(10000);
+        PollingCheck.waitFor(() -> mScrollView.mEdgeGlowBottom.getDistance() > 0);
+        PollingCheck.waitFor(() -> mScrollView.mEdgeGlowBottom.getDistance() == 0f);
+        assertEquals(maxScroll, mScrollView.getScrollY());
+    }
+}
+
diff --git a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
index 7bd6f05..a21c917 100644
--- a/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
+++ b/core/tests/coretests/src/android/window/WindowTokenClientControllerTest.java
@@ -58,8 +58,6 @@
     @Mock
     private WindowTokenClient mWindowTokenClient;
     @Mock
-    private IBinder mClientToken;
-    @Mock
     private IBinder mWindowToken;
     // Can't mock final class.
     private final Configuration mConfiguration = new Configuration();
@@ -70,7 +68,6 @@
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        doReturn(mClientToken).when(mWindowTokenClient).asBinder();
         mController = spy(WindowTokenClientController.createInstanceForTesting());
         doReturn(mWindowManagerService).when(mController).getWindowManagerService();
         mWindowContextInfo = new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY);
@@ -205,7 +202,7 @@
                 .attachWindowContextToWindowToken(any(), any(), any());
 
         // No invoke if not attached.
-        mController.onWindowContextInfoChanged(mClientToken, mWindowContextInfo);
+        mController.onWindowContextInfoChanged(mWindowTokenClient, mWindowContextInfo);
 
         verify(mWindowTokenClient, never()).onConfigurationChanged(any(), anyInt());
 
@@ -216,7 +213,7 @@
 
         // Invoke onConfigurationChanged when onWindowContextInfoChanged
         mController.onWindowContextInfoChanged(
-                mClientToken, new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY + 1));
+                mWindowTokenClient, new WindowContextInfo(mConfiguration, DEFAULT_DISPLAY + 1));
 
         verify(mWindowTokenClient).onConfigurationChanged(mConfiguration, DEFAULT_DISPLAY + 1);
     }
@@ -227,7 +224,7 @@
                 .attachWindowContextToWindowToken(any(), any(), any());
 
         // No invoke if not attached.
-        mController.onWindowContextWindowRemoved(mClientToken);
+        mController.onWindowContextWindowRemoved(mWindowTokenClient);
 
         verify(mWindowTokenClient, never()).onWindowTokenRemoved();
 
@@ -237,7 +234,7 @@
         verify(mWindowTokenClient, never()).onWindowTokenRemoved();
 
         // Invoke onWindowTokenRemoved when onWindowContextWindowRemoved
-        mController.onWindowContextWindowRemoved(mClientToken);
+        mController.onWindowContextWindowRemoved(mWindowTokenClient);
 
         verify(mWindowTokenClient).onWindowTokenRemoved();
     }
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 2de1230..cd5ec85 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -27,9 +27,7 @@
 import static com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME;
 import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME;
 
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
 import static org.mockito.AdditionalMatchers.aryEq;
@@ -68,7 +66,6 @@
 import android.speech.tts.TextToSpeech;
 import android.speech.tts.Voice;
 import android.test.mock.MockContentResolver;
-import android.text.TextUtils;
 import android.view.Window;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
@@ -76,7 +73,7 @@
 import android.widget.Toast;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController.FrameworkObjectProvider;
@@ -232,7 +229,7 @@
             throws Exception {
         configureNoShortcutService();
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
-        assertFalse(getController().isAccessibilityShortcutAvailable(false));
+        assertThat(getController().isAccessibilityShortcutAvailable(false)).isFalse();
     }
 
     @Test
@@ -240,7 +237,7 @@
             throws Exception {
         configureValidShortcutService();
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
-        assertTrue(getController().isAccessibilityShortcutAvailable(false));
+        assertThat(getController().isAccessibilityShortcutAvailable(false)).isTrue();
     }
 
     @Test
@@ -248,7 +245,7 @@
             throws Exception {
         configureValidShortcutService();
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
-        assertFalse(getController().isAccessibilityShortcutAvailable(true));
+        assertThat(getController().isAccessibilityShortcutAvailable(true)).isFalse();
     }
 
     @Test
@@ -256,7 +253,7 @@
             throws Exception {
         configureValidShortcutService();
         configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
-        assertTrue(getController().isAccessibilityShortcutAvailable(true));
+        assertThat(getController().isAccessibilityShortcutAvailable(true)).isTrue();
     }
 
     @Test
@@ -267,10 +264,14 @@
         configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
         Settings.Secure.putString(
                 mContentResolver, ACCESSIBILITY_SHORTCUT_ON_LOCK_SCREEN, null);
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
-        assertFalse(getController().isAccessibilityShortcutAvailable(true));
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
-        assertTrue(getController().isAccessibilityShortcutAvailable(true));
+        Settings.Secure.putInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+        assertThat(getController().isAccessibilityShortcutAvailable(true)).isFalse();
+        Settings.Secure.putInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.SHOWN);
+        assertThat(getController().isAccessibilityShortcutAvailable(true)).isTrue();
     }
 
     @Test
@@ -281,7 +282,9 @@
         AccessibilityShortcutController accessibilityShortcutController = getController();
         configureNoShortcutService();
         accessibilityShortcutController.onSettingsChanged();
-        assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
+        assertThat(
+                accessibilityShortcutController.isAccessibilityShortcutAvailable(false)
+        ).isFalse();
     }
 
     @Test
@@ -292,7 +295,9 @@
         AccessibilityShortcutController accessibilityShortcutController = getController();
         configureValidShortcutService();
         accessibilityShortcutController.onSettingsChanged();
-        assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
+        assertThat(
+                accessibilityShortcutController.isAccessibilityShortcutAvailable(false)
+        ).isTrue();
     }
 
     @Test
@@ -302,7 +307,9 @@
         AccessibilityShortcutController accessibilityShortcutController = getController();
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         accessibilityShortcutController.onSettingsChanged();
-        assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(false));
+        assertThat(
+                accessibilityShortcutController.isAccessibilityShortcutAvailable(false)
+        ).isTrue();
     }
 
     @Test
@@ -313,7 +320,9 @@
         AccessibilityShortcutController accessibilityShortcutController = getController();
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         accessibilityShortcutController.onSettingsChanged();
-        assertFalse(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
+        assertThat(
+                accessibilityShortcutController.isAccessibilityShortcutAvailable(true)
+        ).isFalse();
     }
 
     @Test
@@ -324,7 +333,9 @@
         AccessibilityShortcutController accessibilityShortcutController = getController();
         configureShortcutEnabled(ENABLED_INCLUDING_LOCK_SCREEN);
         accessibilityShortcutController.onSettingsChanged();
-        assertTrue(accessibilityShortcutController.isAccessibilityShortcutAvailable(true));
+        assertThat(
+                accessibilityShortcutController.isAccessibilityShortcutAvailable(true)
+        ).isTrue();
     }
 
     @Test
@@ -341,11 +352,15 @@
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+        Settings.Secure.putInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
         accessibilityShortcutController.performAccessibilityShortcut();
 
-        assertEquals(1, Settings.Secure.getInt(
-                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0));
+        assertThat(Settings.Secure.getInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN)).isEqualTo(
+                AccessibilityShortcutController.DialogStatus.SHOWN);
         verify(mResources).getString(
                 R.string.accessibility_shortcut_single_service_warning_title, PACKAGE_NAME_STRING);
         verify(mAlertDialog).show();
@@ -357,11 +372,12 @@
 
     @Test
     public void testOnAccessibilityShortcut_withDialogShowing_callsServer()
-        throws Exception {
+            throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
         accessibilityShortcutController.performAccessibilityShortcut();
         accessibilityShortcutController.performAccessibilityShortcut();
         verify(mToast).show();
@@ -374,11 +390,12 @@
 
     @Test
     public void testOnAccessibilityShortcut_ifCanceledFirstTime_showsWarningDialog()
-        throws Exception {
+            throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
         AccessibilityShortcutController accessibilityShortcutController = getController();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
         accessibilityShortcutController.performAccessibilityShortcut();
         ArgumentCaptor<AlertDialog.OnCancelListener> cancelListenerCaptor =
                 ArgumentCaptor.forClass(AlertDialog.OnCancelListener.class);
@@ -394,49 +411,98 @@
     public void testClickingDisableButtonInDialog_shouldClearShortcutId() throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
         getController().performAccessibilityShortcut();
 
         ArgumentCaptor<DialogInterface.OnClickListener> captor =
                 ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
         verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
                 captor.capture());
-        // Call the button callback, if one exists
-        if (captor.getValue() != null) {
-            captor.getValue().onClick(null, 0);
-        }
-        assertTrue(TextUtils.isEmpty(
-                Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)));
-        assertEquals(0, Settings.Secure.getInt(
-                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
+        captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(
+                Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)
+        ).isEmpty();
+        assertThat(Settings.Secure.getInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
     }
 
     @Test
     public void testClickingTurnOnButtonInDialog_shouldLeaveShortcutReady() throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
         getController().performAccessibilityShortcut();
 
         ArgumentCaptor<DialogInterface.OnClickListener> captor =
-            ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
         verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on),
                 captor.capture());
-        // Call the button callback, if one exists
-        if (captor.getValue() != null) {
-            captor.getValue().onClick(null, 0);
-        }
-        assertEquals(SERVICE_NAME_STRING,
-                Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE));
-        assertEquals(1, Settings.Secure.getInt(
-                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN));
+        captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+        assertThat(
+                Settings.Secure.getString(mContentResolver, ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)
+        ).isEqualTo(SERVICE_NAME_STRING);
+        assertThat(Settings.Secure.getInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+                AccessibilityShortcutController.DialogStatus.SHOWN);
+    }
+
+    @Test
+    public void testTurnOnDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOn()
+            throws Exception {
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureDefaultAccessibilityService();
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+        getController().performAccessibilityShortcut();
+
+        ArgumentCaptor<DialogInterface.OnClickListener> captor =
+                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+        verify(mAlertDialogBuilder).setNegativeButton(eq(R.string.accessibility_shortcut_on),
+                captor.capture());
+        captor.getValue().onClick(null, DialogInterface.BUTTON_NEGATIVE);
+
+        assertThat(
+                Settings.Secure.getString(mContentResolver,
+                        ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEqualTo(SERVICE_NAME_STRING);
+        assertThat(Settings.Secure.getInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+                AccessibilityShortcutController.DialogStatus.SHOWN);
+    }
+
+    @Test
+    public void testTurnOffDefaultA11yServiceInDialog_defaultServiceShortcutTurnsOff()
+            throws Exception {
+        configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
+        configureDefaultAccessibilityService();
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
+        getController().performAccessibilityShortcut();
+
+        ArgumentCaptor<DialogInterface.OnClickListener> captor =
+                ArgumentCaptor.forClass(DialogInterface.OnClickListener.class);
+        verify(mAlertDialogBuilder).setPositiveButton(eq(R.string.accessibility_shortcut_off),
+                captor.capture());
+        captor.getValue().onClick(null, DialogInterface.BUTTON_POSITIVE);
+
+        assertThat(
+                Settings.Secure.getString(mContentResolver,
+                        ACCESSIBILITY_SHORTCUT_TARGET_SERVICE)).isEmpty();
+        assertThat(Settings.Secure.getInt(
+                mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN)).isEqualTo(
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
     }
 
     @Test
     public void testOnAccessibilityShortcut_afterDialogShown_shouldCallServer() throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.SHOWN);
         getController().performAccessibilityShortcut();
 
         verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
@@ -464,10 +530,10 @@
                 frameworkFeatureMap =
                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
 
-        assertTrue(frameworkFeatureMap.containsKey(COLOR_INVERSION_COMPONENT_NAME));
-        assertTrue(frameworkFeatureMap.containsKey(DALTONIZER_COMPONENT_NAME));
-        assertTrue(frameworkFeatureMap.containsKey(REDUCE_BRIGHT_COLORS_COMPONENT_NAME));
-        assertTrue(frameworkFeatureMap.containsKey(ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME));
+        assertThat(frameworkFeatureMap).containsKey(COLOR_INVERSION_COMPONENT_NAME);
+        assertThat(frameworkFeatureMap).containsKey(DALTONIZER_COMPONENT_NAME);
+        assertThat(frameworkFeatureMap).containsKey(REDUCE_BRIGHT_COLORS_COMPONENT_NAME);
+        assertThat(frameworkFeatureMap).containsKey(ACCESSIBILITY_HEARING_AIDS_COMPONENT_NAME);
     }
 
     @Test
@@ -478,7 +544,7 @@
                 frameworkFeatureMap =
                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
 
-        assertTrue(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME));
+        assertThat(frameworkFeatureMap).containsKey(ONE_HANDED_COMPONENT_NAME);
     }
 
     @Test
@@ -489,7 +555,7 @@
                 frameworkFeatureMap =
                 AccessibilityShortcutController.getFrameworkShortcutFeaturesMap();
 
-        assertFalse(frameworkFeatureMap.containsKey(ONE_HANDED_COMPONENT_NAME));
+        assertThat(frameworkFeatureMap).doesNotContainKey(ONE_HANDED_COMPONENT_NAME);
     }
 
     @Test
@@ -498,7 +564,8 @@
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureValidShortcutService();
         when(mServiceInfo.loadSummary(any())).thenReturn(null);
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.SHOWN);
         getController().performAccessibilityShortcut();
         verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
     }
@@ -508,7 +575,8 @@
             throws Exception {
         configureShortcutEnabled(ENABLED_EXCEPT_LOCK_SCREEN);
         configureFirstFrameworkFeature();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.SHOWN);
         getController().performAccessibilityShortcut();
 
         verifyZeroInteractions(mToast);
@@ -523,7 +591,8 @@
         configureApplicationTargetSdkVersion(Build.VERSION_CODES.R);
         configureRequestAccessibilityButton();
         configureEnabledService();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.SHOWN);
         getController().performAccessibilityShortcut();
 
         verifyZeroInteractions(mToast);
@@ -538,7 +607,8 @@
         configureTtsSpokenPromptEnabled();
         configureHandlerCallbackInvocation();
         AccessibilityShortcutController accessibilityShortcutController = getController();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
         accessibilityShortcutController.performAccessibilityShortcut();
 
         verify(mAlertDialog).show();
@@ -563,7 +633,8 @@
         configureTtsSpokenPromptEnabled();
         configureHandlerCallbackInvocation();
         AccessibilityShortcutController accessibilityShortcutController = getController();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
         accessibilityShortcutController.performAccessibilityShortcut();
 
         verify(mAlertDialog).show();
@@ -583,7 +654,8 @@
         configureTtsSpokenPromptEnabled();
         configureHandlerCallbackInvocation();
         AccessibilityShortcutController accessibilityShortcutController = getController();
-        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 0);
+        Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN,
+                AccessibilityShortcutController.DialogStatus.NOT_SHOWN);
         Set<String> features = new HashSet<>();
         features.add(TextToSpeech.Engine.KEY_FEATURE_NOT_INSTALLED);
         doReturn(features, Collections.emptySet()).when(mVoice).getFeatures();
@@ -682,4 +754,13 @@
         accessibilityShortcutController.mFrameworkObjectProvider = mFrameworkObjectProvider;
         return accessibilityShortcutController;
     }
+
+    private void configureDefaultAccessibilityService() throws Exception {
+        when(mAccessibilityManagerService
+                .getAccessibilityShortcutTargets(ACCESSIBILITY_SHORTCUT_KEY))
+                .thenReturn(Collections.singletonList(SERVICE_NAME_STRING));
+
+        when(mResources.getString(R.string.config_defaultAccessibilityService)).thenReturn(
+                SERVICE_NAME_STRING);
+    }
 }
diff --git a/core/tests/packagemonitortests/Android.bp b/core/tests/packagemonitortests/Android.bp
index 453b476..7b5d7df 100644
--- a/core/tests/packagemonitortests/Android.bp
+++ b/core/tests/packagemonitortests/Android.bp
@@ -24,6 +24,9 @@
 android_test {
     name: "FrameworksCorePackageMonitorTests",
     srcs: ["src/**/*.java"],
+    exclude_srcs: [
+        "src/com/android/internal/content/withoutpermission/PackageMonitorPermissionTest.java",
+    ],
     static_libs: [
         "androidx.test.runner",
         "compatibility-device-util-axt",
@@ -39,3 +42,19 @@
         ":TestVisibilityApp",
     ],
 }
+
+android_test {
+    name: "FrameworksCorePackageMonitorWithoutPermissionTests",
+    srcs: ["src/com/android/internal/content/withoutpermission/PackageMonitorPermissionTest.java"],
+    manifest: "AndroidManifestNoPermission.xml",
+    static_libs: [
+        "androidx.test.runner",
+        "compatibility-device-util-axt",
+        "frameworks-base-testutils",
+    ],
+    libs: ["android.test.runner"],
+    platform_apis: true,
+    certificate: "platform",
+    test_suites: ["device-tests"],
+    test_config: "AndroidTestWithoutPermission.xml",
+}
diff --git a/core/tests/packagemonitortests/AndroidManifestNoPermission.xml b/core/tests/packagemonitortests/AndroidManifestNoPermission.xml
new file mode 100644
index 0000000..5d6308a
--- /dev/null
+++ b/core/tests/packagemonitortests/AndroidManifestNoPermission.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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.frameworks.packagemonitor.withoutpermission">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+            android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.frameworks.packagemonitor.withoutpermission"
+            android:label="Frameworks PackageMonitor Core without Permission Tests" />
+</manifest>
diff --git a/core/tests/packagemonitortests/AndroidTestWithoutPermission.xml b/core/tests/packagemonitortests/AndroidTestWithoutPermission.xml
new file mode 100644
index 0000000..37a6a2f
--- /dev/null
+++ b/core/tests/packagemonitortests/AndroidTestWithoutPermission.xml
@@ -0,0 +1,33 @@
+<?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.
+  -->
+
+<configuration description="Runs Frameworks Core Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="FrameworksCorePackageMonitorWithoutPermissionTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="FrameworksCorePackageMonitorWithoutPermissionTests" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.frameworks.packagemonitor.withoutpermission" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+</configuration>
diff --git a/core/tests/packagemonitortests/src/com/android/internal/content/withoutpermission/PackageMonitorPermissionTest.java b/core/tests/packagemonitortests/src/com/android/internal/content/withoutpermission/PackageMonitorPermissionTest.java
new file mode 100644
index 0000000..659c0c2
--- /dev/null
+++ b/core/tests/packagemonitortests/src/com/android/internal/content/withoutpermission/PackageMonitorPermissionTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.content.withoutpermission;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.content.PackageMonitor;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * A test to verify PackageMonitor implementation without INTERACT_ACROSS_USERS_FULL permission.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PackageMonitorPermissionTest {
+
+    @Test
+    public void testPackageMonitorNoCrossUserPermission() throws Exception {
+        TestVisibilityPackageMonitor testPackageMonitor = new TestVisibilityPackageMonitor();
+
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        assertThrows(SecurityException.class,
+                () -> testPackageMonitor.register(context, UserHandle.ALL,
+                        new Handler(Looper.getMainLooper())));
+    }
+
+    private static class TestVisibilityPackageMonitor extends PackageMonitor {
+    }
+}
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index e278c52..dcc9686 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -37,6 +37,7 @@
         <permission name="android.permission.MANAGE_USER_OEM_UNLOCK_STATE" />
         <permission name="android.permission.MASTER_CLEAR"/>
         <permission name="android.permission.MEDIA_CONTENT_CONTROL"/>
+        <permission name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED" />
         <permission name="android.permission.MODIFY_PHONE_STATE"/>
         <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
         <permission name="android.permission.MOVE_PACKAGE"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 87e6b18..6057852 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -691,6 +691,12 @@
       "group": "WM_DEBUG_WINDOW_TRANSITIONS",
       "at": "com\/android\/server\/wm\/Transition.java"
     },
+    "-1449515133": {
+      "message": "Content Recording: stopping active projection for display %d",
+      "level": "ERROR",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-1443029505": {
       "message": "SAFE MODE ENABLED (menu=%d s=%d dpad=%d trackball=%d)",
       "level": "INFO",
@@ -1285,6 +1291,12 @@
       "group": "WM_DEBUG_STATES",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
+    "-921346089": {
+      "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection for display %d: %s",
+      "level": "ERROR",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-917215012": {
       "message": "%s: caller %d is using old GET_TASKS but privileged; allowing",
       "level": "WARN",
@@ -2233,12 +2245,6 @@
       "group": "WM_DEBUG_CONFIGURATION",
       "at": "com\/android\/server\/wm\/ActivityRecord.java"
     },
-    "-88873335": {
-      "message": "Content Recording: Unable to tell MediaProjectionManagerService to stop the active projection: %s",
-      "level": "ERROR",
-      "group": "WM_DEBUG_CONTENT_RECORDING",
-      "at": "com\/android\/server\/wm\/ContentRecorder.java"
-    },
     "-87705714": {
       "message": "findFocusedWindow: focusedApp=null using new focus @ %s",
       "level": "VERBOSE",
diff --git a/graphics/TEST_MAPPING b/graphics/TEST_MAPPING
index abeaf19..8afc30d 100644
--- a/graphics/TEST_MAPPING
+++ b/graphics/TEST_MAPPING
@@ -7,6 +7,18 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "CtsTextTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ],
+      "file_patterns": ["(/|^)Typeface\\.java", "(/|^)Paint\\.java"]
     }
   ]
 }
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 99bebb8..a2319a5 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -199,6 +199,8 @@
 
     private static final float[] SRGB_PRIMARIES = { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f };
     private static final float[] NTSC_1953_PRIMARIES = { 0.67f, 0.33f, 0.21f, 0.71f, 0.14f, 0.08f };
+    private static final float[] DCI_P3_PRIMARIES =
+            { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f };
     private static final float[] BT2020_PRIMARIES =
             { 0.708f, 0.292f, 0.170f, 0.797f, 0.131f, 0.046f };
     /**
@@ -211,6 +213,9 @@
     private static final Rgb.TransferParameters SRGB_TRANSFER_PARAMETERS =
             new Rgb.TransferParameters(1 / 1.055, 0.055 / 1.055, 1 / 12.92, 0.04045, 2.4);
 
+    private static final Rgb.TransferParameters SMPTE_170M_TRANSFER_PARAMETERS =
+            new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45);
+
     // HLG transfer with an SDR whitepoint of 203 nits
     private static final Rgb.TransferParameters BT2020_HLG_TRANSFER_PARAMETERS =
             new Rgb.TransferParameters(2.0, 2.0, 1 / 0.17883277, 0.28466892, 0.55991073,
@@ -1559,10 +1564,10 @@
                 DataSpace.DATASPACE_SCRGB_LINEAR, Named.LINEAR_EXTENDED_SRGB.ordinal());
         sNamedColorSpaces[Named.BT709.ordinal()] = new ColorSpace.Rgb(
                 "Rec. ITU-R BT.709-5",
-                new float[] { 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
+                SRGB_PRIMARIES,
                 ILLUMINANT_D65,
                 null,
-                new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
+                SMPTE_170M_TRANSFER_PARAMETERS,
                 Named.BT709.ordinal()
         );
         sDataToColorSpaces.put(DataSpace.DATASPACE_BT709, Named.BT709.ordinal());
@@ -1577,7 +1582,7 @@
         sDataToColorSpaces.put(DataSpace.DATASPACE_BT2020, Named.BT2020.ordinal());
         sNamedColorSpaces[Named.DCI_P3.ordinal()] = new ColorSpace.Rgb(
                 "SMPTE RP 431-2-2007 DCI (P3)",
-                new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
+                DCI_P3_PRIMARIES,
                 new float[] { 0.314f, 0.351f },
                 2.6,
                 0.0f, 1.0f,
@@ -1586,7 +1591,7 @@
         sDataToColorSpaces.put(DataSpace.DATASPACE_DCI_P3, Named.DCI_P3.ordinal());
         sNamedColorSpaces[Named.DISPLAY_P3.ordinal()] = new ColorSpace.Rgb(
                 "Display P3",
-                new float[] { 0.680f, 0.320f, 0.265f, 0.690f, 0.150f, 0.060f },
+                DCI_P3_PRIMARIES,
                 ILLUMINANT_D65,
                 null,
                 SRGB_TRANSFER_PARAMETERS,
@@ -1598,7 +1603,7 @@
                 NTSC_1953_PRIMARIES,
                 ILLUMINANT_C,
                 null,
-                new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
+                SMPTE_170M_TRANSFER_PARAMETERS,
                 Named.NTSC_1953.ordinal()
         );
         sNamedColorSpaces[Named.SMPTE_C.ordinal()] = new ColorSpace.Rgb(
@@ -1606,7 +1611,7 @@
                 new float[] { 0.630f, 0.340f, 0.310f, 0.595f, 0.155f, 0.070f },
                 ILLUMINANT_D65,
                 null,
-                new Rgb.TransferParameters(1 / 1.099, 0.099 / 1.099, 1 / 4.5, 0.081, 1 / 0.45),
+                SMPTE_170M_TRANSFER_PARAMETERS,
                 Named.SMPTE_C.ordinal()
         );
         sNamedColorSpaces[Named.ADOBE_RGB.ordinal()] = new ColorSpace.Rgb(
@@ -3057,7 +3062,7 @@
          * primaries for such a ColorSpace does not make sense, so we use a special
          * set of primaries that are all 1s.</p>
          *
-         * @return A new non-null array of 2 floats
+         * @return A new non-null array of 6 floats
          *
          * @see #getPrimaries(float[])
          */
diff --git a/graphics/java/android/graphics/TEST_MAPPING b/graphics/java/android/graphics/TEST_MAPPING
new file mode 100644
index 0000000..df91222
--- /dev/null
+++ b/graphics/java/android/graphics/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTextTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ],
+      "file_patterns": [
+        "Typeface\\.java",
+        "Paint\\.java",
+        "[^/]*Canvas\\.java",
+        "[^/]*Font[^/]*\\.java"
+      ]
+    }
+  ]
+}
diff --git a/graphics/java/android/graphics/fonts/TEST_MAPPING b/graphics/java/android/graphics/fonts/TEST_MAPPING
new file mode 100644
index 0000000..99cbfe7
--- /dev/null
+++ b/graphics/java/android/graphics/fonts/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTextTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/graphics/java/android/graphics/text/TEST_MAPPING b/graphics/java/android/graphics/text/TEST_MAPPING
new file mode 100644
index 0000000..99cbfe7
--- /dev/null
+++ b/graphics/java/android/graphics/text/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTextTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.LargeTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
index 25f5dec..b4d8def 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java
@@ -36,6 +36,7 @@
 import android.security.keystore.SecureKeyImportUnavailableException;
 import android.security.keystore.WrappedKeyEntry;
 import android.system.keystore2.AuthenticatorSpec;
+import android.system.keystore2.Authorization;
 import android.system.keystore2.Domain;
 import android.system.keystore2.IKeystoreSecurityLevel;
 import android.system.keystore2.KeyDescriptor;
@@ -966,6 +967,32 @@
             authenticatorSpecs.add(authSpec);
         }
 
+        if (parts.length > 2) {
+            @KeyProperties.EncryptionPaddingEnum int padding =
+                    KeyProperties.EncryptionPadding.toKeymaster(parts[2]);
+            if (padding == KeymasterDefs.KM_PAD_RSA_OAEP
+                    && response.metadata != null
+                    && response.metadata.authorizations != null) {
+                Authorization[] keyCharacteristics = response.metadata.authorizations;
+
+                for (Authorization authorization : keyCharacteristics) {
+                    // Add default MGF1 digest SHA-1
+                    // when wrapping key has KM_TAG_RSA_OAEP_MGF_DIGEST tag
+                    if (authorization.keyParameter.tag
+                            == KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST) {
+                        // Default MGF1 digest is SHA-1
+                        // and KeyMint only supports default MGF1 digest crypto operations
+                        // for importWrappedKey.
+                        args.add(KeyStore2ParameterUtils.makeEnum(
+                                KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST,
+                                KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST)
+                        ));
+                        break;
+                    }
+                }
+            }
+        }
+
         try {
             securityLevel.importWrappedKey(
                     wrappedKey, wrappingkey,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
index 381e9d4..08b7bb8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitContainer.java
@@ -25,6 +25,7 @@
 import android.window.WindowContainerTransaction;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.window.extensions.core.util.function.Function;
 
 /**
@@ -186,10 +187,21 @@
         return (mSplitRule instanceof SplitPlaceholderRule);
     }
 
-    @NonNull
-    SplitInfo toSplitInfo() {
-        return new SplitInfo(mPrimaryContainer.toActivityStack(),
-                mSecondaryContainer.toActivityStack(), mCurrentSplitAttributes, mToken);
+    /**
+     * Returns the SplitInfo representing this container.
+     *
+     * @return the SplitInfo representing this container if the underlying TaskFragmentContainers
+     * are stable, or {@code null} if any TaskFragmentContainer is in an intermediate state.
+     */
+    @Nullable
+    SplitInfo toSplitInfoIfStable() {
+        final ActivityStack primaryActivityStack = mPrimaryContainer.toActivityStackIfStable();
+        final ActivityStack secondaryActivityStack = mSecondaryContainer.toActivityStackIfStable();
+        if (primaryActivityStack == null || secondaryActivityStack == null) {
+            return null;
+        }
+        return new SplitInfo(primaryActivityStack, secondaryActivityStack,
+                mCurrentSplitAttributes, mToken);
     }
 
     static boolean shouldFinishPrimaryWithSecondary(@NonNull SplitRule splitRule) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index f95f3ff..079cfa3 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -831,7 +831,8 @@
             return true;
         }
 
-        if (!isOnReparent && getContainerWithActivity(activity) == null
+        final TaskFragmentContainer container = getContainerWithActivity(activity);
+        if (!isOnReparent && container == null
                 && getTaskFragmentTokenFromActivityClientRecord(activity) != null) {
             // We can't find the new launched activity in any recorded container, but it is
             // currently placed in an embedded TaskFragment. This can happen in two cases:
@@ -843,11 +844,21 @@
             return true;
         }
 
-        final TaskFragmentContainer container = getContainerWithActivity(activity);
-        if (!isOnReparent && container != null
-                && container.getTaskContainer().getTopNonFinishingTaskFragmentContainer()
+        // Skip resolving if the activity is on a pinned TaskFragmentContainer.
+        // TODO(b/243518738): skip resolving for overlay container.
+        if (container != null) {
+            final TaskContainer taskContainer = container.getTaskContainer();
+            if (taskContainer.isTaskFragmentContainerPinned(container)) {
+                return true;
+            }
+        }
+
+        final TaskContainer taskContainer = container != null ? container.getTaskContainer() : null;
+        if (!isOnReparent && taskContainer != null
+                && taskContainer.getTopNonFinishingTaskFragmentContainer(false /* includePin */)
                         != container) {
-            // Do not resolve if the launched activity is not the top-most container in the Task.
+            // Do not resolve if the launched activity is not the top-most container (excludes
+            // the pinned container) in the Task.
             return true;
         }
 
@@ -1244,6 +1255,19 @@
     @GuardedBy("mLock")
     TaskFragmentContainer resolveStartActivityIntent(@NonNull WindowContainerTransaction wct,
             int taskId, @NonNull Intent intent, @Nullable Activity launchingActivity) {
+        // Skip resolving if started from pinned TaskFragmentContainer.
+        // TODO(b/243518738): skip resolving for overlay container.
+        if (launchingActivity != null) {
+            final TaskFragmentContainer taskFragmentContainer = getContainerWithActivity(
+                    launchingActivity);
+            final TaskContainer taskContainer =
+                    taskFragmentContainer != null ? taskFragmentContainer.getTaskContainer() : null;
+            if (taskContainer != null && taskContainer.isTaskFragmentContainerPinned(
+                    taskFragmentContainer)) {
+                return null;
+            }
+        }
+
         /*
          * We will check the following to see if there is any embedding rule matched:
          * 1. Whether the new activity intent should always expand.
@@ -1584,6 +1608,13 @@
             return;
         }
 
+        // If the secondary container is pinned, it should not be removed.
+        final SplitContainer activeContainer =
+                getActiveSplitForContainer(existingSplitContainer.getSecondaryContainer());
+        if (activeContainer instanceof SplitPinContainer) {
+            return;
+        }
+
         existingSplitContainer.getSecondaryContainer().finish(
                 false /* shouldFinishDependent */, mPresenter, wct, this);
     }
@@ -1625,12 +1656,7 @@
             // background.
             return;
         }
-        final SplitContainer splitContainer = getActiveSplitForContainer(container);
-        if (splitContainer instanceof SplitPinContainer
-                && updateSplitContainerIfNeeded(splitContainer, wct, null /* splitAttributes */)) {
-            // A SplitPinContainer exists and is updated.
-            return;
-        }
+
         if (launchPlaceholderIfNecessary(wct, container)) {
             // Placeholder was launched, the positions will be updated when the activity is added
             // to the secondary container.
@@ -1643,6 +1669,7 @@
             // If the info is not available yet the task fragment will be expanded when it's ready
             return;
         }
+        final SplitContainer splitContainer = getActiveSplitForContainer(container);
         if (splitContainer == null) {
             return;
         }
@@ -1826,6 +1853,10 @@
             // Don't launch placeholder for primary split container.
             return false;
         }
+        if (splitContainer instanceof SplitPinContainer) {
+            // Don't launch placeholder if pinned
+            return false;
+        }
         return true;
     }
 
@@ -1908,8 +1939,8 @@
         if (mEmbeddingCallback == null || !readyToReportToClient()) {
             return;
         }
-        final List<SplitInfo> currentSplitStates = getActiveSplitStates();
-        if (mLastReportedSplitStates.equals(currentSplitStates)) {
+        final List<SplitInfo> currentSplitStates = getActiveSplitStatesIfStable();
+        if (currentSplitStates == null || mLastReportedSplitStates.equals(currentSplitStates)) {
             return;
         }
         mLastReportedSplitStates.clear();
@@ -1919,13 +1950,21 @@
 
     /**
      * Returns a list of descriptors for currently active split states.
+     *
+     * @return a list of descriptors for currently active split states if all the containers are in
+     * a stable state, or {@code null} otherwise.
      */
     @GuardedBy("mLock")
-    @NonNull
-    private List<SplitInfo> getActiveSplitStates() {
+    @Nullable
+    private List<SplitInfo> getActiveSplitStatesIfStable() {
         final List<SplitInfo> splitStates = new ArrayList<>();
         for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
-            mTaskContainers.valueAt(i).getSplitStates(splitStates);
+            final List<SplitInfo> taskSplitStates =
+                    mTaskContainers.valueAt(i).getSplitStatesIfStable();
+            if (taskSplitStates == null) {
+                return null;
+            }
+            splitStates.addAll(taskSplitStates);
         }
         return splitStates;
     }
@@ -2072,8 +2111,9 @@
      * Returns {@code true} if an Activity with the provided component name should always be
      * expanded to occupy full task bounds. Such activity must not be put in a split.
      */
+    @VisibleForTesting
     @GuardedBy("mLock")
-    private boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
+    boolean shouldExpand(@Nullable Activity activity, @Nullable Intent intent) {
         for (EmbeddingRule rule : mSplitRules) {
             if (!(rule instanceof ActivityRule)) {
                 continue;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 969e3ed..16d8cb4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -179,8 +179,16 @@
 
     @Nullable
     TaskFragmentContainer getTopNonFinishingTaskFragmentContainer() {
+        return getTopNonFinishingTaskFragmentContainer(true /* includePin */);
+    }
+
+    @Nullable
+    TaskFragmentContainer getTopNonFinishingTaskFragmentContainer(boolean includePin) {
         for (int i = mContainers.size() - 1; i >= 0; i--) {
             final TaskFragmentContainer container = mContainers.get(i);
+            if (!includePin && isTaskFragmentContainerPinned(container)) {
+                continue;
+            }
             if (!container.isFinished()) {
                 return container;
             }
@@ -266,6 +274,11 @@
         return mSplitPinContainer;
     }
 
+    boolean isTaskFragmentContainerPinned(@NonNull TaskFragmentContainer taskFragmentContainer) {
+        return mSplitPinContainer != null
+                && mSplitPinContainer.getSecondaryContainer() == taskFragmentContainer;
+    }
+
     void addTaskFragmentContainer(@NonNull TaskFragmentContainer taskFragmentContainer) {
         mContainers.add(taskFragmentContainer);
         onTaskFragmentContainerUpdated();
@@ -329,11 +342,23 @@
         }
     }
 
-    /** Adds the descriptors of split states in this Task to {@code outSplitStates}. */
-    void getSplitStates(@NonNull List<SplitInfo> outSplitStates) {
+    /**
+     * Gets the descriptors of split states in this Task.
+     *
+     * @return a list of {@code SplitInfo} if all the SplitContainers are stable, or {@code null} if
+     * any SplitContainer is in an intermediate state.
+     */
+    @Nullable
+    List<SplitInfo> getSplitStatesIfStable() {
+        final List<SplitInfo> splitStates = new ArrayList<>();
         for (SplitContainer container : mSplitContainers) {
-            outSplitStates.add(container.toSplitInfo());
+            final SplitInfo splitInfo = container.toSplitInfoIfStable();
+            if (splitInfo == null) {
+                return null;
+            }
+            splitStates.add(splitInfo);
         }
+        return splitStates;
     }
 
     /** A wrapper class which contains the information of {@link TaskContainer} */
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index 61df335..0a694b5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -217,6 +217,30 @@
     /** List of non-finishing activities that belong to this container and live in this process. */
     @NonNull
     List<Activity> collectNonFinishingActivities() {
+        final List<Activity> activities = collectNonFinishingActivities(false /* checkIfStable */);
+        if (activities == null) {
+            throw new IllegalStateException(
+                    "Result activities should never be null when checkIfstable is false.");
+        }
+        return activities;
+    }
+
+    /**
+     * Collects non-finishing activities that belong to this container and live in this process.
+     *
+     * @param checkIfStable if {@code true}, returns {@code null} when the container is in an
+     *                      intermediate state.
+     * @return List of non-finishing activities that belong to this container and live in this
+     * process, {@code null} if checkIfStable is {@code true} and the container is in an
+     * intermediate state.
+     */
+    @Nullable
+    List<Activity> collectNonFinishingActivities(boolean checkIfStable) {
+        if (checkIfStable
+                && (mInfo == null || mInfo.isEmpty() || !mPendingAppearedActivities.isEmpty())) {
+            return null;
+        }
+
         final List<Activity> allActivities = new ArrayList<>();
         if (mInfo != null) {
             // Add activities reported from the server.
@@ -224,6 +248,15 @@
                 final Activity activity = mController.getActivity(token);
                 if (activity != null && !activity.isFinishing()) {
                     allActivities.add(activity);
+                } else {
+                    if (checkIfStable) {
+                        // Return null except for a special case when the activity is started in
+                        // background.
+                        if (activity == null && !mTaskContainer.isVisible()) {
+                            continue;
+                        }
+                        return null;
+                    }
                 }
             }
         }
@@ -277,9 +310,19 @@
         return false;
     }
 
-    @NonNull
-    ActivityStack toActivityStack() {
-        return new ActivityStack(collectNonFinishingActivities(), isEmpty(), mToken);
+    /**
+     * Returns the ActivityStack representing this container.
+     *
+     * @return ActivityStack representing this container if it is in a stable state. {@code null} if
+     * in an intermediate state.
+     */
+    @Nullable
+    ActivityStack toActivityStackIfStable() {
+        final List<Activity> activities = collectNonFinishingActivities(true /* checkIfStable */);
+        if (activities == null) {
+            return null;
+        }
+        return new ActivityStack(activities, isEmpty(), mToken);
     }
 
     /** Adds the activity that will be reparented to this container. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 9af1fe91..d440a3e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -595,6 +595,18 @@
     }
 
     @Test
+    public void testResolveStartActivityIntent_skipIfPinned() {
+        final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+        final TaskContainer taskContainer = container.getTaskContainer();
+        spyOn(taskContainer);
+        final Intent intent = new Intent();
+        setupSplitRule(mActivity, intent);
+        doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(container);
+        assertNull(mSplitController.resolveStartActivityIntent(mTransaction, TASK_ID, intent,
+                mActivity));
+    }
+
+    @Test
     public void testPlaceActivityInTopContainer() {
         mSplitController.placeActivityInTopContainer(mTransaction, mActivity);
 
@@ -1044,6 +1056,29 @@
     }
 
     @Test
+    public void testResolveActivityToContainer_skipIfNonTopOrPinned() {
+        final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+        final Activity pinnedActivity = createMockActivity();
+        final TaskFragmentContainer topContainer = mSplitController.newContainer(pinnedActivity,
+                TASK_ID);
+        final TaskContainer taskContainer = container.getTaskContainer();
+        spyOn(taskContainer);
+        doReturn(container).when(taskContainer).getTopNonFinishingTaskFragmentContainer(false);
+        doReturn(true).when(taskContainer).isTaskFragmentContainerPinned(topContainer);
+
+        // No need to handle when the new launched activity is in a pinned TaskFragment.
+        assertTrue(mSplitController.resolveActivityToContainer(mTransaction, pinnedActivity,
+                false /* isOnReparent */));
+        verify(mSplitController, never()).shouldExpand(any(), any());
+
+        // Should proceed to resolve if the new launched activity is in the next top TaskFragment
+        // (e.g. the top-most TaskFragment is pinned)
+        mSplitController.resolveActivityToContainer(mTransaction, mActivity,
+                false /* isOnReparent */);
+        verify(mSplitController).shouldExpand(any(), any());
+    }
+
+    @Test
     public void testGetPlaceholderOptions() {
         // Setup to make sure a transaction record is started.
         mTransactionManager.startNewTransaction();
@@ -1251,6 +1286,34 @@
     }
 
     @Test
+    public void testSplitInfoCallback_NotReportSplitIfUnstable() {
+        final Activity r0 = createMockActivity();
+        final Activity r1 = createMockActivity();
+        addSplitTaskFragments(r0, r1);
+
+        // Should report new SplitInfo list if stable.
+        mSplitController.updateCallbackIfNecessary();
+        assertEquals(1, mSplitInfos.size());
+
+        // Should not report new SplitInfo list if unstable, e.g. any Activity is finishing.
+        mSplitInfos.clear();
+        final Activity r2 = createMockActivity();
+        final Activity r3 = createMockActivity();
+        doReturn(true).when(r2).isFinishing();
+        addSplitTaskFragments(r2, r3);
+
+        mSplitController.updateCallbackIfNecessary();
+        assertTrue(mSplitInfos.isEmpty());
+
+        // Should report SplitInfo list if it becomes stable again.
+        mSplitInfos.clear();
+        doReturn(false).when(r2).isFinishing();
+
+        mSplitController.updateCallbackIfNecessary();
+        assertEquals(2, mSplitInfos.size());
+    }
+
+    @Test
     public void testSplitInfoCallback_reportSplitInMultipleTasks() {
         final int taskId0 = 1;
         final int taskId1 = 2;
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
index 000c65a..2188996 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskContainerTest.java
@@ -48,6 +48,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
 /**
  * Test class for {@link TaskContainer}.
  *
@@ -165,4 +167,35 @@
         doReturn(activity1).when(tf1).getTopNonFinishingActivity();
         assertEquals(activity1, taskContainer.getTopNonFinishingActivity());
     }
+
+    @Test
+    public void testGetSplitStatesIfStable() {
+        final TaskContainer taskContainer = createTestTaskContainer();
+
+        final SplitContainer splitContainer0 = mock(SplitContainer.class);
+        final SplitContainer splitContainer1 = mock(SplitContainer.class);
+        final SplitInfo splitInfo0 = mock(SplitInfo.class);
+        final SplitInfo splitInfo1 = mock(SplitInfo.class);
+        taskContainer.addSplitContainer(splitContainer0);
+        taskContainer.addSplitContainer(splitContainer1);
+
+        // When all the SplitContainers are stable, getSplitStatesIfStable() returns the list of
+        // SplitInfo representing the SplitContainers.
+        doReturn(splitInfo0).when(splitContainer0).toSplitInfoIfStable();
+        doReturn(splitInfo1).when(splitContainer1).toSplitInfoIfStable();
+
+        List<SplitInfo> splitInfoList = taskContainer.getSplitStatesIfStable();
+
+        assertEquals(2, splitInfoList.size());
+        assertEquals(splitInfo0, splitInfoList.get(0));
+        assertEquals(splitInfo1, splitInfoList.get(1));
+
+        // When any SplitContainer is in an intermediate state, getSplitStatesIfStable() returns
+        // null.
+        doReturn(null).when(splitContainer0).toSplitInfoIfStable();
+
+        splitInfoList = taskContainer.getSplitStatesIfStable();
+
+        assertNull(splitInfoList);
+    }
 }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 78b85e6..cc00a49 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -321,6 +321,32 @@
     }
 
     @Test
+    public void testCollectNonFinishingActivities_checkIfStable() {
+        final TaskContainer taskContainer = createTestTaskContainer();
+        final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+                mIntent, taskContainer, mController, null /* pairedPrimaryContainer */);
+
+        // In case mInfo is null, collectNonFinishingActivities(true) should return null.
+        List<Activity> activities =
+                container.collectNonFinishingActivities(true /* checkIfStable */);
+        assertNull(activities);
+
+        // collectNonFinishingActivities(true) should return proper value when the container is in a
+        // stable state.
+        final List<IBinder> runningActivities = Lists.newArrayList(mActivity.getActivityToken());
+        doReturn(runningActivities).when(mInfo).getActivities();
+        container.setInfo(mTransaction, mInfo);
+        activities = container.collectNonFinishingActivities(true /* checkIfStable */);
+        assertEquals(1, activities.size());
+
+        // In case any activity is finishing, collectNonFinishingActivities(true) should return
+        // null.
+        doReturn(true).when(mActivity).isFinishing();
+        activities = container.collectNonFinishingActivities(true /* checkIfStable */);
+        assertNull(activities);
+    }
+
+    @Test
     public void testAddPendingActivity() {
         final TaskContainer taskContainer = createTestTaskContainer();
         final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
index 57d374b..06ce371 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -186,6 +186,6 @@
         if (callback == null) {
             throw new IllegalStateException("No finish callback found");
         }
-        callback.onTransitionFinished(null /* wct */, null /* wctCB */);
+        callback.onTransitionFinished(null /* wct */);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
index 9bf3b80..42dc19c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -52,12 +52,13 @@
 
     /**
      * Ensures the back animation background color layer is present.
+     *
      * @param startRect The start bounds of the closing target.
      * @param color The background color.
      * @param transaction The animation transaction.
      */
-    void ensureBackground(Rect startRect, int color,
-            @NonNull SurfaceControl.Transaction transaction) {
+    public void ensureBackground(
+            Rect startRect, int color, @NonNull SurfaceControl.Transaction transaction) {
         if (mBackgroundSurface != null) {
             return;
         }
@@ -81,7 +82,12 @@
         mIsRequestingStatusBarAppearance = false;
     }
 
-    void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
+    /**
+     * Remove the back animation background.
+     *
+     * @param transaction The animation transaction.
+     */
+    public void removeBackground(@NonNull SurfaceControl.Transaction transaction) {
         if (mBackgroundSurface == null) {
             return;
         }
@@ -93,11 +99,21 @@
         mIsRequestingStatusBarAppearance = false;
     }
 
+    /**
+     * Attach a {@link StatusBarCustomizer} instance to allow status bar animate with back progress.
+     *
+     * @param customizer The {@link StatusBarCustomizer} to be used.
+     */
     void setStatusBarCustomizer(StatusBarCustomizer customizer) {
         mCustomizer = customizer;
     }
 
-    void onBackProgressed(float progress) {
+    /**
+     * Update back animation background with for the progress.
+     *
+     * @param progress Progress value from {@link android.window.BackProgressAnimator}
+     */
+    public void onBackProgressed(float progress) {
         if (mCustomizer == null || mStartBounds.isEmpty()) {
             return;
         }
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 bb543f2..3790f04 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
@@ -25,6 +25,7 @@
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
 import android.content.ContentResolver;
@@ -43,7 +44,6 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.MathUtils;
-import android.util.SparseArray;
 import android.view.IRemoteAnimationRunner;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
@@ -70,6 +70,7 @@
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
+
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -113,7 +114,11 @@
     private boolean mShouldStartOnNextMoveEvent = false;
     /** @see #setTriggerBack(boolean) */
     private boolean mTriggerBack;
-    private FlingAnimationUtils mFlingAnimationUtils;
+
+    private final FlingAnimationUtils mFlingAnimationUtils;
+
+    /** Registry for the back animations */
+    private final ShellBackAnimationRegistry mShellBackAnimationRegistry;
 
     @Nullable
     private BackNavigationInfo mBackNavigationInfo;
@@ -135,13 +140,9 @@
 
     private final TouchTracker mTouchTracker = new TouchTracker();
 
-    private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
     @Nullable
     private IOnBackInvokedCallback mActiveCallback;
 
-    private CrossActivityAnimation mDefaultActivityAnimation;
-    private CustomizeActivityAnimation mCustomizeActivityAnimation;
-
     @VisibleForTesting
     final RemoteCallback mNavigationObserver = new RemoteCallback(
             new RemoteCallback.OnResultListener() {
@@ -169,10 +170,18 @@
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler backgroundHandler,
             Context context,
-            @NonNull BackAnimationBackground backAnimationBackground) {
-        this(shellInit, shellController, shellExecutor, backgroundHandler,
-                ActivityTaskManager.getService(), context, context.getContentResolver(),
-                backAnimationBackground);
+            @NonNull BackAnimationBackground backAnimationBackground,
+            ShellBackAnimationRegistry shellBackAnimationRegistry) {
+        this(
+                shellInit,
+                shellController,
+                shellExecutor,
+                backgroundHandler,
+                ActivityTaskManager.getService(),
+                context,
+                context.getContentResolver(),
+                backAnimationBackground,
+                shellBackAnimationRegistry);
     }
 
     @VisibleForTesting
@@ -182,8 +191,10 @@
             @NonNull @ShellMainThread ShellExecutor shellExecutor,
             @NonNull @ShellBackgroundThread Handler bgHandler,
             @NonNull IActivityTaskManager activityTaskManager,
-            Context context, ContentResolver contentResolver,
-            @NonNull BackAnimationBackground backAnimationBackground) {
+            Context context,
+            ContentResolver contentResolver,
+            @NonNull BackAnimationBackground backAnimationBackground,
+            ShellBackAnimationRegistry shellBackAnimationRegistry) {
         mShellController = shellController;
         mShellExecutor = shellExecutor;
         mActivityTaskManager = activityTaskManager;
@@ -197,11 +208,7 @@
                 .setMaxLengthSeconds(FLING_MAX_LENGTH_SECONDS)
                 .setSpeedUpFactor(FLING_SPEED_UP_FACTOR)
                 .build();
-    }
-
-    @VisibleForTesting
-    void setEnableUAnimation(boolean enable) {
-        IS_U_ANIMATION_ENABLED = enable;
+        mShellBackAnimationRegistry = shellBackAnimationRegistry;
     }
 
     private void onInit() {
@@ -209,26 +216,6 @@
         createAdapter();
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
                 this::createExternalInterface, this);
-
-        initBackAnimationRunners();
-    }
-
-    private void initBackAnimationRunners() {
-        if (!IS_U_ANIMATION_ENABLED) {
-            return;
-        }
-
-        final CrossTaskBackAnimation crossTaskAnimation =
-                new CrossTaskBackAnimation(mContext, mAnimationBackground);
-        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
-                crossTaskAnimation.mBackAnimationRunner);
-        mDefaultActivityAnimation =
-                new CrossActivityAnimation(mContext, mAnimationBackground);
-        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
-                mDefaultActivityAnimation.mBackAnimationRunner);
-        mCustomizeActivityAnimation =
-                new CustomizeActivityAnimation(mContext, mAnimationBackground);
-        // TODO (236760237): register dialog close animation when it's completed.
     }
 
     private void setupAnimationDeveloperSettingsObserver(
@@ -359,11 +346,11 @@
 
     void registerAnimation(@BackNavigationInfo.BackTargetType int type,
             @NonNull BackAnimationRunner runner) {
-        mAnimationDefinition.set(type, runner);
+        mShellBackAnimationRegistry.registerAnimation(type, runner);
     }
 
     void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
-        mAnimationDefinition.remove(type);
+        mShellBackAnimationRegistry.unregisterAnimation(type);
     }
 
     /**
@@ -434,9 +421,7 @@
         final int backType = backNavigationInfo.getType();
         final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
         if (shouldDispatchToAnimator) {
-            if (mAnimationDefinition.contains(backType)) {
-                mAnimationDefinition.get(backType).startGesture();
-            } else {
+            if (!mShellBackAnimationRegistry.startGesture(backType)) {
                 mActiveCallback = null;
             }
         } else {
@@ -459,6 +444,7 @@
         sendBackEvent(KeyEvent.ACTION_UP);
     }
 
+    @SuppressLint("MissingPermission")
     private void sendBackEvent(int action) {
         final long when = SystemClock.uptimeMillis();
         final KeyEvent ev = new KeyEvent(when, when, action, KeyEvent.KEYCODE_BACK, 0 /* repeat */,
@@ -671,21 +657,17 @@
         }
 
         final int backType = mBackNavigationInfo.getType();
-        final BackAnimationRunner runner = mAnimationDefinition.get(backType);
         // Simply trigger and finish back navigation when no animator defined.
-        if (!shouldDispatchToAnimator() || runner == null) {
+        if (!shouldDispatchToAnimator()
+                || mShellBackAnimationRegistry.isAnimationCancelledOrNull(backType)) {
             invokeOrCancelBack();
             return;
-        }
-        if (runner.isWaitingAnimation()) {
+        } else if (mShellBackAnimationRegistry.isWaitingAnimation(backType)) {
             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();
-            return;
         }
         startPostCommitAnimation();
     }
@@ -737,12 +719,7 @@
         mShouldStartOnNextMoveEvent = false;
         mTouchTracker.reset();
         mActiveCallback = null;
-        // reset to default
-        if (mDefaultActivityAnimation != null
-                && mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
-            mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
-                    mDefaultActivityAnimation.mBackAnimationRunner);
-        }
+        mShellBackAnimationRegistry.resetDefaultCrossActivity();
         if (mBackNavigationInfo != null) {
             mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
             mBackNavigationInfo = null;
@@ -750,86 +727,88 @@
         mTriggerBack = false;
     }
 
-    private BackAnimationRunner getAnimationRunnerAndInit() {
-        int type = mBackNavigationInfo.getType();
-        // Initiate customized cross-activity animation, or fall back to cross activity animation
-        if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
-            final BackNavigationInfo.CustomAnimationInfo animationInfo =
-                    mBackNavigationInfo.getCustomAnimationInfo();
-            if (animationInfo != null && mCustomizeActivityAnimation != null
-                    && mCustomizeActivityAnimation.prepareNextAnimation(animationInfo)) {
-                mAnimationDefinition.get(type).resetWaitingAnimation();
-                mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
-                        mCustomizeActivityAnimation.mBackAnimationRunner);
-            }
-        }
-        return mAnimationDefinition.get(type);
-    }
 
     private void createAdapter() {
-        IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
-            @Override
-            public void onAnimationStart(RemoteAnimationTarget[] apps,
-                    RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
-                    IBackAnimationFinishedCallback finishedCallback) {
-                mShellExecutor.execute(() -> {
-                    if (mBackNavigationInfo == null) {
-                        Log.e(TAG, "Lack of navigation info to start animation.");
-                        return;
-                    }
-                    final int type = mBackNavigationInfo.getType();
-                    final BackAnimationRunner runner = getAnimationRunnerAndInit();
-                    if (runner == null) {
-                        Log.e(TAG, "Animation didn't be defined for type "
-                                + BackNavigationInfo.typeToString(type));
-                        if (finishedCallback != null) {
-                            try {
-                                finishedCallback.onAnimationFinished(false);
-                            } catch (RemoteException e) {
-                                Log.w(TAG, "Failed call IBackNaviAnimationController", e);
-                            }
-                        }
-                        return;
-                    }
-                    mActiveCallback = runner.getCallback();
-                    mBackAnimationFinishedCallback = finishedCallback;
+        IBackAnimationRunner runner =
+                new IBackAnimationRunner.Stub() {
+                    @Override
+                    public void onAnimationStart(
+                            RemoteAnimationTarget[] apps,
+                            RemoteAnimationTarget[] wallpapers,
+                            RemoteAnimationTarget[] nonApps,
+                            IBackAnimationFinishedCallback finishedCallback) {
+                        mShellExecutor.execute(
+                                () -> {
+                                    if (mBackNavigationInfo == null) {
+                                        Log.e(TAG, "Lack of navigation info to start animation.");
+                                        return;
+                                    }
+                                    final BackAnimationRunner runner =
+                                            mShellBackAnimationRegistry.getAnimationRunnerAndInit(
+                                                    mBackNavigationInfo);
+                                    if (runner == null) {
+                                        if (finishedCallback != null) {
+                                            try {
+                                                finishedCallback.onAnimationFinished(false);
+                                            } catch (RemoteException e) {
+                                                Log.w(
+                                                        TAG,
+                                                        "Failed call IBackNaviAnimationController",
+                                                        e);
+                                            }
+                                        }
+                                        return;
+                                    }
+                                    mActiveCallback = runner.getCallback();
+                                    mBackAnimationFinishedCallback = finishedCallback;
 
-                    ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
-                    runner.startAnimation(apps, wallpapers, nonApps, () -> mShellExecutor.execute(
-                            BackAnimationController.this::onBackAnimationFinished));
+                                    ProtoLog.d(
+                                            WM_SHELL_BACK_PREVIEW,
+                                            "BackAnimationController: startAnimation()");
+                                    runner.startAnimation(
+                                            apps,
+                                            wallpapers,
+                                            nonApps,
+                                            () ->
+                                                    mShellExecutor.execute(
+                                                            BackAnimationController.this
+                                                                    ::onBackAnimationFinished));
 
-                    if (apps.length >= 1) {
-                        dispatchOnBackStarted(
-                                mActiveCallback, mTouchTracker.createStartEvent(apps[0]));
+                                    if (apps.length >= 1) {
+                                        dispatchOnBackStarted(
+                                                mActiveCallback,
+                                                mTouchTracker.createStartEvent(apps[0]));
+                                    }
+
+                                    // Dispatch the first progress after animation start for
+                                    // smoothing the initial animation, instead of waiting for next
+                                    // onMove.
+                                    final BackMotionEvent backFinish =
+                                            mTouchTracker.createProgressEvent();
+                                    dispatchOnBackProgressed(mActiveCallback, backFinish);
+                                    if (!mBackGestureStarted) {
+                                        // if the down -> up gesture happened before animation
+                                        // start, we have to trigger the uninterruptible transition
+                                        // to finish the back animation.
+                                        startPostCommitAnimation();
+                                    }
+                                });
                     }
 
-                    // Dispatch the first progress after animation start for smoothing the initial
-                    // animation, instead of waiting for next onMove.
-                    final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
-                    dispatchOnBackProgressed(mActiveCallback, backFinish);
-                    if (!mBackGestureStarted) {
-                        // if the down -> up gesture happened before animation start, we have to
-                        // trigger the uninterruptible transition to finish the back animation.
-                        startPostCommitAnimation();
+                    @Override
+                    public void onAnimationCancelled() {
+                        mShellExecutor.execute(
+                                () -> {
+                                    if (!mShellBackAnimationRegistry.cancel(
+                                            mBackNavigationInfo.getType())) {
+                                        return;
+                                    }
+                                    if (!mBackGestureStarted) {
+                                        invokeOrCancelBack();
+                                    }
+                                });
                     }
-                });
-            }
-
-            @Override
-            public void onAnimationCancelled() {
-                mShellExecutor.execute(() -> {
-                    final BackAnimationRunner runner = mAnimationDefinition.get(
-                            mBackNavigationInfo.getType());
-                    if (runner == null) {
-                        return;
-                    }
-                    runner.cancelAnimation();
-                    if (!mBackGestureStarted) {
-                        invokeOrCancelBack();
-                    }
-                });
-            }
-        };
+                };
         mBackAnimationAdapter = new BackAnimationAdapter(runner);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
index 913239f7..431df21 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java
@@ -32,7 +32,7 @@
  * before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
  * trigger the real back behavior.
  */
-class BackAnimationRunner {
+public class BackAnimationRunner {
     private static final String TAG = "ShellBackPreview";
 
     private final IOnBackInvokedCallback mCallback;
@@ -44,8 +44,8 @@
     /** True when the back animation is cancelled */
     private boolean mAnimationCancelled;
 
-    BackAnimationRunner(@NonNull IOnBackInvokedCallback callback,
-            @NonNull IRemoteAnimationRunner runner) {
+    public BackAnimationRunner(
+            @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner) {
         mCallback = callback;
         mRunner = runner;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
index edefe9e..114486e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityAnimation.java
@@ -51,9 +51,11 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 
+import javax.inject.Inject;
+
 /** Class that defines cross-activity animation. */
 @ShellMainThread
-class CrossActivityAnimation {
+public class CrossActivityAnimation extends ShellBackAnimation {
     /**
      * Minimum scale of the entering/closing window.
      */
@@ -106,6 +108,7 @@
     private final SpringAnimation mLeavingProgressSpring;
     // Max window x-shift in pixels.
     private final float mWindowXShift;
+    private final BackAnimationRunner mBackAnimationRunner;
 
     private float mEnteringProgress = 0f;
     private float mLeavingProgress = 0f;
@@ -126,11 +129,11 @@
     private IRemoteAnimationFinishedCallback mFinishCallback;
 
     private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
-    final BackAnimationRunner mBackAnimationRunner;
 
     private final BackAnimationBackground mBackground;
 
-    CrossActivityAnimation(Context context, BackAnimationBackground background) {
+    @Inject
+    public CrossActivityAnimation(Context context, BackAnimationBackground background) {
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
         mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
         mBackground = background;
@@ -357,6 +360,11 @@
         mTransaction.apply();
     }
 
+    @Override
+    public BackAnimationRunner getRunner() {
+        return mBackAnimationRunner;
+    }
+
     private final class Callback extends IOnBackInvokedCallback.Default {
         @Override
         public void onBackStarted(BackMotionEvent backEvent) {
@@ -371,7 +379,15 @@
 
         @Override
         public void onBackCancelled() {
-            mProgressAnimator.onBackCancelled(CrossActivityAnimation.this::finishAnimation);
+            mProgressAnimator.onBackCancelled(() -> {
+                // mProgressAnimator can reach finish stage earlier than mLeavingProgressSpring,
+                // and if we release all animation leash first, the leavingProgressSpring won't
+                // able to update the animation anymore, which cause flicker.
+                // Here should force update the closing animation target to the final stage before
+                // release it.
+                setLeavingProgress(0);
+                finishAnimation();
+            });
         }
 
         @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index a7dd27a..209d853 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -47,21 +47,23 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 
+import javax.inject.Inject;
+
 /**
  * Controls the animation of swiping back and returning to another task.
  *
- * This is a two part animation. The first part is an animation that tracks gesture location to
- * scale and move the closing and entering app windows.
- * Once the gesture is committed, the second part remains the closing window in place.
- * The entering window plays the rest of app opening transition to enter full screen.
+ * <p>This is a two part animation. The first part is an animation that tracks gesture location to
+ * scale and move the closing and entering app windows. Once the gesture is committed, the second
+ * part remains the closing window in place. The entering window plays the rest of app opening
+ * transition to enter full screen.
  *
- * This animation is used only for apps that enable back dispatching via
- * {@link android.window.OnBackInvokedDispatcher}. The controller registers
- * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
- * navigation to launcher starts.
+ * <p>This animation is used only for apps that enable back dispatching via {@link
+ * android.window.OnBackInvokedDispatcher}. The controller registers an {@link
+ * IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back navigation to
+ * launcher starts.
  */
 @ShellMainThread
-class CrossTaskBackAnimation {
+public class CrossTaskBackAnimation extends ShellBackAnimation {
     private static final int BACKGROUNDCOLOR = 0x43433A;
 
     /**
@@ -104,28 +106,41 @@
 
     private final float[] mTmpFloat9 = new float[9];
     private final float[] mTmpTranslate = {0, 0, 0};
-
+    private final BackAnimationRunner mBackAnimationRunner;
+    private final BackAnimationBackground mBackground;
     private RemoteAnimationTarget mEnteringTarget;
     private RemoteAnimationTarget mClosingTarget;
     private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
-
     private boolean mBackInProgress = false;
-
     private boolean mIsRightEdge;
     private float mProgress = 0;
     private PointF mTouchPos = new PointF();
     private IRemoteAnimationFinishedCallback mFinishCallback;
     private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
-    final BackAnimationRunner mBackAnimationRunner;
 
-    private final BackAnimationBackground mBackground;
-
-    CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
+    @Inject
+    public CrossTaskBackAnimation(Context context, BackAnimationBackground background) {
         mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
         mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
         mBackground = background;
     }
 
+    private static void computeScaleTransformMatrix(float scale, float[] matrix) {
+        matrix[0] = scale;
+        matrix[1] = 0;
+        matrix[2] = 0;
+        matrix[3] = 0;
+        matrix[4] = scale;
+        matrix[5] = 0;
+        matrix[6] = 0;
+        matrix[7] = 0;
+        matrix[8] = scale;
+    }
+
+    private static float mapRange(float value, float min, float max) {
+        return min + (value * (max - min));
+    }
+
     private float getInterpolatedProgress(float backProgress) {
         return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress);
     }
@@ -233,18 +248,6 @@
         mTransaction.setColorTransform(leash, mTmpFloat9, mTmpTranslate);
     }
 
-    static void computeScaleTransformMatrix(float scale, float[] matrix) {
-        matrix[0] = scale;
-        matrix[1] = 0;
-        matrix[2] = 0;
-        matrix[3] = 0;
-        matrix[4] = scale;
-        matrix[5] = 0;
-        matrix[6] = 0;
-        matrix[7] = 0;
-        matrix[8] = scale;
-    }
-
     private void finishAnimation() {
         if (mEnteringTarget != null) {
             mEnteringTarget.leash.release();
@@ -314,11 +317,12 @@
         valueAnimator.start();
     }
 
-    private static float mapRange(float value, float min, float max) {
-        return min + (value * (max - min));
+    @Override
+    public BackAnimationRunner getRunner() {
+        return mBackAnimationRunner;
     }
 
-    private final class Callback extends IOnBackInvokedCallback.Default  {
+    private final class Callback extends IOnBackInvokedCallback.Default {
         @Override
         public void onBackStarted(BackMotionEvent backEvent) {
             mProgressAnimator.onBackStarted(backEvent,
@@ -340,7 +344,7 @@
             mProgressAnimator.reset();
             onGestureCommitted();
         }
-    };
+    }
 
     private final class Runner extends IRemoteAnimationRunner.Default {
         @Override
@@ -360,5 +364,5 @@
             startBackAnimation();
             mFinishCallback = finishedCallback;
         }
-    };
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
index 2d6ec75..aca638c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -55,13 +55,13 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 
-/**
- * Class that handle customized close activity transition animation.
- */
+import javax.inject.Inject;
+
+/** Class that handle customized close activity transition animation. */
 @ShellMainThread
-class CustomizeActivityAnimation {
+public class CustomizeActivityAnimation extends ShellBackAnimation {
     private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
-    final BackAnimationRunner mBackAnimationRunner;
+    private final BackAnimationRunner mBackAnimationRunner;
     private final float mCornerRadius;
     private final SurfaceControl.Transaction mTransaction;
     private final BackAnimationBackground mBackground;
@@ -88,7 +88,8 @@
 
     private final Choreographer mChoreographer;
 
-    CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
+    @Inject
+    public CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
         this(context, background, new SurfaceControl.Transaction(), null);
     }
 
@@ -258,10 +259,12 @@
         valueAnimator.start();
     }
 
-    /**
-     * Load customize animation before animation start.
-     */
-    boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+    /** Load customize animation before animation start. */
+    @Override
+    public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+        if (animationInfo == null) {
+            return false;
+        }
         final AnimationLoadResult result = mCustomAnimationLoader.loadAll(animationInfo);
         if (result != null) {
             mCloseAnimation = result.mCloseAnimation;
@@ -272,6 +275,11 @@
         return false;
     }
 
+    @Override
+    public BackAnimationRunner getRunner() {
+        return mBackAnimationRunner;
+    }
+
     private final class Callback extends IOnBackInvokedCallback.Default {
         @Override
         public void onBackStarted(BackMotionEvent backEvent) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
new file mode 100644
index 0000000..312e88d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimation.java
@@ -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.wm.shell.back;
+
+import android.window.BackNavigationInfo;
+
+import javax.inject.Qualifier;
+
+/** Base class for all back animations. */
+public abstract class ShellBackAnimation {
+    @Qualifier
+    public @interface CrossActivity {}
+
+    @Qualifier
+    public @interface CrossTask {}
+
+    @Qualifier
+    public @interface CustomizeActivity {}
+
+    @Qualifier
+    public @interface ReturnToHome {}
+
+    /** Retrieve the {@link BackAnimationRunner} associated with this animation. */
+    public abstract BackAnimationRunner getRunner();
+
+    /**
+     * Prepare the next animation with customized animation.
+     *
+     * @return true if this type of back animation should override the default.
+     */
+    public boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+        return false;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
new file mode 100644
index 0000000..62b18f3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/ShellBackAnimationRegistry.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.Log;
+import android.util.SparseArray;
+import android.window.BackNavigationInfo;
+
+/** Registry for all types of default back animations */
+public class ShellBackAnimationRegistry {
+    private static final String TAG = "ShellBackPreview";
+
+    private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
+    private final ShellBackAnimation mDefaultCrossActivityAnimation;
+    private final ShellBackAnimation mCustomizeActivityAnimation;
+
+    public ShellBackAnimationRegistry(
+            @ShellBackAnimation.CrossActivity @Nullable ShellBackAnimation crossActivityAnimation,
+            @ShellBackAnimation.CrossTask @Nullable ShellBackAnimation crossTaskAnimation,
+            @ShellBackAnimation.CustomizeActivity @Nullable
+                    ShellBackAnimation customizeActivityAnimation,
+            @ShellBackAnimation.ReturnToHome @Nullable
+                    ShellBackAnimation defaultBackToHomeAnimation) {
+        if (crossActivityAnimation != null) {
+            mAnimationDefinition.set(
+                    BackNavigationInfo.TYPE_CROSS_TASK, crossTaskAnimation.getRunner());
+        }
+        if (crossActivityAnimation != null) {
+            mAnimationDefinition.set(
+                    BackNavigationInfo.TYPE_CROSS_ACTIVITY, crossActivityAnimation.getRunner());
+        }
+        if (defaultBackToHomeAnimation != null) {
+            mAnimationDefinition.set(
+                    BackNavigationInfo.TYPE_RETURN_TO_HOME, defaultBackToHomeAnimation.getRunner());
+        }
+
+        mDefaultCrossActivityAnimation = crossActivityAnimation;
+        mCustomizeActivityAnimation = customizeActivityAnimation;
+
+        // TODO(b/236760237): register dialog close animation when it's completed.
+    }
+
+    void registerAnimation(
+            @BackNavigationInfo.BackTargetType int type, @NonNull BackAnimationRunner runner) {
+        mAnimationDefinition.set(type, runner);
+    }
+
+    void unregisterAnimation(@BackNavigationInfo.BackTargetType int type) {
+        mAnimationDefinition.remove(type);
+    }
+
+    /**
+     * Start the {@link BackAnimationRunner} associated with a back target type.
+     *
+     * @param type back target type
+     * @return true if the animation is started, false if animation is not found for that type.
+     */
+    boolean startGesture(@BackNavigationInfo.BackTargetType int type) {
+        BackAnimationRunner runner = mAnimationDefinition.get(type);
+        if (runner == null) {
+            return false;
+        }
+        runner.startGesture();
+        return true;
+    }
+
+    /**
+     * Cancel the {@link BackAnimationRunner} associated with a back target type.
+     *
+     * @param type back target type
+     * @return true if the animation is started, false if animation is not found for that type.
+     */
+    boolean cancel(@BackNavigationInfo.BackTargetType int type) {
+        BackAnimationRunner runner = mAnimationDefinition.get(type);
+        if (runner == null) {
+            return false;
+        }
+        runner.cancelAnimation();
+        return true;
+    }
+
+    boolean isAnimationCancelledOrNull(@BackNavigationInfo.BackTargetType int type) {
+        BackAnimationRunner runner = mAnimationDefinition.get(type);
+        if (runner == null) {
+            return true;
+        }
+        return runner.isAnimationCancelled();
+    }
+
+    boolean isWaitingAnimation(@BackNavigationInfo.BackTargetType int type) {
+        BackAnimationRunner runner = mAnimationDefinition.get(type);
+        if (runner == null) {
+            return false;
+        }
+        return runner.isWaitingAnimation();
+    }
+
+    void resetDefaultCrossActivity() {
+        if (mDefaultCrossActivityAnimation == null
+                || !mAnimationDefinition.contains(BackNavigationInfo.TYPE_CROSS_ACTIVITY)) {
+            return;
+        }
+        mAnimationDefinition.set(
+                BackNavigationInfo.TYPE_CROSS_ACTIVITY, mDefaultCrossActivityAnimation.getRunner());
+    }
+
+    BackAnimationRunner getAnimationRunnerAndInit(BackNavigationInfo backNavigationInfo) {
+        int type = backNavigationInfo.getType();
+        // Initiate customized cross-activity animation, or fall back to cross activity animation
+        if (type == BackNavigationInfo.TYPE_CROSS_ACTIVITY && mAnimationDefinition.contains(type)) {
+            if (mCustomizeActivityAnimation != null
+                    && mCustomizeActivityAnimation.prepareNextAnimation(
+                            backNavigationInfo.getCustomAnimationInfo())) {
+                mAnimationDefinition.get(type).resetWaitingAnimation();
+                mAnimationDefinition.set(
+                        BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                        mCustomizeActivityAnimation.getRunner());
+            }
+        }
+        BackAnimationRunner runner = mAnimationDefinition.get(type);
+        if (runner == null) {
+            Log.e(
+                    TAG,
+                    "Animation didn't be defined for type "
+                            + BackNavigationInfo.typeToString(type));
+        }
+        return runner;
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 66b9ade6..8400dde 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -25,7 +25,6 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED;
@@ -37,6 +36,7 @@
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
 
 import android.annotation.BinderThread;
@@ -85,6 +85,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.BubbleIconFactory;
 import com.android.wm.shell.R;
@@ -1008,9 +1009,7 @@
     }
 
     private void onNotificationPanelExpandedChanged(boolean expanded) {
-        if (DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "onNotificationPanelExpandedChanged: expanded=" + expanded);
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "onNotificationPanelExpandedChanged: expanded=%b", expanded);
         if (mStackView != null && mStackView.isExpanded()) {
             if (expanded) {
                 mStackView.stopMonitoringSwipeUpGesture();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index dce6b56..250e010 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -47,7 +47,6 @@
     static final boolean DEBUG_USER_EDUCATION = false;
     static final boolean DEBUG_POSITIONER = false;
     public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
-    static final boolean DEBUG_BUBBLE_GESTURE = false;
     public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
 
     private static final boolean FORCE_SHOW_USER_EDUCATION = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index ee6996d..2c10065 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -661,14 +661,26 @@
         final boolean startOnLeft =
                 mContext.getResources().getConfiguration().getLayoutDirection()
                         != LAYOUT_DIRECTION_RTL;
-        final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset(
-                R.dimen.bubble_stack_starting_offset_y);
-        // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
-        return new BubbleStackView.RelativeStackPosition(
-                startOnLeft,
-                startingVerticalOffset / mPositionRect.height())
-                .getAbsolutePositionInRegion(getAllowableStackPositionRegion(
-                        1 /* default starts with 1 bubble */));
+        final RectF allowableStackPositionRegion = getAllowableStackPositionRegion(
+                1 /* default starts with 1 bubble */);
+        if (isLargeScreen()) {
+            // We want the stack to be visually centered on the edge, so we need to base it
+            // of a rect that includes insets.
+            final float desiredY = mScreenRect.height() / 2f - (mBubbleSize / 2f);
+            final float offset = desiredY / mScreenRect.height();
+            return new BubbleStackView.RelativeStackPosition(
+                    startOnLeft,
+                    offset)
+                    .getAbsolutePositionInRegion(allowableStackPositionRegion);
+        } else {
+            final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset(
+                    R.dimen.bubble_stack_starting_offset_y);
+            // TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
+            return new BubbleStackView.RelativeStackPosition(
+                    startOnLeft,
+                    startingVerticalOffset / mPositionRect.height())
+                    .getAbsolutePositionInRegion(allowableStackPositionRegion);
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index f58b121..da5974f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -21,11 +21,11 @@
 
 import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
 import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -75,6 +75,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
@@ -2024,9 +2025,7 @@
      * Monitor for swipe up gesture that is used to collapse expanded view
      */
     void startMonitoringSwipeUpGesture() {
-        if (DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "startMonitoringSwipeUpGesture");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "startMonitoringSwipeUpGesture");
         stopMonitoringSwipeUpGestureInternal();
 
         if (isGestureNavEnabled()) {
@@ -2046,9 +2045,7 @@
      * Stop monitoring for swipe up gesture
      */
     void stopMonitoringSwipeUpGesture() {
-        if (DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "stopMonitoringSwipeUpGesture");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "stopMonitoringSwipeUpGesture");
         stopMonitoringSwipeUpGestureInternal();
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
index 3a3a378..1375684 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
@@ -18,10 +18,10 @@
 
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.content.Context;
 import android.hardware.input.InputManager;
-import android.util.Log;
 import android.view.Choreographer;
 import android.view.InputChannel;
 import android.view.InputEventReceiver;
@@ -29,6 +29,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
 
 /**
@@ -58,9 +59,7 @@
      * @param listener listener that is notified of touch events
      */
     void start(MotionEventListener listener) {
-        if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "start monitoring bubbles swipe up gesture");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "start monitoring bubbles swipe up gesture");
 
         stopInternal();
 
@@ -76,9 +75,7 @@
     }
 
     void stop() {
-        if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "stop monitoring bubbles swipe up gesture");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "stop monitoring bubbles swipe up gesture");
         stopInternal();
     }
 
@@ -94,9 +91,7 @@
     }
 
     private void onInterceptTouch() {
-        if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
-            Log.d(TAG, "intercept touch event");
-        }
+        ProtoLog.d(WM_SHELL_BUBBLES, "intercept touch event");
         if (mInputMonitor != null) {
             mInputMonitor.pilferPointers();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
index 844526c..b7107f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
@@ -16,19 +16,20 @@
 
 package com.android.wm.shell.bubbles;
 
-import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.content.Context;
 import android.graphics.PointF;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 
 import androidx.annotation.Nullable;
 
+import com.android.internal.protolog.common.ProtoLog;
+
 /**
  * Handles {@link MotionEvent}s for bubbles that begin in the nav bar area
  */
@@ -112,10 +113,8 @@
     private boolean isInGestureRegion(MotionEvent ev) {
         // Only handles touch events beginning in navigation bar system gesture zone
         if (mPositioner.getNavBarGestureZone().contains((int) ev.getX(), (int) ev.getY())) {
-            if (DEBUG_BUBBLE_GESTURE) {
-                Log.d(TAG, "handling touch y=" + ev.getY()
-                        + " navBarGestureZone=" + mPositioner.getNavBarGestureZone());
-            }
+            ProtoLog.d(WM_SHELL_BUBBLES, "handling touch x=%d y=%d navBarGestureZone=%s",
+                    (int) ev.getX(), (int) ev.getY(), mPositioner.getNavBarGestureZone());
             return true;
         }
         return false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index e95e8e5..1b41f79 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -41,9 +41,9 @@
     private val ANIMATE_DURATION: Long = 200
 
     private val positioner: BubblePositioner = positioner
-    private val manageView by lazy { findViewById<ViewGroup>(R.id.manage_education_view) }
-    private val manageButton by lazy { findViewById<Button>(R.id.manage_button) }
-    private val gotItButton by lazy { findViewById<Button>(R.id.got_it) }
+    private val manageView by lazy { requireViewById<ViewGroup>(R.id.manage_education_view) }
+    private val manageButton by lazy { requireViewById<Button>(R.id.manage_button) }
+    private val gotItButton by lazy { requireViewById<Button>(R.id.got_it) }
 
     private var isHiding = false
     private var realManageButtonRect = Rect()
@@ -122,7 +122,7 @@
             manageButton
                 .setOnClickListener {
                     hide()
-                    expandedView.findViewById<View>(R.id.manage_button).performClick()
+                    expandedView.requireViewById<View>(R.id.manage_button).performClick()
                 }
             gotItButton.setOnClickListener { hide() }
             setOnClickListener { hide() }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index d0598cd..5e3a077 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -48,9 +48,9 @@
     private val positioner: BubblePositioner = positioner
     private val controller: BubbleController = controller
 
-    private val view by lazy { findViewById<View>(R.id.stack_education_layout) }
-    private val titleTextView by lazy { findViewById<TextView>(R.id.stack_education_title) }
-    private val descTextView by lazy { findViewById<TextView>(R.id.stack_education_description) }
+    private val view by lazy { requireViewById<View>(R.id.stack_education_layout) }
+    private val titleTextView by lazy { requireViewById<TextView>(R.id.stack_education_title) }
+    private val descTextView by lazy { requireViewById<TextView>(R.id.stack_education_description) }
 
     var isHiding = false
         private set
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
new file mode 100644
index 0000000..fd000ee
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/LegacySizeSpecSource.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip
+
+import android.content.Context
+import android.content.res.Resources
+import android.graphics.PointF
+import android.util.Size
+import com.android.wm.shell.R
+import com.android.wm.shell.pip.PipDisplayLayoutState
+
+class LegacySizeSpecSource(
+        private val context: Context,
+        private val pipDisplayLayoutState: PipDisplayLayoutState
+) : SizeSpecSource {
+
+    private var mDefaultMinSize = 0
+    /** The absolute minimum an overridden size's edge can be */
+    private var mOverridableMinSize = 0
+    /** The preferred minimum (and default minimum) size specified by apps.  */
+    private var mOverrideMinSize: Size? = null
+
+    private var mDefaultSizePercent = 0f
+    private var mMinimumSizePercent = 0f
+    private var mMaxAspectRatioForMinSize = 0f
+    private var mMinAspectRatioForMinSize = 0f
+
+    init {
+        reloadResources()
+    }
+
+    private fun reloadResources() {
+        val res: Resources = context.getResources()
+
+        mDefaultMinSize = res.getDimensionPixelSize(
+                R.dimen.default_minimal_size_pip_resizable_task)
+        mOverridableMinSize = res.getDimensionPixelSize(
+                R.dimen.overridable_minimal_size_pip_resizable_task)
+
+        mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent)
+        mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1)
+
+        mMaxAspectRatioForMinSize = res.getFloat(
+                R.dimen.config_pictureInPictureAspectRatioLimitForMinSize)
+        mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize
+    }
+
+    override fun onConfigurationChanged() {
+        reloadResources()
+    }
+
+    override fun getMaxSize(aspectRatio: Float): Size {
+        val insetBounds = pipDisplayLayoutState.insetBounds
+
+        val shorterLength: Int = Math.min(getDisplayBounds().width(),
+                getDisplayBounds().height())
+        val totalHorizontalPadding: Int = (insetBounds.left +
+                (getDisplayBounds().width() - insetBounds.right))
+        val totalVerticalPadding: Int = (insetBounds.top +
+                (getDisplayBounds().height() - insetBounds.bottom))
+
+        return if (aspectRatio > 1f) {
+            val maxWidth = Math.max(getDefaultSize(aspectRatio).width,
+                    shorterLength - totalHorizontalPadding)
+            val maxHeight = (maxWidth / aspectRatio).toInt()
+            Size(maxWidth, maxHeight)
+        } else {
+            val maxHeight = Math.max(getDefaultSize(aspectRatio).height,
+                    shorterLength - totalVerticalPadding)
+            val maxWidth = (maxHeight * aspectRatio).toInt()
+            Size(maxWidth, maxHeight)
+        }
+    }
+
+    override fun getDefaultSize(aspectRatio: Float): Size {
+        if (mOverrideMinSize != null) {
+            return getMinSize(aspectRatio)
+        }
+        val smallestDisplaySize: Int = Math.min(getDisplayBounds().width(),
+                getDisplayBounds().height())
+        val minSize = Math.max(getMinEdgeSize().toFloat(),
+                smallestDisplaySize * mDefaultSizePercent).toInt()
+        val width: Int
+        val height: Int
+        if (aspectRatio <= mMinAspectRatioForMinSize ||
+                aspectRatio > mMaxAspectRatioForMinSize) {
+            // Beyond these points, we can just use the min size as the shorter edge
+            if (aspectRatio <= 1) {
+                // Portrait, width is the minimum size
+                width = minSize
+                height = Math.round(width / aspectRatio)
+            } else {
+                // Landscape, height is the minimum size
+                height = minSize
+                width = Math.round(height * aspectRatio)
+            }
+        } else {
+            // Within these points, ensure that the bounds fit within the radius of the limits
+            // at the points
+            val widthAtMaxAspectRatioForMinSize: Float = mMaxAspectRatioForMinSize * minSize
+            val radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize.toFloat())
+            height = Math.round(Math.sqrt((radius * radius /
+                    (aspectRatio * aspectRatio + 1)).toDouble())).toInt()
+            width = Math.round(height * aspectRatio)
+        }
+        return Size(width, height)
+    }
+
+    override fun getMinSize(aspectRatio: Float): Size {
+        if (mOverrideMinSize != null) {
+            return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
+        }
+        val shorterLength: Int = Math.min(getDisplayBounds().width(),
+                getDisplayBounds().height())
+        val minWidth: Int
+        val minHeight: Int
+        if (aspectRatio > 1f) {
+            minWidth = Math.min(getDefaultSize(aspectRatio).width.toFloat(),
+                    shorterLength * mMinimumSizePercent).toInt()
+            minHeight = (minWidth / aspectRatio).toInt()
+        } else {
+            minHeight = Math.min(getDefaultSize(aspectRatio).height.toFloat(),
+                    shorterLength * mMinimumSizePercent).toInt()
+            minWidth = (minHeight * aspectRatio).toInt()
+        }
+        return Size(minWidth, minHeight)
+    }
+
+    override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size {
+        val smallestSize = Math.min(size.width, size.height)
+        val minSize = Math.max(getMinEdgeSize(), smallestSize)
+        val width: Int
+        val height: Int
+        if (aspectRatio <= 1) {
+            // Portrait, width is the minimum size.
+            width = minSize
+            height = Math.round(width / aspectRatio)
+        } else {
+            // Landscape, height is the minimum size
+            height = minSize
+            width = Math.round(height * aspectRatio)
+        }
+        return Size(width, height)
+    }
+
+    private fun getDisplayBounds() = pipDisplayLayoutState.displayBounds
+
+    /** Sets the preferred size of PIP as specified by the activity in PIP mode.  */
+    override fun setOverrideMinSize(overrideMinSize: Size?) {
+        mOverrideMinSize = overrideMinSize
+    }
+
+    /** Returns the preferred minimal size specified by the activity in PIP.  */
+    override fun getOverrideMinSize(): Size? {
+        val overrideMinSize = mOverrideMinSize ?: return null
+        return if (overrideMinSize.width < mOverridableMinSize ||
+                overrideMinSize.height < mOverridableMinSize) {
+            Size(mOverridableMinSize, mOverridableMinSize)
+        } else {
+            overrideMinSize
+        }
+    }
+
+    private fun getMinEdgeSize(): Int {
+        return if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize()
+    }
+
+    /**
+     * Returns the adjusted overridden min size if it is set; otherwise, returns null.
+     *
+     *
+     * Overridden min size needs to be adjusted in its own way while making sure that the target
+     * aspect ratio is maintained
+     *
+     * @param aspectRatio target aspect ratio
+     */
+    private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? {
+        val size = getOverrideMinSize() ?: return null
+        val sizeAspectRatio = size.width / size.height.toFloat()
+        return if (sizeAspectRatio > aspectRatio) {
+            // Size is wider, fix the width and increase the height
+            Size(size.width, (size.width / aspectRatio).toInt())
+        } else {
+            // Size is taller, fix the height and adjust the width.
+            Size((size.height * aspectRatio).toInt(), size.height)
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
new file mode 100644
index 0000000..c563068
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PhoneSizeSpecSource.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.SystemProperties
+import android.util.Size
+import com.android.wm.shell.R
+import com.android.wm.shell.pip.PipDisplayLayoutState
+import java.io.PrintWriter
+
+class PhoneSizeSpecSource(
+        private val context: Context,
+        private val pipDisplayLayoutState: PipDisplayLayoutState
+) : SizeSpecSource {
+    private var DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16
+
+    private var mDefaultMinSize = 0
+    /** The absolute minimum an overridden size's edge can be */
+    private var mOverridableMinSize = 0
+    /** The preferred minimum (and default minimum) size specified by apps.  */
+    private var mOverrideMinSize: Size? = null
+
+
+    /** Default and minimum percentages for the PIP size logic.  */
+    private val mDefaultSizePercent: Float
+    private val mMinimumSizePercent: Float
+
+    /** Aspect ratio that the PIP size spec logic optimizes for.  */
+    private var mOptimizedAspectRatio = 0f
+
+    init {
+        mDefaultSizePercent = SystemProperties
+                .get("com.android.wm.shell.pip.phone.def_percentage", "0.6").toFloat()
+        mMinimumSizePercent = SystemProperties
+                .get("com.android.wm.shell.pip.phone.min_percentage", "0.5").toFloat()
+
+        reloadResources()
+    }
+
+    private fun reloadResources() {
+        val res: Resources = context.getResources()
+
+        mDefaultMinSize = res.getDimensionPixelSize(
+                R.dimen.default_minimal_size_pip_resizable_task)
+        mOverridableMinSize = res.getDimensionPixelSize(
+                R.dimen.overridable_minimal_size_pip_resizable_task)
+
+        val requestedOptAspRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio)
+        // make sure the optimized aspect ratio is valid with a default value to fall back to
+        mOptimizedAspectRatio = if (requestedOptAspRatio > 1) {
+            DEFAULT_OPTIMIZED_ASPECT_RATIO
+        } else {
+            requestedOptAspRatio
+        }
+    }
+
+    override fun onConfigurationChanged() {
+        reloadResources()
+    }
+
+    /**
+     * Calculates the max size of PIP.
+     *
+     * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
+     * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
+     * whole screen. A linear function is used to calculate these sizes.
+     *
+     * @param aspectRatio aspect ratio of the PIP window
+     * @return dimensions of the max size of the PIP
+     */
+    override fun getMaxSize(aspectRatio: Float): Size {
+        val insetBounds = pipDisplayLayoutState.insetBounds
+        val displayBounds = pipDisplayLayoutState.displayBounds
+
+        val totalHorizontalPadding: Int = (insetBounds.left +
+                (displayBounds.width() - insetBounds.right))
+        val totalVerticalPadding: Int = (insetBounds.top +
+                (displayBounds.height() - insetBounds.bottom))
+        val shorterLength: Int = Math.min(displayBounds.width() - totalHorizontalPadding,
+                displayBounds.height() - totalVerticalPadding)
+        var maxWidth: Int
+        val maxHeight: Int
+
+        // use the optimized max sizing logic only within a certain aspect ratio range
+        if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
+            // this formula and its derivation is explained in b/198643358#comment16
+            maxWidth = Math.round(mOptimizedAspectRatio * shorterLength +
+                    shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1 + aspectRatio))
+            // make sure the max width doesn't go beyond shorter screen length after rounding
+            maxWidth = Math.min(maxWidth, shorterLength)
+            maxHeight = Math.round(maxWidth / aspectRatio)
+        } else {
+            if (aspectRatio > 1f) {
+                maxWidth = shorterLength
+                maxHeight = Math.round(maxWidth / aspectRatio)
+            } else {
+                maxHeight = shorterLength
+                maxWidth = Math.round(maxHeight * aspectRatio)
+            }
+        }
+        return Size(maxWidth, maxHeight)
+    }
+
+    /**
+     * Decreases the dimensions by a percentage relative to max size to get default size.
+     *
+     * @param aspectRatio aspect ratio of the PIP window
+     * @return dimensions of the default size of the PIP
+     */
+    override fun getDefaultSize(aspectRatio: Float): Size {
+        val minSize = getMinSize(aspectRatio)
+        if (mOverrideMinSize != null) {
+            return minSize
+        }
+        val maxSize = getMaxSize(aspectRatio)
+        val defaultWidth = Math.max(Math.round(maxSize.width * mDefaultSizePercent),
+                minSize.width)
+        val defaultHeight = Math.round(defaultWidth / aspectRatio)
+        return Size(defaultWidth, defaultHeight)
+    }
+
+    /**
+     * Decreases the dimensions by a certain percentage relative to max size to get min size.
+     *
+     * @param aspectRatio aspect ratio of the PIP window
+     * @return dimensions of the min size of the PIP
+     */
+    override fun getMinSize(aspectRatio: Float): Size {
+        // if there is an overridden min size provided, return that
+        if (mOverrideMinSize != null) {
+            return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
+        }
+        val maxSize = getMaxSize(aspectRatio)
+        var minWidth = Math.round(maxSize.width * mMinimumSizePercent)
+        var minHeight = Math.round(maxSize.height * mMinimumSizePercent)
+
+        // make sure the calculated min size is not smaller than the allowed default min size
+        if (aspectRatio > 1f) {
+            minHeight = Math.max(minHeight, mDefaultMinSize)
+            minWidth = Math.round(minHeight * aspectRatio)
+        } else {
+            minWidth = Math.max(minWidth, mDefaultMinSize)
+            minHeight = Math.round(minWidth / aspectRatio)
+        }
+        return Size(minWidth, minHeight)
+    }
+
+    /**
+     * Returns the size for target aspect ratio making sure new size conforms with the rules.
+     *
+     *
+     * Recalculates the dimensions such that the target aspect ratio is achieved, while
+     * maintaining the same maximum size to current size ratio.
+     *
+     * @param size current size
+     * @param aspectRatio target aspect ratio
+     */
+    override fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size {
+        if (size == mOverrideMinSize) {
+            return adjustOverrideMinSizeToAspectRatio(aspectRatio)!!
+        }
+
+        val currAspectRatio = size.width.toFloat() / size.height
+
+        // getting the percentage of the max size that current size takes
+        val currentMaxSize = getMaxSize(currAspectRatio)
+        val currentPercent = size.width.toFloat() / currentMaxSize.width
+
+        // getting the max size for the target aspect ratio
+        val updatedMaxSize = getMaxSize(aspectRatio)
+        var width = Math.round(updatedMaxSize.width * currentPercent)
+        var height = Math.round(updatedMaxSize.height * currentPercent)
+
+        // adjust the dimensions if below allowed min edge size
+        val minEdgeSize =
+                if (mOverrideMinSize == null) mDefaultMinSize else getOverrideMinEdgeSize()
+
+        if (width < minEdgeSize && aspectRatio <= 1) {
+            width = minEdgeSize
+            height = Math.round(width / aspectRatio)
+        } else if (height < minEdgeSize && aspectRatio > 1) {
+            height = minEdgeSize
+            width = Math.round(height * aspectRatio)
+        }
+
+        // reduce the dimensions of the updated size to the calculated percentage
+        return Size(width, height)
+    }
+
+    /** Sets the preferred size of PIP as specified by the activity in PIP mode.  */
+    override fun setOverrideMinSize(overrideMinSize: Size?) {
+        mOverrideMinSize = overrideMinSize
+    }
+
+    /** Returns the preferred minimal size specified by the activity in PIP.  */
+    override fun getOverrideMinSize(): Size? {
+        val overrideMinSize = mOverrideMinSize ?: return null
+        return if (overrideMinSize.width < mOverridableMinSize ||
+                overrideMinSize.height < mOverridableMinSize) {
+            Size(mOverridableMinSize, mOverridableMinSize)
+        } else {
+            overrideMinSize
+        }
+    }
+
+    /**
+     * Returns the adjusted overridden min size if it is set; otherwise, returns null.
+     *
+     *
+     * Overridden min size needs to be adjusted in its own way while making sure that the target
+     * aspect ratio is maintained
+     *
+     * @param aspectRatio target aspect ratio
+     */
+    private fun adjustOverrideMinSizeToAspectRatio(aspectRatio: Float): Size? {
+        val size = getOverrideMinSize() ?: return null
+        val sizeAspectRatio = size.width / size.height.toFloat()
+        return if (sizeAspectRatio > aspectRatio) {
+            // Size is wider, fix the width and increase the height
+            Size(size.width, (size.width / aspectRatio).toInt())
+        } else {
+            // Size is taller, fix the height and adjust the width.
+            Size((size.height * aspectRatio).toInt(), size.height)
+        }
+    }
+
+    override fun dump(pw: PrintWriter, prefix: String) {
+        val innerPrefix = "$prefix  "
+        pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize)
+        pw.println(innerPrefix + "mOverridableMinSize=" + mOverridableMinSize)
+        pw.println(innerPrefix + "mDefaultMinSize=" + mDefaultMinSize)
+        pw.println(innerPrefix + "mDefaultSizePercent=" + mDefaultSizePercent)
+        pw.println(innerPrefix + "mMinimumSizePercent=" + mMinimumSizePercent)
+        pw.println(innerPrefix + "mOptimizedAspectRatio=" + mOptimizedAspectRatio)
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
new file mode 100644
index 0000000..a141ff9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.common.pip
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.content.pm.PackageManager
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.pip.PipUtils
+
+class PipAppOpsListener(
+    private val mContext: Context,
+    private val mCallback: Callback,
+    private val mMainExecutor: ShellExecutor
+) {
+    private val mAppOpsManager: AppOpsManager = checkNotNull(
+        mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager)
+    private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName ->
+        try {
+            // Dismiss the PiP once the user disables the app ops setting for that package
+            val topPipActivityInfo = PipUtils.getTopPipActivity(mContext)
+            val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener
+            val userId = topPipActivityInfo.second
+            val appInfo = mContext.packageManager
+                .getApplicationInfoAsUser(packageName, 0, userId)
+            if (appInfo.packageName == componentName.packageName &&
+                mAppOpsManager.checkOpNoThrow(
+                    AppOpsManager.OP_PICTURE_IN_PICTURE, appInfo.uid,
+                    packageName
+                ) != AppOpsManager.MODE_ALLOWED
+            ) {
+                mMainExecutor.execute { mCallback.dismissPip() }
+            }
+        } catch (e: PackageManager.NameNotFoundException) {
+            // Unregister the listener if the package can't be found
+            unregisterAppOpsListener()
+        }
+    }
+
+    fun onActivityPinned(packageName: String) {
+        // Register for changes to the app ops setting for this package while it is in PiP
+        registerAppOpsListener(packageName)
+    }
+
+    fun onActivityUnpinned() {
+        // Unregister for changes to the previously PiP'ed package
+        unregisterAppOpsListener()
+    }
+
+    private fun registerAppOpsListener(packageName: String) {
+        mAppOpsManager.startWatchingMode(
+            AppOpsManager.OP_PICTURE_IN_PICTURE, packageName,
+            mAppOpsChangedListener
+        )
+    }
+
+    private fun unregisterAppOpsListener() {
+        mAppOpsManager.stopWatchingMode(mAppOpsChangedListener)
+    }
+
+    /** Callback for PipAppOpsListener to request changes to the PIP window.  */
+    interface Callback {
+        /** Dismisses the PIP window.  */
+        fun dismissPip()
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt
new file mode 100644
index 0000000..7b3b9ef
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/SizeSpecSource.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.common.pip
+
+import android.util.Size
+import java.io.PrintWriter
+
+interface SizeSpecSource {
+    /** Returns max size allowed for the PIP window  */
+    fun getMaxSize(aspectRatio: Float): Size
+
+    /** Returns default size for the PIP window  */
+    fun getDefaultSize(aspectRatio: Float): Size
+
+    /** Returns min size allowed for the PIP window  */
+    fun getMinSize(aspectRatio: Float): Size
+
+    /** Returns the adjusted size based on current size and target aspect ratio  */
+    fun getSizeForAspectRatio(size: Size, aspectRatio: Float): Size
+
+    /** Overrides the minimum pip size requested by the app */
+    fun setOverrideMinSize(overrideMinSize: Size?)
+
+    /** Returns the minimum pip size requested by the app */
+    fun getOverrideMinSize(): Size?
+
+    /** Returns the minimum edge size of the override minimum size, or 0 if not set.  */
+    fun getOverrideMinEdgeSize(): Int {
+        val overrideMinSize = getOverrideMinSize() ?: return 0
+        return Math.min(overrideMinSize.width, overrideMinSize.height)
+    }
+
+    fun onConfigurationChanged() {}
+
+    /** Dumps the internal state of the size spec */
+    fun dump(pw: PrintWriter, prefix: String) {}
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
similarity index 98%
rename from core/java/com/android/internal/policy/DividerSnapAlgorithm.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index a065e2b..1901e0b 100644
--- a/core/java/com/android/internal/policy/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.policy;
+package com.android.wm.shell.common.split;
 
 import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.DOCKED_LEFT;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 2dbc444..0b0c693 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -53,7 +53,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.Interpolators;
diff --git a/core/java/com/android/internal/policy/DockedDividerUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
similarity index 97%
rename from core/java/com/android/internal/policy/DockedDividerUtils.java
rename to libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
index b61b9de..f25dfea 100644
--- a/core/java/com/android/internal/policy/DockedDividerUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.internal.policy;
+package com.android.wm.shell.common.split;
 
 import static android.view.WindowManager.DOCKED_BOTTOM;
 import static android.view.WindowManager.DOCKED_INVALID;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index d3fada3..5d7e532 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -25,8 +25,8 @@
 import static android.view.WindowManager.DOCKED_TOP;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
-import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
-import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
+import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
+import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
 import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
 import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -58,8 +58,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.internal.policy.DockedDividerUtils;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.animation.Interpolators;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 9facbd5..b52a118 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -49,11 +49,11 @@
 import java.util.Optional;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
- * accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
+ * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
+ * {@link com.android.systemui.dagger.WMComponent}).
  *
- * This module only defines Shell dependencies for the TV SystemUI implementation.  Common
+ * <p>This module only defines Shell dependencies for the TV SystemUI implementation. Common
  * dependencies should go into {@link WMShellBaseModule}.
  */
 @Module(includes = {TvPipModule.class})
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 422e3b0..c06b22c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -36,6 +36,7 @@
 import com.android.wm.shell.back.BackAnimation;
 import com.android.wm.shell.back.BackAnimationBackground;
 import com.android.wm.shell.back.BackAnimationController;
+import com.android.wm.shell.back.ShellBackAnimationRegistry;
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.Bubbles;
 import com.android.wm.shell.common.DevicePostureController;
@@ -44,6 +45,7 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.DockStateReader;
+import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
@@ -73,7 +75,6 @@
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 import com.android.wm.shell.onehanded.OneHanded;
 import com.android.wm.shell.onehanded.OneHandedController;
-import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.recents.RecentTasks;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -107,9 +108,9 @@
 /**
  * Provides basic dependencies from {@link com.android.wm.shell}, these dependencies are only
  * accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * SysUIComponent, see {@link com.android.systemui.dagger.WMComponent}).
  *
- * This module only defines *common* dependencies across various SystemUI implementations,
+ * <p>This module only defines *common* dependencies across various SystemUI implementations,
  * dependencies that are device/form factor SystemUI implementation specific should go into their
  * respective modules (ie. {@link WMShellModule} for handheld, {@link TvWMShellModule} for tv, etc.)
  */
@@ -122,6 +123,12 @@
 
     @WMSingleton
     @Provides
+    static FloatingContentCoordinator provideFloatingContentCoordinator() {
+        return new FloatingContentCoordinator();
+    }
+
+    @WMSingleton
+    @Provides
     static DisplayController provideDisplayController(Context context,
             IWindowManager wmService,
             ShellInit shellInit,
@@ -303,16 +310,25 @@
             ShellController shellController,
             @ShellMainThread ShellExecutor shellExecutor,
             @ShellBackgroundThread Handler backgroundHandler,
-            BackAnimationBackground backAnimationBackground
-    ) {
+            BackAnimationBackground backAnimationBackground,
+            Optional<ShellBackAnimationRegistry> shellBackAnimationRegistry) {
         if (BackAnimationController.IS_ENABLED) {
-            return Optional.of(
-                    new BackAnimationController(shellInit, shellController, shellExecutor,
-                            backgroundHandler, context, backAnimationBackground));
+            return shellBackAnimationRegistry.map(
+                    (animations) ->
+                            new BackAnimationController(
+                                    shellInit,
+                                    shellController,
+                                    shellExecutor,
+                                    backgroundHandler,
+                                    context,
+                                    backAnimationBackground,
+                                    animations));
         }
         return Optional.empty();
     }
 
+    @BindsOptionalOf
+    abstract ShellBackAnimationRegistry optionalBackAnimationRegistry();
 
     //
     // Bubbles (optional feature)
@@ -797,7 +813,6 @@
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<BubbleController> bubblesOptional,
             Optional<SplitScreenController> splitScreenOptional,
-            Optional<Pip> pipOptional,
             FullscreenTaskListener fullscreenTaskListener,
             Optional<UnfoldAnimationController> unfoldAnimationController,
             Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 881c8f5..36d2a70 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -52,6 +52,7 @@
 import com.android.wm.shell.common.annotations.ShellAnimationThread;
 import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
 import com.android.wm.shell.dagger.pip.PipModule;
 import com.android.wm.shell.desktopmode.DesktopModeController;
 import com.android.wm.shell.desktopmode.DesktopModeStatus;
@@ -100,17 +101,19 @@
 import java.util.Optional;
 
 /**
- * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only
- * accessible from components within the WM subcomponent (can be explicitly exposed to the
- * SysUIComponent, see {@link WMComponent}).
+ * Provides dependencies from {@link com.android.wm.shell}, these dependencies are only accessible
+ * from components within the WM subcomponent (can be explicitly exposed to the SysUIComponent, see
+ * {@link WMComponent}).
  *
- * This module only defines Shell dependencies for handheld SystemUI implementation.  Common
+ * <p>This module only defines Shell dependencies for handheld SystemUI implementation. Common
  * dependencies should go into {@link WMShellBaseModule}.
  */
-@Module(includes = {
-        WMShellBaseModule.class,
-        PipModule.class
-})
+@Module(
+        includes = {
+            WMShellBaseModule.class,
+            PipModule.class,
+            ShellBackAnimationModule.class,
+        })
 public abstract class WMShellModule {
 
     //
@@ -325,12 +328,13 @@
             Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
             Optional<WindowDecorViewModel> windowDecorViewModel,
+            Optional<DesktopTasksController> desktopTasksController,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
                 shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
                 displayImeController, displayInsetsController, dragAndDropController, transitions,
                 transactionPool, iconProvider, recentTasks, launchAdjacentController,
-                windowDecorViewModel, mainExecutor);
+                windowDecorViewModel, desktopTasksController, mainExecutor);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
new file mode 100644
index 0000000..b34c6b2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/back/ShellBackAnimationModule.java
@@ -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.wm.shell.dagger.back;
+
+import com.android.wm.shell.back.CrossActivityAnimation;
+import com.android.wm.shell.back.CrossTaskBackAnimation;
+import com.android.wm.shell.back.CustomizeActivityAnimation;
+import com.android.wm.shell.back.ShellBackAnimation;
+import com.android.wm.shell.back.ShellBackAnimationRegistry;
+
+import dagger.Binds;
+import dagger.Module;
+import dagger.Provides;
+
+/** Default animation definitions for predictive back. */
+@Module
+public interface ShellBackAnimationModule {
+    /** Default animation registry */
+    @Provides
+    static ShellBackAnimationRegistry provideBackAnimationRegistry(
+            @ShellBackAnimation.CrossActivity ShellBackAnimation crossActivity,
+            @ShellBackAnimation.CrossTask ShellBackAnimation crossTask,
+            @ShellBackAnimation.CustomizeActivity ShellBackAnimation customizeActivity) {
+        return new ShellBackAnimationRegistry(
+                crossActivity,
+                crossTask,
+                customizeActivity,
+                /* defaultBackToHomeAnimation= */ null);
+    }
+
+    /** Default cross activity back animation */
+    @Binds
+    @ShellBackAnimation.CrossActivity
+    ShellBackAnimation bindCrossActivityShellBackAnimation(
+            CrossActivityAnimation crossActivityAnimation);
+
+    /** Default cross task back animation */
+    @Binds
+    @ShellBackAnimation.CrossTask
+    ShellBackAnimation provideCrossTaskShellBackAnimation(
+            CrossTaskBackAnimation crossTaskBackAnimation);
+
+    /** Default customized activity back animation */
+    @Binds
+    @ShellBackAnimation.CustomizeActivity
+    ShellBackAnimation provideCustomizeActivityShellBackAnimation(
+            CustomizeActivityAnimation customizeActivityAnimation);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 16c3960..9bf973f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -30,12 +30,14 @@
 import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
@@ -53,7 +55,6 @@
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
 import com.android.wm.shell.pip.phone.PipController;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.pip.phone.PipTouchHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -87,7 +88,6 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
             PipBoundsState pipBoundsState,
-            PipSizeSpecHandler pipSizeSpecHandler,
             PipDisplayLayoutState pipDisplayLayoutState,
             PipMotionHelper pipMotionHelper,
             PipMediaController pipMediaController,
@@ -110,8 +110,7 @@
                     context, shellInit, shellCommandHandler, shellController,
                     displayController, pipAnimationController, pipAppOpsListener,
                     pipBoundsAlgorithm,
-                    pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
-                    pipDisplayLayoutState,
+                    pipKeepClearAlgorithm, pipBoundsState, pipDisplayLayoutState,
                     pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer,
                     pipTransitionState, pipTouchHandler, pipTransitionController,
                     windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
@@ -123,8 +122,8 @@
     @WMSingleton
     @Provides
     static PipBoundsState providePipBoundsState(Context context,
-            PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
-        return new PipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
+            SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+        return new PipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
     }
 
     @WMSingleton
@@ -141,19 +140,12 @@
 
     @WMSingleton
     @Provides
-    static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
-            PipDisplayLayoutState pipDisplayLayoutState) {
-        return new PipSizeSpecHandler(context, pipDisplayLayoutState);
-    }
-
-    @WMSingleton
-    @Provides
     static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
             PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
             PhonePipKeepClearAlgorithm pipKeepClearAlgorithm,
-            PipSizeSpecHandler pipSizeSpecHandler) {
+            PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
         return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm,
-                pipKeepClearAlgorithm, pipSizeSpecHandler);
+                pipKeepClearAlgorithm, pipDisplayLayoutState, sizeSpecSource);
     }
 
     // Handler is used by Icon.loadDrawableAsync
@@ -177,14 +169,14 @@
             PhonePipMenuController menuPhoneController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipBoundsState pipBoundsState,
-            PipSizeSpecHandler pipSizeSpecHandler,
+            SizeSpecSource sizeSpecSource,
             PipTaskOrganizer pipTaskOrganizer,
             PipMotionHelper pipMotionHelper,
             FloatingContentCoordinator floatingContentCoordinator,
             PipUiEventLogger pipUiEventLogger,
             @ShellMainThread ShellExecutor mainExecutor) {
         return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
-                pipBoundsState, pipSizeSpecHandler, pipTaskOrganizer, pipMotionHelper,
+                pipBoundsState, sizeSpecSource, pipTaskOrganizer, pipMotionHelper,
                 floatingContentCoordinator, pipUiEventLogger, mainExecutor);
     }
 
@@ -243,6 +235,13 @@
 
     @WMSingleton
     @Provides
+    static SizeSpecSource provideSizeSpecSource(Context context,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        return new PhoneSizeSpecSource(context, pipDisplayLayoutState);
+    }
+
+    @WMSingleton
+    @Provides
     static PipAppOpsListener providePipAppOpsListener(Context context,
             PipTouchHandler pipTouchHandler,
             @ShellMainThread ShellExecutor mainExecutor) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
index f29b3a3..e8fae24 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1SharedModule.java
@@ -21,7 +21,6 @@
 import android.os.Handler;
 
 import com.android.internal.logging.UiEventLogger;
-import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.pip.PipMediaController;
@@ -37,12 +36,6 @@
  */
 @Module
 public abstract class Pip1SharedModule {
-    @WMSingleton
-    @Provides
-    static FloatingContentCoordinator provideFloatingContentCoordinator() {
-        return new FloatingContentCoordinator();
-    }
-
     // Needs handler for registering broadcast receivers
     @WMSingleton
     @Provides
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 360bf8b..80ffbb0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -28,11 +28,13 @@
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -42,7 +44,6 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
 import com.android.wm.shell.pip.PipUiEventLogger;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.pip.tv.TvPipBoundsAlgorithm;
 import com.android.wm.shell.pip.tv.TvPipBoundsController;
 import com.android.wm.shell.pip.tv.TvPipBoundsState;
@@ -138,23 +139,23 @@
     @Provides
     static TvPipBoundsAlgorithm provideTvPipBoundsAlgorithm(Context context,
             TvPipBoundsState tvPipBoundsState, PipSnapAlgorithm pipSnapAlgorithm,
-            PipSizeSpecHandler pipSizeSpecHandler) {
+            PipDisplayLayoutState pipDisplayLayoutState, SizeSpecSource sizeSpecSource) {
         return new TvPipBoundsAlgorithm(context, tvPipBoundsState, pipSnapAlgorithm,
-                pipSizeSpecHandler);
+                pipDisplayLayoutState, sizeSpecSource);
     }
 
     @WMSingleton
     @Provides
     static TvPipBoundsState provideTvPipBoundsState(Context context,
-            PipSizeSpecHandler pipSizeSpecHandler, PipDisplayLayoutState pipDisplayLayoutState) {
-        return new TvPipBoundsState(context, pipSizeSpecHandler, pipDisplayLayoutState);
+            SizeSpecSource sizeSpecSource, PipDisplayLayoutState pipDisplayLayoutState) {
+        return new TvPipBoundsState(context, sizeSpecSource, pipDisplayLayoutState);
     }
 
     @WMSingleton
     @Provides
-    static PipSizeSpecHandler providePipSizeSpecHelper(Context context,
+    static SizeSpecSource provideSizeSpecSource(Context context,
             PipDisplayLayoutState pipDisplayLayoutState) {
-        return new PipSizeSpecHandler(context, pipDisplayLayoutState);
+        return new LegacySizeSpecSource(context, pipDisplayLayoutState);
     }
 
     // Handler needed for loadDrawableAsync() in PipControlsViewController
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
index db6c258..5b24d7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeController.java
@@ -535,6 +535,11 @@
         }
 
         @Override
+        public void onDesktopSplitSelectAnimComplete(RunningTaskInfo taskInfo) {
+
+        }
+
+        @Override
         public void stashDesktopApps(int displayId) throws RemoteException {
             // Stashing of desktop apps not needed. Apps always launch on desktop
         }
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 b15fd91..1d46e75 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
@@ -22,6 +22,7 @@
 import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
+import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
@@ -33,7 +34,6 @@
 import android.os.SystemProperties
 import android.util.DisplayMetrics.DENSITY_DEFAULT
 import android.view.SurfaceControl
-import android.view.SurfaceControl.Transaction
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_NONE
 import android.view.WindowManager.TRANSIT_OPEN
@@ -56,6 +56,7 @@
 import com.android.wm.shell.common.annotations.ShellMainThread
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
 import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
@@ -105,6 +106,9 @@
         get() = context.resources.getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.desktop_mode_transition_area_height)
 
+    // This is public to avoid cyclic dependency; it is set by SplitScreenController
+    lateinit var splitScreenController: SplitScreenController
+
     init {
         desktopMode = DesktopModeImpl()
         if (DesktopModeStatus.isProto2Enabled()) {
@@ -262,6 +266,19 @@
         }
     }
 
+    /**
+     * Perform needed cleanup transaction once animation is complete. Bounds need to be set
+     * here instead of initial wct to both avoid flicker and to have task bounds to use for
+     * the staging animation.
+     *
+     * @param taskInfo task entering split that requires a bounds update
+     */
+    fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
+        val wct = WindowContainerTransaction()
+        wct.setBounds(taskInfo.token, Rect())
+        shellTaskOrganizer.applyTransaction(wct)
+    }
+
     /** Move a task with given `taskId` to fullscreen */
     fun moveToFullscreen(taskId: Int) {
         shellTaskOrganizer.getRunningTaskInfo(taskId)?.let { task -> moveToFullscreen(task) }
@@ -296,7 +313,7 @@
             task.taskId
         )
         val wct = WindowContainerTransaction()
-        wct.setBounds(task.token, null)
+        wct.setBounds(task.token, Rect())
 
         if (Transitions.ENABLE_SHELL_TRANSITIONS) {
             enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct,
@@ -533,6 +550,7 @@
         )
         // Check if we should skip handling this transition
         var reason = ""
+        val triggerTask = request.triggerTask
         val shouldHandleRequest =
             when {
                 // Only handle open or to front transitions
@@ -541,19 +559,19 @@
                     false
                 }
                 // Only handle when it is a task transition
-                request.triggerTask == null -> {
+                triggerTask == null -> {
                     reason = "triggerTask is null"
                     false
                 }
                 // Only handle standard type tasks
-                request.triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> {
-                    reason = "activityType not handled (${request.triggerTask.activityType})"
+                triggerTask.activityType != ACTIVITY_TYPE_STANDARD -> {
+                    reason = "activityType not handled (${triggerTask.activityType})"
                     false
                 }
                 // Only handle fullscreen or freeform tasks
-                request.triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
-                        request.triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
-                    reason = "windowingMode not handled (${request.triggerTask.windowingMode})"
+                triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
+                        triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
+                    reason = "windowingMode not handled (${triggerTask.windowingMode})"
                     false
                 }
                 // Otherwise process it
@@ -569,17 +587,17 @@
             return null
         }
 
-        val task: RunningTaskInfo = request.triggerTask
-
-        val result = when {
-            // If display has tasks stashed, handle as stashed launch
-            desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
-            // Check if fullscreen task should be updated
-            task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
-            // Check if freeform task should be updated
-            task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
-            else -> {
-                null
+        val result = triggerTask?.let { task ->
+            when {
+                // If display has tasks stashed, handle as stashed launch
+                desktopModeTaskRepository.isStashed(task.displayId) -> handleStashedTaskLaunch(task)
+                // Check if fullscreen task should be updated
+                task.windowingMode == WINDOWING_MODE_FULLSCREEN -> handleFullscreenTaskLaunch(task)
+                // Check if freeform task should be updated
+                task.windowingMode == WINDOWING_MODE_FREEFORM -> handleFreeformTaskLaunch(task)
+                else -> {
+                    null
+                }
             }
         }
         KtProtoLog.v(
@@ -686,13 +704,43 @@
             WINDOWING_MODE_FULLSCREEN
         }
         wct.setWindowingMode(taskInfo.token, targetWindowingMode)
-        wct.setBounds(taskInfo.token, null)
+        wct.setBounds(taskInfo.token, Rect())
         if (isDesktopDensityOverrideSet()) {
-            wct.setDensityDpi(taskInfo.token, getFullscreenDensityDpi())
+            wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
         }
     }
 
-    private fun getFullscreenDensityDpi(): Int {
+    /**
+     * Adds split screen changes to a transaction. Note that bounds are not reset here due to
+     * animation; see {@link onDesktopSplitSelectAnimComplete}
+     */
+    private fun addMoveToSplitChanges(
+        wct: WindowContainerTransaction,
+        taskInfo: RunningTaskInfo
+    ) {
+        wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW)
+        // The task's density may have been overridden in freeform; revert it here as we don't
+        // want it overridden in multi-window.
+        wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi())
+    }
+
+    /**
+     * Requests a task be transitioned from desktop to split select. Applies needed windowing
+     * changes if this transition is enabled.
+     */
+    fun requestSplit(
+        taskInfo: RunningTaskInfo
+    ) {
+        val windowingMode = taskInfo.windowingMode
+        if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM
+        ) {
+            val wct = WindowContainerTransaction()
+            addMoveToSplitChanges(wct, taskInfo)
+            splitScreenController.requestEnterSplitSelect(taskInfo, wct)
+        }
+    }
+
+    private fun getDefaultDensityDpi(): Int {
         return context.resources.displayMetrics.densityDpi
     }
 
@@ -969,6 +1017,13 @@
             return result[0]
         }
 
+        override fun onDesktopSplitSelectAnimComplete(taskInfo: RunningTaskInfo) {
+            ExecutorUtils.executeRemoteCallWithTaskPermission(
+                controller,
+                "onDesktopSplitSelectAnimComplete"
+            ) { c -> c.onDesktopSplitSelectAnimComplete(taskInfo) }
+        }
+
         override fun setTaskListener(listener: IDesktopTaskListener?) {
             KtProtoLog.v(
                     WM_SHELL_DESKTOP_MODE,
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 16b2393..024465b 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
@@ -241,7 +241,7 @@
             public void onAnimationEnd(Animator animation) {
                 mDesktopModeWindowDecoration.hideResizeVeil();
                 mTransitions.getMainExecutor().execute(
-                        () -> finishCallback.onTransitionFinished(null, null));
+                        () -> finishCallback.onTransitionFinished(null));
             }
         });
         animator.start();
@@ -271,8 +271,7 @@
 
         startT.apply();
 
-        mTransitions.getMainExecutor().execute(
-                () -> finishCallback.onTransitionFinished(null, null));
+        mTransitions.getMainExecutor().execute(() -> finishCallback.onTransitionFinished(null));
 
         return true;
     }
@@ -324,7 +323,7 @@
                     mOnAnimationFinishedCallback.accept(finishT);
                 }
                 mTransitions.getMainExecutor().execute(
-                        () -> finishCallback.onTransitionFinished(null, null));
+                        () -> finishCallback.onTransitionFinished(null));
             }
         });
 
@@ -378,7 +377,7 @@
                     mOnAnimationFinishedCallback.accept(finishT);
                 }
                 mTransitions.getMainExecutor().execute(
-                        () -> finishCallback.onTransitionFinished(null, null));
+                        () -> finishCallback.onTransitionFinished(null));
             }
         });
         animator.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
index 3ad5edf..7342bd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java
@@ -168,7 +168,7 @@
                         mOnAnimationFinishedCallback.accept(finishT);
                     }
                     mTransitions.getMainExecutor().execute(
-                            () -> finishCallback.onTransitionFinished(null, null));
+                            () -> finishCallback.onTransitionFinished(null));
                 }
             });
             animator.start();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
index ee3a080..47edfd4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.desktopmode;
 
+import android.app.ActivityManager.RunningTaskInfo;
 import com.android.wm.shell.desktopmode.IDesktopTaskListener;
 
 /**
@@ -38,6 +39,9 @@
     /** Get count of visible desktop tasks on the given display */
     int getVisibleTaskCount(int displayId);
 
+    /** Perform cleanup transactions after the animation to split select is complete */
+    oneway void onDesktopSplitSelectAnimComplete(in RunningTaskInfo taskInfo);
+
     /** Set listener that will receive callbacks about updates to desktop tasks */
     oneway void setTaskListener(IDesktopTaskListener listener);
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
index 94788e4..9debb25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ToggleResizeDesktopTaskTransitionHandler.kt
@@ -69,7 +69,7 @@
     ): Boolean {
         val change = findRelevantChange(info)
         val leash = change.leash
-        val taskId = change.taskInfo.taskId
+        val taskId = checkNotNull(change.taskInfo).taskId
         val startBounds = change.startAbsBounds
         val endBounds = change.endAbsBounds
         val windowDecor =
@@ -104,7 +104,7 @@
                                 .setWindowCrop(leash, endBounds.width(), endBounds.height())
                                 .show(leash)
                             windowDecor.hideResizeVeil()
-                            finishCallback.onTransitionFinished(null, null)
+                            finishCallback.onTransitionFinished(null)
                             boundsAnimator = null
                         }
                     )
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
index 55e34fe..8402775 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskTransitionHandler.java
@@ -130,7 +130,7 @@
             if (!animations.isEmpty()) return;
             mMainExecutor.execute(() -> {
                 mAnimations.remove(transition);
-                finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+                finishCallback.onTransitionFinished(null /* wct */);
             });
         };
         for (TransitionInfo.Change change : info.getChanges()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 56bd188..2ef92ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -19,7 +19,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
 import static android.view.WindowManager.TRANSIT_SLEEP;
@@ -169,7 +168,7 @@
                             // Post our finish callback to let startAnimation finish first.
                             mMainExecutor.executeDelayed(() -> {
                                 mStartedTransitions.remove(transition);
-                                finishCallback.onTransitionFinished(wct, null);
+                                finishCallback.onTransitionFinished(wct);
                             }, 0);
                         }
                     });
@@ -206,7 +205,7 @@
                 // implementing an AIDL interface.
                 Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
             }
-            nextFinishCallback.onTransitionFinished(null, null);
+            nextFinishCallback.onTransitionFinished(null);
         } else if (nextInfo.getType() == TRANSIT_SLEEP) {
             // An empty SLEEP transition comes in as a signal to abort transitions whenever a sleep
             // token is held. In cases where keyguard is showing, we are running the animation for
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
deleted file mode 100644
index 48a3fc2..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
-
-import android.app.AppOpsManager;
-import android.app.AppOpsManager.OnOpChangedListener;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.util.Pair;
-
-import com.android.wm.shell.common.ShellExecutor;
-
-public class PipAppOpsListener {
-    private static final String TAG = PipAppOpsListener.class.getSimpleName();
-
-    private Context mContext;
-    private ShellExecutor mMainExecutor;
-    private AppOpsManager mAppOpsManager;
-    private Callback mCallback;
-
-    private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
-        @Override
-        public void onOpChanged(String op, String packageName) {
-            try {
-                // Dismiss the PiP once the user disables the app ops setting for that package
-                final Pair<ComponentName, Integer> topPipActivityInfo =
-                        PipUtils.getTopPipActivity(mContext);
-                if (topPipActivityInfo.first != null) {
-                    final ApplicationInfo appInfo = mContext.getPackageManager()
-                            .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
-                    if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
-                            mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
-                                    packageName) != MODE_ALLOWED) {
-                        mMainExecutor.execute(() -> mCallback.dismissPip());
-                    }
-                }
-            } catch (NameNotFoundException e) {
-                // Unregister the listener if the package can't be found
-                unregisterAppOpsListener();
-            }
-        }
-    };
-
-    public PipAppOpsListener(Context context, Callback callback, ShellExecutor mainExecutor) {
-        mContext = context;
-        mMainExecutor = mainExecutor;
-        mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
-        mCallback = callback;
-    }
-
-    public void onActivityPinned(String packageName) {
-        // Register for changes to the app ops setting for this package while it is in PiP
-        registerAppOpsListener(packageName);
-    }
-
-    public void onActivityUnpinned() {
-        // Unregister for changes to the previously PiP'ed package
-        unregisterAppOpsListener();
-    }
-
-    private void registerAppOpsListener(String packageName) {
-        mAppOpsManager.startWatchingMode(OP_PICTURE_IN_PICTURE, packageName,
-                mAppOpsChangedListener);
-    }
-
-    private void unregisterAppOpsListener() {
-        mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
-    }
-
-    /** Callback for PipAppOpsListener to request changes to the PIP window. */
-    public interface Callback {
-        /** Dismisses the PIP window. */
-        void dismissPip();
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
index f51eb52..ac711ea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsAlgorithm.java
@@ -28,7 +28,7 @@
 import android.view.Gravity;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 
 import java.io.PrintWriter;
 
@@ -41,7 +41,8 @@
     private static final float INVALID_SNAP_FRACTION = -1f;
 
     @NonNull private final PipBoundsState mPipBoundsState;
-    @NonNull protected final PipSizeSpecHandler mPipSizeSpecHandler;
+    @NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState;
+    @NonNull protected final SizeSpecSource mSizeSpecSource;
     private final PipSnapAlgorithm mSnapAlgorithm;
     private final PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
 
@@ -53,11 +54,13 @@
     public PipBoundsAlgorithm(Context context, @NonNull PipBoundsState pipBoundsState,
             @NonNull PipSnapAlgorithm pipSnapAlgorithm,
             @NonNull PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
-            @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
+            @NonNull PipDisplayLayoutState pipDisplayLayoutState,
+            @NonNull SizeSpecSource sizeSpecSource) {
         mPipBoundsState = pipBoundsState;
         mSnapAlgorithm = pipSnapAlgorithm;
         mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
-        mPipSizeSpecHandler = pipSizeSpecHandler;
+        mPipDisplayLayoutState = pipDisplayLayoutState;
+        mSizeSpecSource = sizeSpecSource;
         reloadResources(context);
         // Initialize the aspect ratio to the default aspect ratio.  Don't do this in reload
         // resources as it would clobber mAspectRatio when entering PiP from fullscreen which
@@ -74,11 +77,6 @@
                 R.dimen.config_pictureInPictureDefaultAspectRatio);
         mDefaultStackGravity = res.getInteger(
                 R.integer.config_defaultPictureInPictureGravity);
-        final String screenEdgeInsetsDpString = res.getString(
-                R.string.config_defaultPictureInPictureScreenEdgeInsets);
-        final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
-                ? Size.parseSize(screenEdgeInsetsDpString)
-                : null;
         mMinAspectRatio = res.getFloat(
                 com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio);
         mMaxAspectRatio = res.getFloat(
@@ -160,8 +158,8 @@
             // If either dimension is smaller than the allowed minimum, adjust them
             // according to mOverridableMinSize
             return new Size(
-                    Math.max(windowLayout.minWidth, mPipSizeSpecHandler.getOverrideMinEdgeSize()),
-                    Math.max(windowLayout.minHeight, mPipSizeSpecHandler.getOverrideMinEdgeSize()));
+                    Math.max(windowLayout.minWidth, getOverrideMinEdgeSize()),
+                    Math.max(windowLayout.minHeight, getOverrideMinEdgeSize()));
         }
         return null;
     }
@@ -255,10 +253,10 @@
         final Size size;
         if (useCurrentMinEdgeSize || useCurrentSize) {
             // Use the existing size but adjusted to the new aspect ratio.
-            size = mPipSizeSpecHandler.getSizeForAspectRatio(
+            size = mSizeSpecSource.getSizeForAspectRatio(
                     new Size(stackBounds.width(), stackBounds.height()), aspectRatio);
         } else {
-            size = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
+            size = mSizeSpecSource.getDefaultSize(aspectRatio);
         }
 
         final int left = (int) (stackBounds.centerX() - size.getWidth() / 2f);
@@ -287,7 +285,7 @@
         getInsetBounds(insetBounds);
 
         // Calculate the default size
-        defaultSize = mPipSizeSpecHandler.getDefaultSize(mDefaultAspectRatio);
+        defaultSize = mSizeSpecSource.getDefaultSize(mDefaultAspectRatio);
 
         // Now that we have the default size, apply the snap fraction if valid or position the
         // bounds using the default gravity.
@@ -309,7 +307,11 @@
      * Populates the bounds on the screen that the PIP can be visible in.
      */
     public void getInsetBounds(Rect outRect) {
-        outRect.set(mPipSizeSpecHandler.getInsetBounds());
+        outRect.set(mPipDisplayLayoutState.getInsetBounds());
+    }
+
+    private int getOverrideMinEdgeSize() {
+        return mSizeSpecSource.getOverrideMinEdgeSize();
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
index 9a775df..279ffc5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java
@@ -36,7 +36,7 @@
 import com.android.internal.util.function.TriConsumer;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.io.PrintWriter;
@@ -87,7 +87,7 @@
     private int mStashOffset;
     private @Nullable PipReentryState mPipReentryState;
     private final LauncherState mLauncherState = new LauncherState();
-    private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler;
+    private final @NonNull SizeSpecSource mSizeSpecSource;
     private @Nullable ComponentName mLastPipComponentName;
     private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState();
     private boolean mIsImeShowing;
@@ -127,17 +127,20 @@
     private @Nullable TriConsumer<Boolean, Integer, Boolean> mOnShelfVisibilityChangeCallback;
     private List<Consumer<Rect>> mOnPipExclusionBoundsChangeCallbacks = new ArrayList<>();
 
-    public PipBoundsState(@NonNull Context context, PipSizeSpecHandler pipSizeSpecHandler,
-            PipDisplayLayoutState pipDisplayLayoutState) {
+    public PipBoundsState(@NonNull Context context, @NonNull SizeSpecSource sizeSpecSource,
+            @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
         mContext = context;
         reloadResources();
-        mPipSizeSpecHandler = pipSizeSpecHandler;
+        mSizeSpecSource = sizeSpecSource;
         mPipDisplayLayoutState = pipDisplayLayoutState;
     }
 
     /** Reloads the resources. */
     public void onConfigurationChanged() {
         reloadResources();
+
+        // update the size spec resources upon config change too
+        mSizeSpecSource.onConfigurationChanged();
     }
 
     private void reloadResources() {
@@ -319,7 +322,7 @@
     /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
     public void setOverrideMinSize(@Nullable Size overrideMinSize) {
         final boolean changed = !Objects.equals(overrideMinSize, getOverrideMinSize());
-        mPipSizeSpecHandler.setOverrideMinSize(overrideMinSize);
+        mSizeSpecSource.setOverrideMinSize(overrideMinSize);
         if (changed && mOnMinimalSizeChangeCallback != null) {
             mOnMinimalSizeChangeCallback.run();
         }
@@ -328,12 +331,12 @@
     /** Returns the preferred minimal size specified by the activity in PIP. */
     @Nullable
     public Size getOverrideMinSize() {
-        return mPipSizeSpecHandler.getOverrideMinSize();
+        return mSizeSpecSource.getOverrideMinSize();
     }
 
     /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
     public int getOverrideMinEdgeSize() {
-        return mPipSizeSpecHandler.getOverrideMinEdgeSize();
+        return mSizeSpecSource.getOverrideMinEdgeSize();
     }
 
     /** Get the state of the bounds in motion. */
@@ -613,5 +616,6 @@
         }
         mLauncherState.dump(pw, innerPrefix);
         mMotionBoundsState.dump(pw, innerPrefix);
+        mSizeSpecSource.dump(pw, innerPrefix);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
index 0f76af4..456f85b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipDisplayLayoutState.java
@@ -16,12 +16,18 @@
 
 package com.android.wm.shell.pip;
 
+import static com.android.wm.shell.pip.PipUtils.dpToPx;
+
 import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Point;
 import android.graphics.Rect;
+import android.util.Size;
 import android.view.Surface;
 
 import androidx.annotation.NonNull;
 
+import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.dagger.WMSingleton;
 
@@ -40,13 +46,51 @@
     private int mDisplayId;
     @NonNull private DisplayLayout mDisplayLayout;
 
+    private Point mScreenEdgeInsets = null;
+
     @Inject
     public PipDisplayLayoutState(Context context) {
         mContext = context;
         mDisplayLayout = new DisplayLayout();
+        reloadResources();
     }
 
-    /** Update the display layout. */
+    /** Responds to configuration change. */
+    public void onConfigurationChanged() {
+        reloadResources();
+    }
+
+    private void reloadResources() {
+        Resources res = mContext.getResources();
+
+        final String screenEdgeInsetsDpString = res.getString(
+                R.string.config_defaultPictureInPictureScreenEdgeInsets);
+        final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
+                ? Size.parseSize(screenEdgeInsetsDpString)
+                : null;
+        mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
+                : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
+                        dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
+    }
+
+    public Point getScreenEdgeInsets() {
+        return mScreenEdgeInsets;
+    }
+
+    /**
+     * Returns the inset bounds the PIP window can be visible in.
+     */
+    public Rect getInsetBounds() {
+        Rect insetBounds = new Rect();
+        Rect insets = getDisplayLayout().stableInsets();
+        insetBounds.set(insets.left + getScreenEdgeInsets().x,
+                insets.top + getScreenEdgeInsets().y,
+                getDisplayLayout().width() - insets.right - getScreenEdgeInsets().x,
+                getDisplayLayout().height() - insets.bottom - getScreenEdgeInsets().y);
+        return insetBounds;
+    }
+
+    /** Set the display layout. */
     public void setDisplayLayout(@NonNull DisplayLayout displayLayout) {
         mDisplayLayout.set(displayLayout);
     }
@@ -87,5 +131,6 @@
         pw.println(prefix + TAG);
         pw.println(innerPrefix + "mDisplayId=" + mDisplayId);
         pw.println(innerPrefix + "getDisplayBounds=" + getDisplayBounds());
+        pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets);
     }
 }
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 e3d53fc..2563d98 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
@@ -447,7 +447,7 @@
         // handler if there is a pending PiP animation.
         final Transitions.TransitionFinishCallback finishCallback = mFinishCallback;
         mFinishCallback = null;
-        finishCallback.onTransitionFinished(wct, null /* callback */);
+        finishCallback.onTransitionFinished(wct);
     }
 
     @Override
@@ -456,7 +456,7 @@
         // for example, when app crashes while in PiP and exit transition has not started
         mCurrentPipTaskToken = null;
         if (mFinishCallback == null) return;
-        mFinishCallback.onTransitionFinished(null /* wct */, null /* callback */);
+        mFinishCallback.onTransitionFinished(null /* wct */);
         mFinishCallback = null;
         mFinishTransaction = null;
     }
@@ -586,7 +586,7 @@
         final boolean useLocalLeash = activitySc != null;
         final boolean toFullscreen = pipChange.getEndAbsBounds().equals(
                 mPipBoundsState.getDisplayBounds());
-        mFinishCallback = (wct, wctCB) -> {
+        mFinishCallback = (wct) -> {
             mPipOrganizer.onExitPipFinished(taskInfo);
 
             // TODO(b/286346098): remove the OPEN app flicker completely
@@ -610,7 +610,7 @@
                 mPipAnimationController.resetAnimatorState();
                 finishTransaction.remove(pipLeash);
             }
-            finishCallback.onTransitionFinished(wct, wctCB);
+            finishCallback.onTransitionFinished(wct);
         };
         mFinishTransaction = finishTransaction;
 
@@ -750,7 +750,7 @@
         finishTransaction.setWindowCrop(info.getChanges().get(0).getLeash(),
                 mPipDisplayLayoutState.getDisplayBounds());
         mPipOrganizer.onExitPipFinished(taskInfo);
-        finishCallback.onTransitionFinished(null, null);
+        finishCallback.onTransitionFinished(null);
     }
 
     /** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */
@@ -1045,7 +1045,7 @@
         startTransaction.apply();
 
         mPipOrganizer.onExitPipFinished(taskInfo);
-        finishCallback.onTransitionFinished(null, null);
+        finishCallback.onTransitionFinished(null);
     }
 
     private void resetPrevPip(@NonNull TransitionInfo.Change prevPipTaskChange,
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 26b7b68..b872267 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
@@ -75,6 +75,7 @@
 import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
 import com.android.wm.shell.pip.IPip;
@@ -82,7 +83,6 @@
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
@@ -142,7 +142,6 @@
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
     private PipKeepClearAlgorithmInterface mPipKeepClearAlgorithm;
     private PipBoundsState mPipBoundsState;
-    private PipSizeSpecHandler mPipSizeSpecHandler;
     private PipDisplayLayoutState mPipDisplayLayoutState;
     private PipMotionHelper mPipMotionHelper;
     private PipTouchHandler mTouchHandler;
@@ -406,7 +405,6 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
             PipBoundsState pipBoundsState,
-            PipSizeSpecHandler pipSizeSpecHandler,
             PipDisplayLayoutState pipDisplayLayoutState,
             PipMotionHelper pipMotionHelper,
             PipMediaController pipMediaController,
@@ -430,7 +428,7 @@
 
         return new PipController(context, shellInit, shellCommandHandler, shellController,
                 displayController, pipAnimationController, pipAppOpsListener,
-                pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipSizeSpecHandler,
+                pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
                 pipDisplayLayoutState, pipMotionHelper, pipMediaController, phonePipMenuController,
                 pipTaskOrganizer, pipTransitionState, pipTouchHandler, pipTransitionController,
                 windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
@@ -448,7 +446,6 @@
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipKeepClearAlgorithmInterface pipKeepClearAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
-            PipSizeSpecHandler pipSizeSpecHandler,
             @NonNull PipDisplayLayoutState pipDisplayLayoutState,
             PipMotionHelper pipMotionHelper,
             PipMediaController pipMediaController,
@@ -474,7 +471,6 @@
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
         mPipBoundsState = pipBoundsState;
-        mPipSizeSpecHandler = pipSizeSpecHandler;
         mPipDisplayLayoutState = pipDisplayLayoutState;
         mPipMotionHelper = pipMotionHelper;
         mPipTaskOrganizer = pipTaskOrganizer;
@@ -711,7 +707,7 @@
             // Try to move the PiP window if we have entered PiP mode.
             if (mPipTransitionState.hasEnteredPip()) {
                 final Rect pipBounds = mPipBoundsState.getBounds();
-                final Point edgeInsets = mPipSizeSpecHandler.getScreenEdgeInsets();
+                final Point edgeInsets = mPipDisplayLayoutState.getScreenEdgeInsets();
                 if ((pipBounds.height() + 2 * edgeInsets.y) > (displayBounds.height() / 2)) {
                     // PiP bounds is too big to fit either half, bail early.
                     return;
@@ -770,7 +766,7 @@
         mPipBoundsAlgorithm.onConfigurationChanged(mContext);
         mTouchHandler.onConfigurationChanged();
         mPipBoundsState.onConfigurationChanged();
-        mPipSizeSpecHandler.onConfigurationChanged();
+        mPipDisplayLayoutState.onConfigurationChanged();
     }
 
     @Override
@@ -1224,7 +1220,6 @@
         mPipTaskOrganizer.dump(pw, innerPrefix);
         mPipBoundsState.dump(pw, innerPrefix);
         mPipInputConsumer.dump(pw, innerPrefix);
-        mPipSizeSpecHandler.dump(pw, innerPrefix);
         mPipDisplayLayoutState.dump(pw, innerPrefix);
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 43d3f36..b251f6f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -41,7 +41,7 @@
 import com.android.wm.shell.animation.PhysicsAnimator;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
-import com.android.wm.shell.pip.PipAppOpsListener;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
 import com.android.wm.shell.pip.PipTaskOrganizer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
deleted file mode 100644
index c6e5cf2..0000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipSizeSpecHandler.java
+++ /dev/null
@@ -1,536 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip.phone;
-
-import static com.android.wm.shell.pip.PipUtils.dpToPx;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.graphics.Point;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.os.SystemProperties;
-import android.util.Size;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.PipDisplayLayoutState;
-
-import java.io.PrintWriter;
-
-/**
- * Acts as a source of truth for appropriate size spec for PIP.
- */
-public class PipSizeSpecHandler {
-    private static final String TAG = PipSizeSpecHandler.class.getSimpleName();
-
-    @NonNull private final PipDisplayLayoutState mPipDisplayLayoutState;
-
-    private final SizeSpecSource mSizeSpecSourceImpl;
-
-    /** The preferred minimum (and default minimum) size specified by apps. */
-    @Nullable private Size mOverrideMinSize;
-    private int mOverridableMinSize;
-
-    /** Used to store values obtained from resource files. */
-    private Point mScreenEdgeInsets;
-    private float mMinAspectRatioForMinSize;
-    private float mMaxAspectRatioForMinSize;
-    private int mDefaultMinSize;
-
-    @NonNull private final Context mContext;
-
-    private interface SizeSpecSource {
-        /** Returns max size allowed for the PIP window */
-        Size getMaxSize(float aspectRatio);
-
-        /** Returns default size for the PIP window */
-        Size getDefaultSize(float aspectRatio);
-
-        /** Returns min size allowed for the PIP window */
-        Size getMinSize(float aspectRatio);
-
-        /** Returns the adjusted size based on current size and target aspect ratio */
-        Size getSizeForAspectRatio(Size size, float aspectRatio);
-
-        /** Updates internal resources on configuration changes */
-        default void reloadResources() {}
-    }
-
-    /**
-     * Determines PIP window size optimized for large screens and aspect ratios close to 1:1
-     */
-    private class SizeSpecLargeScreenOptimizedImpl implements SizeSpecSource {
-        private static final float DEFAULT_OPTIMIZED_ASPECT_RATIO = 9f / 16;
-
-        /** Default and minimum percentages for the PIP size logic. */
-        private final float mDefaultSizePercent;
-        private final float mMinimumSizePercent;
-
-        /** Aspect ratio that the PIP size spec logic optimizes for. */
-        private float mOptimizedAspectRatio;
-
-        private SizeSpecLargeScreenOptimizedImpl() {
-            mDefaultSizePercent = Float.parseFloat(SystemProperties
-                    .get("com.android.wm.shell.pip.phone.def_percentage", "0.6"));
-            mMinimumSizePercent = Float.parseFloat(SystemProperties
-                    .get("com.android.wm.shell.pip.phone.min_percentage", "0.5"));
-        }
-
-        @Override
-        public void reloadResources() {
-            final Resources res = mContext.getResources();
-
-            mOptimizedAspectRatio = res.getFloat(R.dimen.config_pipLargeScreenOptimizedAspectRatio);
-            // make sure the optimized aspect ratio is valid with a default value to fall back to
-            if (mOptimizedAspectRatio > 1) {
-                mOptimizedAspectRatio = DEFAULT_OPTIMIZED_ASPECT_RATIO;
-            }
-        }
-
-        /**
-         * Calculates the max size of PIP.
-         *
-         * Optimizes for 16:9 aspect ratios, making them take full length of shortest display edge.
-         * As aspect ratio approaches values close to 1:1, the logic does not let PIP occupy the
-         * whole screen. A linear function is used to calculate these sizes.
-         *
-         * @param aspectRatio aspect ratio of the PIP window
-         * @return dimensions of the max size of the PIP
-         */
-        @Override
-        public Size getMaxSize(float aspectRatio) {
-            final int totalHorizontalPadding = getInsetBounds().left
-                    + (getDisplayBounds().width() - getInsetBounds().right);
-            final int totalVerticalPadding = getInsetBounds().top
-                    + (getDisplayBounds().height() - getInsetBounds().bottom);
-
-            final int shorterLength = Math.min(getDisplayBounds().width() - totalHorizontalPadding,
-                    getDisplayBounds().height() - totalVerticalPadding);
-
-            int maxWidth, maxHeight;
-
-            // use the optimized max sizing logic only within a certain aspect ratio range
-            if (aspectRatio >= mOptimizedAspectRatio && aspectRatio <= 1 / mOptimizedAspectRatio) {
-                // this formula and its derivation is explained in b/198643358#comment16
-                maxWidth = Math.round(mOptimizedAspectRatio * shorterLength
-                        + shorterLength * (aspectRatio - mOptimizedAspectRatio) / (1
-                        + aspectRatio));
-                // make sure the max width doesn't go beyond shorter screen length after rounding
-                maxWidth = Math.min(maxWidth, shorterLength);
-                maxHeight = Math.round(maxWidth / aspectRatio);
-            } else {
-                if (aspectRatio > 1f) {
-                    maxWidth = shorterLength;
-                    maxHeight = Math.round(maxWidth / aspectRatio);
-                } else {
-                    maxHeight = shorterLength;
-                    maxWidth = Math.round(maxHeight * aspectRatio);
-                }
-            }
-
-            return new Size(maxWidth, maxHeight);
-        }
-
-        /**
-         * Decreases the dimensions by a percentage relative to max size to get default size.
-         *
-         * @param aspectRatio aspect ratio of the PIP window
-         * @return dimensions of the default size of the PIP
-         */
-        @Override
-        public Size getDefaultSize(float aspectRatio) {
-            Size minSize = this.getMinSize(aspectRatio);
-
-            if (mOverrideMinSize != null) {
-                return minSize;
-            }
-
-            Size maxSize = this.getMaxSize(aspectRatio);
-
-            int defaultWidth = Math.max(Math.round(maxSize.getWidth() * mDefaultSizePercent),
-                    minSize.getWidth());
-            int defaultHeight = Math.round(defaultWidth / aspectRatio);
-
-            return new Size(defaultWidth, defaultHeight);
-        }
-
-        /**
-         * Decreases the dimensions by a certain percentage relative to max size to get min size.
-         *
-         * @param aspectRatio aspect ratio of the PIP window
-         * @return dimensions of the min size of the PIP
-         */
-        @Override
-        public Size getMinSize(float aspectRatio) {
-            // if there is an overridden min size provided, return that
-            if (mOverrideMinSize != null) {
-                return adjustOverrideMinSizeToAspectRatio(aspectRatio);
-            }
-
-            Size maxSize = this.getMaxSize(aspectRatio);
-
-            int minWidth = Math.round(maxSize.getWidth() * mMinimumSizePercent);
-            int minHeight = Math.round(maxSize.getHeight() * mMinimumSizePercent);
-
-            // make sure the calculated min size is not smaller than the allowed default min size
-            if (aspectRatio > 1f) {
-                minHeight = Math.max(minHeight, mDefaultMinSize);
-                minWidth = Math.round(minHeight * aspectRatio);
-            } else {
-                minWidth = Math.max(minWidth, mDefaultMinSize);
-                minHeight = Math.round(minWidth / aspectRatio);
-            }
-            return new Size(minWidth, minHeight);
-        }
-
-        /**
-         * Returns the size for target aspect ratio making sure new size conforms with the rules.
-         *
-         * <p>Recalculates the dimensions such that the target aspect ratio is achieved, while
-         * maintaining the same maximum size to current size ratio.
-         *
-         * @param size current size
-         * @param aspectRatio target aspect ratio
-         */
-        @Override
-        public Size getSizeForAspectRatio(Size size, float aspectRatio) {
-            float currAspectRatio = (float) size.getWidth() / size.getHeight();
-
-            // getting the percentage of the max size that current size takes
-            Size currentMaxSize = getMaxSize(currAspectRatio);
-            float currentPercent = (float) size.getWidth() / currentMaxSize.getWidth();
-
-            // getting the max size for the target aspect ratio
-            Size updatedMaxSize = getMaxSize(aspectRatio);
-
-            int width = Math.round(updatedMaxSize.getWidth() * currentPercent);
-            int height = Math.round(updatedMaxSize.getHeight() * currentPercent);
-
-            // adjust the dimensions if below allowed min edge size
-            if (width < getMinEdgeSize() && aspectRatio <= 1) {
-                width = getMinEdgeSize();
-                height = Math.round(width / aspectRatio);
-            } else if (height < getMinEdgeSize() && aspectRatio > 1) {
-                height = getMinEdgeSize();
-                width = Math.round(height * aspectRatio);
-            }
-
-            // reduce the dimensions of the updated size to the calculated percentage
-            return new Size(width, height);
-        }
-    }
-
-    private class SizeSpecDefaultImpl implements SizeSpecSource {
-        private float mDefaultSizePercent;
-        private float mMinimumSizePercent;
-
-        @Override
-        public void reloadResources() {
-            final Resources res = mContext.getResources();
-
-            mMaxAspectRatioForMinSize = res.getFloat(
-                    R.dimen.config_pictureInPictureAspectRatioLimitForMinSize);
-            mMinAspectRatioForMinSize = 1f / mMaxAspectRatioForMinSize;
-
-            mDefaultSizePercent = res.getFloat(R.dimen.config_pictureInPictureDefaultSizePercent);
-            mMinimumSizePercent = res.getFraction(R.fraction.config_pipShortestEdgePercent, 1, 1);
-        }
-
-        @Override
-        public Size getMaxSize(float aspectRatio) {
-            final int shorterLength = Math.min(getDisplayBounds().width(),
-                    getDisplayBounds().height());
-
-            final int totalHorizontalPadding = getInsetBounds().left
-                    + (getDisplayBounds().width() - getInsetBounds().right);
-            final int totalVerticalPadding = getInsetBounds().top
-                    + (getDisplayBounds().height() - getInsetBounds().bottom);
-
-            final int maxWidth, maxHeight;
-
-            if (aspectRatio > 1f) {
-                maxWidth = (int) Math.max(getDefaultSize(aspectRatio).getWidth(),
-                        shorterLength - totalHorizontalPadding);
-                maxHeight = (int) (maxWidth / aspectRatio);
-            } else {
-                maxHeight = (int) Math.max(getDefaultSize(aspectRatio).getHeight(),
-                        shorterLength - totalVerticalPadding);
-                maxWidth = (int) (maxHeight * aspectRatio);
-            }
-
-            return new Size(maxWidth, maxHeight);
-        }
-
-        @Override
-        public Size getDefaultSize(float aspectRatio) {
-            if (mOverrideMinSize != null) {
-                return this.getMinSize(aspectRatio);
-            }
-
-            final int smallestDisplaySize = Math.min(getDisplayBounds().width(),
-                    getDisplayBounds().height());
-            final int minSize = (int) Math.max(getMinEdgeSize(),
-                    smallestDisplaySize * mDefaultSizePercent);
-
-            final int width;
-            final int height;
-
-            if (aspectRatio <= mMinAspectRatioForMinSize
-                    || aspectRatio > mMaxAspectRatioForMinSize) {
-                // Beyond these points, we can just use the min size as the shorter edge
-                if (aspectRatio <= 1) {
-                    // Portrait, width is the minimum size
-                    width = minSize;
-                    height = Math.round(width / aspectRatio);
-                } else {
-                    // Landscape, height is the minimum size
-                    height = minSize;
-                    width = Math.round(height * aspectRatio);
-                }
-            } else {
-                // Within these points, ensure that the bounds fit within the radius of the limits
-                // at the points
-                final float widthAtMaxAspectRatioForMinSize = mMaxAspectRatioForMinSize * minSize;
-                final float radius = PointF.length(widthAtMaxAspectRatioForMinSize, minSize);
-                height = (int) Math.round(Math.sqrt((radius * radius)
-                        / (aspectRatio * aspectRatio + 1)));
-                width = Math.round(height * aspectRatio);
-            }
-
-            return new Size(width, height);
-        }
-
-        @Override
-        public Size getMinSize(float aspectRatio) {
-            if (mOverrideMinSize != null) {
-                return adjustOverrideMinSizeToAspectRatio(aspectRatio);
-            }
-
-            final int shorterLength = Math.min(getDisplayBounds().width(),
-                    getDisplayBounds().height());
-            final int minWidth, minHeight;
-
-            if (aspectRatio > 1f) {
-                minWidth = (int) Math.min(getDefaultSize(aspectRatio).getWidth(),
-                        shorterLength * mMinimumSizePercent);
-                minHeight = (int) (minWidth / aspectRatio);
-            } else {
-                minHeight = (int) Math.min(getDefaultSize(aspectRatio).getHeight(),
-                        shorterLength * mMinimumSizePercent);
-                minWidth = (int) (minHeight * aspectRatio);
-            }
-
-            return new Size(minWidth, minHeight);
-        }
-
-        @Override
-        public Size getSizeForAspectRatio(Size size, float aspectRatio) {
-            final int smallestSize = Math.min(size.getWidth(), size.getHeight());
-            final int minSize = Math.max(getMinEdgeSize(), smallestSize);
-
-            final int width;
-            final int height;
-            if (aspectRatio <= 1) {
-                // Portrait, width is the minimum size.
-                width = minSize;
-                height = Math.round(width / aspectRatio);
-            } else {
-                // Landscape, height is the minimum size
-                height = minSize;
-                width = Math.round(height * aspectRatio);
-            }
-
-            return new Size(width, height);
-        }
-    }
-
-    public PipSizeSpecHandler(Context context, PipDisplayLayoutState pipDisplayLayoutState) {
-        mContext = context;
-        mPipDisplayLayoutState = pipDisplayLayoutState;
-
-        // choose between two implementations of size spec logic
-        if (supportsPipSizeLargeScreen()) {
-            mSizeSpecSourceImpl = new SizeSpecLargeScreenOptimizedImpl();
-        } else {
-            mSizeSpecSourceImpl = new SizeSpecDefaultImpl();
-        }
-
-        reloadResources();
-    }
-
-    /** Reloads the resources */
-    public void onConfigurationChanged() {
-        reloadResources();
-    }
-
-    private void reloadResources() {
-        final Resources res = mContext.getResources();
-
-        mDefaultMinSize = res.getDimensionPixelSize(
-                R.dimen.default_minimal_size_pip_resizable_task);
-        mOverridableMinSize = res.getDimensionPixelSize(
-                R.dimen.overridable_minimal_size_pip_resizable_task);
-
-        final String screenEdgeInsetsDpString = res.getString(
-                R.string.config_defaultPictureInPictureScreenEdgeInsets);
-        final Size screenEdgeInsetsDp = !screenEdgeInsetsDpString.isEmpty()
-                ? Size.parseSize(screenEdgeInsetsDpString)
-                : null;
-        mScreenEdgeInsets = screenEdgeInsetsDp == null ? new Point()
-                : new Point(dpToPx(screenEdgeInsetsDp.getWidth(), res.getDisplayMetrics()),
-                        dpToPx(screenEdgeInsetsDp.getHeight(), res.getDisplayMetrics()));
-
-        // update the internal resources of the size spec source's stub
-        mSizeSpecSourceImpl.reloadResources();
-    }
-
-    @NonNull
-    private Rect getDisplayBounds() {
-        return mPipDisplayLayoutState.getDisplayBounds();
-    }
-
-    public Point getScreenEdgeInsets() {
-        return mScreenEdgeInsets;
-    }
-
-    /**
-     * Returns the inset bounds the PIP window can be visible in.
-     */
-    public Rect getInsetBounds() {
-        Rect insetBounds = new Rect();
-        DisplayLayout displayLayout = mPipDisplayLayoutState.getDisplayLayout();
-        Rect insets = displayLayout.stableInsets();
-        insetBounds.set(insets.left + mScreenEdgeInsets.x,
-                insets.top + mScreenEdgeInsets.y,
-                displayLayout.width() - insets.right - mScreenEdgeInsets.x,
-                displayLayout.height() - insets.bottom - mScreenEdgeInsets.y);
-        return insetBounds;
-    }
-
-    /** Sets the preferred size of PIP as specified by the activity in PIP mode. */
-    public void setOverrideMinSize(@Nullable Size overrideMinSize) {
-        mOverrideMinSize = overrideMinSize;
-    }
-
-    /** Returns the preferred minimal size specified by the activity in PIP. */
-    @Nullable
-    public Size getOverrideMinSize() {
-        if (mOverrideMinSize != null
-                && (mOverrideMinSize.getWidth() < mOverridableMinSize
-                || mOverrideMinSize.getHeight() < mOverridableMinSize)) {
-            return new Size(mOverridableMinSize, mOverridableMinSize);
-        }
-
-        return mOverrideMinSize;
-    }
-
-    /** Returns the minimum edge size of the override minimum size, or 0 if not set. */
-    public int getOverrideMinEdgeSize() {
-        if (mOverrideMinSize == null) return 0;
-        return Math.min(getOverrideMinSize().getWidth(), getOverrideMinSize().getHeight());
-    }
-
-    public int getMinEdgeSize() {
-        return mOverrideMinSize == null ? mDefaultMinSize : getOverrideMinEdgeSize();
-    }
-
-    /**
-     * Returns the size for the max size spec.
-     */
-    public Size getMaxSize(float aspectRatio) {
-        return mSizeSpecSourceImpl.getMaxSize(aspectRatio);
-    }
-
-    /**
-     * Returns the size for the default size spec.
-     */
-    public Size getDefaultSize(float aspectRatio) {
-        return mSizeSpecSourceImpl.getDefaultSize(aspectRatio);
-    }
-
-    /**
-     * Returns the size for the min size spec.
-     */
-    public Size getMinSize(float aspectRatio) {
-        return mSizeSpecSourceImpl.getMinSize(aspectRatio);
-    }
-
-    /**
-     * Returns the adjusted size so that it conforms to the given aspectRatio.
-     *
-     * @param size current size
-     * @param aspectRatio target aspect ratio
-     */
-    public Size getSizeForAspectRatio(@NonNull Size size, float aspectRatio) {
-        if (size.equals(mOverrideMinSize)) {
-            return adjustOverrideMinSizeToAspectRatio(aspectRatio);
-        }
-
-        return mSizeSpecSourceImpl.getSizeForAspectRatio(size, aspectRatio);
-    }
-
-    /**
-     * Returns the adjusted overridden min size if it is set; otherwise, returns null.
-     *
-     * <p>Overridden min size needs to be adjusted in its own way while making sure that the target
-     * aspect ratio is maintained
-     *
-     * @param aspectRatio target aspect ratio
-     */
-    @Nullable
-    @VisibleForTesting
-    Size adjustOverrideMinSizeToAspectRatio(float aspectRatio) {
-        if (mOverrideMinSize == null) {
-            return null;
-        }
-        final Size size = getOverrideMinSize();
-        final float sizeAspectRatio = size.getWidth() / (float) size.getHeight();
-        if (sizeAspectRatio > aspectRatio) {
-            // Size is wider, fix the width and increase the height
-            return new Size(size.getWidth(), (int) (size.getWidth() / aspectRatio));
-        } else {
-            // Size is taller, fix the height and adjust the width.
-            return new Size((int) (size.getHeight() * aspectRatio), size.getHeight());
-        }
-    }
-
-    @VisibleForTesting
-    boolean supportsPipSizeLargeScreen() {
-        // TODO(b/271468706): switch Tv to having a dedicated SizeSpecSource once the SizeSpecSource
-        // can be injected
-        return SystemProperties
-                .getBoolean("persist.wm.debug.enable_pip_size_large_screen", true) && !isTv();
-    }
-
-    private boolean isTv() {
-        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK);
-    }
-
-    /** Dumps internal state. */
-    public void dump(PrintWriter pw, String prefix) {
-        final String innerPrefix = prefix + "  ";
-        pw.println(prefix + TAG);
-        pw.println(innerPrefix + "mSizeSpecSourceImpl=" + mSizeSpecSourceImpl);
-        pw.println(innerPrefix + "mOverrideMinSize=" + mOverrideMinSize);
-        pw.println(innerPrefix + "mScreenEdgeInsets=" + mScreenEdgeInsets);
-    }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 372ade3..3e95498a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -51,6 +51,7 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipAnimationController;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
@@ -85,7 +86,7 @@
     private final Context mContext;
     private final PipBoundsAlgorithm mPipBoundsAlgorithm;
     @NonNull private final PipBoundsState mPipBoundsState;
-    @NonNull private final PipSizeSpecHandler mPipSizeSpecHandler;
+    @NonNull private final SizeSpecSource mSizeSpecSource;
     private final PipUiEventLogger mPipUiEventLogger;
     private final PipDismissTargetHandler mPipDismissTargetHandler;
     private final PipTaskOrganizer mPipTaskOrganizer;
@@ -179,7 +180,7 @@
             PhonePipMenuController menuController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             @NonNull PipBoundsState pipBoundsState,
-            @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+            @NonNull SizeSpecSource sizeSpecSource,
             PipTaskOrganizer pipTaskOrganizer,
             PipMotionHelper pipMotionHelper,
             FloatingContentCoordinator floatingContentCoordinator,
@@ -190,7 +191,7 @@
         mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
         mPipBoundsAlgorithm = pipBoundsAlgorithm;
         mPipBoundsState = pipBoundsState;
-        mPipSizeSpecHandler = pipSizeSpecHandler;
+        mSizeSpecSource = sizeSpecSource;
         mPipTaskOrganizer = pipTaskOrganizer;
         mMenuController = menuController;
         mPipUiEventLogger = pipUiEventLogger;
@@ -413,7 +414,7 @@
 
         // Calculate the expanded size
         float aspectRatio = (float) normalBounds.width() / normalBounds.height();
-        Size expandedSize = mPipSizeSpecHandler.getDefaultSize(aspectRatio);
+        Size expandedSize = mSizeSpecSource.getDefaultSize(aspectRatio);
         mPipBoundsState.setExpandedBounds(
                 new Rect(0, 0, expandedSize.getWidth(), expandedSize.getHeight()));
         Rect expandedMovementBounds = new Rect();
@@ -517,10 +518,10 @@
     private void updatePinchResizeSizeConstraints(float aspectRatio) {
         final int minWidth, minHeight, maxWidth, maxHeight;
 
-        minWidth = mPipSizeSpecHandler.getMinSize(aspectRatio).getWidth();
-        minHeight = mPipSizeSpecHandler.getMinSize(aspectRatio).getHeight();
-        maxWidth = mPipSizeSpecHandler.getMaxSize(aspectRatio).getWidth();
-        maxHeight = mPipSizeSpecHandler.getMaxSize(aspectRatio).getHeight();
+        minWidth = mSizeSpecSource.getMinSize(aspectRatio).getWidth();
+        minHeight = mSizeSpecSource.getMinSize(aspectRatio).getHeight();
+        maxWidth = mSizeSpecSource.getMaxSize(aspectRatio).getWidth();
+        maxHeight = mSizeSpecSource.getMaxSize(aspectRatio).getHeight();
 
         mPipResizeGestureHandler.updateMinSize(minWidth, minHeight);
         mPipResizeGestureHandler.updateMaxSize(maxWidth, maxHeight);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
index 825b969..cd58ff4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsAlgorithm.java
@@ -36,10 +36,11 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipKeepClearAlgorithmInterface;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.pip.tv.TvPipKeepClearAlgorithm.Placement;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
@@ -62,9 +63,10 @@
     public TvPipBoundsAlgorithm(Context context,
             @NonNull TvPipBoundsState tvPipBoundsState,
             @NonNull PipSnapAlgorithm pipSnapAlgorithm,
-            @NonNull PipSizeSpecHandler pipSizeSpecHandler) {
+            @NonNull PipDisplayLayoutState pipDisplayLayoutState,
+            @NonNull SizeSpecSource sizeSpecSource) {
         super(context, tvPipBoundsState, pipSnapAlgorithm,
-                new PipKeepClearAlgorithmInterface() {}, pipSizeSpecHandler);
+                new PipKeepClearAlgorithmInterface() {}, pipDisplayLayoutState, sizeSpecSource);
         this.mTvPipBoundsState = tvPipBoundsState;
         this.mKeepClearAlgorithm = new TvPipKeepClearAlgorithm();
         reloadResources(context);
@@ -291,7 +293,7 @@
                 expandedSize = mTvPipBoundsState.getTvExpandedSize();
             } else {
                 int maxHeight = displayLayout.height()
-                        - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().y)
+                        - (2 * mPipDisplayLayoutState.getScreenEdgeInsets().y)
                         - pipDecorations.top - pipDecorations.bottom;
                 float aspectRatioHeight = mFixedExpandedWidthInPx / expandedRatio;
 
@@ -311,7 +313,7 @@
                 expandedSize = mTvPipBoundsState.getTvExpandedSize();
             } else {
                 int maxWidth = displayLayout.width()
-                        - (2 * mPipSizeSpecHandler.getScreenEdgeInsets().x)
+                        - (2 * mPipDisplayLayoutState.getScreenEdgeInsets().x)
                         - pipDecorations.left - pipDecorations.right;
                 float aspectRatioWidth = mFixedExpandedHeightInPx * expandedRatio;
                 if (maxWidth > aspectRatioWidth) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
index 3f3951a..d11f4d5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipBoundsState.java
@@ -29,10 +29,10 @@
 import android.view.Gravity;
 import android.view.View;
 
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -76,9 +76,9 @@
     private Insets mPipMenuTemporaryDecorInsets = Insets.NONE;
 
     public TvPipBoundsState(@NonNull Context context,
-            @NonNull PipSizeSpecHandler pipSizeSpecHandler,
+            @NonNull SizeSpecSource sizeSpecSource,
             @NonNull PipDisplayLayoutState pipDisplayLayoutState) {
-        super(context, pipSizeSpecHandler, pipDisplayLayoutState);
+        super(context, sizeSpecSource, pipDisplayLayoutState);
         mContext = context;
         updateDefaultGravity();
         mTvPipGravity = mDefaultGravity;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 2482acf..e3544c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -47,10 +47,10 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerCallback;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PinnedStackListenerForwarder;
 import com.android.wm.shell.pip.Pip;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipMediaController;
 import com.android.wm.shell.pip.PipParamsChangedForwarder;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index 3af1b75..05e4af3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -55,6 +55,8 @@
             Consts.TAG_WM_SHELL),
     WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
+    WM_SHELL_BUBBLES(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            "Bubbles"),
     TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
 
     private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 88a81fc..a11d952 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -771,7 +771,7 @@
                     Slog.e(TAG, "Error sending appeared tasks to recents animation", e);
                 }
             }
-            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            finishCallback.onTransitionFinished(null /* wct */);
         }
 
         /** For now, just set-up a jump-cut to the new activity. */
@@ -937,7 +937,7 @@
                 }
             }
             cleanUp();
-            finishCB.onTransitionFinished(wct.isEmpty() ? null : wct, null /* wctCB */);
+            finishCB.onTransitionFinished(wct.isEmpty() ? null : wct);
         }
 
         @Override
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 c414e70..14304a3 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
@@ -27,6 +27,7 @@
 import android.window.RemoteTransition;
 
 import com.android.wm.shell.splitscreen.ISplitScreenListener;
+import com.android.wm.shell.splitscreen.ISplitSelectListener;
 
 /**
  * Interface that is exposed to remote callers to manipulate the splitscreen feature.
@@ -44,6 +45,16 @@
     oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
 
     /**
+     * Registers a split select listener.
+     */
+    oneway void registerSplitSelectListener(in ISplitSelectListener listener) = 20;
+
+    /**
+     * Unregisters a split select listener.
+     */
+    oneway void unregisterSplitSelectListener(in ISplitSelectListener listener) = 21;
+
+    /**
      * Removes a task from the side stage.
      */
     oneway void removeFromSideStage(int taskId) = 4;
@@ -148,4 +159,4 @@
      */
     RemoteAnimationTarget[] onStartingSplitLegacy(in RemoteAnimationTarget[] appTargets) = 14;
 }
-// Last id = 19
\ No newline at end of file
+// Last id = 21
\ No newline at end of file
diff --git a/core/java/android/view/selectiontoolbar/WidgetInfo.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
similarity index 63%
copy from core/java/android/view/selectiontoolbar/WidgetInfo.aidl
copy to libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
index 1057c51..7171da5 100644
--- a/core/java/android/view/selectiontoolbar/WidgetInfo.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitSelectListener.aidl
@@ -14,9 +14,16 @@
  * limitations under the License.
  */
 
-package android.view.selectiontoolbar;
+package com.android.wm.shell.splitscreen;
+
+import android.app.ActivityManager.RunningTaskInfo;
 
 /**
- * @hide
+ * Listener interface that Launcher attaches to SystemUI to get split-select callbacks.
  */
-parcelable WidgetInfo;
+interface ISplitSelectListener {
+    /**
+     * Called when a task requests to enter split select
+     */
+    boolean onRequestSplitSelect(in RunningTaskInfo taskInfo);
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 2f2bc77..f20fe0b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.app.ActivityManager;
 import android.graphics.Rect;
 
 import com.android.wm.shell.common.annotations.ExternalThread;
@@ -63,6 +64,13 @@
         default void onSplitVisibilityChanged(boolean visible) {}
     }
 
+    /** Callback interface for listening to requests to enter split select */
+    interface SplitSelectListener {
+        default boolean onRequestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo) {
+            return false;
+        }
+    }
+
     /** Registers listener that gets split screen callback. */
     void registerSplitScreenListener(@NonNull SplitScreenListener listener,
             @NonNull Executor executor);
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 5fa2654..210bf68 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
@@ -87,6 +87,7 @@
 import com.android.wm.shell.common.annotations.ExternalThread;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.common.split.SplitScreenUtils;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.draganddrop.DragAndDropPolicy;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -104,6 +105,7 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.Optional;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Class manages split-screen multitasking mode and implements the main interface
@@ -177,6 +179,7 @@
     private final Optional<RecentTasksController> mRecentTasksOptional;
     private final LaunchAdjacentController mLaunchAdjacentController;
     private final Optional<WindowDecorViewModel> mWindowDecorViewModel;
+    private final Optional<DesktopTasksController> mDesktopTasksController;
     private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
     private final String[] mAppsSupportMultiInstances;
 
@@ -205,6 +208,7 @@
             Optional<RecentTasksController> recentTasks,
             LaunchAdjacentController launchAdjacentController,
             Optional<WindowDecorViewModel> windowDecorViewModel,
+            Optional<DesktopTasksController> desktopTasksController,
             ShellExecutor mainExecutor) {
         mShellCommandHandler = shellCommandHandler;
         mShellController = shellController;
@@ -223,6 +227,7 @@
         mRecentTasksOptional = recentTasks;
         mLaunchAdjacentController = launchAdjacentController;
         mWindowDecorViewModel = windowDecorViewModel;
+        mDesktopTasksController = desktopTasksController;
         mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
         //                    override for this controller from the base module
@@ -254,6 +259,7 @@
             RecentTasksController recentTasks,
             LaunchAdjacentController launchAdjacentController,
             WindowDecorViewModel windowDecorViewModel,
+            DesktopTasksController desktopTasksController,
             ShellExecutor mainExecutor,
             StageCoordinator stageCoordinator) {
         mShellCommandHandler = shellCommandHandler;
@@ -273,6 +279,7 @@
         mRecentTasksOptional = Optional.of(recentTasks);
         mLaunchAdjacentController = launchAdjacentController;
         mWindowDecorViewModel = Optional.of(windowDecorViewModel);
+        mDesktopTasksController = Optional.of(desktopTasksController);
         mStageCoordinator = stageCoordinator;
         mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
         shellInit.addInitCallback(this::onInit, this);
@@ -306,6 +313,7 @@
         }
         mDragAndDropController.ifPresent(controller -> controller.setSplitScreenController(this));
         mWindowDecorViewModel.ifPresent(viewModel -> viewModel.setSplitScreenController(this));
+        mDesktopTasksController.ifPresent(controller -> controller.setSplitScreenController(this));
     }
 
     protected StageCoordinator createStageCoordinator() {
@@ -468,6 +476,16 @@
         mStageCoordinator.unregisterSplitScreenListener(listener);
     }
 
+    /** Register a split select listener */
+    public void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+        mStageCoordinator.registerSplitSelectListener(listener);
+    }
+
+    /** Unregister a split select listener */
+    public void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+        mStageCoordinator.unregisterSplitSelectListener(listener);
+    }
+
     public void goToFullscreenFromSplit() {
         mStageCoordinator.goToFullscreenFromSplit();
     }
@@ -485,6 +503,16 @@
         return mStageCoordinator.getActivateSplitPosition(taskInfo);
     }
 
+    /**
+     * Move a task to split select
+     * @param taskInfo the task being moved to split select
+     * @param wct transaction to apply if this is a valid request
+     */
+    public void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+            WindowContainerTransaction wct) {
+        mStageCoordinator.requestEnterSplitSelect(taskInfo, wct);
+    }
+
     public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
         final int[] result = new int[1];
         IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
@@ -1088,6 +1116,8 @@
         private SplitScreenController mController;
         private final SingleInstanceRemoteListener<SplitScreenController,
                 ISplitScreenListener> mListener;
+        private final SingleInstanceRemoteListener<SplitScreenController,
+                ISplitSelectListener> mSelectListener;
         private final SplitScreen.SplitScreenListener mSplitScreenListener =
                 new SplitScreen.SplitScreenListener() {
                     @Override
@@ -1101,11 +1131,25 @@
                     }
                 };
 
+        private final SplitScreen.SplitSelectListener mSplitSelectListener =
+                new SplitScreen.SplitSelectListener() {
+                    @Override
+                    public boolean onRequestEnterSplitSelect(
+                            ActivityManager.RunningTaskInfo taskInfo) {
+                        AtomicBoolean result = new AtomicBoolean(false);
+                        mSelectListener.call(l -> result.set(l.onRequestSplitSelect(taskInfo)));
+                        return result.get();
+                    }
+                };
+
         public ISplitScreenImpl(SplitScreenController controller) {
             mController = controller;
             mListener = new SingleInstanceRemoteListener<>(controller,
                     c -> c.registerSplitScreenListener(mSplitScreenListener),
                     c -> c.unregisterSplitScreenListener(mSplitScreenListener));
+            mSelectListener = new SingleInstanceRemoteListener<>(controller,
+                    c -> c.registerSplitSelectListener(mSplitSelectListener),
+                    c -> c.unregisterSplitSelectListener(mSplitSelectListener));
         }
 
         /**
@@ -1131,6 +1175,18 @@
         }
 
         @Override
+        public void registerSplitSelectListener(ISplitSelectListener listener) {
+            executeRemoteCallWithTaskPermission(mController, "registerSplitSelectListener",
+                    (controller) -> mSelectListener.register(listener));
+        }
+
+        @Override
+        public void unregisterSplitSelectListener(ISplitSelectListener listener) {
+            executeRemoteCallWithTaskPermission(mController, "unregisterSplitSelectListener",
+                    (controller) -> mSelectListener.unregister());
+        }
+
+        @Override
         public void exitSplitScreen(int toTopTaskId) {
             executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
                     (controller) -> controller.exitSplitScreen(toTopTaskId, EXIT_REASON_UNKNOWN));
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 d21f8a4..7dec12a 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
@@ -43,7 +43,6 @@
 import android.window.TransitionInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.TransactionPool;
@@ -109,7 +108,7 @@
             if (pendingTransition.mCanceled) {
                 // The pending transition was canceled, so skip playing animation.
                 startTransaction.apply();
-                onFinish(null /* wct */, null /* wctCB */);
+                onFinish(null /* wct */);
                 return;
             }
 
@@ -211,7 +210,7 @@
             }
         }
         t.apply();
-        onFinish(null /* wct */, null /* wctCB */);
+        onFinish(null /* wct */);
     }
 
     /** Play animation for drag divider dismiss transition. */
@@ -238,7 +237,7 @@
                     mAnimations.remove(va);
                     if (animated) {
                         mTransitions.getMainExecutor().execute(() -> {
-                            onFinish(null /* wct */, null /* wctCB */);
+                            onFinish(null /* wct */);
                         });
                     }
                 });
@@ -250,7 +249,7 @@
             }
         }
         startTransaction.apply();
-        onFinish(null /* wct */, null /* wctCB */);
+        onFinish(null /* wct */);
     }
 
     /** Play animation for resize transition. */
@@ -283,7 +282,7 @@
                     mAnimations.remove(va);
                     if (animated) {
                         mTransitions.getMainExecutor().execute(() -> {
-                            onFinish(null /* wct */, null /* wctCB */);
+                            onFinish(null /* wct */);
                         });
                     }
                 });
@@ -291,7 +290,7 @@
         }
 
         startTransaction.apply();
-        onFinish(null /* wct */, null /* wctCB */);
+        onFinish(null /* wct */);
     }
 
     boolean isPendingTransition(IBinder transition) {
@@ -325,8 +324,10 @@
 
     void startFullscreenTransition(WindowContainerTransaction wct,
             @Nullable RemoteTransition handler) {
-        mTransitions.startTransition(TRANSIT_OPEN, wct,
-                new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler));
+        OneShotRemoteHandler fullscreenHandler =
+                new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler);
+        fullscreenHandler.setTransition(mTransitions
+                .startTransition(TRANSIT_OPEN, wct, fullscreenHandler));
     }
 
 
@@ -391,7 +392,7 @@
         if (mPendingResize != null) {
             mPendingResize.cancel(null);
             mAnimations.clear();
-            onFinish(null /* wct */, null /* wctCB */);
+            onFinish(null /* wct */);
         }
 
         IBinder transition = mTransitions.startTransition(TRANSIT_CHANGE, wct, handler);
@@ -450,7 +451,7 @@
         }
     }
 
-    void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
+    void onFinish(WindowContainerTransaction wct) {
         if (!mAnimations.isEmpty()) return;
 
         if (wct == null) wct = new WindowContainerTransaction();
@@ -470,7 +471,7 @@
 
         mOnFinish.run();
         if (mFinishCallback != null) {
-            mFinishCallback.onTransitionFinished(wct /* wct */, wctCB /* wctCB */);
+            mFinishCallback.onTransitionFinished(wct /* wct */);
             mFinishCallback = null;
         }
     }
@@ -495,7 +496,7 @@
                 mTransactionPool.release(transaction);
                 mTransitions.getMainExecutor().execute(() -> {
                     mAnimations.remove(va);
-                    onFinish(null /* wct */, null /* wctCB */);
+                    onFinish(null /* wct */);
                 });
             }
         });
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 69609ac..6970068 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
@@ -144,8 +144,10 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Optional;
+import java.util.Set;
 
 /**
  * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
@@ -185,6 +187,7 @@
     private final ShellTaskOrganizer mTaskOrganizer;
     private final Context mContext;
     private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
+    private final Set<SplitScreen.SplitSelectListener> mSelectListeners = new HashSet<>();
     private final DisplayController mDisplayController;
     private final DisplayImeController mDisplayImeController;
     private final DisplayInsetsController mDisplayInsetsController;
@@ -462,6 +465,15 @@
         return mLogger;
     }
 
+    void requestEnterSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
+            WindowContainerTransaction wct) {
+        boolean enteredSplitSelect = false;
+        for (SplitScreen.SplitSelectListener listener : mSelectListeners) {
+            enteredSplitSelect |= listener.onRequestEnterSplitSelect(taskInfo);
+        }
+        if (enteredSplitSelect) mTaskOrganizer.applyTransaction(wct);
+    }
+
     void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
             Bundle options, UserHandle user) {
         final boolean isEnteringSplit = !isSplitActive();
@@ -1657,6 +1669,14 @@
         mListeners.remove(listener);
     }
 
+    void registerSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+        mSelectListeners.add(listener);
+    }
+
+    void unregisterSplitSelectListener(SplitScreen.SplitSelectListener listener) {
+        mSelectListeners.remove(listener);
+    }
+
     void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
         listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
         listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
@@ -2720,7 +2740,8 @@
                 == TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE) {
             // Open to side should only be used when split already active and foregorund.
             if (mainChild == null && sideChild == null) {
-                Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
+                Log.w(TAG, splitFailureMessage("startPendingEnterAnimation",
+                        "Launched a task in split, but didn't receive any task in transition."));
                 // This should happen when the target app is already on front, so just cancel.
                 mSplitTransitions.mPendingEnter.cancel(null);
                 return true;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index a2301b1..c101425 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -87,7 +87,7 @@
                 syncQueue, rootTDAOrganizer, displayController, displayImeController,
                 displayInsetsController, dragAndDropController, transitions, transactionPool,
                 iconProvider, recentTasks, launchAdjacentController, Optional.empty(),
-                mainExecutor);
+                Optional.empty(), mainExecutor);
 
         mTaskOrganizer = shellTaskOrganizer;
         mSyncQueue = syncQueue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 84dcd4d..0c6adc9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -376,8 +376,8 @@
     private static int estimateWindowBGColor(Drawable themeBGDrawable) {
         final DrawableColorTester themeBGTester = new DrawableColorTester(
                 themeBGDrawable, DrawableColorTester.TRANSLUCENT_FILTER /* filterType */);
-        if (themeBGTester.passFilterRatio() != 1) {
-            // the window background is translucent, unable to draw
+        if (themeBGTester.passFilterRatio() < 0.5f) {
+            // more than half pixels of the window background is translucent, unable to draw
             Slog.w(TAG, "Window background is translucent, fill background with black color");
             return getSystemBGColor();
         } else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
index a743e99..adae21b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTaskController.java
@@ -94,9 +94,11 @@
         mShellExecutor = organizer.getExecutor();
         mSyncQueue = syncQueue;
         mTaskViewTransitions = taskViewTransitions;
-        if (mTaskViewTransitions != null) {
-            mTaskViewTransitions.addTaskView(this);
-        }
+        mShellExecutor.execute(() -> {
+            if (mTaskViewTransitions != null) {
+                mTaskViewTransitions.addTaskView(this);
+            }
+        });
         mGuard.open("release");
     }
 
@@ -225,10 +227,10 @@
     }
 
     private void performRelease() {
-        if (mTaskViewTransitions != null) {
-            mTaskViewTransitions.removeTaskView(this);
-        }
         mShellExecutor.execute(() -> {
+            if (mTaskViewTransitions != null) {
+                mTaskViewTransitions.removeTaskView(this);
+            }
             mTaskOrganizer.removeListener(this);
             resetTaskInfo();
         });
@@ -410,9 +412,12 @@
         if (mTaskToken == null) {
             return;
         }
-        // Sync Transactions can't operate simultaneously with shell transition collection.
+
         if (isUsingShellTransitions()) {
-            mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
+            mShellExecutor.execute(() -> {
+                // Sync Transactions can't operate simultaneously with shell transition collection.
+                mTaskViewTransitions.setTaskBounds(this, boundsOnScreen);
+            });
             return;
         }
 
@@ -430,9 +435,11 @@
             Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)");
             return;
         }
-        WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.removeTask(mTaskToken);
-        mTaskViewTransitions.closeTaskView(wct, this);
+        mShellExecutor.execute(() -> {
+            WindowContainerTransaction wct = new WindowContainerTransaction();
+            wct.removeTask(mTaskToken);
+            mTaskViewTransitions.closeTaskView(wct, this);
+        });
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index daf8be6..e03f825 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -390,7 +390,7 @@
         }
         // No animation, just show it immediately.
         startTransaction.apply();
-        finishCallback.onTransitionFinished(wct, null /* wctCB */);
+        finishCallback.onTransitionFinished(wct);
         startNextTransition();
         return true;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 052af3a..986560b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -40,7 +40,6 @@
 import android.window.TransitionInfo;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
 
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.split.SplitScreenUtils;
@@ -124,14 +123,7 @@
             mTransition = transition;
         }
 
-        void joinFinishArgs(WindowContainerTransaction wct,
-                WindowContainerTransactionCallback wctCB) {
-            if (wctCB != null) {
-                // Technically can probably support 1, but don't want to encourage CB usage since
-                // it creates instabliity, so just throw.
-                throw new IllegalArgumentException("Can't mix transitions that require finish"
-                        + " sync callback");
-            }
+        void joinFinishArgs(WindowContainerTransaction wct) {
             if (wct != null) {
                 if (mFinishWCT == null) {
                     mFinishWCT = wct;
@@ -389,12 +381,12 @@
                 info.getChanges().remove(i);
             }
         }
-        Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
             --mixed.mInFlightSubAnimations;
-            mixed.joinFinishArgs(wct, wctCB);
+            mixed.joinFinishArgs(wct);
             if (mixed.mInFlightSubAnimations > 0) return;
             mActiveTransitions.remove(mixed);
-            finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
+            finishCallback.onTransitionFinished(mixed.mFinishWCT);
         };
         if (pipChange == null) {
             if (mixed.mLeftoversHandler != null) {
@@ -461,15 +453,15 @@
             return false;
         }
         final boolean isGoingHome = homeIsOpening;
-        Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
             --mixed.mInFlightSubAnimations;
-            mixed.joinFinishArgs(wct, wctCB);
+            mixed.joinFinishArgs(wct);
             if (mixed.mInFlightSubAnimations > 0) return;
             mActiveTransitions.remove(mixed);
             if (isGoingHome) {
                 mSplitHandler.onTransitionAnimationComplete();
             }
-            finishCallback.onTransitionFinished(mixed.mFinishWCT, wctCB);
+            finishCallback.onTransitionFinished(mixed.mFinishWCT);
         };
         if (isGoingHome || mSplitHandler.getSplitItemPosition(pipChange.getLastParent())
                 != SPLIT_POSITION_UNDEFINED) {
@@ -586,12 +578,12 @@
         // We need to split the transition into 2 parts: the split part and the display part.
         mixed.mInFlightSubAnimations = 2;
 
-        Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
             --mixed.mInFlightSubAnimations;
-            mixed.joinFinishArgs(wct, wctCB);
+            mixed.joinFinishArgs(wct);
             if (mixed.mInFlightSubAnimations > 0) return;
             mActiveTransitions.remove(mixed);
-            finishCallback.onTransitionFinished(mixed.mFinishWCT, null /* wctCB */);
+            finishCallback.onTransitionFinished(mixed.mFinishWCT);
         };
 
         // Dispatch the display change. This will most-likely be taken by the default handler.
@@ -614,7 +606,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         // Split-screen is only interested in the recents transition finishing (and merging), so
         // just wrap finish and start recents animation directly.
-        Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+        Transitions.TransitionFinishCallback finishCB = (wct) -> {
             mixed.mInFlightSubAnimations = 0;
             mActiveTransitions.remove(mixed);
             // If pair-to-pair switching, the post-recents clean-up isn't needed.
@@ -626,7 +618,7 @@
                 mSplitHandler.onRecentsPairToPairAnimationFinish(wct);
             }
             mSplitHandler.onTransitionAnimationComplete();
-            finishCallback.onTransitionFinished(wct, wctCB);
+            finishCallback.onTransitionFinished(wct);
         };
         mixed.mInFlightSubAnimations = 1;
         mSplitHandler.onRecentsInSplitAnimationStart(info);
@@ -644,11 +636,11 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+        final Transitions.TransitionFinishCallback finishCB = (wct) -> {
             mixed.mInFlightSubAnimations--;
             if (mixed.mInFlightSubAnimations == 0) {
                 mActiveTransitions.remove(mixed);
-                finishCallback.onTransitionFinished(wct, wctCB);
+                finishCallback.onTransitionFinished(wct);
             }
         };
         mixed.mInFlightSubAnimations++;
@@ -693,11 +685,11 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+        final Transitions.TransitionFinishCallback finishCB = (wct) -> {
             mixed.mInFlightSubAnimations--;
             if (mixed.mInFlightSubAnimations > 0) return;
             mActiveTransitions.remove(mixed);
-            finishCallback.onTransitionFinished(wct, wctCB);
+            finishCallback.onTransitionFinished(wct);
         };
         mixed.mInFlightSubAnimations = 1;
         // Sync pip state.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index dc78c9b..7df658e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -300,7 +300,7 @@
         // immediately finishes since there is no animation for screen-wake.
         if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) {
             startTransaction.apply();
-            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            finishCallback.onTransitionFinished(null /* wct */);
             return true;
         }
 
@@ -309,7 +309,7 @@
                 || (info.getFlags() & WindowManager.TRANSIT_FLAG_INVISIBLE) != 0) {
             startTransaction.apply();
             finishTransaction.apply();
-            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            finishCallback.onTransitionFinished(null /* wct */);
             return true;
         }
 
@@ -323,7 +323,7 @@
         final Runnable onAnimFinish = () -> {
             if (!animations.isEmpty()) return;
             mAnimations.remove(transition);
-            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            finishCallback.onTransitionFinished(null /* wct */);
         };
 
         final List<Consumer<SurfaceControl.Transaction>> postStartTransactionCallbacks =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 4e3d220..fab2dd2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -68,7 +68,7 @@
         final IBinder.DeathRecipient remoteDied = () -> {
             Log.e(Transitions.TAG, "Remote transition died, finishing");
             mMainExecutor.execute(
-                    () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+                    () -> finishCallback.onTransitionFinished(null /* wct */));
         };
         IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
             @Override
@@ -81,7 +81,7 @@
                     finishTransaction.merge(sct);
                 }
                 mMainExecutor.execute(() -> {
-                    finishCallback.onTransitionFinished(wct, null /* wctCB */);
+                    finishCallback.onTransitionFinished(wct);
                 });
             }
         };
@@ -104,7 +104,7 @@
             if (mRemote.asBinder() != null) {
                 mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */);
             }
-            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            finishCallback.onTransitionFinished(null /* wct */);
         }
         return true;
     }
@@ -122,8 +122,7 @@
                 // remote applied the transaction, but applying twice will break surfaceflinger
                 // so just assume the worst-case and clear the local transaction.
                 t.clear();
-                mMainExecutor.execute(
-                        () -> finishCallback.onTransitionFinished(wct, null /* wctCB */));
+                mMainExecutor.execute(() -> finishCallback.onTransitionFinished(wct));
             }
         };
         try {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index c22cc6f..bbf67a6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -133,7 +133,7 @@
                 }
                 mMainExecutor.execute(() -> {
                     mRequestedRemotes.remove(transition);
-                    finishCallback.onTransitionFinished(wct, null /* wctCB */);
+                    finishCallback.onTransitionFinished(wct);
                 });
             }
         };
@@ -153,8 +153,7 @@
             Log.e(Transitions.TAG, "Error running remote transition.", e);
             unhandleDeath(remote.asBinder(), finishCallback);
             mRequestedRemotes.remove(transition);
-            mMainExecutor.execute(
-                    () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */));
+            mMainExecutor.execute(() -> finishCallback.onTransitionFinished(null /* wct */));
         }
         return true;
     }
@@ -210,7 +209,7 @@
                                 + "that the mergeTarget's RemoteTransition impl erroneously "
                                 + "accepted/ran the merge request after finishing the mergeTarget");
                     }
-                    finishCallback.onTransitionFinished(wct, null /* wctCB */);
+                    finishCallback.onTransitionFinished(wct);
                 });
             }
         };
@@ -316,8 +315,7 @@
                     }
                 }
                 for (int i = mPendingFinishCallbacks.size() - 1; i >= 0; --i) {
-                    mPendingFinishCallbacks.get(i).onTransitionFinished(
-                            null /* wct */, null /* wctCB */);
+                    mPendingFinishCallbacks.get(i).onTransitionFinished(null /* wct */);
                 }
                 mPendingFinishCallbacks.clear();
             });
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
index d279595..87c438a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/SleepHandler.java
@@ -43,7 +43,7 @@
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
         mSleepTransitions.remove(transition);
         startTransaction.apply();
-        finishCallback.onTransitionFinished(null, null);
+        finishCallback.onTransitionFinished(null);
         return true;
     }
 
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 e2dce88..b4d0a31 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
@@ -62,7 +62,6 @@
 import android.window.TransitionMetrics;
 import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
-import android.window.WindowContainerTransactionCallback;
 import android.window.WindowOrganizer;
 
 import androidx.annotation.BinderThread;
@@ -829,7 +828,7 @@
                     ready.mStartT.apply();
                 }
                 // finish now since there's nothing to animate. Calls back into processReadyQueue
-                onFinish(ready, null, null);
+                onFinish(ready, null);
                 return;
             }
             playTransition(ready);
@@ -849,7 +848,7 @@
                 + " in case they can be merged", ready, playing);
         mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId());
         playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
-                playing.mToken, (wct, cb) -> onMerged(playing, ready));
+                playing.mToken, (wct) -> onMerged(playing, ready));
     }
 
     private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
@@ -899,7 +898,7 @@
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
                     active.mHandler);
             boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo,
-                    active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active, wct, cb));
+                    active.mStartT, active.mFinishT, (wct) -> onFinish(active, wct));
             if (consumed) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
                 mTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
@@ -908,7 +907,7 @@
         }
         // Otherwise give every other handler a chance
         active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
-                active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler);
+                active.mFinishT, (wct) -> onFinish(active, wct), active.mHandler);
     }
 
     /**
@@ -985,8 +984,7 @@
     }
 
     private void onFinish(ActiveTransition active,
-            @Nullable WindowContainerTransaction wct,
-            @Nullable WindowContainerTransactionCallback wctCB) {
+            @Nullable WindowContainerTransaction wct) {
         final Track track = mTracks.get(active.getTrack());
         if (track.mActiveTransition != active) {
             Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
@@ -1035,11 +1033,11 @@
         // Now perform all the finish callbacks (starting with the playing one and then all the
         // transitions merged into it).
         releaseSurfaces(active.mInfo);
-        mOrganizer.finishTransition(active.mToken, wct, wctCB);
+        mOrganizer.finishTransition(active.mToken, wct);
         if (active.mMerged != null) {
             for (int iM = 0; iM < active.mMerged.size(); ++iM) {
                 ActiveTransition merged = active.mMerged.get(iM);
-                mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
+                mOrganizer.finishTransition(merged.mToken, null /* wct */);
                 releaseSurfaces(merged.mInfo);
             }
             active.mMerged.clear();
@@ -1178,7 +1176,7 @@
                     forceFinish.mHandler.onTransitionConsumed(
                             forceFinish.mToken, true /* aborted */, null /* finishTransaction */);
                 }
-                onFinish(forceFinish, null, null);
+                onFinish(forceFinish, null);
             }
         }
         if (track.isIdle() || mReadyDuringSync.isEmpty()) {
@@ -1198,7 +1196,7 @@
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Attempt to merge sync %s"
                     + " into %s via a SLEEP proxy", nextSync, playing);
             playing.mHandler.mergeAnimation(nextSync.mToken, dummyInfo, dummyT,
-                    playing.mToken, (wct, cb) -> {});
+                    playing.mToken, (wct) -> {});
             // it's possible to complete immediately. If that happens, just repeat the signal
             // loop until we either finish everything or start playing an animation that isn't
             // finishing immediately.
@@ -1226,11 +1224,8 @@
          * The transition must not touch the surfaces after this has been called.
          *
          * @param wct A WindowContainerTransaction to run along with the transition clean-up.
-         * @param wctCB A sync callback that will be run when the transition clean-up is done and
-         *              wct has been applied.
          */
-        void onTransitionFinished(@Nullable WindowContainerTransaction wct,
-                @Nullable WindowContainerTransactionCallback wctCB);
+        void onTransitionFinished(@Nullable WindowContainerTransaction wct);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index f148412..2eb6e71 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -169,7 +169,7 @@
             animator.stop();
         }
 
-        mFinishCallback.onTransitionFinished(null, null);
+        mFinishCallback.onTransitionFinished(null);
         mFinishCallback = null;
         mTransition = null;
     }
@@ -193,7 +193,7 @@
             }
             // Apply changes happening during the unfold animation immediately
             t.apply();
-            finishCallback.onTransitionFinished(null, null);
+            finishCallback.onTransitionFinished(null);
         }
     }
 
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 2b19da2..2be7a49 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
@@ -365,6 +365,11 @@
                 mDesktopModeController.ifPresent(c -> c.setDesktopModeActive(false));
                 mDesktopTasksController.ifPresent(c -> c.moveToFullscreen(mTaskId));
                 decoration.closeHandleMenu();
+            } else if (id == R.id.split_screen_button) {
+                decoration.closeHandleMenu();
+                mDesktopTasksController.ifPresent(c -> {
+                    c.requestSplit(decoration.mTaskInfo);
+                });
             } else if (id == R.id.collapse_menu_button) {
                 decoration.closeHandleMenu();
             } else if (id == R.id.select_button) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index fb05c69..c9c58de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -168,7 +168,7 @@
         startTransaction.apply();
         mDesktopWindowDecoration.hideResizeVeil();
         mCtrlType = CTRL_TYPE_UNDEFINED;
-        finishCallback.onTransitionFinished(null, null);
+        finishCallback.onTransitionFinished(null);
         return true;
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
index 672e57a..a9eb882 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeAppControlsWindowDecorationViewHolder.kt
@@ -23,14 +23,14 @@
         appIcon: Drawable
 ) : DesktopModeWindowDecorationViewHolder(rootView) {
 
-    private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
-    private val captionHandle: View = rootView.findViewById(R.id.caption_handle)
-    private val openMenuButton: View = rootView.findViewById(R.id.open_menu_button)
-    private val closeWindowButton: ImageButton = rootView.findViewById(R.id.close_window)
-    private val expandMenuButton: ImageButton = rootView.findViewById(R.id.expand_menu_button)
-    private val maximizeWindowButton: ImageButton = rootView.findViewById(R.id.maximize_window)
-    private val appNameTextView: TextView = rootView.findViewById(R.id.application_name)
-    private val appIconImageView: ImageView = rootView.findViewById(R.id.application_icon)
+    private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
+    private val captionHandle: View = rootView.requireViewById(R.id.caption_handle)
+    private val openMenuButton: View = rootView.requireViewById(R.id.open_menu_button)
+    private val closeWindowButton: ImageButton = rootView.requireViewById(R.id.close_window)
+    private val expandMenuButton: ImageButton = rootView.requireViewById(R.id.expand_menu_button)
+    private val maximizeWindowButton: ImageButton = rootView.requireViewById(R.id.maximize_window)
+    private val appNameTextView: TextView = rootView.requireViewById(R.id.application_name)
+    private val appIconImageView: ImageView = rootView.requireViewById(R.id.application_icon)
 
     init {
         captionView.setOnTouchListener(onCaptionTouchListener)
@@ -47,7 +47,9 @@
     override fun bindData(taskInfo: RunningTaskInfo) {
 
         val captionDrawable = captionView.background as GradientDrawable
-        captionDrawable.setColor(taskInfo.taskDescription.statusBarColor)
+        taskInfo.taskDescription?.statusBarColor?.let {
+            captionDrawable.setColor(it)
+        }
 
         closeWindowButton.imageTintList = ColorStateList.valueOf(
                 getCaptionCloseButtonColor(taskInfo))
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 47a12a0..9374ac9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -17,8 +17,8 @@
         onCaptionButtonClickListener: View.OnClickListener
 ) : DesktopModeWindowDecorationViewHolder(rootView) {
 
-    private val captionView: View = rootView.findViewById(R.id.desktop_mode_caption)
-    private val captionHandle: ImageButton = rootView.findViewById(R.id.caption_handle)
+    private val captionView: View = rootView.requireViewById(R.id.desktop_mode_caption)
+    private val captionHandle: ImageButton = rootView.requireViewById(R.id.caption_handle)
 
     init {
         captionView.setOnTouchListener(onCaptionTouchListener)
@@ -27,9 +27,10 @@
     }
 
     override fun bindData(taskInfo: RunningTaskInfo) {
-        val captionColor = taskInfo.taskDescription.statusBarColor
-        val captionDrawable = captionView.background as GradientDrawable
-        captionDrawable.setColor(captionColor)
+        taskInfo.taskDescription?.statusBarColor?.let { captionColor ->
+            val captionDrawable = captionView.background as GradientDrawable
+            captionDrawable.setColor(captionColor)
+        }
 
         captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index d293cf7..49e8d15 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -25,11 +25,14 @@
    * with the caption background color.
    */
   protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
-    return if (Color.alpha(taskInfo.taskDescription.statusBarColor) != 0 &&
-        taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
-      Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
-    } else {
-      taskInfo.taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
-    }
+    return taskInfo.taskDescription
+        ?.let { taskDescription ->
+          if (Color.alpha(taskDescription.statusBarColor) != 0 &&
+              taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+            Color.valueOf(taskDescription.statusBarColor).luminance() < 0.5
+          } else {
+            taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
+          }
+        } ?: false
   }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
index 9cc9fb9..55039f5 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/ChangeActiveActivityFromBubbleTest.kt
@@ -17,11 +17,11 @@
 package com.android.wm.shell.flicker.bubble
 
 import android.os.SystemClock
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.FlakyTest
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
index 26aca18..b007e6b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/bubble/OpenActivityFromBubbleOnLocksreenTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.bubble
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.view.WindowInsets
 import android.view.WindowManager
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.Until
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index bf686d6..e38c4c3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -16,11 +16,11 @@
 
 package com.android.wm.shell.flicker.pip
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import org.junit.Assume
 import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index c003da6..b4cedd9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.pip
 
 import android.app.Activity
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
@@ -28,6 +27,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.entireScreenCovered
 import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index de64f78..42be818 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -24,6 +24,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.testapp.ActivityOptions
@@ -34,6 +35,7 @@
 import org.junit.runners.Parameterized
 
 /** Test the snapping of a PIP window via dragging, releasing, and checking its final location. */
+@FlakyTest(bugId = 294993100)
 @RequiresDevice
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index c315e74..a236126 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.flicker.pip
 
 import android.app.Activity
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
@@ -27,6 +26,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 3702be9..6b97169 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.EdgeExtensionComponentMatcher
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.CopyContentInSplitBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 8b90630..51588569 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -16,13 +16,13 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.helpers.WindowUtils
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByDividerBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 50f6a38..fc6c2b3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -16,12 +16,12 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.DismissSplitScreenByGoHomeBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index cc3b783..8b1689a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -16,12 +16,12 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.DragDividerToResizeBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
index f8d1e1f..99613f3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromAllAppsBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
index ff5d935..756a7fa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromNotificationBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
index 7c71077..121b46a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -16,13 +16,13 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromShortcutBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
index 8371706..99deb92 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenByDragFromTaskbarBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index 0bfdbb4..212a4e3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -16,12 +16,12 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.EnterSplitScreenFromOverviewBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 88bbc0e..284c32e 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -16,13 +16,13 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromAnotherAppBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index e85dc24..9e6448f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -16,13 +16,13 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromHomeBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index f7a9ed0..8e28712 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -16,13 +16,13 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBackToSplitFromRecentBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
index 66f9b85..fb0193b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -16,12 +16,12 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.SwitchBetweenSplitPairsBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
index 4c44028..f3145c9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/UnlockKeyguardToSplitScreen.kt
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.flicker.splitscreen
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
@@ -27,6 +26,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.wm.shell.flicker.splitscreen.benchmark.UnlockKeyguardToSplitScreenBenchmark
 import com.android.wm.shell.flicker.utils.ICommonAssertions
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index 38e9f39..54f9498 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -43,6 +43,7 @@
         "frameworks-base-testutils",
         "kotlinx-coroutines-android",
         "kotlinx-coroutines-core",
+        "mockito-kotlin2",
         "mockito-target-extended-minus-junit4",
         "truth-prebuilt",
         "testables",
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
index 4fca8b4..2d93047 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunnerTests.java
@@ -92,7 +92,7 @@
                 .build();
         final Animator animator = mAnimRunner.createAnimator(
                 info, mStartTransaction, mFinishTransaction,
-                () -> mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */),
+                () -> mFinishCallback.onTransitionFinished(null /* wct */),
                 new ArrayList());
 
         // The animation should be empty when it is behind starting window.
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
index ab1ccd4..0b2265d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationTestBase.java
@@ -75,7 +75,7 @@
         assertNotNull(mAnimRunner);
         mAnimSpec = mAnimRunner.mAnimationSpec;
         assertNotNull(mAnimSpec);
-        mFinishCallback = (wct, wctCB) -> {};
+        mFinishCallback = (wct) -> {};
         spyOn(mController);
         spyOn(mAnimRunner);
         spyOn(mAnimSpec);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
index ba34f1f7..270dbc4 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java
@@ -217,12 +217,10 @@
         doReturn(animator).when(mAnimRunner).createAnimator(any(), any(), any(), any(), any());
         mController.startAnimation(mTransition, info, mStartTransaction,
                 mFinishTransaction, mFinishCallback);
-        verify(mFinishCallback, never()).onTransitionFinished(any(), any());
+        verify(mFinishCallback, never()).onTransitionFinished(any());
         mController.mergeAnimation(mTransition, info, new SurfaceControl.Transaction(),
-                mTransition,
-                (wct, cb) -> {
-                });
-        verify(mFinishCallback).onTransitionFinished(any(), any());
+                mTransition, (wct) -> {});
+        verify(mFinishCallback).onTransitionFinished(any());
     }
 
     @Test
@@ -238,9 +236,9 @@
         mController.startAnimation(mTransition, info, mStartTransaction,
                 mFinishTransaction, mFinishCallback);
 
-        verify(mFinishCallback, never()).onTransitionFinished(any(), any());
+        verify(mFinishCallback, never()).onTransitionFinished(any());
         mController.onAnimationFinished(mTransition);
-        verify(mFinishCallback).onTransitionFinished(any(), any());
+        verify(mFinishCallback).onTransitionFinished(any());
 
         // Should not call finish when the finish has already been called.
         assertThrows(IllegalStateException.class,
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 3d8bd38..e7d0f60 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
@@ -67,6 +67,7 @@
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.sysui.ShellSharedConstants;
 
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -85,12 +86,11 @@
 
     private static final String ANIMATION_ENABLED = "1";
     private final TestShellExecutor mShellExecutor = new TestShellExecutor();
-    private ShellInit mShellInit;
-
     @Rule
     public TestableContext mContext =
             new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
 
+    private ShellInit mShellInit;
     @Mock
     private IActivityTaskManager mActivityTaskManager;
 
@@ -116,6 +116,8 @@
     private TestableContentResolver mContentResolver;
     private TestableLooper mTestableLooper;
 
+    private ShellBackAnimationRegistry mShellBackAnimationRegistry;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
@@ -126,11 +128,23 @@
                 ANIMATION_ENABLED);
         mTestableLooper = TestableLooper.get(this);
         mShellInit = spy(new ShellInit(mShellExecutor));
-        mController = new BackAnimationController(mShellInit, mShellController,
-                mShellExecutor, new Handler(mTestableLooper.getLooper()),
-                mActivityTaskManager, mContext,
-                mContentResolver, mAnimationBackground);
-        mController.setEnableUAnimation(true);
+        mShellBackAnimationRegistry =
+                new ShellBackAnimationRegistry(
+                        new CrossActivityAnimation(mContext, mAnimationBackground),
+                        new CrossTaskBackAnimation(mContext, mAnimationBackground),
+                        new CustomizeActivityAnimation(mContext, mAnimationBackground),
+                        null);
+        mController =
+                new BackAnimationController(
+                        mShellInit,
+                        mShellController,
+                        mShellExecutor,
+                        new Handler(mTestableLooper.getLooper()),
+                        mActivityTaskManager,
+                        mContext,
+                        mContentResolver,
+                        mAnimationBackground,
+                        mShellBackAnimationRegistry);
         mShellInit.init();
         mShellExecutor.flushAll();
     }
@@ -138,12 +152,13 @@
     private void createNavigationInfo(int backType,
             boolean enableAnimation,
             boolean isAnimationCallback) {
-        BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
-                .setType(backType)
-                .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
-                .setOnBackInvokedCallback(mAppCallback)
-                .setPrepareRemoteAnimation(enableAnimation)
-                .setAnimationCallback(isAnimationCallback);
+        BackNavigationInfo.Builder builder =
+                new BackNavigationInfo.Builder()
+                        .setType(backType)
+                        .setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
+                        .setOnBackInvokedCallback(mAppCallback)
+                        .setPrepareRemoteAnimation(enableAnimation)
+                        .setAnimationCallback(isAnimationCallback);
 
         createNavigationInfo(builder);
     }
@@ -188,18 +203,21 @@
 
     @Test
     public void verifyNavigationFinishes() throws RemoteException {
-        final int[] testTypes = new int[] {BackNavigationInfo.TYPE_RETURN_TO_HOME,
-                BackNavigationInfo.TYPE_CROSS_TASK,
-                BackNavigationInfo.TYPE_CROSS_ACTIVITY,
-                BackNavigationInfo.TYPE_DIALOG_CLOSE,
-                BackNavigationInfo.TYPE_CALLBACK };
+        final int[] testTypes =
+                new int[] {
+                    BackNavigationInfo.TYPE_RETURN_TO_HOME,
+                    BackNavigationInfo.TYPE_CROSS_TASK,
+                    BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                    BackNavigationInfo.TYPE_DIALOG_CLOSE,
+                    BackNavigationInfo.TYPE_CALLBACK
+                };
 
-        for (int type: testTypes) {
+        for (int type : testTypes) {
             registerAnimation(type);
         }
 
-        for (int type: testTypes) {
-            final ResultListener result  = new ResultListener();
+        for (int type : testTypes) {
+            final ResultListener result = new ResultListener();
             createNavigationInfo(new BackNavigationInfo.Builder()
                     .setType(type)
                     .setOnBackInvokedCallback(mAppCallback)
@@ -275,10 +293,17 @@
         // Toggle the setting off
         Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
         ShellInit shellInit = new ShellInit(mShellExecutor);
-        mController = new BackAnimationController(shellInit, mShellController,
-                mShellExecutor, new Handler(mTestableLooper.getLooper()),
-                mActivityTaskManager, mContext,
-                mContentResolver, mAnimationBackground);
+        mController =
+                new BackAnimationController(
+                        shellInit,
+                        mShellController,
+                        mShellExecutor,
+                        new Handler(mTestableLooper.getLooper()),
+                        mActivityTaskManager,
+                        mContext,
+                        mContentResolver,
+                        mAnimationBackground,
+                        mShellBackAnimationRegistry);
         shellInit.init();
         registerAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME);
 
@@ -398,17 +423,19 @@
 
     @Test
     public void animationNotDefined() throws RemoteException {
-        final int[] testTypes = new int[] {
-                BackNavigationInfo.TYPE_RETURN_TO_HOME,
-                BackNavigationInfo.TYPE_CROSS_TASK,
-                BackNavigationInfo.TYPE_CROSS_ACTIVITY,
-                BackNavigationInfo.TYPE_DIALOG_CLOSE};
+        final int[] testTypes =
+                new int[] {
+                    BackNavigationInfo.TYPE_RETURN_TO_HOME,
+                    BackNavigationInfo.TYPE_CROSS_TASK,
+                    BackNavigationInfo.TYPE_CROSS_ACTIVITY,
+                    BackNavigationInfo.TYPE_DIALOG_CLOSE
+                };
 
-        for (int type: testTypes) {
+        for (int type : testTypes) {
             unregisterAnimation(type);
         }
 
-        for (int type: testTypes) {
+        for (int type : testTypes) {
             final ResultListener result = new ResultListener();
             createNavigationInfo(new BackNavigationInfo.Builder()
                     .setType(type)
@@ -468,16 +495,14 @@
     public void testBackToActivity() throws RemoteException {
         final CrossActivityAnimation animation = new CrossActivityAnimation(mContext,
                 mAnimationBackground);
-        verifySystemBackBehavior(
-                BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.mBackAnimationRunner);
+        verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.getRunner());
     }
 
     @Test
     public void testBackToTask() throws RemoteException {
         final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext,
                 mAnimationBackground);
-        verifySystemBackBehavior(
-                BackNavigationInfo.TYPE_CROSS_TASK, animation.mBackAnimationRunner);
+        verifySystemBackBehavior(BackNavigationInfo.TYPE_CROSS_TASK, animation.getRunner());
     }
 
     private void verifySystemBackBehavior(int type, BackAnimationRunner animation)
@@ -554,10 +579,12 @@
     private static class ResultListener implements RemoteCallback.OnResultListener {
         boolean mBackNavigationDone = false;
         boolean mTriggerBack = false;
+
         @Override
         public void onResult(@Nullable Bundle result) {
             mBackNavigationDone = true;
             mTriggerBack = result.getBoolean(KEY_TRIGGER_BACK);
         }
-    };
+    }
+    ;
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
index e7d4598..cebbbd8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
@@ -102,15 +102,17 @@
         // start animation with remote animation targets
         final CountDownLatch finishCalled = new CountDownLatch(1);
         final Runnable finishCallback = finishCalled::countDown;
-        mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
-                new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+        mCustomizeActivityAnimation
+                .getRunner()
+                .startAnimation(
+                        new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
         verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
                 eq(BOUND_SIZE), eq(BOUND_SIZE));
         verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
                 eq(BOUND_SIZE), eq(BOUND_SIZE));
 
         try {
-            mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+            mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
         } catch (RemoteException r) {
             fail("onBackInvoked throw remote exception");
         }
@@ -133,15 +135,17 @@
         // start animation with remote animation targets
         final CountDownLatch finishCalled = new CountDownLatch(1);
         final Runnable finishCallback = finishCalled::countDown;
-        mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
-                new RemoteAnimationTarget[]{close, open}, null, null, finishCallback);
+        mCustomizeActivityAnimation
+                .getRunner()
+                .startAnimation(
+                        new RemoteAnimationTarget[] {close, open}, null, null, finishCallback);
         verify(mMockCloseAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
                 eq(BOUND_SIZE), eq(BOUND_SIZE));
         verify(mMockOpenAnimation).initialize(eq(BOUND_SIZE), eq(BOUND_SIZE),
                 eq(BOUND_SIZE), eq(BOUND_SIZE));
 
         try {
-            mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackCancelled();
+            mCustomizeActivityAnimation.getRunner().getCallback().onBackCancelled();
         } catch (RemoteException r) {
             fail("onBackCancelled throw remote exception");
         }
@@ -155,11 +159,12 @@
         // start animation without any remote animation targets
         final CountDownLatch finishCalled = new CountDownLatch(1);
         final Runnable finishCallback = finishCalled::countDown;
-        mCustomizeActivityAnimation.mBackAnimationRunner.startAnimation(
-                new RemoteAnimationTarget[]{}, null, null, finishCallback);
+        mCustomizeActivityAnimation
+                .getRunner()
+                .startAnimation(new RemoteAnimationTarget[] {}, null, null, finishCallback);
 
         try {
-            mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+            mCustomizeActivityAnimation.getRunner().getCallback().onBackInvoked();
         } catch (RemoteException r) {
             fail("onBackInvoked throw remote exception");
         }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
index 0e05e01..e359957 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
@@ -29,11 +29,11 @@
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
-import org.mockito.Mockito
-import org.mockito.Mockito.mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.spy
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.verify
 
 class BubbleDataRepositoryTest : ShellTestCase() {
 
@@ -124,7 +124,7 @@
 
     private val testHandler = Handler(Looper.getMainLooper())
     private val mainExecutor = HandlerExecutor(testHandler)
-    private val launcherApps = mock(LauncherApps::class.java)
+    private val launcherApps = mock<LauncherApps>()
 
     private val persistedBubbles = SparseArray<List<BubbleEntity>>()
 
@@ -158,8 +158,7 @@
         assertThat(persistedBubbles).isEqualTo(validEntitiesByUser)
 
         // No invalid users, so no persist to disk happened
-        verify(dataRepository, never()).persistToDisk(
-            any(SparseArray<List<BubbleEntity>>()::class.java))
+        verify(dataRepository, never()).persistToDisk(any())
     }
 
     @Test
@@ -199,6 +198,4 @@
         // Verify that persist to disk happened with the new valid entities list.
         verify(dataRepository).persistToDisk(validEntitiesByUser)
     }
-
-    fun <T> any(type: Class<T>): T = Mockito.any<T>(type)
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
new file mode 100644
index 0000000..139724f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblePositionerTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.view.View.LAYOUT_DIRECTION_LTR;
+import static android.view.View.LAYOUT_DIRECTION_RTL;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.res.Configuration;
+import android.graphics.Insets;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.testing.TestableResources;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests operations and the resulting state managed by {@link BubblePositioner}.
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class BubblePositionerTest extends ShellTestCase {
+
+    private static final int MIN_WIDTH_FOR_TABLET = 600;
+
+    private BubblePositioner mPositioner;
+    private Configuration mConfiguration;
+
+    @Mock
+    private WindowManager mWindowManager;
+    @Mock
+    private WindowMetrics mWindowMetrics;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mConfiguration = spy(new Configuration());
+        TestableResources testableResources = mContext.getOrCreateTestableResources();
+        testableResources.overrideConfiguration(mConfiguration);
+
+        mPositioner = new BubblePositioner(mContext, mWindowManager);
+    }
+
+    @Test
+    public void testUpdate() {
+        Insets insets = Insets.of(10, 20, 5, 15);
+        Rect screenBounds = new Rect(0, 0, 1000, 1200);
+        Rect availableRect = new Rect(screenBounds);
+        availableRect.inset(insets);
+
+        new WindowManagerConfig()
+                .setInsets(insets)
+                .setScreenBounds(screenBounds)
+                .setUpConfig();
+        mPositioner.update();
+
+        assertThat(mPositioner.getAvailableRect()).isEqualTo(availableRect);
+        assertThat(mPositioner.isLandscape()).isFalse();
+        assertThat(mPositioner.isLargeScreen()).isFalse();
+        assertThat(mPositioner.getInsets()).isEqualTo(insets);
+    }
+
+    @Test
+    public void testShowBubblesVertically_phonePortrait() {
+        new WindowManagerConfig().setOrientation(ORIENTATION_PORTRAIT).setUpConfig();
+        mPositioner.update();
+
+        assertThat(mPositioner.showBubblesVertically()).isFalse();
+    }
+
+    @Test
+    public void testShowBubblesVertically_phoneLandscape() {
+        new WindowManagerConfig().setOrientation(ORIENTATION_LANDSCAPE).setUpConfig();
+        mPositioner.update();
+
+        assertThat(mPositioner.isLandscape()).isTrue();
+        assertThat(mPositioner.showBubblesVertically()).isTrue();
+    }
+
+    @Test
+    public void testShowBubblesVertically_tablet() {
+        new WindowManagerConfig().setLargeScreen().setUpConfig();
+        mPositioner.update();
+
+        assertThat(mPositioner.showBubblesVertically()).isTrue();
+    }
+
+    /** If a resting position hasn't been set, calling it will return the default position. */
+    @Test
+    public void testGetRestingPosition_returnsDefaultPosition() {
+        new WindowManagerConfig().setUpConfig();
+        mPositioner.update();
+
+        PointF restingPosition = mPositioner.getRestingPosition();
+        PointF defaultPosition = mPositioner.getDefaultStartPosition();
+
+        assertThat(restingPosition).isEqualTo(defaultPosition);
+    }
+
+    /** If a resting position has been set, it'll return that instead of the default position. */
+    @Test
+    public void testGetRestingPosition_returnsRestingPosition() {
+        new WindowManagerConfig().setUpConfig();
+        mPositioner.update();
+
+        PointF restingPosition = new PointF(100, 100);
+        mPositioner.setRestingPosition(restingPosition);
+
+        assertThat(mPositioner.getRestingPosition()).isEqualTo(restingPosition);
+    }
+
+    /** Test that the default resting position on phone is in upper left. */
+    @Test
+    public void testGetRestingPosition_bubble_onPhone() {
+        new WindowManagerConfig().setUpConfig();
+        mPositioner.update();
+
+        RectF allowableStackRegion =
+                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+        PointF restingPosition = mPositioner.getRestingPosition();
+
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
+        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
+    }
+
+    @Test
+    public void testGetRestingPosition_bubble_onPhone_RTL() {
+        new WindowManagerConfig().setLayoutDirection(LAYOUT_DIRECTION_RTL).setUpConfig();
+        mPositioner.update();
+
+        RectF allowableStackRegion =
+                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+        PointF restingPosition = mPositioner.getRestingPosition();
+
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
+        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
+    }
+
+    /** Test that the default resting position on tablet is middle left. */
+    @Test
+    public void testGetRestingPosition_chatBubble_onTablet() {
+        new WindowManagerConfig().setLargeScreen().setUpConfig();
+        mPositioner.update();
+
+        RectF allowableStackRegion =
+                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+        PointF restingPosition = mPositioner.getRestingPosition();
+
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.left);
+        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
+    }
+
+    @Test
+    public void testGetRestingPosition_chatBubble_onTablet_RTL() {
+        new WindowManagerConfig().setLargeScreen().setLayoutDirection(
+                LAYOUT_DIRECTION_RTL).setUpConfig();
+        mPositioner.update();
+
+        RectF allowableStackRegion =
+                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+        PointF restingPosition = mPositioner.getRestingPosition();
+
+        assertThat(restingPosition.x).isEqualTo(allowableStackRegion.right);
+        assertThat(restingPosition.y).isEqualTo(getDefaultYPosition());
+    }
+
+    /**
+     * Calculates the Y position bubbles should be placed based on the config. Based on
+     * the calculations in {@link BubblePositioner#getDefaultStartPosition()} and
+     * {@link BubbleStackView.RelativeStackPosition}.
+     */
+    private float getDefaultYPosition() {
+        final boolean isTablet = mPositioner.isLargeScreen();
+
+        // On tablet the position is centered, on phone it is an offset from the top.
+        final float desiredY = isTablet
+                ? mPositioner.getScreenRect().height() / 2f - (mPositioner.getBubbleSize() / 2f)
+                : mContext.getResources().getDimensionPixelOffset(
+                        R.dimen.bubble_stack_starting_offset_y);
+        // Since we're visually centering the bubbles on tablet, use total screen height rather
+        // than the available height.
+        final float height = isTablet
+                ? mPositioner.getScreenRect().height()
+                : mPositioner.getAvailableRect().height();
+        float offsetPercent = desiredY / height;
+        offsetPercent = Math.max(0f, Math.min(1f, offsetPercent));
+        final RectF allowableStackRegion =
+                mPositioner.getAllowableStackPositionRegion(1 /* bubbleCount */);
+        return allowableStackRegion.top + allowableStackRegion.height() * offsetPercent;
+    }
+
+    /**
+     * Sets up window manager to return config values based on what you need for the test.
+     * By default it sets up a portrait phone without any insets.
+     */
+    private class WindowManagerConfig {
+        private Rect mScreenBounds = new Rect(0, 0, 1000, 2000);
+        private boolean mIsLargeScreen = false;
+        private int mOrientation = ORIENTATION_PORTRAIT;
+        private int mLayoutDirection = LAYOUT_DIRECTION_LTR;
+        private Insets mInsets = Insets.of(0, 0, 0, 0);
+
+        public WindowManagerConfig setScreenBounds(Rect screenBounds) {
+            mScreenBounds = screenBounds;
+            return this;
+        }
+
+        public WindowManagerConfig setLargeScreen() {
+            mIsLargeScreen = true;
+            return this;
+        }
+
+        public WindowManagerConfig setOrientation(int orientation) {
+            mOrientation = orientation;
+            return this;
+        }
+
+        public WindowManagerConfig setLayoutDirection(int layoutDirection) {
+            mLayoutDirection = layoutDirection;
+            return this;
+        }
+
+        public WindowManagerConfig setInsets(Insets insets) {
+            mInsets = insets;
+            return this;
+        }
+
+        public void setUpConfig() {
+            mConfiguration.smallestScreenWidthDp = mIsLargeScreen
+                    ? MIN_WIDTH_FOR_TABLET
+                    : MIN_WIDTH_FOR_TABLET - 1;
+            mConfiguration.orientation = mOrientation;
+
+            when(mConfiguration.getLayoutDirection()).thenReturn(mLayoutDirection);
+            WindowInsets windowInsets = mock(WindowInsets.class);
+            when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(mInsets);
+            when(mWindowMetrics.getWindowInsets()).thenReturn(windowInsets);
+            when(mWindowMetrics.getBounds()).thenReturn(mScreenBounds);
+            when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index 443cea2..fe2da5d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -38,7 +38,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
-import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
index addc233..bf1b7f9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsAlgorithmTest.java
@@ -32,7 +32,8 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -60,7 +61,8 @@
 
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
     private DisplayInfo mDefaultDisplayInfo;
-    private PipBoundsState mPipBoundsState; private PipSizeSpecHandler mPipSizeSpecHandler;
+    private PipBoundsState mPipBoundsState;
+    private SizeSpecSource mSizeSpecSource;
     private PipDisplayLayoutState mPipDisplayLayoutState;
 
 
@@ -68,11 +70,12 @@
     public void setUp() throws Exception {
         initializeMockResources();
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
-        mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+
+        mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+        mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
                 new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
-                mPipSizeSpecHandler);
+                mPipDisplayLayoutState, mSizeSpecSource);
 
         DisplayLayout layout =
                 new DisplayLayout(mDefaultDisplayInfo, mContext.getResources(), true, true);
@@ -132,7 +135,7 @@
 
     @Test
     public void getDefaultBounds_noOverrideMinSize_matchesDefaultSizeAndAspectRatio() {
-        final Size defaultSize = mPipSizeSpecHandler.getDefaultSize(DEFAULT_ASPECT_RATIO);
+        final Size defaultSize = mSizeSpecSource.getDefaultSize(DEFAULT_ASPECT_RATIO);
 
         mPipBoundsState.setOverrideMinSize(null);
         final Rect defaultBounds = mPipBoundsAlgorithm.getDefaultBounds();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
index f320004..4341c4c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipBoundsStateTest.java
@@ -35,7 +35,8 @@
 import com.android.internal.util.function.TriConsumer;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -58,6 +59,7 @@
     private static final int OVERRIDABLE_MIN_SIZE = 40;
 
     private PipBoundsState mPipBoundsState;
+    private SizeSpecSource mSizeSpecSource;
     private ComponentName mTestComponentName1;
     private ComponentName mTestComponentName2;
 
@@ -69,8 +71,8 @@
                 OVERRIDABLE_MIN_SIZE);
 
         PipDisplayLayoutState pipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipBoundsState = new PipBoundsState(mContext,
-                new PipSizeSpecHandler(mContext, pipDisplayLayoutState), pipDisplayLayoutState);
+        mSizeSpecSource = new PhoneSizeSpecSource(mContext, pipDisplayLayoutState);
+        mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, pipDisplayLayoutState);
         mTestComponentName1 = new ComponentName(mContext, "component1");
         mTestComponentName2 = new ComponentName(mContext, "component2");
     }
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 842c699..1e3fe42 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
@@ -52,8 +52,9 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.phone.PhonePipMenuController;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import org.junit.Before;
@@ -87,7 +88,7 @@
     private PipBoundsState mPipBoundsState;
     private PipTransitionState mPipTransitionState;
     private PipBoundsAlgorithm mPipBoundsAlgorithm;
-    private PipSizeSpecHandler mPipSizeSpecHandler;
+    private SizeSpecSource mSizeSpecSource;
     private PipDisplayLayoutState mPipDisplayLayoutState;
 
     private ComponentName mComponent1;
@@ -99,12 +100,12 @@
         mComponent1 = new ComponentName(mContext, "component1");
         mComponent2 = new ComponentName(mContext, "component2");
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
-        mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+        mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+        mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
         mPipTransitionState = new PipTransitionState();
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState,
                 new PipSnapAlgorithm(), new PipKeepClearAlgorithmInterface() {},
-                mPipSizeSpecHandler);
+                mPipDisplayLayoutState, mSizeSpecSource);
         mMainExecutor = new TestShellExecutor();
         mPipTaskOrganizer = new PipTaskOrganizer(mContext, mMockSyncTransactionQueue,
                 mPipTransitionState, mPipBoundsState, mPipDisplayLayoutState,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
similarity index 84%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
index 528c23c..024cba3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipSizeSpecHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PhoneSizeSpecSourceTest.java
@@ -32,6 +32,8 @@
 import com.android.dx.mockito.inline.extended.StaticMockitoSession;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
 
 import org.junit.After;
@@ -47,10 +49,10 @@
 import java.util.function.Function;
 
 /**
- * Unit test against {@link PipSizeSpecHandler} with feature flag on.
+ * Unit test against {@link PhoneSizeSpecSource}
  */
 @RunWith(AndroidTestingRunner.class)
-public class PipSizeSpecHandlerTest extends ShellTestCase {
+public class PhoneSizeSpecSourceTest extends ShellTestCase {
     /** A sample overridden min edge size. */
     private static final int OVERRIDE_MIN_EDGE_SIZE = 40;
     /** A sample default min edge size */
@@ -75,7 +77,7 @@
     @Mock private Resources mResources;
 
     private PipDisplayLayoutState mPipDisplayLayoutState;
-    private TestPipSizeSpecHandler mPipSizeSpecHandler;
+    private SizeSpecSource mSizeSpecSource;
 
     /**
      * Sets up static Mockito session for SystemProperties and mocks necessary static methods.
@@ -158,10 +160,10 @@
         mPipDisplayLayoutState.setDisplayLayout(displayLayout);
 
         setUpStaticSystemPropertiesSession();
-        mPipSizeSpecHandler = new TestPipSizeSpecHandler(mContext, mPipDisplayLayoutState);
+        mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
 
         // no overridden min edge size by default
-        mPipSizeSpecHandler.setOverrideMinSize(null);
+        mSizeSpecSource.setOverrideMinSize(null);
     }
 
     @After
@@ -172,19 +174,19 @@
     @Test
     public void testGetMaxSize() {
         forEveryTestCaseCheck(sExpectedMaxSizes,
-                (aspectRatio) -> mPipSizeSpecHandler.getMaxSize(aspectRatio));
+                (aspectRatio) -> mSizeSpecSource.getMaxSize(aspectRatio));
     }
 
     @Test
     public void testGetDefaultSize() {
         forEveryTestCaseCheck(sExpectedDefaultSizes,
-                (aspectRatio) -> mPipSizeSpecHandler.getDefaultSize(aspectRatio));
+                (aspectRatio) -> mSizeSpecSource.getDefaultSize(aspectRatio));
     }
 
     @Test
     public void testGetMinSize() {
         forEveryTestCaseCheck(sExpectedMinSizes,
-                (aspectRatio) -> mPipSizeSpecHandler.getMinSize(aspectRatio));
+                (aspectRatio) -> mSizeSpecSource.getMinSize(aspectRatio));
     }
 
     @Test
@@ -193,7 +195,7 @@
         Size initSize = new Size(600, 337);
 
         Size expectedSize = new Size(338, 601);
-        Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
+        Size actualSize = mSizeSpecSource.getSizeForAspectRatio(initSize, 9f / 16);
 
         Assert.assertEquals(expectedSize, actualSize);
     }
@@ -201,26 +203,12 @@
     @Test
     public void testGetSizeForAspectRatio_withOverrideMinSize() {
         // an initial size with a 1:1 aspect ratio
-        mPipSizeSpecHandler.setOverrideMinSize(new Size(OVERRIDE_MIN_EDGE_SIZE,
-                OVERRIDE_MIN_EDGE_SIZE));
-        // make sure initial size is same as override min size
-        Size initSize = mPipSizeSpecHandler.getOverrideMinSize();
+        Size initSize = new Size(OVERRIDE_MIN_EDGE_SIZE, OVERRIDE_MIN_EDGE_SIZE);
+        mSizeSpecSource.setOverrideMinSize(initSize);
 
         Size expectedSize = new Size(40, 71);
-        Size actualSize = mPipSizeSpecHandler.getSizeForAspectRatio(initSize, 9f / 16);
+        Size actualSize = mSizeSpecSource.getSizeForAspectRatio(initSize, 9f / 16);
 
         Assert.assertEquals(expectedSize, actualSize);
     }
-
-    static class TestPipSizeSpecHandler extends PipSizeSpecHandler {
-
-        TestPipSizeSpecHandler(Context context, PipDisplayLayoutState displayLayoutState) {
-            super(context, displayLayoutState);
-        }
-
-        @Override
-        boolean supportsPipSizeLargeScreen() {
-            return true;
-        }
-    }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 85167cb..0ae2908 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -55,9 +55,9 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.onehanded.OneHandedController;
 import com.android.wm.shell.pip.PipAnimationController;
-import com.android.wm.shell.pip.PipAppOpsListener;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
@@ -109,7 +109,6 @@
     @Mock private PipMotionHelper mMockPipMotionHelper;
     @Mock private WindowManagerShellWrapper mMockWindowManagerShellWrapper;
     @Mock private PipBoundsState mMockPipBoundsState;
-    @Mock private PipSizeSpecHandler mMockPipSizeSpecHandler;
     @Mock private PipDisplayLayoutState mMockPipDisplayLayoutState;
     @Mock private TaskStackListenerImpl mMockTaskStackListener;
     @Mock private ShellExecutor mMockExecutor;
@@ -134,7 +133,7 @@
         mPipController = new PipController(mContext, mShellInit, mMockShellCommandHandler,
                 mShellController, mMockDisplayController, mMockPipAnimationController,
                 mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
-                mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+                mMockPipBoundsState, mMockPipDisplayLayoutState,
                 mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
                 mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
@@ -226,7 +225,7 @@
         assertNull(PipController.create(spyContext, shellInit, mMockShellCommandHandler,
                 mShellController, mMockDisplayController, mMockPipAnimationController,
                 mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm,
-                mMockPipBoundsState, mMockPipSizeSpecHandler, mMockPipDisplayLayoutState,
+                mMockPipBoundsState, mMockPipDisplayLayoutState,
                 mMockPipMotionHelper, mMockPipMediaController, mMockPhonePipMenuController,
                 mMockPipTaskOrganizer, mMockPipTransitionState, mMockPipTouchHandler,
                 mMockPipTransitionController, mMockWindowManagerShellWrapper,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index 1dfdbf6..689b5c5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -36,6 +36,8 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
@@ -87,7 +89,7 @@
 
     private PipBoundsState mPipBoundsState;
 
-    private PipSizeSpecHandler mPipSizeSpecHandler;
+    private SizeSpecSource mSizeSpecSource;
 
     private PipDisplayLayoutState mPipDisplayLayoutState;
 
@@ -97,13 +99,14 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
-        mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+        mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+        mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
         final PipSnapAlgorithm pipSnapAlgorithm = new PipSnapAlgorithm();
         final PipKeepClearAlgorithmInterface pipKeepClearAlgorithm =
                 new PipKeepClearAlgorithmInterface() {};
         final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
-                mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipSizeSpecHandler);
+                mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
+                mSizeSpecSource);
         final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
                 mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
                 mMockPipTransitionController, mFloatingContentCoordinator);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 10b1ddf..852183c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -33,6 +33,8 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.pip.PipBoundsState;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
@@ -92,7 +94,7 @@
     private PipSnapAlgorithm mPipSnapAlgorithm;
     private PipMotionHelper mMotionHelper;
     private PipResizeGestureHandler mPipResizeGestureHandler;
-    private PipSizeSpecHandler mPipSizeSpecHandler;
+    private SizeSpecSource mSizeSpecSource;
     private PipDisplayLayoutState mPipDisplayLayoutState;
 
     private DisplayLayout mDisplayLayout;
@@ -108,16 +110,16 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
-        mPipBoundsState = new PipBoundsState(mContext, mPipSizeSpecHandler, mPipDisplayLayoutState);
+        mSizeSpecSource = new PhoneSizeSpecSource(mContext, mPipDisplayLayoutState);
+        mPipBoundsState = new PipBoundsState(mContext, mSizeSpecSource, mPipDisplayLayoutState);
         mPipSnapAlgorithm = new PipSnapAlgorithm();
         mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
-                new PipKeepClearAlgorithmInterface() {}, mPipSizeSpecHandler);
+                new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
         PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
                 mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
                 mMockPipTransitionController, mFloatingContentCoordinator);
         mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
-                mPipBoundsAlgorithm, mPipBoundsState, mPipSizeSpecHandler, mPipTaskOrganizer,
+                mPipBoundsAlgorithm, mPipBoundsState, mSizeSpecSource, mPipTaskOrganizer,
                 pipMotionHelper, mFloatingContentCoordinator, mPipUiEventLogger, mMainExecutor);
         // We aren't actually using ShellInit, so just call init directly
         mPipTouchHandler.onInit();
@@ -162,8 +164,8 @@
 
         // getting the expected min and max size
         float aspectRatio = (float) mPipBounds.width() / mPipBounds.height();
-        Size expectedMinSize = mPipSizeSpecHandler.getMinSize(aspectRatio);
-        Size expectedMaxSize = mPipSizeSpecHandler.getMaxSize(aspectRatio);
+        Size expectedMinSize = mSizeSpecSource.getMinSize(aspectRatio);
+        Size expectedMaxSize = mSizeSpecSource.getMaxSize(aspectRatio);
 
         assertEquals(expectedMovementBounds, mPipBoundsState.getNormalMovementBounds());
         verify(mPipResizeGestureHandler, times(1))
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
index 91ff3cb..256610b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipGravityTest.java
@@ -26,9 +26,10 @@
 import android.view.Gravity;
 
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.pip.LegacySizeSpecSource;
+import com.android.wm.shell.common.pip.SizeSpecSource;
 import com.android.wm.shell.pip.PipDisplayLayoutState;
 import com.android.wm.shell.pip.PipSnapAlgorithm;
-import com.android.wm.shell.pip.phone.PipSizeSpecHandler;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -47,7 +48,7 @@
 
     private TvPipBoundsState mTvPipBoundsState;
     private TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
-    private PipSizeSpecHandler mPipSizeSpecHandler;
+    private SizeSpecSource mSizeSpecSource;
     private PipDisplayLayoutState mPipDisplayLayoutState;
 
     @Before
@@ -56,11 +57,11 @@
 
         MockitoAnnotations.initMocks(this);
         mPipDisplayLayoutState = new PipDisplayLayoutState(mContext);
-        mPipSizeSpecHandler = new PipSizeSpecHandler(mContext, mPipDisplayLayoutState);
-        mTvPipBoundsState = new TvPipBoundsState(mContext, mPipSizeSpecHandler,
+        mSizeSpecSource = new LegacySizeSpecSource(mContext, mPipDisplayLayoutState);
+        mTvPipBoundsState = new TvPipBoundsState(mContext, mSizeSpecSource,
                 mPipDisplayLayoutState);
         mTvPipBoundsAlgorithm = new TvPipBoundsAlgorithm(mContext, mTvPipBoundsState,
-                mMockPipSnapAlgorithm, mPipSizeSpecHandler);
+                mMockPipSnapAlgorithm, mPipDisplayLayoutState, mSizeSpecSource);
 
         setRTL(false);
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index e8a1e91..568db91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -65,6 +65,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -106,6 +107,7 @@
     @Mock RecentTasksController mRecentTasks;
     @Mock LaunchAdjacentController mLaunchAdjacentController;
     @Mock WindowDecorViewModel mWindowDecorViewModel;
+    @Mock DesktopTasksController mDesktopTasksController;
     @Captor ArgumentCaptor<Intent> mIntentCaptor;
 
     private ShellController mShellController;
@@ -122,7 +124,7 @@
                 mRootTDAOrganizer, mDisplayController, mDisplayImeController,
                 mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
                 mIconProvider, mRecentTasks, mLaunchAdjacentController, mWindowDecorViewModel,
-                mMainExecutor, mStageCoordinator));
+                mDesktopTasksController, mMainExecutor, mStageCoordinator));
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index ff380e9..99a1ac6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -176,7 +176,7 @@
         assertEquals(1, mDefaultHandler.activeCount());
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any());
     }
 
     @Test
@@ -299,7 +299,7 @@
         assertTrue(remoteCalled[0]);
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), eq(remoteFinishWCT), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), eq(remoteFinishWCT));
     }
 
     @Test
@@ -449,7 +449,7 @@
         assertTrue(remoteCalled[0]);
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any());
     }
 
     @Test
@@ -524,20 +524,20 @@
         // default handler doesn't merge by default, so it shouldn't increment active count.
         assertEquals(1, mDefaultHandler.activeCount());
         assertEquals(0, mDefaultHandler.mergeCount());
-        verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any(), any());
-        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any());
+        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any());
 
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
         // first transition finished
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any());
-        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any());
+        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any());
         // But now the "queued" transition is running
         assertEquals(1, mDefaultHandler.activeCount());
 
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any());
     }
 
     @Test
@@ -565,15 +565,15 @@
         // it should still only have 1 active, but then show 1 merged
         assertEquals(1, mDefaultHandler.activeCount());
         assertEquals(1, mDefaultHandler.mergeCount());
-        verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any(), any());
+        verify(mOrganizer, times(0)).finishTransition(eq(transitToken1), any());
         // We don't tell organizer it is finished yet (since we still want to maintain ordering)
-        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any());
 
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
         // transition + merged all finished.
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any());
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any());
         // Make sure nothing was queued
         assertEquals(0, mDefaultHandler.activeCount());
     }
@@ -599,8 +599,7 @@
         requestStartTransition(transitions, transitTokenNotReady);
 
         mDefaultHandler.setSimulateMerge(true);
-        mDefaultHandler.mFinishes.get(0).second.onTransitionFinished(
-                null /* wct */, null /* wctCB */);
+        mDefaultHandler.mFinishes.get(0).second.onTransitionFinished(null /* wct */);
 
         // Make sure that the non-ready transition is not merged.
         assertEquals(0, mDefaultHandler.mergeCount());
@@ -823,8 +822,8 @@
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
         // first transition finished
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any(), any());
-        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken1), any());
+        verify(mOrganizer, times(0)).finishTransition(eq(transitToken2), any());
         // But now the "queued" transition is running
         assertEquals(1, mDefaultHandler.activeCount());
 
@@ -835,7 +834,7 @@
 
         mDefaultHandler.finishAll();
         mMainExecutor.flushAll();
-        verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any(), any());
+        verify(mOrganizer, times(1)).finishTransition(eq(transitToken2), any());
 
         // runnable2 and runnable3 are executed after the second transition finishes because there
         // are no other active transitions, runnable1 isn't executed again.
@@ -1449,13 +1448,13 @@
             if (mFinishOnSync && info.getType() == TRANSIT_SLEEP) {
                 for (int i = 0; i < mFinishes.size(); ++i) {
                     if (mFinishes.get(i).first != mergeTarget) continue;
-                    mFinishes.remove(i).second.onTransitionFinished(null, null);
+                    mFinishes.remove(i).second.onTransitionFinished(null);
                     return;
                 }
             }
             if (!(mSimulateMerge || mShouldMerge.contains(transition))) return;
             mMerged.add(transition);
-            finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+            finishCallback.onTransitionFinished(null /* wct */);
         }
 
         @Nullable
@@ -1478,7 +1477,7 @@
                     mFinishes;
             mFinishes = new ArrayList<>();
             for (int i = finishes.size() - 1; i >= 0; --i) {
-                finishes.get(i).second.onTransitionFinished(null /* wct */, null /* wctCB */);
+                finishes.get(i).second.onTransitionFinished(null /* wct */);
             }
             mShouldMerge.clear();
         }
@@ -1486,7 +1485,7 @@
         void finishOne() {
             Pair<IBinder, Transitions.TransitionFinishCallback> fin = mFinishes.remove(0);
             mMerged.clear();
-            fin.second.onTransitionFinished(null /* wct */, null /* wctCB */);
+            fin.second.onTransitionFinished(null /* wct */);
         }
 
         int activeCount() {
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 5ffec34..b1ef4e5 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -91,17 +91,12 @@
   StringPoolRef entry_string_ref;
 };
 
-AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration) {
-  configurations_.push_back(configuration);
-
+AssetManager2::AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration)
+    : configuration_(configuration) {
   // Don't invalidate caches here as there's nothing cached yet.
   SetApkAssets(apk_assets, false);
 }
 
-AssetManager2::AssetManager2() {
-  configurations_.resize(1);
-}
-
 bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) {
   BuildDynamicRefTable(apk_assets);
   RebuildFilterList();
@@ -426,16 +421,9 @@
   return false;
 }
 
-void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations) {
-  int diff = 0;
-  if (configurations_.size() != configurations.size()) {
-    diff = -1;
-  } else {
-    for (int i = 0; i < configurations_.size(); i++) {
-      diff |= configurations_[i].diff(configurations[i]);
-    }
-  }
-  configurations_ = std::move(configurations);
+void AssetManager2::SetConfiguration(const ResTable_config& configuration) {
+  const int diff = configuration_.diff(configuration);
+  configuration_ = configuration;
 
   if (diff) {
     RebuildFilterList();
@@ -632,6 +620,16 @@
 
   auto op = StartOperation();
 
+  // Might use this if density_override != 0.
+  ResTable_config density_override_config;
+
+  // Select our configuration or generate a density override configuration.
+  const ResTable_config* desired_config = &configuration_;
+  if (density_override != 0 && density_override != configuration_.density) {
+    density_override_config = configuration_;
+    density_override_config.density = density_override;
+    desired_config = &density_override_config;
+  }
 
   // Retrieve the package group from the package id of the resource id.
   if (UNLIKELY(!is_valid_resid(resid))) {
@@ -650,160 +648,119 @@
   }
 
   const PackageGroup& package_group = package_groups_[package_idx];
-  std::optional<FindEntryResult> final_result;
-  bool final_has_locale = false;
-  bool final_overlaid = false;
-  for (auto & config : configurations_) {
-    // Might use this if density_override != 0.
-    ResTable_config density_override_config;
+  auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
+                                  stop_at_first_match, ignore_configuration);
+  if (UNLIKELY(!result.has_value())) {
+    return base::unexpected(result.error());
+  }
 
-    // Select our configuration or generate a density override configuration.
-    const ResTable_config* desired_config = &config;
-    if (density_override != 0 && density_override != config.density) {
-      density_override_config = config;
-      density_override_config.density = density_override;
-      desired_config = &density_override_config;
+  bool overlaid = false;
+  if (!stop_at_first_match && !ignore_configuration) {
+    const auto& assets = GetApkAssets(result->cookie);
+    if (!assets) {
+      ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
+      return base::unexpected(std::nullopt);
     }
-
-    auto result = FindEntryInternal(package_group, type_idx, entry_idx, *desired_config,
-                                    stop_at_first_match, ignore_configuration);
-    if (UNLIKELY(!result.has_value())) {
-      return base::unexpected(result.error());
-    }
-    bool overlaid = false;
-    if (!stop_at_first_match && !ignore_configuration) {
-      const auto& assets = GetApkAssets(result->cookie);
-      if (!assets) {
-        ALOGE("Found expired ApkAssets #%d for resource ID 0x%08x.", result->cookie, resid);
-        return base::unexpected(std::nullopt);
-      }
-      if (!assets->IsLoader()) {
-        for (const auto& id_map : package_group.overlays_) {
-          auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
-          if (!overlay_entry) {
-            // No id map entry exists for this target resource.
-            continue;
-          }
-          if (overlay_entry.IsInlineValue()) {
-            // The target resource is overlaid by an inline value not represented by a resource.
-            ConfigDescription best_frro_config;
-            Res_value best_frro_value;
-            bool frro_found = false;
-            for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
-              if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
-                  && config.match(*desired_config)) {
-                frro_found = true;
-                best_frro_config = config;
-                best_frro_value = value;
-              }
+    if (!assets->IsLoader()) {
+      for (const auto& id_map : package_group.overlays_) {
+        auto overlay_entry = id_map.overlay_res_maps_.Lookup(resid);
+        if (!overlay_entry) {
+          // No id map entry exists for this target resource.
+          continue;
+        }
+        if (overlay_entry.IsInlineValue()) {
+          // The target resource is overlaid by an inline value not represented by a resource.
+          ConfigDescription best_frro_config;
+          Res_value best_frro_value;
+          bool frro_found = false;
+          for( const auto& [config, value] : overlay_entry.GetInlineValue()) {
+            if ((!frro_found || config.isBetterThan(best_frro_config, desired_config))
+                && config.match(*desired_config)) {
+              frro_found = true;
+              best_frro_config = config;
+              best_frro_value = value;
             }
-            if (!frro_found) {
-              continue;
-            }
-            result->entry = best_frro_value;
-            result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
-            result->cookie = id_map.cookie;
-
-            if (UNLIKELY(logging_enabled)) {
-              last_resolution_.steps.push_back(Resolution::Step{
-                  Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
-              if (auto path = assets->GetPath()) {
-                const std::string overlay_path = path->data();
-                if (IsFabricatedOverlay(overlay_path)) {
-                  // FRRO don't have package name so we use the creating package here.
-                  String8 frro_name = String8("FRRO");
-                  // Get the first part of it since the expected one should be like
-                  // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
-                  // under /data/resource-cache/.
-                  const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
-                  const size_t end = name.find('-');
-                  if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
-                    frro_name.append(base::StringPrintf(" created by %s",
-                                                        name.substr(0 /* pos */,
-                                                                    end).c_str()).c_str());
-                  }
-                  last_resolution_.best_package_name = frro_name;
-                } else {
-                  last_resolution_.best_package_name = result->package_name->c_str();
-                }
-              }
-              overlaid = true;
-            }
+          }
+          if (!frro_found) {
             continue;
           }
-
-          auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
-                                          false /* stop_at_first_match */,
-                                          false /* ignore_configuration */);
-          if (UNLIKELY(IsIOError(overlay_result))) {
-            return base::unexpected(overlay_result.error());
-          }
-          if (!overlay_result.has_value()) {
-            continue;
-          }
-
-          if (!overlay_result->config.isBetterThan(result->config, desired_config)
-              && overlay_result->config.compare(result->config) != 0) {
-            // The configuration of the entry for the overlay must be equal to or better than the
-            // target configuration to be chosen as the better value.
-            continue;
-          }
-
-          result->cookie = overlay_result->cookie;
-          result->entry = overlay_result->entry;
-          result->config = overlay_result->config;
+          result->entry = best_frro_value;
           result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
+          result->cookie = id_map.cookie;
 
           if (UNLIKELY(logging_enabled)) {
             last_resolution_.steps.push_back(
-                Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
-                                 overlay_result->config.toString()});
-            last_resolution_.best_package_name =
-                overlay_result->package_name->c_str();
+                Resolution::Step{Resolution::Step::Type::OVERLAID_INLINE, result->cookie, String8()});
+            if (auto path = assets->GetPath()) {
+              const std::string overlay_path = path->data();
+              if (IsFabricatedOverlay(overlay_path)) {
+                // FRRO don't have package name so we use the creating package here.
+                String8 frro_name = String8("FRRO");
+                // Get the first part of it since the expected one should be like
+                // {overlayPackageName}-{overlayName}-{4 alphanumeric chars}.frro
+                // under /data/resource-cache/.
+                const std::string name = overlay_path.substr(overlay_path.rfind('/') + 1);
+                const size_t end = name.find('-');
+                if (frro_name.size() != overlay_path.size() && end != std::string::npos) {
+                  frro_name.append(base::StringPrintf(" created by %s",
+                                                      name.substr(0 /* pos */,
+                                                                  end).c_str()).c_str());
+                }
+                last_resolution_.best_package_name = frro_name;
+              } else {
+                last_resolution_.best_package_name = result->package_name->c_str();
+              }
+            }
             overlaid = true;
           }
+          continue;
+        }
+
+        auto overlay_result = FindEntry(overlay_entry.GetResourceId(), density_override,
+                                        false /* stop_at_first_match */,
+                                        false /* ignore_configuration */);
+        if (UNLIKELY(IsIOError(overlay_result))) {
+          return base::unexpected(overlay_result.error());
+        }
+        if (!overlay_result.has_value()) {
+          continue;
+        }
+
+        if (!overlay_result->config.isBetterThan(result->config, desired_config)
+            && overlay_result->config.compare(result->config) != 0) {
+          // The configuration of the entry for the overlay must be equal to or better than the target
+          // configuration to be chosen as the better value.
+          continue;
+        }
+
+        result->cookie = overlay_result->cookie;
+        result->entry = overlay_result->entry;
+        result->config = overlay_result->config;
+        result->dynamic_ref_table = id_map.overlay_res_maps_.GetOverlayDynamicRefTable();
+
+        if (UNLIKELY(logging_enabled)) {
+          last_resolution_.steps.push_back(
+              Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result->cookie,
+                               overlay_result->config.toString()});
+          last_resolution_.best_package_name =
+              overlay_result->package_name->c_str();
+          overlaid = true;
         }
       }
     }
-
-    bool has_locale = false;
-    if (result->config.locale == 0) {
-      if (default_locale_ != 0) {
-        ResTable_config conf;
-        conf.locale = default_locale_;
-        // Since we know conf has a locale and only a locale, match will tell us if that locale
-        // matches
-        has_locale = conf.match(config);
-      }
-    } else {
-      has_locale = true;
-    }
-
-    // if we don't have a result yet
-    if (!final_result ||
-        // or this config is better before the locale than the existing result
-        result->config.isBetterThanBeforeLocale(final_result->config, desired_config) ||
-        // or the existing config isn't better before locale and this one specifies a locale
-        // whereas the existing one doesn't
-        (!final_result->config.isBetterThanBeforeLocale(result->config, desired_config)
-            && has_locale && !final_has_locale)) {
-      final_result = result.value();
-      final_overlaid = overlaid;
-      final_has_locale = has_locale;
-    }
   }
 
   if (UNLIKELY(logging_enabled)) {
-    last_resolution_.cookie = final_result->cookie;
-    last_resolution_.type_string_ref = final_result->type_string_ref;
-    last_resolution_.entry_string_ref = final_result->entry_string_ref;
-    last_resolution_.best_config_name = final_result->config.toString();
-    if (!final_overlaid) {
-      last_resolution_.best_package_name = final_result->package_name->c_str();
+    last_resolution_.cookie = result->cookie;
+    last_resolution_.type_string_ref = result->type_string_ref;
+    last_resolution_.entry_string_ref = result->entry_string_ref;
+    last_resolution_.best_config_name = result->config.toString();
+    if (!overlaid) {
+      last_resolution_.best_package_name = result->package_name->c_str();
     }
   }
 
-  return *final_result;
+  return result;
 }
 
 base::expected<FindEntryResult, NullOrIOError> AssetManager2::FindEntryInternal(
@@ -821,10 +778,8 @@
   // If `desired_config` is not the same as the set configuration or the caller will accept a value
   // from any configuration, then we cannot use our filtered list of types since it only it contains
   // types matched to the set configuration.
-  const bool use_filtered = !ignore_configuration && std::find_if(
-      configurations_.begin(), configurations_.end(),
-      [&desired_config](auto& value) { return &desired_config == &value; })
-      != configurations_.end();
+  const bool use_filtered = !ignore_configuration && &desired_config == &configuration_;
+
   const size_t package_count = package_group.packages_.size();
   for (size_t pi = 0; pi < package_count; pi++) {
     const ConfiguredPackage& loaded_package_impl = package_group.packages_[pi];
@@ -979,22 +934,10 @@
   }
 
   std::stringstream log_stream;
-  if (configurations_.size() == 1) {
-    log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
-                                     "\tFor config - %s", resid, resource_name_string.c_str(),
-                                     configurations_[0].toString().c_str());
-  } else {
-    ResTable_config conf = configurations_[0];
-    conf.clearLocale();
-    log_stream << base::StringPrintf("Resolution for 0x%08x %s\n\tFor config - %s and locales",
-                                     resid, resource_name_string.c_str(), conf.toString().c_str());
-    char str[40];
-    str[0] = '\0';
-    for(auto iter = configurations_.begin(); iter < configurations_.end(); iter++) {
-      iter->getBcp47Locale(str);
-      log_stream << base::StringPrintf(" %s%s", str, iter < configurations_.end() ? "," : "");
-    }
-  }
+  log_stream << base::StringPrintf("Resolution for 0x%08x %s\n"
+                                   "\tFor config - %s", resid, resource_name_string.c_str(),
+                                   configuration_.toString().c_str());
+
   for (const Resolution::Step& step : last_resolution_.steps) {
     constexpr static std::array kStepStrings = {
         "Found initial",
@@ -1484,14 +1427,11 @@
       package.loaded_package_->ForEachTypeSpec([&](const TypeSpec& type_spec, uint8_t type_id) {
         FilteredConfigGroup* group = nullptr;
         for (const auto& type_entry : type_spec.type_entries) {
-          for (auto & config : configurations_) {
-            if (type_entry.config.match(config)) {
-              if (!group) {
-                group = &package.filtered_configs_.editItemAt(type_id - 1);
-              }
-              group->type_entries.push_back(&type_entry);
-              break;
+          if (type_entry.config.match(configuration_)) {
+            if (!group) {
+              group = &package.filtered_configs_.editItemAt(type_id - 1);
             }
+            group->type_entries.push_back(&type_entry);
           }
         }
       });
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 06d19e0..5a63612 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2568,22 +2568,6 @@
     return false;
 }
 
-bool ResTable_config::isBetterThanBeforeLocale(const ResTable_config& o,
-        const ResTable_config* requested) const {
-    if (requested) {
-        if (imsi || o.imsi) {
-            if ((mcc != o.mcc) && requested->mcc) {
-                return (mcc);
-            }
-
-            if ((mnc != o.mnc) && requested->mnc) {
-                return (mnc);
-            }
-        }
-    }
-    return false;
-}
-
 bool ResTable_config::isBetterThan(const ResTable_config& o,
         const ResTable_config* requested) const {
     if (requested) {
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index d9ff35b..f611d0d 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -100,7 +100,7 @@
   using ApkAssetsWPtr = wp<const ApkAssets>;
   using ApkAssetsList = std::span<const ApkAssetsPtr>;
 
-  AssetManager2();
+  AssetManager2() = default;
   explicit AssetManager2(AssetManager2&& other) = default;
   AssetManager2(ApkAssetsList apk_assets, const ResTable_config& configuration);
 
@@ -156,14 +156,10 @@
 
   // Sets/resets the configuration for this AssetManager. This will cause all
   // caches that are related to the configuration change to be invalidated.
-  void SetConfigurations(std::vector<ResTable_config> configurations);
+  void SetConfiguration(const ResTable_config& configuration);
 
-  inline const std::vector<ResTable_config>& GetConfigurations() const {
-    return configurations_;
-  }
-
-  inline void SetDefaultLocale(uint32_t default_locale) {
-    default_locale_ = default_locale;
+  inline const ResTable_config& GetConfiguration() const {
+    return configuration_;
   }
 
   // Returns all configurations for which there are resources defined, or an I/O error if reading
@@ -469,11 +465,9 @@
   // without taking too much memory.
   std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
 
-  uint32_t default_locale_;
-
-  // The current configurations set for this AssetManager. When this changes, cached resources
+  // The current configuration set for this AssetManager. When this changes, cached resources
   // may need to be purged.
-  std::vector<ResTable_config> configurations_;
+  ResTable_config configuration_ = {};
 
   // Cached set of bags. These are cached because they can inherit keys from parent bags,
   // which involves some calculation.
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 52666ab..6de1d1e 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1375,8 +1375,6 @@
     // match the requested configuration at all.
     bool isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const;
 
-    bool isBetterThanBeforeLocale(const ResTable_config& o, const ResTable_config* requested) const;
-
     String8 toString() const;
 };
 
@@ -1872,6 +1870,8 @@
   DataValue data_value;
   std::string data_string_value;
   std::optional<android::base::borrowed_fd> data_binary_value;
+  off64_t binary_data_offset;
+  size_t binary_data_size;
   std::string configuration;
 };
 
diff --git a/libs/androidfw/tests/AssetManager2_bench.cpp b/libs/androidfw/tests/AssetManager2_bench.cpp
index 2caa98c..6fae72a 100644
--- a/libs/androidfw/tests/AssetManager2_bench.cpp
+++ b/libs/androidfw/tests/AssetManager2_bench.cpp
@@ -228,12 +228,10 @@
 
   ResTable_config config;
   memset(&config, 0, sizeof(config));
-  std::vector<ResTable_config> configs;
-  configs.push_back(config);
 
   while (state.KeepRunning()) {
-    configs[0].sdkVersion = ~configs[0].sdkVersion;
-    assets.SetConfigurations(configs);
+    config.sdkVersion = ~config.sdkVersion;
+    assets.SetConfiguration(config);
   }
 }
 BENCHMARK(BM_AssetManagerSetConfigurationFramework);
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index c62f095..df3fa02 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -113,7 +113,7 @@
   desired_config.language[1] = 'e';
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -137,7 +137,7 @@
   desired_config.language[1] = 'e';
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -466,10 +466,10 @@
 TEST_F(AssetManager2Test, DensityOverride) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({basic_assets_, basic_xhdpi_assets_, basic_xxhdpi_assets_});
-  assetmanager.SetConfigurations({{
+  assetmanager.SetConfiguration({
     .density = ResTable_config::DENSITY_XHIGH,
     .sdkVersion = 21,
-  }});
+  });
 
   auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/);
   ASSERT_TRUE(value.has_value());
@@ -721,7 +721,7 @@
   ResTable_config desired_config;
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_});
   assetmanager.SetResourceResolutionLoggingEnabled(false);
 
@@ -736,7 +736,7 @@
   ResTable_config desired_config;
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_});
 
   auto result = assetmanager.GetLastResourceResolution();
@@ -751,7 +751,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -774,7 +774,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -796,7 +796,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -817,7 +817,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfiguration(desired_config);
   assetmanager.SetApkAssets({overlayable_assets_});
 
   const auto map = assetmanager.GetOverlayableMapForPackage(0x7f);
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index 8b883f4..b97dd96 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -66,7 +66,7 @@
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets(apk_assets);
   if (config != nullptr) {
-    assetmanager.SetConfigurations({*config});
+    assetmanager.SetConfiguration(*config);
   }
 
   while (state.KeepRunning()) {
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index 181d141..e08a6a7 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -260,7 +260,7 @@
   ResTable_config night{};
   night.uiMode = ResTable_config::UI_MODE_NIGHT_YES;
   night.version = 8u;
-  am_night.SetConfigurations({night});
+  am_night.SetConfiguration(night);
 
   auto theme = am.NewTheme();
   {
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index b5e6f94..7f80dff 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -44,7 +44,7 @@
         "-DEGL_EGLEXT_PROTOTYPES",
         "-DGL_GLEXT_PROTOTYPES",
         "-DATRACE_TAG=ATRACE_TAG_VIEW",
-        "-DLOG_TAG=\"OpenGLRenderer\"",
+        "-DLOG_TAG=\"HWUI\"",
         "-Wall",
         "-Wthread-safety",
         "-Wno-unused-parameter",
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index d237cc2..fcb1bfe 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -35,15 +35,47 @@
     AHardwareBuffer_Desc desc;
     AHardwareBuffer_describe(buffer, &desc);
     bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
-    GrBackendFormat backendFormat =
-            GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+
+    GrBackendFormat backendFormat;
+    GrBackendApi backend = context->backend();
+    if (backend == GrBackendApi::kOpenGL) {
+        backendFormat =
+                GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
+        mBackendTexture =
+                GrAHardwareBufferUtils::MakeGLBackendTexture(context,
+                                                             buffer,
+                                                             desc.width,
+                                                             desc.height,
+                                                             &mDeleteProc,
+                                                             &mUpdateProc,
+                                                             &mImageCtx,
+                                                             createProtectedImage,
+                                                             backendFormat,
+                                                             false);
+    } else if (backend == GrBackendApi::kVulkan) {
+        backendFormat =
+                GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
+                                                               buffer,
+                                                               desc.format,
+                                                               false);
+        mBackendTexture =
+                GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
+                                                                 buffer,
+                                                                 desc.width,
+                                                                 desc.height,
+                                                                 &mDeleteProc,
+                                                                 &mUpdateProc,
+                                                                 &mImageCtx,
+                                                                 createProtectedImage,
+                                                                 backendFormat,
+                                                                 false);
+    } else {
+        LOG_ALWAYS_FATAL("Unexpected backend %d", backend);
+    }
     LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(),
                         __FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32
                                  ", AHardwareBuffer_Format==%" PRIu32 ".",
                         static_cast<int>(context->backend()), desc.format);
-    mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture(
-            context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx,
-            createProtectedImage, backendFormat, false);
     LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(),
                         __FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32
                                  ", protected==%d",
diff --git a/libs/hwui/Tonemapper.cpp b/libs/hwui/Tonemapper.cpp
index 974a5d0..ae29edf 100644
--- a/libs/hwui/Tonemapper.cpp
+++ b/libs/hwui/Tonemapper.cpp
@@ -20,6 +20,7 @@
 #include <log/log.h>
 // libshaders only exists on Android devices
 #ifdef __ANDROID__
+#include <renderthread/CanvasContext.h>
 #include <shaders/shaders.h>
 #endif
 
@@ -53,8 +54,17 @@
 
     ColorFilterRuntimeEffectBuilder effectBuilder(std::move(runtimeEffect));
 
+    auto colorTransform = android::mat4();
+    const auto* context = renderthread::CanvasContext::getActiveContext();
+    if (context) {
+        const auto ratio = context->targetSdrHdrRatio();
+        if (ratio > 1.0f) {
+            colorTransform = android::mat4::scale(vec4(ratio, ratio, ratio, 1.f));
+        }
+    }
+
     const auto uniforms =
-            shaders::buildLinearEffectUniforms(linearEffect, android::mat4(), maxDisplayLuminance,
+            shaders::buildLinearEffectUniforms(linearEffect, colorTransform, maxDisplayLuminance,
                                                currentDisplayLuminanceNits, maxLuminance);
 
     for (const auto& uniform : uniforms) {
diff --git a/libs/hwui/apex/android_bitmap.cpp b/libs/hwui/apex/android_bitmap.cpp
index c442a7b..c80a9b4 100644
--- a/libs/hwui/apex/android_bitmap.cpp
+++ b/libs/hwui/apex/android_bitmap.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
 #include <log/log.h>
 
 #include "android/graphics/bitmap.h"
diff --git a/libs/hwui/apex/jni_runtime.cpp b/libs/hwui/apex/jni_runtime.cpp
index 09ae7e7..883f273 100644
--- a/libs/hwui/apex/jni_runtime.cpp
+++ b/libs/hwui/apex/jni_runtime.cpp
@@ -25,9 +25,6 @@
 #include <sys/cdefs.h>
 #include <vulkan/vulkan.h>
 
-#undef LOG_TAG
-#define LOG_TAG "AndroidGraphicsJNI"
-
 extern int register_android_graphics_Bitmap(JNIEnv*);
 extern int register_android_graphics_BitmapFactory(JNIEnv*);
 extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
diff --git a/libs/hwui/effects/GainmapRenderer.cpp b/libs/hwui/effects/GainmapRenderer.cpp
index 613f52b..db58b2b 100644
--- a/libs/hwui/effects/GainmapRenderer.cpp
+++ b/libs/hwui/effects/GainmapRenderer.cpp
@@ -245,11 +245,18 @@
             // This can happen if a BitmapShader is used on multiple canvas', such as a
             // software + hardware canvas, which is otherwise valid as SkShader is "immutable"
             std::lock_guard _lock(mUniformGuard);
-            const float Wunclamped = (sk_float_log(targetHdrSdrRatio) -
-                                      sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
-                                     (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
-                                      sk_float_log(mGainmapInfo.fDisplayRatioSdr));
-            const float W = std::max(std::min(Wunclamped, 1.f), 0.f);
+            // Compute the weight parameter that will be used to blend between the images.
+            float W = 0.f;
+            if (targetHdrSdrRatio > mGainmapInfo.fDisplayRatioSdr) {
+                if (targetHdrSdrRatio < mGainmapInfo.fDisplayRatioHdr) {
+                    W = (sk_float_log(targetHdrSdrRatio) -
+                         sk_float_log(mGainmapInfo.fDisplayRatioSdr)) /
+                        (sk_float_log(mGainmapInfo.fDisplayRatioHdr) -
+                         sk_float_log(mGainmapInfo.fDisplayRatioSdr));
+                } else {
+                    W = 1.f;
+                }
+            }
             mBuilder.uniform("W") = W;
             uniforms = mBuilder.uniforms();
         }
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 701a87f..588463c 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -43,9 +43,6 @@
 
 #include <memory>
 
-#undef LOG_TAG
-#define LOG_TAG "ImageDecoder"
-
 using namespace android;
 
 sk_sp<SkColorSpace> ImageDecoder::getDefaultColorSpace() const {
diff --git a/libs/hwui/jni/Bitmap.cpp b/libs/hwui/jni/Bitmap.cpp
index 6ee7576..9e21f86 100644
--- a/libs/hwui/jni/Bitmap.cpp
+++ b/libs/hwui/jni/Bitmap.cpp
@@ -1,5 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "Bitmap"
 // #define LOG_NDEBUG 0
 #include "Bitmap.h"
 
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp
index 8abcd9a..3d0a534 100644
--- a/libs/hwui/jni/BitmapFactory.cpp
+++ b/libs/hwui/jni/BitmapFactory.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "BitmapFactory"
-
 #include "BitmapFactory.h"
 
 #include <Gainmap.h>
diff --git a/libs/hwui/jni/BitmapRegionDecoder.cpp b/libs/hwui/jni/BitmapRegionDecoder.cpp
index 740988f..ea5c144 100644
--- a/libs/hwui/jni/BitmapRegionDecoder.cpp
+++ b/libs/hwui/jni/BitmapRegionDecoder.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "BitmapRegionDecoder"
-
 #include "BitmapRegionDecoder.h"
 
 #include <HardwareBitmapUploader.h>
diff --git a/libs/hwui/jni/FontFamily.cpp b/libs/hwui/jni/FontFamily.cpp
index af1668f..0c3af61 100644
--- a/libs/hwui/jni/FontFamily.cpp
+++ b/libs/hwui/jni/FontFamily.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
 #include <nativehelper/ScopedPrimitiveArray.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include "FontUtils.h"
diff --git a/libs/hwui/jni/Graphics.cpp b/libs/hwui/jni/Graphics.cpp
index 78b4f7b..7cc4866 100644
--- a/libs/hwui/jni/Graphics.cpp
+++ b/libs/hwui/jni/Graphics.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "GraphicsJNI"
-
 #include <assert.h>
 #include <unistd.h>
 
diff --git a/libs/hwui/jni/GraphicsStatsService.cpp b/libs/hwui/jni/GraphicsStatsService.cpp
index e32c911..54369b9 100644
--- a/libs/hwui/jni/GraphicsStatsService.cpp
+++ b/libs/hwui/jni/GraphicsStatsService.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "GraphicsStatsService"
-
 #include <JankTracker.h>
 #include <log/log.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/jni/NinePatch.cpp b/libs/hwui/jni/NinePatch.cpp
index d50a8a2..67ef143 100644
--- a/libs/hwui/jni/NinePatch.cpp
+++ b/libs/hwui/jni/NinePatch.cpp
@@ -15,8 +15,6 @@
 ** limitations under the License.
 */
 
-#undef LOG_TAG
-#define LOG_TAG "9patch"
 #define LOG_NDEBUG 1
 
 #include <androidfw/ResourceTypes.h>
diff --git a/libs/hwui/jni/Paint.cpp b/libs/hwui/jni/Paint.cpp
index d2a4efe..1ba7f70 100644
--- a/libs/hwui/jni/Paint.cpp
+++ b/libs/hwui/jni/Paint.cpp
@@ -15,9 +15,6 @@
 ** limitations under the License.
 */
 
-#undef LOG_TAG
-#define LOG_TAG "Paint"
-
 #include <hwui/BlurDrawLooper.h>
 #include <hwui/MinikinSkia.h>
 #include <hwui/MinikinUtils.h>
diff --git a/libs/hwui/jni/Shader.cpp b/libs/hwui/jni/Shader.cpp
index 7eb79be..2c13ceb 100644
--- a/libs/hwui/jni/Shader.cpp
+++ b/libs/hwui/jni/Shader.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "ShaderJNI"
-
 #include <vector>
 
 #include "Gainmap.h"
diff --git a/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp
index 69418b0..4dbfa88 100644
--- a/libs/hwui/jni/YuvToJpegEncoder.cpp
+++ b/libs/hwui/jni/YuvToJpegEncoder.cpp
@@ -1,6 +1,3 @@
-#undef LOG_TAG
-#define LOG_TAG "YuvToJpegEncoder"
-
 #include "CreateJavaOutputStreamAdaptor.h"
 #include "SkStream.h"
 #include "YuvToJpegEncoder.h"
diff --git a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
index 706f18c..e3cdee6 100644
--- a/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareBufferRenderer.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "HardwareBufferRenderer"
 #define ATRACE_TAG ATRACE_TAG_VIEW
 
 #include <GraphicsJNI.h>
diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
index ee22f7c..422ffea 100644
--- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
+++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "ThreadedRenderer"
 #define ATRACE_TAG ATRACE_TAG_VIEW
 
 #include <FrameInfo.h>
diff --git a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
index 764eff9..b86c74fe 100644
--- a/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
+++ b/libs/hwui/jni/android_graphics_animation_NativeInterpolatorFactory.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "OpenGLRenderer"
-
 #include <Interpolator.h>
 #include <cutils/log.h>
 
diff --git a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
index c6d26f8..40be924 100644
--- a/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
+++ b/libs/hwui/jni/android_graphics_animation_RenderNodeAnimator.cpp
@@ -14,8 +14,6 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "OpenGLRenderer"
-
 #include <Animator.h>
 #include <Interpolator.h>
 #include <RenderProperties.h>
diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp
index 8cfdeeb7..2ec94c9 100644
--- a/libs/hwui/jni/fonts/Font.cpp
+++ b/libs/hwui/jni/fonts/Font.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
 #include "Font.h"
 #include "SkData.h"
 #include "SkFont.h"
diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp
index 1e392b1..462c8c8 100644
--- a/libs/hwui/jni/fonts/FontFamily.cpp
+++ b/libs/hwui/jni/fonts/FontFamily.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "Minikin"
-
 #include "graphics_jni_helpers.h"
 #include <nativehelper/ScopedUtfChars.h>
 
diff --git a/libs/hwui/jni/pdf/PdfEditor.cpp b/libs/hwui/jni/pdf/PdfEditor.cpp
index 427bafa..3b18f5f 100644
--- a/libs/hwui/jni/pdf/PdfEditor.cpp
+++ b/libs/hwui/jni/pdf/PdfEditor.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "PdfEditor"
-
 #include <sys/types.h>
 #include <unistd.h>
 
diff --git a/libs/hwui/jni/pdf/PdfUtils.cpp b/libs/hwui/jni/pdf/PdfUtils.cpp
index 06d2028..6887fda 100644
--- a/libs/hwui/jni/pdf/PdfUtils.cpp
+++ b/libs/hwui/jni/pdf/PdfUtils.cpp
@@ -16,14 +16,11 @@
 
 #include "PdfUtils.h"
 
-#include "jni.h"
 #include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
 
 #include "fpdfview.h"
-
-#undef LOG_TAG
-#define LOG_TAG "PdfUtils"
-#include <utils/Log.h>
+#include "jni.h"
 
 namespace android {
 
diff --git a/libs/hwui/jni/text/GraphemeBreak.cpp b/libs/hwui/jni/text/GraphemeBreak.cpp
index 55f03bd..322af7e 100644
--- a/libs/hwui/jni/text/GraphemeBreak.cpp
+++ b/libs/hwui/jni/text/GraphemeBreak.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "GraphemeBreaker"
-
 #include <minikin/GraphemeBreak.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
 
diff --git a/libs/hwui/jni/text/LineBreaker.cpp b/libs/hwui/jni/text/LineBreaker.cpp
index 6986517..9ebf23c 100644
--- a/libs/hwui/jni/text/LineBreaker.cpp
+++ b/libs/hwui/jni/text/LineBreaker.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "LineBreaker"
-
 #include "utils/misc.h"
 #include "utils/Log.h"
 #include "graphics_jni_helpers.h"
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index c13c800..081713a 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "MeasuredText"
-
 #include "GraphicsJNI.h"
 #include "utils/misc.h"
 #include "utils/Log.h"
diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp
index 8c377b9..6c05346 100644
--- a/libs/hwui/jni/text/TextShaper.cpp
+++ b/libs/hwui/jni/text/TextShaper.cpp
@@ -14,9 +14,6 @@
  * limitations under the License.
  */
 
-#undef LOG_TAG
-#define LOG_TAG "TextShaper"
-
 #include "graphics_jni_helpers.h"
 #include <nativehelper/ScopedStringChars.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
diff --git a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
index dbd9ef3..5d3fb30 100644
--- a/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
+++ b/libs/hwui/pipeline/skia/GLFunctorDrawable.cpp
@@ -28,6 +28,8 @@
 #include "SkM44.h"
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
 #include "include/gpu/GpuTypes.h" // from Skia
+#include <include/gpu/gl/GrGLTypes.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
 #include "utils/GLUtils.h"
 #include <effects/GainmapRenderer.h>
 #include "renderthread/CanvasContext.h"
@@ -47,7 +49,7 @@
 static void GetFboDetails(SkCanvas* canvas, GLuint* outFboID, SkISize* outFboSize) {
     GrBackendRenderTarget renderTarget = skgpu::ganesh::TopLayerBackendRenderTarget(canvas);
     GrGLFramebufferInfo fboInfo;
-    LOG_ALWAYS_FATAL_IF(!renderTarget.getGLFramebufferInfo(&fboInfo),
+    LOG_ALWAYS_FATAL_IF(!GrBackendRenderTargets::GetGLFramebufferInfo(renderTarget, &fboInfo),
         "getGLFrameBufferInfo failed");
 
     *outFboID = fboInfo.fFBOID;
@@ -102,9 +104,10 @@
         tmpSurface->getCanvas()->clear(SK_ColorTRANSPARENT);
 
         GrGLFramebufferInfo fboInfo;
-        if (!SkSurfaces::GetBackendRenderTarget(tmpSurface.get(),
-                                                SkSurfaces::BackendHandleAccess::kFlushWrite)
-                     .getGLFramebufferInfo(&fboInfo)) {
+        if (!GrBackendRenderTargets::GetGLFramebufferInfo(
+                    SkSurfaces::GetBackendRenderTarget(
+                        tmpSurface.get(), SkSurfaces::BackendHandleAccess::kFlushWrite),
+                    &fboInfo)) {
             ALOGW("Unable to extract renderTarget info from offscreen canvas; aborting GLFunctor");
             return;
         }
diff --git a/libs/hwui/private/hwui/DrawGlInfo.h b/libs/hwui/private/hwui/DrawGlInfo.h
index eb1f930..ed3fabc 100644
--- a/libs/hwui/private/hwui/DrawGlInfo.h
+++ b/libs/hwui/private/hwui/DrawGlInfo.h
@@ -24,8 +24,7 @@
 namespace uirenderer {
 
 /**
- * Structure used by OpenGLRenderer::callDrawGLFunction() to pass and
- * receive data from OpenGL functors.
+ * Structure used to pass and receive data from OpenGL functors.
  */
 struct DrawGlInfo {
     // Input: current clip rect
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index f340945..a6e8c08f 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -34,9 +34,6 @@
 #include "pipeline/skia/ShaderCache.h"
 #include "renderstate/RenderState.h"
 
-#undef LOG_TAG
-#define LOG_TAG "VulkanManager"
-
 namespace android {
 namespace uirenderer {
 namespace renderthread {
diff --git a/libs/hwui/renderthread/VulkanSurface.cpp b/libs/hwui/renderthread/VulkanSurface.cpp
index b0ba619..20b743b 100644
--- a/libs/hwui/renderthread/VulkanSurface.cpp
+++ b/libs/hwui/renderthread/VulkanSurface.cpp
@@ -25,9 +25,6 @@
 #include "VulkanManager.h"
 #include "utils/Color.h"
 
-#undef LOG_TAG
-#define LOG_TAG "VulkanSurface"
-
 namespace android {
 namespace uirenderer {
 namespace renderthread {
diff --git a/libs/protoutil/Android.bp b/libs/protoutil/Android.bp
index 128be3c..28856c8 100644
--- a/libs/protoutil/Android.bp
+++ b/libs/protoutil/Android.bp
@@ -80,6 +80,10 @@
         "libgmock",
     ],
 
+    test_suites: [
+        "general-tests",
+    ],
+
     proto: {
         type: "full",
     },
diff --git a/libs/protoutil/AndroidTest.xml b/libs/protoutil/AndroidTest.xml
deleted file mode 100644
index 46d418e..0000000
--- a/libs/protoutil/AndroidTest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 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="Config for libprotoutil_test">
-    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
-        <option name="cleanup" value="true" />
-        <option name="push" value="libprotoutil_test->/data/nativetest/libprotoutil_test" />
-    </target_preparer>
-    <option name="test-suite-tag" value="apct" />
-    <test class="com.android.tradefed.testtype.GTest" >
-        <option name="native-test-device-path" value="/data/nativetest" />
-        <option name="module-name" value="libprotoutil_test" />
-    </test>
-</configuration>
diff --git a/libs/protoutil/TEST_MAPPING b/libs/protoutil/TEST_MAPPING
new file mode 100644
index 0000000..b10dd9b
--- /dev/null
+++ b/libs/protoutil/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "libprotoutil_test"
+    }
+  ],
+  "hwasan-postsubmit": [
+    {
+      "name": "libprotoutil_test"
+    }
+  ]
+}
diff --git a/libs/protoutil/src/EncodedBuffer.cpp b/libs/protoutil/src/EncodedBuffer.cpp
index 96b54c6..afb54a6 100644
--- a/libs/protoutil/src/EncodedBuffer.cpp
+++ b/libs/protoutil/src/EncodedBuffer.cpp
@@ -17,6 +17,7 @@
 
 #include <stdlib.h>
 #include <sys/mman.h>
+#include <unistd.h>
 
 #include <android/util/EncodedBuffer.h>
 #include <android/util/protobuf.h>
@@ -25,7 +26,8 @@
 namespace android {
 namespace util {
 
-const size_t BUFFER_SIZE = 8 * 1024; // 8 KB
+constexpr size_t BUFFER_SIZE = 8 * 1024; // 8 KB
+const size_t kPageSize = getpagesize();
 
 EncodedBuffer::Pointer::Pointer() : Pointer(BUFFER_SIZE)
 {
@@ -92,7 +94,7 @@
 {
     // Align chunkSize to memory page size
     chunkSize = chunkSize == 0 ? BUFFER_SIZE : chunkSize;
-    mChunkSize = (chunkSize / PAGE_SIZE + ((chunkSize % PAGE_SIZE == 0) ? 0 : 1)) * PAGE_SIZE;
+    mChunkSize = (chunkSize + (kPageSize - 1)) & ~(kPageSize - 1);
     mWp = Pointer(mChunkSize);
     mEp = Pointer(mChunkSize);
 }
diff --git a/libs/protoutil/tests/EncodedBuffer_test.cpp b/libs/protoutil/tests/EncodedBuffer_test.cpp
index f895154..a0955854 100644
--- a/libs/protoutil/tests/EncodedBuffer_test.cpp
+++ b/libs/protoutil/tests/EncodedBuffer_test.cpp
@@ -15,12 +15,16 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include <unistd.h>
+
 using namespace android::util;
 using android::sp;
 
-constexpr size_t TEST_CHUNK_SIZE = 16UL;
-constexpr size_t TEST_CHUNK_HALF_SIZE = TEST_CHUNK_SIZE / 2;
-constexpr size_t TEST_CHUNK_3X_SIZE = 3 * TEST_CHUNK_SIZE;
+constexpr size_t __TEST_CHUNK_SIZE = 16UL;
+const size_t kPageSize = getpagesize();
+const size_t TEST_CHUNK_SIZE = (__TEST_CHUNK_SIZE + (kPageSize - 1)) & ~(kPageSize - 1);
+const size_t TEST_CHUNK_HALF_SIZE = TEST_CHUNK_SIZE / 2;
+const size_t TEST_CHUNK_3X_SIZE = 3 * TEST_CHUNK_SIZE;
 
 static void expectPointer(EncodedBuffer::Pointer* p, size_t pos) {
     EXPECT_EQ(p->pos(), pos);
@@ -34,13 +38,13 @@
     expectPointer(buffer->wp(), 0);
     EXPECT_EQ(buffer->currentToWrite(), TEST_CHUNK_SIZE);
     for (size_t i = 0; i < TEST_CHUNK_HALF_SIZE; i++) {
-        buffer->writeRawByte(50 + i);
+        buffer->writeRawByte(static_cast<uint8_t>(50 + i));
     }
     EXPECT_EQ(buffer->size(), TEST_CHUNK_HALF_SIZE);
     expectPointer(buffer->wp(), TEST_CHUNK_HALF_SIZE);
     EXPECT_EQ(buffer->currentToWrite(), TEST_CHUNK_HALF_SIZE);
     for (size_t i = 0; i < TEST_CHUNK_SIZE; i++) {
-        buffer->writeRawByte(80 + i);
+        buffer->writeRawByte(static_cast<uint8_t>(80 + i));
     }
     EXPECT_EQ(buffer->size(), TEST_CHUNK_SIZE + TEST_CHUNK_HALF_SIZE);
     expectPointer(buffer->wp(), TEST_CHUNK_SIZE + TEST_CHUNK_HALF_SIZE);
@@ -49,10 +53,10 @@
     // verifies the buffer's data
     expectPointer(buffer->ep(), 0);
     for (size_t i = 0; i < TEST_CHUNK_HALF_SIZE; i++) {
-        EXPECT_EQ(buffer->readRawByte(), 50 + i);
+        EXPECT_EQ(buffer->readRawByte(), static_cast<uint8_t>(50 + i));
     }
     for (size_t i = 0; i < TEST_CHUNK_SIZE; i++) {
-        EXPECT_EQ(buffer->readRawByte(), 80 + i);
+        EXPECT_EQ(buffer->readRawByte(), static_cast<uint8_t>(80 + i));
     }
 
     // clears the buffer
diff --git a/location/TEST_MAPPING b/location/TEST_MAPPING
index 214d2f3..f5deb2b 100644
--- a/location/TEST_MAPPING
+++ b/location/TEST_MAPPING
@@ -1,7 +1,13 @@
 {
   "presubmit": [
     {
-      "name": "CtsLocationFineTestCases"
+      "name": "CtsLocationFineTestCases",
+      "options": [
+          {
+             // TODO: Wait for test to deflake - b/293934372
+             "exclude-filter":"android.location.cts.fine.ScanningSettingsTest"
+          }
+      ]
     },
     {
       "name": "CtsLocationCoarseTestCases"
@@ -16,4 +22,4 @@
       }]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/location/java/android/location/GnssMeasurementRequest.java b/location/java/android/location/GnssMeasurementRequest.java
index 3f3ad75..65af392 100644
--- a/location/java/android/location/GnssMeasurementRequest.java
+++ b/location/java/android/location/GnssMeasurementRequest.java
@@ -16,11 +16,15 @@
 
 package android.location;
 
+import android.Manifest;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.WorkSource;
 import android.util.TimeUtils;
 
 import com.android.internal.util.Preconditions;
@@ -46,15 +50,16 @@
     private final boolean mCorrelationVectorOutputsEnabled;
     private final boolean mFullTracking;
     private final int mIntervalMillis;
-
+    private WorkSource mWorkSource;
     /**
      * Creates a {@link GnssMeasurementRequest} with a full list of parameters.
      */
     private GnssMeasurementRequest(boolean fullTracking, boolean correlationVectorOutputsEnabled,
-            int intervalMillis) {
+            int intervalMillis, WorkSource workSource) {
         mFullTracking = fullTracking;
         mCorrelationVectorOutputsEnabled = correlationVectorOutputsEnabled;
         mIntervalMillis = intervalMillis;
+        mWorkSource = Objects.requireNonNull(workSource);
     }
 
     /**
@@ -107,14 +112,31 @@
         return mIntervalMillis;
     }
 
+    /**
+     * Returns the work source used for power blame for this request. If empty (i.e.,
+     * {@link WorkSource#isEmpty()} is {@code true}, the system is free to assign power blame as it
+     * deems most appropriate.
+     *
+     * @return the work source used for power blame for this request
+     *
+     * @hide
+     */
+    @SystemApi
+    public @NonNull WorkSource getWorkSource() {
+        return mWorkSource;
+    }
+
     @NonNull
     public static final Creator<GnssMeasurementRequest> CREATOR =
             new Creator<GnssMeasurementRequest>() {
                 @Override
                 @NonNull
                 public GnssMeasurementRequest createFromParcel(@NonNull Parcel parcel) {
-                    return new GnssMeasurementRequest(parcel.readBoolean(), parcel.readBoolean(),
-                            parcel.readInt());
+                    return new GnssMeasurementRequest(
+                            /* fullTracking= */ parcel.readBoolean(),
+                            /* correlationVectorOutputsEnabled= */ parcel.readBoolean(),
+                            /* intervalMillis= */ parcel.readInt(),
+                            /* workSource= */ parcel.readTypedObject(WorkSource.CREATOR));
                 }
 
                 @Override
@@ -128,6 +150,7 @@
         parcel.writeBoolean(mFullTracking);
         parcel.writeBoolean(mCorrelationVectorOutputsEnabled);
         parcel.writeInt(mIntervalMillis);
+        parcel.writeTypedObject(mWorkSource, 0);
     }
 
     @NonNull
@@ -147,6 +170,9 @@
         if (mCorrelationVectorOutputsEnabled) {
             s.append(", CorrelationVectorOutputs");
         }
+        if (mWorkSource != null && !mWorkSource.isEmpty()) {
+            s.append(", ").append(mWorkSource);
+        }
         s.append(']');
         return s.toString();
     }
@@ -165,12 +191,16 @@
         if (mIntervalMillis != other.mIntervalMillis) {
             return false;
         }
+        if (!Objects.equals(mWorkSource, other.mWorkSource)) {
+            return false;
+        }
         return true;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mFullTracking, mCorrelationVectorOutputsEnabled, mIntervalMillis);
+        return Objects.hash(mFullTracking, mCorrelationVectorOutputsEnabled, mIntervalMillis,
+                mWorkSource);
     }
 
     @Override
@@ -183,6 +213,7 @@
         private boolean mCorrelationVectorOutputsEnabled;
         private boolean mFullTracking;
         private int mIntervalMillis;
+        private WorkSource mWorkSource;
 
         /**
          * Constructs a {@link Builder} instance.
@@ -197,6 +228,7 @@
             mCorrelationVectorOutputsEnabled = request.isCorrelationVectorOutputsEnabled();
             mFullTracking = request.isFullTracking();
             mIntervalMillis = request.getIntervalMillis();
+            mWorkSource = request.getWorkSource();
         }
 
         /**
@@ -255,11 +287,29 @@
             return this;
         }
 
+        /**
+         * Sets the work source to use for power blame for this request. Passing in null or leaving
+         * it unset will be an empty WorkSource, which implies the system is free to assign power
+         * blame as it determines best for this request (which usually means blaming the owner of
+         * the GnssMeasurement listener).
+         *
+         * <p>Permissions enforcement occurs when resulting request is actually used, not when this
+         * method is invoked.
+         *
+         * @hide
+         */
+        @SystemApi
+        @RequiresPermission(Manifest.permission.UPDATE_DEVICE_STATS)
+        public @NonNull Builder setWorkSource(@Nullable WorkSource workSource) {
+            mWorkSource = workSource;
+            return this;
+        }
+
         /** Builds a {@link GnssMeasurementRequest} instance as specified by this builder. */
         @NonNull
         public GnssMeasurementRequest build() {
             return new GnssMeasurementRequest(mFullTracking, mCorrelationVectorOutputsEnabled,
-                    mIntervalMillis);
+                    mIntervalMillis, new WorkSource(mWorkSource));
         }
     }
 }
diff --git a/location/java/android/location/LocationManagerInternal.java b/location/java/android/location/LocationManagerInternal.java
index d59756d..a48cc19 100644
--- a/location/java/android/location/LocationManagerInternal.java
+++ b/location/java/android/location/LocationManagerInternal.java
@@ -87,12 +87,6 @@
     public abstract boolean isProvider(@Nullable String provider, @NonNull CallerIdentity identity);
 
     /**
-     * Should only be used by GNSS code.
-     */
-    // TODO: there is no reason for this to exist as part of any API. move all the logic into gnss
-    public abstract void sendNiResponse(int notifId, int userResponse);
-
-    /**
      * Returns the GNSS provided time.
      *
      * @return LocationTime object that includes the current time, according to the GNSS location
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index fba4249..ee2510f 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -18,28 +18,14 @@
 
 import android.Manifest;
 import android.annotation.RequiresPermission;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.compat.annotation.UnsupportedAppUsage;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.location.INetInitiatedListener;
 import android.location.LocationManager;
-import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.util.Log;
 
-import com.android.internal.R;
-import com.android.internal.notification.SystemNotificationChannels;
-import com.android.internal.telephony.GsmAlphabet;
-
-import java.io.UnsupportedEncodingException;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -53,95 +39,20 @@
 
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    // string constants for defining data fields in NI Intent
-    public static final String NI_INTENT_KEY_NOTIF_ID = "notif_id";
-    public static final String NI_INTENT_KEY_TITLE = "title";
-    public static final String NI_INTENT_KEY_MESSAGE = "message";
-    public static final String NI_INTENT_KEY_TIMEOUT = "timeout";
-    public static final String NI_INTENT_KEY_DEFAULT_RESPONSE = "default_resp";
-
-    // the extra command to send NI response to GnssLocationProvider
-    public static final String NI_RESPONSE_EXTRA_CMD = "send_ni_response";
-
-    // the extra command parameter names in the Bundle
-    public static final String NI_EXTRA_CMD_NOTIF_ID = "notif_id";
-    public static final String NI_EXTRA_CMD_RESPONSE = "response";
-
-    // these need to match GpsNiType constants in gps_ni.h
-    public static final int GPS_NI_TYPE_VOICE = 1;
-    public static final int GPS_NI_TYPE_UMTS_SUPL = 2;
-    public static final int GPS_NI_TYPE_UMTS_CTRL_PLANE = 3;
-    public static final int GPS_NI_TYPE_EMERGENCY_SUPL = 4;
-
-    // these need to match GpsUserResponseType constants in gps_ni.h
-    public static final int GPS_NI_RESPONSE_ACCEPT = 1;
-    public static final int GPS_NI_RESPONSE_DENY = 2;
-    public static final int GPS_NI_RESPONSE_NORESP = 3;
-    public static final int GPS_NI_RESPONSE_IGNORE = 4;
-
-    // these need to match GpsNiNotifyFlags constants in gps_ni.h
-    public static final int GPS_NI_NEED_NOTIFY = 0x0001;
-    public static final int GPS_NI_NEED_VERIFY = 0x0002;
-    public static final int GPS_NI_PRIVACY_OVERRIDE = 0x0004;
-
-    // these need to match GpsNiEncodingType in gps_ni.h
-    public static final int GPS_ENC_NONE = 0;
-    public static final int GPS_ENC_SUPL_GSM_DEFAULT = 1;
-    public static final int GPS_ENC_SUPL_UTF8 = 2;
-    public static final int GPS_ENC_SUPL_UCS2 = 3;
-    public static final int GPS_ENC_UNKNOWN = -1;
-
     private final Context mContext;
     private final TelephonyManager mTelephonyManager;
 
     // parent gps location provider
     private final LocationManager mLocationManager;
 
-    // configuration of notificaiton behavior
-    private boolean mPlaySounds = false;
-    private boolean mPopupImmediately = true;
-
-    // read the SUPL_ES form gps.conf
-    private volatile boolean mIsSuplEsEnabled;
-
     // Set to true if the phone is having emergency call.
     private volatile boolean mIsInEmergencyCall;
 
-    // If Location function is enabled.
-    private volatile boolean mIsLocationEnabled = false;
-
-    private final INetInitiatedListener mNetInitiatedListener;
-
-    // Set to true if string from HAL is encoded as Hex, e.g., "3F0039"
-    @UnsupportedAppUsage
-    static private boolean mIsHexInput = true;
 
     // End time of emergency call, and extension, if set
     private volatile long mCallEndElapsedRealtimeMillis = 0;
     private volatile long mEmergencyExtensionMillis = 0;
 
-    public static class GpsNiNotification
-    {
-        @android.compat.annotation.UnsupportedAppUsage
-        public GpsNiNotification() {
-        }
-        public int notificationId;
-        public int niType;
-        public boolean needNotify;
-        public boolean needVerify;
-        public boolean privacyOverride;
-        public int timeout;
-        public int defaultResponse;
-        @UnsupportedAppUsage
-        public String requestorId;
-        @UnsupportedAppUsage
-        public String text;
-        @UnsupportedAppUsage
-        public int requestorIdEncoding;
-        @UnsupportedAppUsage
-        public int textEncoding;
-    }
-
     /** Callbacks for Emergency call events. */
     public interface EmergencyCallCallback {
         /** Callback invoked when an emergency call starts */
@@ -182,72 +93,20 @@
     // reference here.
     private final EmergencyCallListener mEmergencyCallListener = new EmergencyCallListener();
 
-    /**
-     * The notification that is shown when a network-initiated notification
-     * (and verification) event is received.
-     * <p>
-     * This is lazily created, so use {@link #setNINotification()}.
-     */
-    private Notification.Builder mNiNotificationBuilder;
-
     private final EmergencyCallCallback mEmergencyCallCallback;
 
     public GpsNetInitiatedHandler(Context context,
-                                  INetInitiatedListener netInitiatedListener,
                                   EmergencyCallCallback emergencyCallCallback,
                                   boolean isSuplEsEnabled) {
         mContext = context;
-
-        if (netInitiatedListener == null) {
-            throw new IllegalArgumentException("netInitiatedListener is null");
-        } else {
-            mNetInitiatedListener = netInitiatedListener;
-        }
         mEmergencyCallCallback = emergencyCallCallback;
 
-        setSuplEsEnabled(isSuplEsEnabled);
         mLocationManager = (LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
-        updateLocationMode();
         mTelephonyManager =
             (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
         mTelephonyManager.registerTelephonyCallback(mContext.getMainExecutor(),
                 mEmergencyCallListener);
 
-        BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
-
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                String action = intent.getAction();
-                if (action.equals(LocationManager.MODE_CHANGED_ACTION)) {
-                    updateLocationMode();
-                    if (DEBUG) Log.d(TAG, "location enabled :" + getLocationEnabled());
-                }
-            }
-        };
-        mContext.registerReceiver(broadcastReceiver,
-                new IntentFilter(LocationManager.MODE_CHANGED_ACTION));
-    }
-
-    public void setSuplEsEnabled(boolean isEnabled) {
-        mIsSuplEsEnabled = isEnabled;
-    }
-
-    public boolean getSuplEsEnabled() {
-        return mIsSuplEsEnabled;
-    }
-
-    /**
-     * Updates Location enabler based on location setting.
-     */
-    public void updateLocationMode() {
-        mIsLocationEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
-    }
-
-    /**
-     * Checks if user agreed to use location.
-     */
-    public boolean getLocationEnabled() {
-        return mIsLocationEnabled;
     }
 
     /**
@@ -289,346 +148,4 @@
     public void setEmergencyExtensionSeconds(int emergencyExtensionSeconds) {
         mEmergencyExtensionMillis = TimeUnit.SECONDS.toMillis(emergencyExtensionSeconds);
     }
-
-    // Handles NI events from HAL
-    @UnsupportedAppUsage
-    public void handleNiNotification(GpsNiNotification notif) {
-        if (DEBUG) Log.d(TAG, "in handleNiNotification () :"
-                        + " notificationId: " + notif.notificationId
-                        + " requestorId: " + notif.requestorId
-                        + " text: " + notif.text
-                        + " mIsSuplEsEnabled" + getSuplEsEnabled()
-                        + " mIsLocationEnabled" + getLocationEnabled());
-
-        if (getSuplEsEnabled()) {
-            handleNiInEs(notif);
-        } else {
-            handleNi(notif);
-        }
-
-        //////////////////////////////////////////////////////////////////////////
-        //   A note about timeout
-        //   According to the protocol, in the need_notify and need_verify case,
-        //   a default response should be sent when time out.
-        //
-        //   In some GPS hardware, the GPS driver (under HAL) can handle the timeout case
-        //   and this class GpsNetInitiatedHandler does not need to do anything.
-        //
-        //   However, the UI should at least close the dialog when timeout. Further,
-        //   for more general handling, timeout response should be added to the Handler here.
-        //
-    }
-
-    // handle NI form HAL when SUPL_ES is disabled.
-    private void handleNi(GpsNiNotification notif) {
-        if (DEBUG) Log.d(TAG, "in handleNi () :"
-                        + " needNotify: " + notif.needNotify
-                        + " needVerify: " + notif.needVerify
-                        + " privacyOverride: " + notif.privacyOverride
-                        + " mPopupImmediately: " + mPopupImmediately
-                        + " mInEmergency: " + getInEmergency());
-
-        if (!getLocationEnabled() && !getInEmergency()) {
-            // Location is currently disabled, ignore all NI requests.
-            try {
-                mNetInitiatedListener.sendNiResponse(notif.notificationId,
-                                                     GPS_NI_RESPONSE_IGNORE);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException in sendNiResponse");
-            }
-        }
-        if (notif.needNotify) {
-        // If NI does not need verify or the dialog is not requested
-        // to pop up immediately, the dialog box will not pop up.
-            if (notif.needVerify && mPopupImmediately) {
-                // Popup the dialog box now
-                openNiDialog(notif);
-            } else {
-                // Show the notification
-                setNiNotification(notif);
-            }
-        }
-        // ACCEPT cases: 1. Notify, no verify; 2. no notify, no verify;
-        // 3. privacy override.
-        if (!notif.needVerify || notif.privacyOverride) {
-            try {
-                mNetInitiatedListener.sendNiResponse(notif.notificationId,
-                                                     GPS_NI_RESPONSE_ACCEPT);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException in sendNiResponse");
-            }
-        }
-    }
-
-    // handle NI from HAL when the SUPL_ES is enabled
-    private void handleNiInEs(GpsNiNotification notif) {
-
-        if (DEBUG) Log.d(TAG, "in handleNiInEs () :"
-                    + " niType: " + notif.niType
-                    + " notificationId: " + notif.notificationId);
-
-        // UE is in emergency mode when in emergency call mode or in emergency call back mode
-        /*
-           1. When SUPL ES bit is off and UE is not in emergency mode:
-                  Call handleNi() to do legacy behaviour.
-           2. When SUPL ES bit is on and UE is in emergency mode:
-                  Call handleNi() to do acceptance behaviour.
-           3. When SUPL ES bit is off but UE is in emergency mode:
-                  Ignore the emergency SUPL INIT.
-           4. When SUPL ES bit is on but UE is not in emergency mode:
-                  Ignore the emergency SUPL INIT.
-        */
-        boolean isNiTypeES = (notif.niType == GPS_NI_TYPE_EMERGENCY_SUPL);
-        if (isNiTypeES != getInEmergency()) {
-            try {
-                mNetInitiatedListener.sendNiResponse(notif.notificationId,
-                                                     GPS_NI_RESPONSE_IGNORE);
-            } catch (RemoteException e) {
-                Log.e(TAG, "RemoteException in sendNiResponse");
-            }
-        } else {
-            handleNi(notif);
-        }
-    }
-
-    /**
-     * Posts a notification in the status bar using the contents in {@code notif} object.
-     */
-    private synchronized void setNiNotification(GpsNiNotification notif) {
-        NotificationManager notificationManager = (NotificationManager) mContext
-                .getSystemService(Context.NOTIFICATION_SERVICE);
-        if (notificationManager == null) {
-            return;
-        }
-
-        String title = getNotifTitle(notif, mContext);
-        String message = getNotifMessage(notif, mContext);
-
-        if (DEBUG) Log.d(TAG, "setNiNotification, notifyId: " + notif.notificationId +
-                ", title: " + title +
-                ", message: " + message);
-
-        // Construct Notification
-        if (mNiNotificationBuilder == null) {
-            mNiNotificationBuilder = new Notification.Builder(mContext,
-                SystemNotificationChannels.NETWORK_ALERTS)
-                    .setSmallIcon(com.android.internal.R.drawable.stat_sys_gps_on)
-                    .setWhen(0)
-                    .setOngoing(true)
-                    .setAutoCancel(true)
-                    .setColor(mContext.getColor(
-                            com.android.internal.R.color.system_notification_accent_color));
-        }
-
-        if (mPlaySounds) {
-            mNiNotificationBuilder.setDefaults(Notification.DEFAULT_SOUND);
-        } else {
-            mNiNotificationBuilder.setDefaults(0);
-        }
-
-        mNiNotificationBuilder.setTicker(getNotifTicker(notif, mContext))
-                .setContentTitle(title)
-                .setContentText(message);
-
-        notificationManager.notifyAsUser(null, notif.notificationId, mNiNotificationBuilder.build(),
-                UserHandle.ALL);
-    }
-
-    // Opens the notification dialog and waits for user input
-    private void openNiDialog(GpsNiNotification notif)
-    {
-        Intent intent = getDlgIntent(notif);
-
-        if (DEBUG) Log.d(TAG, "openNiDialog, notifyId: " + notif.notificationId +
-                ", requestorId: " + notif.requestorId +
-                ", text: " + notif.text);
-
-        mContext.startActivity(intent);
-    }
-
-    // Construct the intent for bringing up the dialog activity, which shows the
-    // notification and takes user input
-    private Intent getDlgIntent(GpsNiNotification notif)
-    {
-        Intent intent = new Intent();
-        String title = getDialogTitle(notif, mContext);
-        String message = getDialogMessage(notif, mContext);
-
-        // directly bring up the NI activity
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        intent.setClass(mContext, com.android.internal.app.NetInitiatedActivity.class);
-
-        // put data in the intent
-        intent.putExtra(NI_INTENT_KEY_NOTIF_ID, notif.notificationId);
-        intent.putExtra(NI_INTENT_KEY_TITLE, title);
-        intent.putExtra(NI_INTENT_KEY_MESSAGE, message);
-        intent.putExtra(NI_INTENT_KEY_TIMEOUT, notif.timeout);
-        intent.putExtra(NI_INTENT_KEY_DEFAULT_RESPONSE, notif.defaultResponse);
-
-        if (DEBUG) Log.d(TAG, "generateIntent, title: " + title + ", message: " + message +
-                ", timeout: " + notif.timeout);
-
-        return intent;
-    }
-
-    // Converts a string (or Hex string) to a char array
-    static byte[] stringToByteArray(String original, boolean isHex)
-    {
-        int length = isHex ? original.length() / 2 : original.length();
-        byte[] output = new byte[length];
-        int i;
-
-        if (isHex)
-        {
-            for (i = 0; i < length; i++)
-            {
-                output[i] = (byte) Integer.parseInt(original.substring(i*2, i*2+2), 16);
-            }
-        }
-        else {
-            for (i = 0; i < length; i++)
-            {
-                output[i] = (byte) original.charAt(i);
-            }
-        }
-
-        return output;
-    }
-
-    /**
-     * Unpacks an byte array containing 7-bit packed characters into a String.
-     *
-     * @param input a 7-bit packed char array
-     * @return the unpacked String
-     */
-    static String decodeGSMPackedString(byte[] input)
-    {
-        final char PADDING_CHAR = 0x00;
-        int lengthBytes = input.length;
-        int lengthSeptets = (lengthBytes * 8) / 7;
-        String decoded;
-
-        /* Special case where the last 7 bits in the last byte could hold a valid
-         * 7-bit character or a padding character. Drop the last 7-bit character
-         * if it is a padding character.
-         */
-        if (lengthBytes % 7 == 0) {
-            if (lengthBytes > 0) {
-                if ((input[lengthBytes - 1] >> 1) == PADDING_CHAR) {
-                    lengthSeptets = lengthSeptets - 1;
-                }
-            }
-        }
-
-        decoded = GsmAlphabet.gsm7BitPackedToString(input, 0, lengthSeptets);
-
-        // Return "" if decoding of GSM packed string fails
-        if (null == decoded) {
-            Log.e(TAG, "Decoding of GSM packed string failed");
-            decoded = "";
-        }
-
-        return decoded;
-    }
-
-    static String decodeUTF8String(byte[] input)
-    {
-        String decoded = "";
-        try {
-            decoded = new String(input, "UTF-8");
-        }
-        catch (UnsupportedEncodingException e)
-        {
-            throw new AssertionError();
-        }
-        return decoded;
-    }
-
-    static String decodeUCS2String(byte[] input)
-    {
-        String decoded = "";
-        try {
-            decoded = new String(input, "UTF-16");
-        }
-        catch (UnsupportedEncodingException e)
-        {
-            throw new AssertionError();
-        }
-        return decoded;
-    }
-
-    /** Decode NI string
-     *
-     * @param original   The text string to be decoded
-     * @param isHex      Specifies whether the content of the string has been encoded as a Hex string. Encoding
-     *                   a string as Hex can allow zeros inside the coded text.
-     * @param coding     Specifies the coding scheme of the string, such as GSM, UTF8, UCS2, etc. This coding scheme
-     *                      needs to match those used passed to HAL from the native GPS driver. Decoding is done according
-     *                   to the <code> coding </code>, after a Hex string is decoded. Generally, if the
-     *                   notification strings don't need further decoding, <code> coding </code> encoding can be
-     *                   set to -1, and <code> isHex </code> can be false.
-     * @return the decoded string
-     */
-    @UnsupportedAppUsage
-    static private String decodeString(String original, boolean isHex, int coding)
-    {
-        if (coding == GPS_ENC_NONE || coding == GPS_ENC_UNKNOWN) {
-            return original;
-        }
-
-        byte[] input = stringToByteArray(original, isHex);
-
-        switch (coding) {
-            case GPS_ENC_SUPL_GSM_DEFAULT:
-                return decodeGSMPackedString(input);
-
-            case GPS_ENC_SUPL_UTF8:
-                return decodeUTF8String(input);
-
-            case GPS_ENC_SUPL_UCS2:
-                return decodeUCS2String(input);
-
-            default:
-                Log.e(TAG, "Unknown encoding " + coding + " for NI text " + original);
-                return original;
-        }
-    }
-
-    // change this to configure notification display
-    static private String getNotifTicker(GpsNiNotification notif, Context context)
-    {
-        String ticker = String.format(context.getString(R.string.gpsNotifTicker),
-                decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
-                decodeString(notif.text, mIsHexInput, notif.textEncoding));
-        return ticker;
-    }
-
-    // change this to configure notification display
-    static private String getNotifTitle(GpsNiNotification notif, Context context)
-    {
-        String title = String.format(context.getString(R.string.gpsNotifTitle));
-        return title;
-    }
-
-    // change this to configure notification display
-    static private String getNotifMessage(GpsNiNotification notif, Context context)
-    {
-        String message = String.format(context.getString(R.string.gpsNotifMessage),
-                decodeString(notif.requestorId, mIsHexInput, notif.requestorIdEncoding),
-                decodeString(notif.text, mIsHexInput, notif.textEncoding));
-        return message;
-    }
-
-    // change this to configure dialog display (for verification)
-    static public String getDialogTitle(GpsNiNotification notif, Context context)
-    {
-        return getNotifTitle(notif, context);
-    }
-
-    // change this to configure dialog display (for verification)
-    static private String getDialogMessage(GpsNiNotification notif, Context context)
-    {
-        return getNotifMessage(notif, context);
-    }
-
 }
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 0ad8c24..0ff1b1e 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -42,7 +42,9 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.provider.BaseColumns;
 import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.AudioColumns;
 import android.provider.MediaStore.MediaColumns;
 import android.provider.Settings;
 import android.provider.Settings.System;
@@ -543,6 +545,95 @@
         return getUriFromCursor(mContext, mCursor);
     }
 
+    /**
+     * Gets the valid ringtone uri by a given uri string and ringtone type for the restore purpose.
+     *
+     * @param contentResolver ContentResolver to execute media query.
+     * @param value a canonicalized uri which refers to the ringtone.
+     * @param ringtoneType an integer representation of the kind of uri that is being restored, can
+     *     be RingtoneManager.TYPE_RINGTONE, RingtoneManager.TYPE_NOTIFICATION, or
+     *     RingtoneManager.TYPE_ALARM.
+     * @hide
+     */
+    public static @Nullable Uri getRingtoneUriForRestore(
+            @NonNull ContentResolver contentResolver, @Nullable String value, int ringtoneType)
+            throws FileNotFoundException, IllegalArgumentException {
+        if (value == null) {
+            // Return a valid null. It means the null value is intended instead of a failure.
+            return null;
+        }
+
+        Uri ringtoneUri;
+        final Uri canonicalUri = Uri.parse(value);
+
+        // Try to get the media uri via the regular uncanonicalize method first.
+        ringtoneUri = contentResolver.uncanonicalize(canonicalUri);
+        if (ringtoneUri != null) {
+            // Canonicalize it to make the result contain the right metadata of the media asset.
+            ringtoneUri = contentResolver.canonicalize(ringtoneUri);
+            return ringtoneUri;
+        }
+
+        // Query the media by title and ringtone type.
+        final String title = canonicalUri.getQueryParameter(AudioColumns.TITLE);
+        Uri baseUri = ContentUris.removeId(canonicalUri).buildUpon().clearQuery().build();
+        String ringtoneTypeSelection = "";
+        switch (ringtoneType) {
+            case RingtoneManager.TYPE_RINGTONE:
+                ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_RINGTONE;
+                break;
+            case RingtoneManager.TYPE_NOTIFICATION:
+                ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_NOTIFICATION;
+                break;
+            case RingtoneManager.TYPE_ALARM:
+                ringtoneTypeSelection = MediaStore.Audio.AudioColumns.IS_ALARM;
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown ringtone type: " + ringtoneType);
+        }
+
+        final String selection = ringtoneTypeSelection + "=1 AND " + AudioColumns.TITLE + "=?";
+        Cursor cursor = null;
+        try {
+            cursor =
+                    contentResolver.query(
+                            baseUri,
+                            /* projection */ new String[] {BaseColumns._ID},
+                            /* selection */ selection,
+                            /* selectionArgs */ new String[] {title},
+                            /* sortOrder */ null,
+                            /* cancellationSignal */ null);
+
+        } catch (IllegalArgumentException e) {
+            throw new FileNotFoundException("Volume not found for " + baseUri);
+        }
+        if (cursor == null) {
+            throw new FileNotFoundException("Missing cursor for " + baseUri);
+        } else if (cursor.getCount() == 0) {
+            FileUtils.closeQuietly(cursor);
+            throw new FileNotFoundException("No item found for " + baseUri);
+        } else if (cursor.getCount() > 1) {
+            // Find more than 1 result.
+            // We are not sure which one is the right ringtone file so just abandon this case.
+            FileUtils.closeQuietly(cursor);
+            throw new FileNotFoundException(
+                    "Find multiple ringtone candidates by title+ringtone_type query: count: "
+                            + cursor.getCount());
+        }
+        if (cursor.moveToFirst()) {
+            ringtoneUri = ContentUris.withAppendedId(baseUri, cursor.getLong(0));
+            FileUtils.closeQuietly(cursor);
+        } else {
+            FileUtils.closeQuietly(cursor);
+            throw new FileNotFoundException("Failed to read row from the result.");
+        }
+
+        // Canonicalize it to make the result contain the right metadata of the media asset.
+        ringtoneUri = contentResolver.canonicalize(ringtoneUri);
+        Log.v(TAG, "Find a valid result: " + ringtoneUri);
+        return ringtoneUri;
+    }
+
     private static Uri getUriFromCursor(Context context, Cursor cursor) {
         final Uri uri = ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)),
                 cursor.getLong(ID_COLUMN_INDEX));
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index fb72c7b..223b432c 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -83,6 +83,7 @@
         try {
             mImpl.start(new MediaProjectionCallback());
         } catch (RemoteException e) {
+            Log.e(TAG, "Content Recording: Failed to start media projection", e);
             throw new RuntimeException("Failed to start media projection", e);
         }
         mDisplayManager = displayManager;
@@ -105,11 +106,18 @@
      * @see #unregisterCallback
      */
     public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
-        final Callback c = Objects.requireNonNull(callback);
-        if (handler == null) {
-            handler = new Handler();
+        try {
+            final Callback c = Objects.requireNonNull(callback);
+            if (handler == null) {
+                handler = new Handler();
+            }
+            mCallbacks.put(c, new CallbackRecord(c, handler));
+        } catch (NullPointerException e) {
+            Log.e(TAG, "Content Recording: cannot register null Callback", e);
+            throw e;
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Content Recording: failed to create new Handler to register Callback", e);
         }
-        mCallbacks.put(c, new CallbackRecord(c, handler));
     }
 
     /**
@@ -120,8 +128,13 @@
      * @see #registerCallback
      */
     public void unregisterCallback(@NonNull Callback callback) {
-        final Callback c = Objects.requireNonNull(callback);
-        mCallbacks.remove(c);
+        try {
+            final Callback c = Objects.requireNonNull(callback);
+            mCallbacks.remove(c);
+        } catch (NullPointerException e) {
+            Log.d(TAG, "Content Recording: cannot unregister null Callback", e);
+            throw e;
+        }
     }
 
     /**
@@ -203,9 +216,11 @@
             @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
         if (shouldMediaProjectionRequireCallback()) {
             if (mCallbacks.isEmpty()) {
-                throw new IllegalStateException(
+                final IllegalStateException e = new IllegalStateException(
                         "Must register a callback before starting capture, to manage resources in"
                                 + " response to MediaProjection states.");
+                Log.e(TAG, "Content Recording: no callback registered for virtual display", e);
+                throw e;
             }
         }
         final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
@@ -272,6 +287,7 @@
      */
     public void stop() {
         try {
+            Log.d(TAG, "Content Recording: stopping projection");
             mImpl.stop();
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to stop projection", e);
diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java
index 5a68c53..9790d02 100644
--- a/media/java/android/media/projection/MediaProjectionManager.java
+++ b/media/java/android/media/projection/MediaProjectionManager.java
@@ -256,6 +256,7 @@
      */
     public void stopActiveProjection() {
         try {
+            Log.d(TAG, "Content Recording: stopping active projection");
             mService.stopActiveProjection();
         } catch (RemoteException e) {
             Log.e(TAG, "Unable to stop the currently active media projection", e);
@@ -269,6 +270,7 @@
      */
     public void addCallback(@NonNull Callback callback, @Nullable Handler handler) {
         if (callback == null) {
+            Log.w(TAG, "Content Recording: cannot add null callback");
             throw new IllegalArgumentException("callback must not be null");
         }
         CallbackDelegate delegate = new CallbackDelegate(callback, handler);
@@ -286,6 +288,7 @@
      */
     public void removeCallback(@NonNull Callback callback) {
         if (callback == null) {
+            Log.w(TAG, "ContentRecording: cannot remove null callback");
             throw new IllegalArgumentException("callback must not be null");
         }
         CallbackDelegate delegate = mCallbacks.remove(callback);
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index f664fdc..1d6e38d 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -450,6 +450,7 @@
      * but it must be released if your activity or service is being destroyed.
      */
     public void release() {
+        setCallback(null);
         try {
             mBinder.destroySession();
         } catch (RemoteException e) {
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index d81843d..f87e47e 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -452,6 +452,12 @@
         acquireTRMSLock("shareFrontendFromTuner()");
         mFrontendLock.lock();
         try {
+            if (mFeOwnerTuner != null) {
+                // unregister self from the Frontend callback
+                mFeOwnerTuner.unregisterFrontendCallbackListener(this);
+                mFeOwnerTuner = null;
+                nativeUnshareFrontend();
+            }
             mTunerResourceManager.shareFrontend(mClientId, tuner.mClientId);
             mFeOwnerTuner = tuner;
             mFeOwnerTuner.registerFrontendCallbackListener(this);
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
index c39a6db..d058600 100644
--- a/media/java/android/media/tv/tuner/filter/Filter.java
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -32,7 +32,6 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.lang.NullPointerException;
 import java.util.concurrent.Executor;
 
 /**
@@ -270,16 +269,20 @@
         synchronized (mCallbackLock) {
             if (mCallback != null && mExecutor != null) {
                 mExecutor.execute(() -> {
+                    FilterCallback callback;
                     synchronized (mCallbackLock) {
-                        if (mCallback != null) {
-                            try {
-                                mCallback.onFilterStatusChanged(this, status);
-                            }
-                            catch (NullPointerException e) {
-                                Log.d(TAG, "catch exception:" + e);
-                            }
+                        callback = mCallback;
+                    }
+                    if (callback != null) {
+                        try {
+                            callback.onFilterStatusChanged(this, status);
+                        } catch (NullPointerException e) {
+                            Log.d(TAG, "catch exception:" + e);
                         }
                     }
+                    if (callback != null) {
+                        callback.onFilterStatusChanged(this, status);
+                    }
                 });
             }
         }
@@ -289,19 +292,20 @@
         synchronized (mCallbackLock) {
             if (mCallback != null && mExecutor != null) {
                 mExecutor.execute(() -> {
+                    FilterCallback callback;
                     synchronized (mCallbackLock) {
-                        if (mCallback != null) {
-                            try {
-                                mCallback.onFilterEvent(this, events);
-                            }
-                            catch (NullPointerException e) {
-                                Log.d(TAG, "catch exception:" + e);
-                            }
-                        } else {
-                            for (FilterEvent event : events) {
-                                if (event instanceof MediaEvent) {
-                                    ((MediaEvent)event).release();
-                                }
+                        callback = mCallback;
+                    }
+                    if (callback != null) {
+                        try {
+                            callback.onFilterEvent(this, events);
+                        } catch (NullPointerException e) {
+                            Log.d(TAG, "catch exception:" + e);
+                        }
+                    } else {
+                        for (FilterEvent event : events) {
+                            if (event instanceof MediaEvent) {
+                                ((MediaEvent) event).release();
                             }
                         }
                     }
@@ -309,7 +313,7 @@
             } else {
                 for (FilterEvent event : events) {
                     if (event instanceof MediaEvent) {
-                        ((MediaEvent)event).release();
+                        ((MediaEvent) event).release();
                     }
                 }
             }
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 5ea98c0c..b03a039 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -3450,7 +3450,8 @@
         JNIEnv *env, jobject thiz,
         jstring name, jboolean nameIsType, jboolean encoder, int pid, int uid) {
     if (name == NULL) {
-        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        jniThrowException(env, "java/lang/NullPointerException",
+                          "No codec name specified");
         return;
     }
 
diff --git a/media/tests/mediatestutils/Android.bp b/media/tests/mediatestutils/Android.bp
index e50e69a..15bc177 100644
--- a/media/tests/mediatestutils/Android.bp
+++ b/media/tests/mediatestutils/Android.bp
@@ -24,9 +24,13 @@
 
 java_library {
     name: "mediatestutils",
+    srcs: [
+        "java/com/android/media/mediatestutils/TestUtils.java",
+    ],
     static_libs: [
+        "androidx.concurrent_concurrent-futures",
+        "guava",
         "mediatestutils_host",
-        "junit",
     ],
     visibility: [
         "//cts/tests/tests/media:__subpackages__",
diff --git a/media/tests/mediatestutils/TEST_MAPPING b/media/tests/mediatestutils/TEST_MAPPING
new file mode 100644
index 0000000..6dd09ae
--- /dev/null
+++ b/media/tests/mediatestutils/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "mediatestutilstests"
+    }
+  ]
+}
diff --git a/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
new file mode 100644
index 0000000..69d836d
--- /dev/null
+++ b/media/tests/mediatestutils/java/com/android/media/mediatestutils/TestUtils.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.mediatestutils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
+
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/** Utils for audio tests. */
+public class TestUtils {
+    /**
+     * Return a future for an intent delivered by a broadcast receiver which matches an
+     * action and predicate.
+     * @param context - Context to register the receiver with
+     * @param action - String representing action to register receiver for
+     * @param pred - Predicate which sets the future if evaluates to true, otherwise, leaves
+     * the future unset. If the predicate throws, the future is set exceptionally
+     * @return - The future representing intent delivery matching predicate.
+     */
+    public static ListenableFuture<Intent> getFutureForIntent(
+            Context context, String action, Predicate<Intent> pred) {
+        // These are evaluated async
+        Objects.requireNonNull(action);
+        Objects.requireNonNull(pred);
+        return getFutureForListener(
+                (recv) ->
+                        context.registerReceiver(
+                                recv, new IntentFilter(action), Context.RECEIVER_NOT_EXPORTED),
+                (recv) -> {
+                    try {
+                        context.unregisterReceiver(recv);
+                    } catch (IllegalArgumentException e) {
+                        // Thrown when receiver is already unregistered, nothing to do
+                    }
+                },
+                (completer) ->
+                        new BroadcastReceiver() {
+                            @Override
+                            public void onReceive(Context context, Intent intent) {
+                                try {
+                                    if (action.equals(intent.getAction()) && pred.test(intent)) {
+                                        completer.set(intent);
+                                    }
+                                } catch (Exception e) {
+                                    completer.setException(e);
+                                }
+                            }
+                        },
+                "Intent receiver future for action: " + action);
+    }
+
+    /**
+     * Return a future for a callback registered to a listener interface.
+     * @param registerFunc - Function which consumes the callback object for registration
+     * @param unregisterFunc - Function which consumes the callback object for unregistration
+     * This function is called when the future is completed or cancelled
+     * @param instantiateCallback - Factory function for the callback object, provided a completer
+     * object (see {@code CallbackToFutureAdapter.Completer<T>}), which is a logical reference
+     * to the future returned by this function
+     * @param debug - Debug string contained in future {@code toString} representation.
+     */
+    public static <T, V> ListenableFuture<T> getFutureForListener(
+            Consumer<V> registerFunc,
+            Consumer<V> unregisterFunc,
+            Function<CallbackToFutureAdapter.Completer<T>, V> instantiateCallback,
+            String debug) {
+        // Doesn't need to be thread safe since the resolver is called inline
+        final WeakReference<V> wrapper[] = new WeakReference[1];
+        ListenableFuture<T> future =
+                CallbackToFutureAdapter.getFuture(
+                        completer -> {
+                            final var cb = instantiateCallback.apply(completer);
+                            wrapper[0] = new WeakReference(cb);
+                            registerFunc.accept(cb);
+                            return debug;
+                        });
+        if (wrapper[0] == null) {
+            throw new AssertionError("Resolver should be called inline");
+        }
+        final var weakref = wrapper[0];
+        future.addListener(
+                () -> {
+                    var cb = weakref.get();
+                    // If there is no reference left, the receiver has already been unregistered
+                    if (cb != null) {
+                        unregisterFunc.accept(cb);
+                        return;
+                    }
+                },
+                MoreExecutors.directExecutor()); // Direct executor is fine since lightweight
+        return future;
+    }
+}
diff --git a/media/tests/mediatestutils/tests/Android.bp b/media/tests/mediatestutils/tests/Android.bp
new file mode 100644
index 0000000..24a8360
--- /dev/null
+++ b/media/tests/mediatestutils/tests/Android.bp
@@ -0,0 +1,22 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "mediatestutilstests",
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "mockito-target-minus-junit4",
+        "androidx.test.runner",
+        "androidx.test.core",
+        "mediatestutils",
+        "junit",
+        "truth",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/media/tests/mediatestutils/tests/AndroidManifest.xml b/media/tests/mediatestutils/tests/AndroidManifest.xml
new file mode 100644
index 0000000..662bc45
--- /dev/null
+++ b/media/tests/mediatestutils/tests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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"
+          package="com.android.media.mediatestutils">
+
+    <application android:testOnly="false"
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.media.mediatestutils"
+        android:label="mediatestutils tests" />
+
+</manifest>
diff --git a/media/tests/mediatestutils/tests/src/java/com/android/media/mediatestutils/GetFutureForIntentTest.java b/media/tests/mediatestutils/tests/src/java/com/android/media/mediatestutils/GetFutureForIntentTest.java
new file mode 100644
index 0000000..a4266a3
--- /dev/null
+++ b/media/tests/mediatestutils/tests/src/java/com/android/media/mediatestutils/GetFutureForIntentTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.mediatestutils;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.media.mediatestutils.TestUtils.getFutureForIntent;
+import static com.android.media.mediatestutils.TestUtils.getFutureForListener;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemClock;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.ExecutionException;
+import java.util.function.Predicate;
+
+@RunWith(AndroidJUnit4.class)
+public class GetFutureForIntentTest {
+    public static final String INTENT_ACTION = "com.android.media.mediatestutils.TEST_ACTION";
+    public static final String INTENT_EXTRA = "com.android.media.mediatestutils.TEST_EXTRA";
+    public static final int MAGIC_VALUE = 7;
+
+    public final Context mContext = getApplicationContext();
+    public final Predicate<Intent> mPred =
+            i -> (i != null) && (i.getIntExtra(INTENT_EXTRA, -1) == MAGIC_VALUE);
+
+    @Test
+    public void futureCompletes_afterBroadcastFiresPredicatePasses() throws Exception {
+        final var future = getFutureForIntent(mContext, INTENT_ACTION, mPred);
+        sendIntent(true);
+        var intent = future.get();
+        assertThat(intent.getAction()).isEqualTo(INTENT_ACTION);
+        assertThat(intent.getIntExtra(INTENT_EXTRA, -1)).isEqualTo(MAGIC_VALUE);
+    }
+
+    @Test
+    public void futureDoesNotComplete_afterBroadcastFiresPredicateFails() throws Exception {
+        final var future = getFutureForIntent(mContext, INTENT_ACTION, mPred);
+        sendIntent(false);
+
+        // Wait a bit, and ensure the future hasn't completed
+        SystemClock.sleep(100);
+        assertThat(future.isDone()).isFalse();
+
+        // Future should still respond to subsequent passing intent
+        sendIntent(true);
+        var intent = future.get();
+        assertThat(intent.getAction()).isEqualTo(INTENT_ACTION);
+        assertThat(intent.getIntExtra(INTENT_EXTRA, -1)).isEqualTo(MAGIC_VALUE);
+    }
+
+    @Test
+    public void futureCompletesExceptionally_afterBroadcastFiresPredicateThrows() throws Exception {
+        final var future =
+                getFutureForIntent(
+                        mContext,
+                        INTENT_ACTION,
+                        i -> {
+                            throw new IllegalStateException();
+                        });
+
+        sendIntent(true);
+        try {
+            var intent = future.get();
+            fail("Exception expected if predicate throws");
+        } catch (ExecutionException e) {
+            assertThat(e.getCause().getClass()).isEqualTo(IllegalStateException.class);
+        }
+    }
+
+    @Test
+    public void doesNotThrow_whenDoubleSet() throws Exception {
+        final var future = getFutureForIntent(mContext, INTENT_ACTION, mPred);
+        sendIntent(true);
+        sendIntent(true);
+        var intent = future.get();
+        assertThat(intent.getAction()).isEqualTo(INTENT_ACTION);
+        assertThat(intent.getIntExtra(INTENT_EXTRA, -1)).isEqualTo(MAGIC_VALUE);
+    }
+
+    @Test
+    public void unregisterListener_whenComplete() throws Exception {
+        final var service = new FakeService();
+        final ListenableFuture<Void> future =
+                getFutureForListener(
+                        service::registerListener,
+                        service::unregisterListener,
+                        completer ->
+                                () -> {
+                                    completer.set(null);
+                                },
+                        "FakeService listener future");
+        service.mRunnable.run();
+        assertThat(service.mRunnable).isNull();
+    }
+
+    @Test
+    public void unregisterListener_whenCancel() throws Exception {
+        final var service = new FakeService();
+        final ListenableFuture<Void> future =
+                getFutureForListener(
+                        service::registerListener,
+                        service::unregisterListener,
+                        completer ->
+                                () -> {
+                                    completer.set(null);
+                                },
+                        "FakeService listener future");
+        future.cancel(false);
+        assertThat(service.mRunnable).isNull();
+    }
+
+    private static class FakeService {
+        Runnable mRunnable;
+
+        void registerListener(Runnable r) {
+            mRunnable = r;
+        }
+
+        void unregisterListener(Runnable r) {
+            assertThat(r).isEqualTo(mRunnable);
+            mRunnable = null;
+        }
+    }
+
+    private void sendIntent(boolean correctValue) {
+        final Intent intent = new Intent(INTENT_ACTION).setPackage(mContext.getPackageName());
+        intent.putExtra(INTENT_EXTRA, correctValue ? MAGIC_VALUE : MAGIC_VALUE + 1);
+        mContext.sendBroadcast(intent);
+    }
+}
diff --git a/native/android/Android.bp b/native/android/Android.bp
index 254eb44..7f3792d 100644
--- a/native/android/Android.bp
+++ b/native/android/Android.bp
@@ -46,7 +46,10 @@
 
 cc_library_shared {
     name: "libandroid",
-    defaults: ["libandroid_defaults"],
+    defaults: [
+        "libandroid_defaults",
+        "android.hardware.power-ndk_shared",
+    ],
 
     srcs: [
         "activity_manager.cpp",
@@ -95,7 +98,6 @@
         "libpowermanager",
         "android.hardware.configstore@1.0",
         "android.hardware.configstore-utils",
-        "android.hardware.power-V4-ndk",
         "libnativedisplay",
     ],
 
diff --git a/native/android/OWNERS b/native/android/OWNERS
index d41652f..0b86909 100644
--- a/native/android/OWNERS
+++ b/native/android/OWNERS
@@ -26,3 +26,6 @@
 
 # Input
 per-file input.cpp = file:/INPUT_OWNERS
+
+# PerformanceHint
+per-file performance_hint.cpp = file:/ADPF_OWNERS
diff --git a/native/android/configuration.cpp b/native/android/configuration.cpp
index 283445f..b50514d 100644
--- a/native/android/configuration.cpp
+++ b/native/android/configuration.cpp
@@ -36,7 +36,7 @@
 
 void AConfiguration_fromAssetManager(AConfiguration* out, AAssetManager* am) {
     ScopedLock<AssetManager2> locked_mgr(*AssetManagerForNdkAssetManager(am));
-    ResTable_config config = locked_mgr->GetConfigurations()[0];
+    ResTable_config config = locked_mgr->GetConfiguration();
 
     // AConfiguration is not a virtual subclass, so we can memcpy.
     memcpy(out, &config, sizeof(config));
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index d74f9b7..b0af09c 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -334,6 +334,7 @@
     APerformanceHint_reportActualWorkDuration; # introduced=Tiramisu
     APerformanceHint_closeSession; # introduced=Tiramisu
     APerformanceHint_setThreads; # introduced=UpsideDownCake
+    APerformanceHint_setPreferPowerEfficiency; # introduced=VanillaIceCream
   local:
     *;
 };
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 6198f40..c25df6e 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -17,6 +17,7 @@
 #define LOG_TAG "perf_hint"
 
 #include <aidl/android/hardware/power/SessionHint.h>
+#include <aidl/android/hardware/power/SessionMode.h>
 #include <android/os/IHintManager.h>
 #include <android/os/IHintSession.h>
 #include <android/performance_hint.h>
@@ -36,6 +37,7 @@
 using namespace std::chrono_literals;
 
 using AidlSessionHint = aidl::android::hardware::power::SessionHint;
+using AidlSessionMode = aidl::android::hardware::power::SessionMode;
 
 struct APerformanceHintSession;
 
@@ -72,6 +74,7 @@
     int sendHint(SessionHint hint);
     int setThreads(const int32_t* threadIds, size_t size);
     int getThreadIds(int32_t* const threadIds, size_t* size);
+    int setPreferPowerEfficiency(bool enabled);
 
 private:
     friend struct APerformanceHintManager;
@@ -307,6 +310,18 @@
     return 0;
 }
 
+int APerformanceHintSession::setPreferPowerEfficiency(bool enabled) {
+    binder::Status ret =
+            mHintSession->setMode(static_cast<int32_t>(AidlSessionMode::POWER_EFFICIENCY), enabled);
+
+    if (!ret.isOk()) {
+        ALOGE("%s: HintSession setPreferPowerEfficiency failed: %s", __FUNCTION__,
+              ret.exceptionMessage().c_str());
+        return EPIPE;
+    }
+    return OK;
+}
+
 // ===================================== C API
 APerformanceHintManager* APerformanceHint_getManager() {
     return APerformanceHintManager::getInstance();
@@ -357,6 +372,10 @@
             ->getThreadIds(threadIds, size);
 }
 
+int APerformanceHint_setPreferPowerEfficiency(APerformanceHintSession* session, bool enabled) {
+    return session->setPreferPowerEfficiency(enabled);
+}
+
 void APerformanceHint_setIHintManagerForTesting(void* iManager) {
     delete gHintManagerForTesting;
     gHintManagerForTesting = nullptr;
diff --git a/native/android/tests/performance_hint/OWNERS b/native/android/tests/performance_hint/OWNERS
new file mode 100644
index 0000000..e3bbee92
--- /dev/null
+++ b/native/android/tests/performance_hint/OWNERS
@@ -0,0 +1 @@
+include /ADPF_OWNERS
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 6f7562b..22d33b1 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -56,7 +56,8 @@
                 (const ::std::vector<int64_t>& actualDurationNanos,
                  const ::std::vector<int64_t>& timeStampNanos),
                 (override));
-    MOCK_METHOD(Status, sendHint, (int32_t hints), (override));
+    MOCK_METHOD(Status, sendHint, (int32_t hint), (override));
+    MOCK_METHOD(Status, setMode, (int32_t mode, bool enabled), (override));
     MOCK_METHOD(Status, close, (), (override));
     MOCK_METHOD(IBinder*, onAsBinder, (), (override));
 };
@@ -190,3 +191,51 @@
     result = APerformanceHint_setThreads(session, invalidTids.data(), invalidTids.size());
     EXPECT_EQ(EPERM, result);
 }
+
+TEST_F(PerformanceHintTest, SetPowerEfficient) {
+    APerformanceHintManager* manager = createManager();
+
+    std::vector<int32_t> tids;
+    tids.push_back(1);
+    tids.push_back(2);
+    int64_t targetDuration = 56789L;
+
+    StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>();
+    sp<IHintSession> session_sp(iSession);
+
+    EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status())));
+
+    APerformanceHintSession* session =
+            APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
+    ASSERT_TRUE(session);
+
+    EXPECT_CALL(*iSession, setMode(_, Eq(true))).Times(Exactly(1));
+    int result = APerformanceHint_setPreferPowerEfficiency(session, true);
+    EXPECT_EQ(0, result);
+
+    EXPECT_CALL(*iSession, setMode(_, Eq(false))).Times(Exactly(1));
+    result = APerformanceHint_setPreferPowerEfficiency(session, false);
+    EXPECT_EQ(0, result);
+}
+
+TEST_F(PerformanceHintTest, CreateZeroTargetDurationSession) {
+    APerformanceHintManager* manager = createManager();
+
+    std::vector<int32_t> tids;
+    tids.push_back(1);
+    tids.push_back(2);
+    int64_t targetDuration = 0;
+
+    StrictMock<MockIHintSession>* iSession = new StrictMock<MockIHintSession>();
+    sp<IHintSession> session_sp(iSession);
+
+    EXPECT_CALL(*mMockIHintManager, createHintSession(_, Eq(tids), Eq(targetDuration), _))
+            .Times(Exactly(1))
+            .WillRepeatedly(DoAll(SetArgPointee<3>(std::move(session_sp)), Return(Status())));
+
+    APerformanceHintSession* session =
+            APerformanceHint_createSession(manager, tids.data(), tids.size(), targetDuration);
+    ASSERT_TRUE(session);
+}
\ No newline at end of file
diff --git a/packages/EasterEgg/src/com/android/egg/quares/Quare.kt b/packages/EasterEgg/src/com/android/egg/quares/Quare.kt
index eb77362..f0cc2a1 100644
--- a/packages/EasterEgg/src/com/android/egg/quares/Quare.kt
+++ b/packages/EasterEgg/src/com/android/egg/quares/Quare.kt
@@ -137,14 +137,12 @@
         return 0
     }
 
-    override fun writeToParcel(p: Parcel?, flags: Int) {
-        p?.let {
-            p.writeInt(width)
-            p.writeInt(height)
-            p.writeInt(depth)
-            p.writeIntArray(data)
-            p.writeIntArray(user)
-        }
+    override fun writeToParcel(p: Parcel, flags: Int) {
+        p.writeInt(width)
+        p.writeInt(height)
+        p.writeInt(depth)
+        p.writeIntArray(data)
+        p.writeIntArray(user)
     }
 
     companion object CREATOR : Parcelable.Creator<Quare> {
diff --git a/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt b/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt
index 578de01..5fa6137 100644
--- a/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt
+++ b/packages/EasterEgg/src/com/android/egg/quares/QuaresActivity.kt
@@ -60,8 +60,8 @@
 
         setContentView(R.layout.activity_quares)
 
-        grid = findViewById(R.id.grid)
-        label = findViewById(R.id.label)
+        grid = requireViewById(R.id.grid)
+        label = requireViewById(R.id.label)
 
         if (savedInstanceState != null) {
             Log.v(TAG, "restoring puzzle from state")
@@ -135,7 +135,7 @@
         if (q.check()) {
             val dp = resources.displayMetrics.density
 
-            val label: Button = findViewById(R.id.label)
+            val label: Button = requireViewById(R.id.label)
             label.text = resName.replace(Regex("^.*/"), "")
             val drawable = icon?.loadDrawable(this)?.also {
                 it.setBounds(0, 0, (32 * dp).toInt(), (32 * dp).toInt())
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index 2d6cc69..df91d98 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -806,12 +806,12 @@
             }
             new Handler(Looper.getMainLooper()).postDelayed(() -> {
                 if (!isDestroyed()) {
+                    startActivity(getIntent());
                     // The start flag (FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP) doesn't
                     // work for the multiple user case, i.e. the caller task user and started
                     // Activity user are not the same. To avoid having multiple PIAs in the task,
                     // finish the current PackageInstallerActivity
                     finish();
-                    startActivity(getIntent());
                 }
             }, 500);
 
diff --git a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
index 0c4cb8e..74acf67 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/ui/SelectPrinterActivity.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.LoaderManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -714,8 +715,13 @@
 
                     try {
                         mPrinterForInfoIntent = printer;
+                        Bundle options = ActivityOptions.makeBasic()
+                                .setPendingIntentBackgroundActivityStartMode(
+                                        ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                                .toBundle();
                         startIntentSenderForResult(printer.getInfoIntent().getIntentSender(),
-                                INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0);
+                                INFO_INTENT_REQUEST_CODE, fillInIntent, 0, 0, 0,
+                                options);
                     } catch (SendIntentException e) {
                         mPrinterForInfoIntent = null;
                         Log.e(LOG_TAG, "Could not execute pending info intent: %s", e);
diff --git a/packages/SettingsLib/Spa/gallery/res/values/strings.xml b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
index 0d1a1fe..ec60f8c 100644
--- a/packages/SettingsLib/Spa/gallery/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/gallery/res/values/strings.xml
@@ -24,4 +24,6 @@
     <string name="single_line_summary_preference_title" translatable="false">Preference (singleLineSummary = true)</string>
     <!-- Summary for single line summary preference. [DO NOT TRANSLATE] -->
     <string name="single_line_summary_preference_summary" translatable="false">A very long summary to show case a preference which only shows a single line summary.</string>
+    <!-- Footer text with two links. [DO NOT TRANSLATE] -->
+    <string name="footer_with_two_links" translatable="false">Annotated string with <a href="https://www.android.com/">link 1</a> and <a href="https://source.android.com/">link 2</a>.</string>
 </resources>
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
similarity index 92%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
index 9c7e0ce..50c0eb7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPageProvider.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -28,9 +28,11 @@
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.android.settingslib.spa.widget.ui.AnnotatedText
 import com.android.settingslib.spa.widget.ui.Footer
 
 private const val TITLE = "Sample Footer"
@@ -78,6 +80,9 @@
                 entry.UiLayout()
             }
             Footer(footerText = "Footer text always at the end of page.")
+            Footer {
+                AnnotatedText(R.string.footer_with_two_links)
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/res/values/colors.xml b/packages/SettingsLib/Spa/spa/res/values/colors.xml
new file mode 100644
index 0000000..ca4a0b2
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/res/values/colors.xml
@@ -0,0 +1,67 @@
+<?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>
+    <color name="settingslib_protection_color">@android:color/white</color>
+
+    <!-- Dynamic colors-->
+    <color name="settingslib_color_blue600">#1a73e8</color>
+    <color name="settingslib_color_blue400">#669df6</color>
+    <color name="settingslib_color_blue300">#8ab4f8</color>
+    <color name="settingslib_color_blue100">#d2e3fc</color>
+    <color name="settingslib_color_blue50">#e8f0fe</color>
+    <color name="settingslib_color_green600">#1e8e3e</color>
+    <color name="settingslib_color_green500">#34A853</color>
+    <color name="settingslib_color_green400">#5bb974</color>
+    <color name="settingslib_color_green100">#ceead6</color>
+    <color name="settingslib_color_green50">#e6f4ea</color>
+    <color name="settingslib_color_red600">#d93025</color>
+    <color name="settingslib_color_red500">#B3261E</color>
+    <color name="settingslib_color_red400">#ee675c</color>
+    <color name="settingslib_color_red100">#fad2cf</color>
+    <color name="settingslib_color_red50">#fce8e6</color>
+    <color name="settingslib_color_yellow600">#f9ab00</color>
+    <color name="settingslib_color_yellow400">#fcc934</color>
+    <color name="settingslib_color_yellow100">#feefc3</color>
+    <color name="settingslib_color_yellow50">#fef7e0</color>
+    <color name="settingslib_color_grey900">#202124</color>
+    <color name="settingslib_color_grey800">#3c4043</color>
+    <color name="settingslib_color_grey700">#5f6368</color>
+    <color name="settingslib_color_grey600">#80868b</color>
+    <color name="settingslib_color_grey500">#9AA0A6</color>
+    <color name="settingslib_color_grey400">#bdc1c6</color>
+    <color name="settingslib_color_grey300">#dadce0</color>
+    <color name="settingslib_color_grey200">#e8eaed</color>
+    <color name="settingslib_color_grey100">#f1f3f4</color>
+    <color name="settingslib_color_grey50">#f8f9fa</color>
+    <color name="settingslib_color_orange600">#e8710a</color>
+    <color name="settingslib_color_orange400">#fa903e</color>
+    <color name="settingslib_color_orange300">#fcad70</color>
+    <color name="settingslib_color_orange100">#fedfc8</color>
+    <color name="settingslib_color_pink600">#e52592</color>
+    <color name="settingslib_color_pink400">#ff63b8</color>
+    <color name="settingslib_color_pink300">#ff8bcb</color>
+    <color name="settingslib_color_pink100">#fdcfe8</color>
+    <color name="settingslib_color_purple600">#9334e6</color>
+    <color name="settingslib_color_purple400">#af5cf7</color>
+    <color name="settingslib_color_purple300">#c58af9</color>
+    <color name="settingslib_color_purple100">#e9d2fd</color>
+    <color name="settingslib_color_cyan600">#12b5c8</color>
+    <color name="settingslib_color_cyan400">#4ecde6</color>
+    <color name="settingslib_color_cyan300">#78d9ec</color>
+    <color name="settingslib_color_cyan100">#cbf0f8</color>
+</resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
index 9ddd0c6..88ba4b0 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/util/AnnotatedStringResource.kt
@@ -16,105 +16,93 @@
 
 package com.android.settingslib.spa.framework.util
 
-import android.content.res.Resources
 import android.graphics.Typeface
 import android.text.Spanned
 import android.text.style.StyleSpan
 import android.text.style.URLSpan
 import androidx.annotation.StringRes
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalConfiguration
-import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.buildAnnotatedString
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.unit.Density
+import androidx.compose.ui.text.style.TextDecoration
 
-const val URLSPAN_TAG = "URLSPAN_TAG"
+const val URL_SPAN_TAG = "URL_SPAN_TAG"
 
 @Composable
-fun annotatedStringResource(@StringRes id: Int, urlSpanColor: Color): AnnotatedString {
-    LocalConfiguration.current
+fun annotatedStringResource(@StringRes id: Int): AnnotatedString {
     val resources = LocalContext.current.resources
-    val density = LocalDensity.current
+    val urlSpanColor = MaterialTheme.colorScheme.primary
     return remember(id) {
         val text = resources.getText(id)
-        spannableStringToAnnotatedString(text, density, urlSpanColor)
+        spannableStringToAnnotatedString(text, urlSpanColor)
     }
 }
 
-private fun spannableStringToAnnotatedString(text: CharSequence, density: Density, urlSpanColor: Color): AnnotatedString {
-    return if (text is Spanned) {
-        with(density) {
-            buildAnnotatedString {
-                append((text.toString()))
-                text.getSpans(0, text.length, Any::class.java).forEach {
-                    val start = text.getSpanStart(it)
-                    val end = text.getSpanEnd(it)
-                    when (it) {
-                        is StyleSpan ->
-                            when (it.style) {
-                                Typeface.NORMAL -> addStyle(
-                                        SpanStyle(
-                                                fontWeight = FontWeight.Normal,
-                                                fontStyle = FontStyle.Normal
-                                        ),
-                                        start,
-                                        end
-                                )
-                                Typeface.BOLD -> addStyle(
-                                        SpanStyle(
-                                                fontWeight = FontWeight.Bold,
-                                                fontStyle = FontStyle.Normal
-                                        ),
-                                        start,
-                                        end
-                                )
-                                Typeface.ITALIC -> addStyle(
-                                        SpanStyle(
-                                                fontWeight = FontWeight.Normal,
-                                                fontStyle = FontStyle.Italic
-                                        ),
-                                        start,
-                                        end
-                                )
-                                Typeface.BOLD_ITALIC -> addStyle(
-                                        SpanStyle(
-                                                fontWeight = FontWeight.Bold,
-                                                fontStyle = FontStyle.Italic
-                                        ),
-                                        start,
-                                        end
-                                )
-                            }
-                        is URLSpan -> {
-                            addStyle(
-                                    SpanStyle(
-                                            color = urlSpanColor,
-                                    ),
-                                    start,
-                                    end
-                            )
-                            if (!it.url.isNullOrEmpty()) {
-                                addStringAnnotation(
-                                        URLSPAN_TAG,
-                                        it.url,
-                                        start,
-                                        end
-                                )
-                            }
-                        }
-                        else -> addStyle(SpanStyle(), start, end)
-                    }
+private fun spannableStringToAnnotatedString(text: CharSequence, urlSpanColor: Color) =
+    if (text is Spanned) {
+        buildAnnotatedString {
+            append((text.toString()))
+            for (span in text.getSpans(0, text.length, Any::class.java)) {
+                val start = text.getSpanStart(span)
+                val end = text.getSpanEnd(span)
+                when (span) {
+                    is StyleSpan -> addStyleSpan(span, start, end)
+                    is URLSpan -> addUrlSpan(span, urlSpanColor, start, end)
+                    else -> addStyle(SpanStyle(), start, end)
                 }
             }
         }
     } else {
         AnnotatedString(text.toString())
     }
+
+private fun AnnotatedString.Builder.addStyleSpan(styleSpan: StyleSpan, start: Int, end: Int) {
+    when (styleSpan.style) {
+        Typeface.NORMAL -> addStyle(
+            SpanStyle(fontWeight = FontWeight.Normal, fontStyle = FontStyle.Normal),
+            start,
+            end,
+        )
+
+        Typeface.BOLD -> addStyle(
+            SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal),
+            start,
+            end,
+        )
+
+        Typeface.ITALIC -> addStyle(
+            SpanStyle(fontWeight = FontWeight.Normal, fontStyle = FontStyle.Italic),
+            start,
+            end,
+        )
+
+        Typeface.BOLD_ITALIC -> addStyle(
+            SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Italic),
+            start,
+            end,
+        )
+    }
+}
+
+private fun AnnotatedString.Builder.addUrlSpan(
+    urlSpan: URLSpan,
+    urlSpanColor: Color,
+    start: Int,
+    end: Int,
+) {
+    addStyle(
+        SpanStyle(color = urlSpanColor, textDecoration = TextDecoration.Underline),
+        start,
+        end,
+    )
+    if (!urlSpan.url.isNullOrEmpty()) {
+        addStringAnnotation(URL_SPAN_TAG, urlSpan.url, start, end)
+    }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
index 7cc9bf7..6a2163c 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/illustration/Illustration.kt
@@ -22,11 +22,11 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.widget.ui.ImageBox
 import com.android.settingslib.spa.widget.ui.Lottie
@@ -81,7 +81,7 @@
                 maxHeight = SettingsDimension.illustrationMaxHeight,
             )
             .clip(RoundedCornerShape(SettingsDimension.illustrationCornerRadius))
-            .background(color = MaterialTheme.colorScheme.surface)
+            .background(color = Color.Transparent)
 
         when (resourceType) {
             ResourceType.LOTTIE -> {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
new file mode 100644
index 0000000..82ac7e3
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/AnnotatedText.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.widget.ui
+
+import androidx.annotation.StringRes
+import androidx.compose.foundation.text.ClickableText
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalUriHandler
+import com.android.settingslib.spa.framework.util.URL_SPAN_TAG
+import com.android.settingslib.spa.framework.util.annotatedStringResource
+
+@Composable
+fun AnnotatedText(@StringRes id: Int) {
+    val uriHandler = LocalUriHandler.current
+    val annotatedString = annotatedStringResource(id)
+    ClickableText(
+        text = annotatedString,
+        style = MaterialTheme.typography.bodyMedium.copy(
+            color = MaterialTheme.colorScheme.onSurfaceVariant,
+        ),
+    ) { offset ->
+        // Gets the url at the clicked position.
+        annotatedString.getStringAnnotations(URL_SPAN_TAG, offset, offset)
+            .firstOrNull()
+            ?.let { uriHandler.openUri(it.item) }
+    }
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
index 915c6e2..5f7fe85 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
@@ -16,15 +16,24 @@
 
 package com.android.settingslib.spa.widget.ui
 
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import androidx.compose.foundation.isSystemInDarkTheme
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.res.colorResource
+import com.airbnb.lottie.LottieProperty
 import com.airbnb.lottie.compose.LottieAnimation
 import com.airbnb.lottie.compose.LottieCompositionSpec
 import com.airbnb.lottie.compose.LottieConstants
 import com.airbnb.lottie.compose.animateLottieCompositionAsState
 import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.airbnb.lottie.compose.rememberLottieDynamicProperty
+import com.android.settingslib.spa.R
 
 @Composable
 fun Lottie(
@@ -38,6 +47,34 @@
     }
 }
 
+object LottieColorUtils {
+    private val DARK_TO_LIGHT_THEME_COLOR_MAP = mapOf(
+        ".grey600" to R.color.settingslib_color_grey400,
+        ".grey800" to R.color.settingslib_color_grey300,
+        ".grey900" to R.color.settingslib_color_grey50,
+        ".red400" to R.color.settingslib_color_red600,
+        ".black" to android.R.color.white,
+        ".blue400" to R.color.settingslib_color_blue600,
+        ".green400" to R.color.settingslib_color_green600,
+        ".green200" to R.color.settingslib_color_green500,
+        ".red200" to R.color.settingslib_color_red500,
+    )
+
+    @Composable
+    private fun getDefaultPropertiesList() =
+        DARK_TO_LIGHT_THEME_COLOR_MAP.map { (key, colorRes) ->
+            val color = colorResource(colorRes).toArgb()
+            rememberLottieDynamicProperty(
+                property = LottieProperty.COLOR_FILTER,
+                keyPath = arrayOf("**", key, "**")
+            ){ PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP) }
+        }
+
+    @Composable
+    fun getDefaultDynamicProperties() =
+        rememberLottieDynamicProperties(*getDefaultPropertiesList().toTypedArray())
+}
+
 @Composable
 private fun BaseLottie(resId: Int) {
     val composition by rememberLottieComposition(
@@ -47,8 +84,10 @@
         composition,
         iterations = LottieConstants.IterateForever,
     )
+    val isLightMode = !isSystemInDarkTheme()
     LottieAnimation(
         composition = composition,
+        dynamicProperties = LottieColorUtils.getDefaultDynamicProperties().takeIf { isLightMode },
         progress = { progress },
     )
 }
diff --git a/packages/SettingsLib/Spa/tests/res/values/strings.xml b/packages/SettingsLib/Spa/tests/res/values/strings.xml
index cbfea06..fb8f878 100644
--- a/packages/SettingsLib/Spa/tests/res/values/strings.xml
+++ b/packages/SettingsLib/Spa/tests/res/values/strings.xml
@@ -26,5 +26,7 @@
         other {There are # songs found in {place}.}
     }</string>
 
-    <string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.google.com/">link</a>.</string>
+    <string name="test_annotated_string_resource">Annotated string with <b>bold</b> and <a href="https://www.android.com/">link</a>.</string>
+
+    <string name="test_link"><a href="https://www.android.com/">link</a></string>
 </resources>
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OverridableFlowTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OverridableFlowTest.kt
index c94572b..a47ccc1 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OverridableFlowTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/OverridableFlowTest.kt
@@ -18,7 +18,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.toList
@@ -28,7 +27,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class OverridableFlowTest {
 
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
index b65be42..9928355 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedStringResourceTest.kt
@@ -16,14 +16,14 @@
 
 package com.android.settingslib.spa.framework.util
 
-import androidx.compose.ui.graphics.Color
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.SpanStyle
 import androidx.compose.ui.text.font.FontStyle
 import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.style.TextDecoration
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.util.URLSPAN_TAG
-import com.android.settingslib.spa.framework.util.annotatedStringResource
 import com.android.settingslib.spa.test.R
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
@@ -38,24 +38,34 @@
     @Test
     fun testAnnotatedStringResource() {
         composeTestRule.setContent {
-            val annotatedString = annotatedStringResource(R.string.test_annotated_string_resource, Color.Blue)
+            val annotatedString =
+                annotatedStringResource(R.string.test_annotated_string_resource)
 
             val annotations = annotatedString.getStringAnnotations(0, annotatedString.length)
-            assertThat(annotations).hasSize(1)
-            assertThat(annotations[0].start).isEqualTo(31)
-            assertThat(annotations[0].end).isEqualTo(35)
-            assertThat(annotations[0].tag).isEqualTo(URLSPAN_TAG)
-            assertThat(annotations[0].item).isEqualTo("https://www.google.com/")
+            assertThat(annotations).containsExactly(
+                AnnotatedString.Range(
+                    item = "https://www.android.com/",
+                    start = 31,
+                    end = 35,
+                    tag = URL_SPAN_TAG,
+                )
+            )
 
-            assertThat(annotatedString.spanStyles).hasSize(2)
-            assertThat(annotatedString.spanStyles[0].start).isEqualTo(22)
-            assertThat(annotatedString.spanStyles[0].end).isEqualTo(26)
-            assertThat(annotatedString.spanStyles[0].item).isEqualTo(
-                    SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal))
-
-            assertThat(annotatedString.spanStyles[1].start).isEqualTo(31)
-            assertThat(annotatedString.spanStyles[1].end).isEqualTo(35)
-            assertThat(annotatedString.spanStyles[1].item).isEqualTo(SpanStyle(color = Color.Blue))
+            assertThat(annotatedString.spanStyles).containsExactly(
+                AnnotatedString.Range(
+                    item = SpanStyle(fontWeight = FontWeight.Bold, fontStyle = FontStyle.Normal),
+                    start = 22,
+                    end = 26,
+                ),
+                AnnotatedString.Range(
+                    item = SpanStyle(
+                        color = MaterialTheme.colorScheme.primary,
+                        textDecoration = TextDecoration.Underline,
+                    ),
+                    start = 31,
+                    end = 35,
+                ),
+            )
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt
new file mode 100644
index 0000000..2c218e3
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/AnnotatedTextTest.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.framework.util
+
+import android.content.Context
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalUriHandler
+import androidx.compose.ui.platform.UriHandler
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.test.R
+import com.android.settingslib.spa.widget.ui.AnnotatedText
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@RunWith(AndroidJUnit4::class)
+class AnnotatedTextTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var uriHandler: UriHandler
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun text_isDisplayed() {
+        composeTestRule.setContent {
+            AnnotatedText(R.string.test_annotated_string_resource)
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.test_annotated_string_resource))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun onUriClick_openUri() {
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalUriHandler provides uriHandler) {
+                AnnotatedText(R.string.test_link)
+            }
+        }
+
+        composeTestRule.onNodeWithText(context.getString(R.string.test_link)).performClick()
+
+        verify(uriHandler).openUri("https://www.android.com/")
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/CollectionsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/CollectionsTest.kt
index 62f4707..693cd77 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/CollectionsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/CollectionsTest.kt
@@ -18,12 +18,10 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class CollectionsTest {
     @Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt
index 4dcdea9..71d69b2 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/FlowsTest.kt
@@ -18,7 +18,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.count
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.first
@@ -28,7 +27,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class FlowsTest {
     @Test
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt
index e1d9a28..f0e57b9 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/StateFlowBridgeTest.kt
@@ -21,13 +21,11 @@
 import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class StateFlowBridgeTest {
     @get:Rule
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
index e2ff7b0..a370ebf 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt
@@ -22,7 +22,6 @@
 import android.os.UserManager
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
-import androidx.lifecycle.compose.ExperimentalLifecycleComposeApi
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.RestrictedLockUtils
 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin
@@ -90,7 +89,6 @@
         emit(getRestrictedMode())
     }.flowOn(Dispatchers.IO)
 
-    @OptIn(ExperimentalLifecycleComposeApi::class)
     @Composable
     override fun restrictedModeState() =
         restrictedMode.collectAsStateWithLifecycle(initialValue = null)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
index 21c9e34..945f2e2 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfoPage.kt
@@ -28,8 +28,7 @@
     title: String,
     packageName: String,
     userId: Int,
-    footerText: String,
-    footerContent: (@Composable () -> Unit)?,
+    footerContent: @Composable () -> Unit,
     packageManagers: IPackageManagers,
     content: @Composable PackageInfo.() -> Unit,
 ) {
@@ -41,10 +40,6 @@
 
         packageInfo.content()
 
-        if (footerContent != null) {
-            Footer(footerContent)
-        } else {
-            Footer(footerText)
-        }
+        Footer(footerContent)
     }
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
index 5fc1972..626c913 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppStorageSize.kt
@@ -22,25 +22,27 @@
 import android.util.Log
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
-import androidx.compose.runtime.produceState
+import androidx.compose.runtime.remember
 import androidx.compose.ui.platform.LocalContext
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.settingslib.spaprivileged.framework.common.storageStatsManager
 import com.android.settingslib.spaprivileged.framework.compose.placeholder
 import com.android.settingslib.spaprivileged.model.app.userHandle
 import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
 
 private const val TAG = "AppStorageSize"
 
 @Composable
 fun ApplicationInfo.getStorageSize(): State<String> {
     val context = LocalContext.current
-    return produceState(initialValue = placeholder()) {
-        withContext(Dispatchers.IO) {
+    return remember {
+        flow {
             val sizeBytes = calculateSizeBytes(context)
-            value = if (sizeBytes != null) Formatter.formatFileSize(context, sizeBytes) else ""
-        }
-    }
+            this.emit(if (sizeBytes != null) Formatter.formatFileSize(context, sizeBytes) else "")
+        }.flowOn(Dispatchers.IO)
+    }.collectAsStateWithLifecycle(initialValue = placeholder())
 }
 
 fun ApplicationInfo.calculateSizeBytes(context: Context): Long? {
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
index 7c689c6..7f82be4 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt
@@ -38,6 +38,7 @@
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
+import com.android.settingslib.spa.widget.ui.AnnotatedText
 import com.android.settingslib.spaprivileged.model.app.AppRecord
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.model.app.PackageManagers
@@ -140,8 +141,7 @@
         title = stringResource(pageTitleResId),
         packageName = packageName,
         userId = userId,
-        footerText = stringResource(footerResId),
-        footerContent = footerContent(),
+        footerContent = { AnnotatedText(footerResId) },
         packageManagers = packageManagers,
     ) {
         val model = createSwitchModel(applicationInfo)
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
index f4b3204..1ab6230 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt
@@ -20,7 +20,6 @@
 import android.content.pm.ApplicationInfo
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
-import androidx.compose.ui.text.AnnotatedString
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
 import com.android.settingslib.spa.framework.compose.rememberContext
@@ -37,10 +36,7 @@
     val footerResId: Int
     val switchRestrictionKeys: List<String>
         get() = emptyList()
-    @Composable
-    fun footerContent(): (@Composable () -> Unit)? {
-        return null
-    }
+
     /**
      * Loads the extra info for the App List, and generates the [AppRecord] List.
      *
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 375ed60..2281cd8 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
@@ -31,7 +31,6 @@
 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
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
@@ -50,7 +49,6 @@
 import org.mockito.junit.MockitoRule
 import org.mockito.Mockito.`when` as whenever
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class AppListRepositoryTest {
     @get:Rule
@@ -312,7 +310,12 @@
     fun getSystemPackageNames_returnExpectedValues() = runTest {
         mockInstalledApplications(
             apps = listOf(
-                NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
+                NORMAL_APP,
+                INSTANT_APP,
+                SYSTEM_APP,
+                UPDATED_SYSTEM_APP,
+                HOME_APP,
+                IN_LAUNCHER_APP,
             ),
             userId = ADMIN_USER_ID,
         )
@@ -329,7 +332,12 @@
     fun loadAndFilterApps_loadNonSystemApp_returnExpectedValues() = runTest {
         mockInstalledApplications(
             apps = listOf(
-                NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP
+                NORMAL_APP,
+                INSTANT_APP,
+                SYSTEM_APP,
+                UPDATED_SYSTEM_APP,
+                HOME_APP,
+                IN_LAUNCHER_APP,
             ),
             userId = ADMIN_USER_ID,
         )
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 9b22497..4d9d6da 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
@@ -25,7 +25,6 @@
 import com.android.settingslib.spa.testutils.waitUntil
 import com.android.settingslib.spaprivileged.template.app.AppListConfig
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flowOf
@@ -37,7 +36,6 @@
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class AppListViewModelTest {
     @get:Rule
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
index 6831e56..ab34f68 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppInfoTest.kt
@@ -23,10 +23,12 @@
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.waitUntilExists
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -51,7 +53,7 @@
             }
         }
 
-        composeTestRule.onNodeWithText(LABEL).assertIsDisplayed()
+        composeTestRule.waitUntilExists(hasText(LABEL))
     }
 
     @Test
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 241a134..124ced6 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
@@ -21,14 +21,16 @@
 import android.icu.text.CollationKey
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.unit.dp
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.compose.stateOf
 import com.android.settingslib.spa.widget.ui.SpinnerOption
 import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.model.app.AppEntry
@@ -42,6 +44,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
+@OptIn(ExperimentalTestApi::class)
 @RunWith(AndroidJUnit4::class)
 class AppListTest {
     @get:Rule
@@ -53,7 +56,10 @@
     fun whenHasOptions_firstOptionDisplayed() {
         setContent(options = listOf(OPTION_0, OPTION_1))
 
-        composeTestRule.onNodeWithText(OPTION_0).assertIsDisplayed()
+        composeTestRule.waitUntilExactlyOneExists(
+            matcher = hasText(OPTION_0),
+            timeoutMillis = 5_000,
+        )
         composeTestRule.onNodeWithText(OPTION_1).assertDoesNotExist()
     }
 
@@ -61,6 +67,10 @@
     fun whenHasOptions_couldSwitchOption() {
         setContent(options = listOf(OPTION_0, OPTION_1))
 
+        composeTestRule.waitUntilExactlyOneExists(
+            matcher = hasText(OPTION_0),
+            timeoutMillis = 5_000,
+        )
         composeTestRule.onNodeWithText(OPTION_0).performClick()
         composeTestRule.onNodeWithText(OPTION_1).performClick()
 
@@ -72,22 +82,30 @@
     fun whenNoApps() {
         setContent(appEntries = emptyList())
 
-        composeTestRule.onNodeWithText(context.getString(R.string.no_applications))
-            .assertIsDisplayed()
+        composeTestRule.waitUntilExactlyOneExists(
+            matcher = hasText(context.getString(R.string.no_applications)),
+            timeoutMillis = 5_000,
+        )
     }
 
     @Test
     fun couldShowAppItem() {
         setContent(appEntries = listOf(APP_ENTRY_A))
 
-        composeTestRule.onNodeWithText(APP_ENTRY_A.label).assertIsDisplayed()
+        composeTestRule.waitUntilExactlyOneExists(
+            matcher = hasText(APP_ENTRY_A.label),
+            timeoutMillis = 5_000,
+        )
     }
 
     @Test
     fun couldShowHeader() {
         setContent(appEntries = listOf(APP_ENTRY_A), header = { Text(HEADER) })
 
-        composeTestRule.onNodeWithText(HEADER).assertIsDisplayed()
+        composeTestRule.waitUntilExactlyOneExists(
+            matcher = hasText(HEADER),
+            timeoutMillis = 5_000,
+        )
     }
 
     @Test
@@ -102,7 +120,10 @@
     fun whenGrouped_groupTitleDisplayed() {
         setContent(appEntries = listOf(APP_ENTRY_A, APP_ENTRY_B), enableGrouping = true)
 
-        composeTestRule.onNodeWithText(GROUP_A).assertIsDisplayed()
+        composeTestRule.waitUntilExactlyOneExists(
+            matcher = hasText(GROUP_A),
+            timeoutMillis = 5_000,
+        )
         composeTestRule.onNodeWithText(GROUP_B).assertIsDisplayed()
     }
 
@@ -112,29 +133,26 @@
         header: @Composable () -> Unit = {},
         enableGrouping: Boolean = false,
     ) {
+        val appListInput = AppListInput(
+            config = AppListConfig(
+                userIds = listOf(USER_ID),
+                showInstantApps = false,
+                matchAnyUserForAdmin = false,
+            ),
+            listModel = TestAppListModel(enableGrouping = enableGrouping),
+            state = AppListState(showSystem = stateOf(false), searchQuery = stateOf("")),
+            header = header,
+            bottomPadding = 0.dp,
+        )
+        val listViewModel = object : IAppListViewModel<TestAppRecord> {
+            override val optionFlow = MutableStateFlow<Int?>(null)
+            override val spinnerOptionsFlow = flowOf(options.mapIndexed { index, option ->
+                SpinnerOption(id = index, text = option)
+            })
+            override val appListDataFlow = flowOf(AppListData(appEntries, option = 0))
+        }
         composeTestRule.setContent {
-            AppListInput(
-                config = AppListConfig(
-                    userIds = listOf(USER_ID),
-                    showInstantApps = false,
-                    matchAnyUserForAdmin = false,
-                ),
-                listModel = TestAppListModel(enableGrouping = enableGrouping),
-                state = AppListState(
-                    showSystem = false.toState(),
-                    searchQuery = "".toState(),
-                ),
-                header = header,
-                bottomPadding = 0.dp,
-            ).AppListImpl {
-                object : IAppListViewModel<TestAppRecord> {
-                    override val optionFlow: MutableStateFlow<Int?> = MutableStateFlow(null)
-                    override val spinnerOptionsFlow = flowOf(options.mapIndexed { index, option ->
-                        SpinnerOption(id = index, text = option)
-                    })
-                    override val appListDataFlow = flowOf(AppListData(appEntries, option = 0))
-                }
-            }
+            appListInput.AppListImpl { listViewModel }
         }
     }
 
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
index c54f4f8..f4faa0a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppListTest.kt
@@ -31,7 +31,6 @@
 import com.android.settingslib.spaprivileged.model.app.IPackageManagers
 import com.android.settingslib.spaprivileged.test.R
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -44,12 +43,11 @@
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.doNothing
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.Spy
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(AndroidJUnit4::class)
 class AppOpPermissionAppListTest {
     @get:Rule val mockito: MockitoRule = MockitoJUnit.rule()
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index dab3bcb..0acce03 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -225,9 +225,9 @@
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] -->
     <string name="bluetooth_profile_pan">Internet access</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] -->
-    <string name="bluetooth_profile_pbap">Contacts and call history sharing</string>
+    <string name="bluetooth_profile_pbap">Allow access to contacts and call history</string>
     <!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] -->
-    <string name="bluetooth_profile_pbap_summary">Use for contacts and call history sharing</string>
+    <string name="bluetooth_profile_pbap_summary">Info will be used for call announcements and more</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] -->
     <string name="bluetooth_profile_pan_nap">Internet connection sharing</string>
     <!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 1251b0d..9ab84d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -26,6 +26,7 @@
 import static android.os.BatteryManager.EXTRA_PLUGGED;
 import static android.os.BatteryManager.EXTRA_PRESENT;
 import static android.os.BatteryManager.EXTRA_STATUS;
+import static android.os.OsProtoEnums.BATTERY_PLUGGED_NONE;
 
 import android.content.Context;
 import android.content.Intent;
@@ -40,6 +41,8 @@
  */
 public class BatteryStatus {
     private static final int LOW_BATTERY_THRESHOLD = 20;
+    private static final int SEVERE_LOW_BATTERY_THRESHOLD = 10;
+    private static final int EXTREME_LOW_BATTERY_THRESHOLD = 3;
     private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
 
     public static final int CHARGING_UNKNOWN = -1;
@@ -90,21 +93,7 @@
         present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
         this.incompatibleCharger = incompatibleCharger;
 
-        final int maxChargingMicroAmp = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT,
-                -1);
-        int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
-
-        if (maxChargingMicroVolt <= 0) {
-            maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
-        }
-        if (maxChargingMicroAmp > 0) {
-            // Calculating muW = muA * muV / (10^6 mu^2 / mu); splitting up the divisor
-            // to maintain precision equally on both factors.
-            maxChargingWattage = (maxChargingMicroAmp / 1000)
-                    * (maxChargingMicroVolt / 1000);
-        } else {
-            maxChargingWattage = -1;
-        }
+        maxChargingWattage = calculateMaxChargingMicroWatt(batteryChangedIntent);
     }
 
     /** Determine whether the device is plugged. */
@@ -126,7 +115,7 @@
 
     /** Determine whether the device is plugged in dock. */
     public boolean isPluggedInDock() {
-        return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+        return isPluggedInDock(plugged);
     }
 
     /**
@@ -140,15 +129,15 @@
 
     /** Whether battery is low and needs to be charged. */
     public boolean isBatteryLow() {
-        return level < LOW_BATTERY_THRESHOLD;
+        return isLowBattery(level);
     }
 
     /** Whether battery defender is enabled. */
     public boolean isBatteryDefender() {
-        return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+        return isBatteryDefender(chargingStatus);
     }
 
-    /** Return current chargin speed is fast, slow or normal. */
+    /** Return current charging speed is fast, slow or normal. */
     public final int getChargingSpeed(Context context) {
         final int slowThreshold = context.getResources().getInteger(
                 R.integer.config_chargingSlowlyThreshold);
@@ -218,4 +207,126 @@
                 || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
                 || plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
     }
+
+    /** Determine whether the device is plugged in dock. */
+    public static boolean isPluggedInDock(Intent batteryChangedIntent) {
+        return isPluggedInDock(
+                batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, BATTERY_PLUGGED_NONE));
+    }
+
+    /** Determine whether the device is plugged in dock. */
+    public static boolean isPluggedInDock(int plugged) {
+        return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
+    }
+
+    /**
+     * Whether the battery is low or not.
+     *
+     * @param batteryChangedIntent the {@link ACTION_BATTERY_CHANGED} intent
+     * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isLowBattery(Intent batteryChangedIntent) {
+        int level = getBatteryLevel(batteryChangedIntent);
+        return isLowBattery(level);
+    }
+
+    /**
+     * Whether the battery is low or not.
+     *
+     * @param batteryLevel the battery level
+     * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isLowBattery(int batteryLevel) {
+        return batteryLevel <= LOW_BATTERY_THRESHOLD;
+    }
+
+    /**
+     * Whether the battery is severe low or not.
+     *
+     * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+     * @return {@code true} if the battery level is less or equal to {@link
+     *     SEVERE_LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isSevereLowBattery(Intent batteryChangedIntent) {
+        int level = getBatteryLevel(batteryChangedIntent);
+        return level <= SEVERE_LOW_BATTERY_THRESHOLD;
+    }
+
+    /**
+     * Whether the battery is extreme low or not.
+     *
+     * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+     * @return {@code true} if the battery level is less or equal to {@link
+     *     EXTREME_LOW_BATTERY_THRESHOLD}
+     */
+    public static boolean isExtremeLowBattery(Intent batteryChangedIntent) {
+        int level = getBatteryLevel(batteryChangedIntent);
+        return level <= EXTREME_LOW_BATTERY_THRESHOLD;
+    }
+
+    /**
+     * Whether the battery defender is enabled or not.
+     *
+     * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
+     * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
+     *     defend, or temp defend
+     */
+    public static boolean isBatteryDefender(Intent batteryChangedIntent) {
+        int chargingStatus =
+                batteryChangedIntent.getIntExtra(EXTRA_CHARGING_STATUS, CHARGING_POLICY_DEFAULT);
+        return isBatteryDefender(chargingStatus);
+    }
+
+    /**
+     * Whether the battery defender is enabled or not.
+     *
+     * @param chargingStatus for {@link EXTRA_CHARGING_STATUS} field in the ACTION_BATTERY_CHANGED
+     *     intent
+     * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
+     *     defend, or temp defend
+     */
+    public static boolean isBatteryDefender(int chargingStatus) {
+        return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
+    }
+
+    /**
+     * Gets the max charging current and max charging voltage form {@link
+     * Intent.ACTION_BATTERY_CHANGED} and calculates the charging speed based on the {@link
+     * R.integer.config_chargingSlowlyThreshold} and {@link R.integer.config_chargingFastThreshold}.
+     *
+     * @param context the application context
+     * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED}
+     * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link
+     *     CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN}
+     */
+    public static int getChargingSpeed(Context context, Intent batteryChangedIntent) {
+        final int maxChargingMicroWatt = calculateMaxChargingMicroWatt(batteryChangedIntent);
+        if (maxChargingMicroWatt <= 0) {
+            return CHARGING_UNKNOWN;
+        } else if (maxChargingMicroWatt
+                < context.getResources().getInteger(R.integer.config_chargingSlowlyThreshold)) {
+            return CHARGING_SLOWLY;
+        } else if (maxChargingMicroWatt
+                > context.getResources().getInteger(R.integer.config_chargingFastThreshold)) {
+            return CHARGING_FAST;
+        } else {
+            return CHARGING_REGULAR;
+        }
+    }
+
+    private static int calculateMaxChargingMicroWatt(Intent batteryChangedIntent) {
+        final int maxChargingMicroAmp =
+                batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
+        int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
+        if (maxChargingMicroVolt <= 0) {
+            maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
+        }
+
+        if (maxChargingMicroAmp > 0) {
+            // Calculating µW = mA * mV
+            return (int) Math.round(maxChargingMicroAmp * 0.001 * maxChargingMicroVolt * 0.001);
+        } else {
+            return -1;
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
index a03acc3..73f6db6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/ThemedBatteryDrawable.kt
@@ -325,7 +325,7 @@
         return batteryLevel
     }
 
-    override fun onBoundsChange(bounds: Rect?) {
+    override fun onBoundsChange(bounds: Rect) {
         super.onBoundsChange(bounds)
         updateSize()
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/dpp/WifiDppIntentHelper.java b/packages/SettingsLib/src/com/android/settingslib/wifi/dpp/WifiDppIntentHelper.java
new file mode 100644
index 0000000..1134d13
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/dpp/WifiDppIntentHelper.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.wifi.dpp;
+
+import android.content.Intent;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+import android.text.TextUtils;
+
+import java.util.List;
+
+
+/**
+ * Wifi dpp intent helper functions to share between the Settings App and SystemUI.
+ */
+public class WifiDppIntentHelper {
+    static final String EXTRA_WIFI_SECURITY = "security";
+
+    /** The data corresponding to {@code WifiConfiguration} SSID */
+    static final String EXTRA_WIFI_SSID = "ssid";
+
+    /** The data corresponding to {@code WifiConfiguration} preSharedKey */
+    static final String EXTRA_WIFI_PRE_SHARED_KEY = "preSharedKey";
+
+    /** The data corresponding to {@code WifiConfiguration} hiddenSSID */
+    static final String EXTRA_WIFI_HIDDEN_SSID = "hiddenSsid";
+    static final String SECURITY_NO_PASSWORD = "nopass"; //open network or OWE
+    static final String SECURITY_WEP = "WEP";
+    static final String SECURITY_WPA_PSK = "WPA";
+    static final String SECURITY_SAE = "SAE";
+
+    /**
+     * Set all extra except {@code EXTRA_WIFI_NETWORK_ID} for the intent to
+     * launch configurator activity later.
+     *
+     * @param intent the target to set extra
+     * @param wifiManager an instance of {@code WifiManager}
+     * @param wifiConfiguration the Wi-Fi network for launching configurator activity
+     */
+    public static void setConfiguratorIntentExtra(Intent intent, WifiManager wifiManager,
+            WifiConfiguration wifiConfiguration) {
+        String ssid = removeFirstAndLastDoubleQuotes(wifiConfiguration.SSID);
+        String security = getSecurityString(wifiConfiguration);
+
+        // When the value of this key is read, the actual key is not returned, just a "*".
+        // Call privileged system API to obtain actual key.
+        String preSharedKey = removeFirstAndLastDoubleQuotes(getPresharedKey(wifiManager,
+                wifiConfiguration));
+
+        if (!TextUtils.isEmpty(ssid)) {
+            intent.putExtra(EXTRA_WIFI_SSID, ssid);
+        }
+        if (!TextUtils.isEmpty(security)) {
+            intent.putExtra(EXTRA_WIFI_SECURITY, security);
+        }
+        if (!TextUtils.isEmpty(preSharedKey)) {
+            intent.putExtra(EXTRA_WIFI_PRE_SHARED_KEY, preSharedKey);
+        }
+        intent.putExtra(EXTRA_WIFI_HIDDEN_SSID, wifiConfiguration.hiddenSSID);
+    }
+
+    private static String getSecurityString(WifiConfiguration config) {
+        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
+            return SECURITY_SAE;
+        }
+        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
+            return SECURITY_NO_PASSWORD;
+        }
+        if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)
+                || config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA2_PSK)) {
+            return SECURITY_WPA_PSK;
+        }
+        return (config.wepKeys[0] == null) ? SECURITY_NO_PASSWORD : SECURITY_WEP;
+    }
+
+    private static String removeFirstAndLastDoubleQuotes(String str) {
+        if (TextUtils.isEmpty(str)) {
+            return str;
+        }
+
+        int begin = 0;
+        int end = str.length() - 1;
+        if (str.charAt(begin) == '\"') {
+            begin++;
+        }
+        if (str.charAt(end) == '\"') {
+            end--;
+        }
+        return str.substring(begin, end + 1);
+    }
+
+    private static String getPresharedKey(WifiManager wifiManager,
+            WifiConfiguration wifiConfiguration) {
+        List<WifiConfiguration> privilegedWifiConfigurations =
+                wifiManager.getPrivilegedConfiguredNetworks();
+
+        for (WifiConfiguration privilegedWifiConfiguration : privilegedWifiConfigurations) {
+            if (privilegedWifiConfiguration.networkId == wifiConfiguration.networkId) {
+                // WEP uses a shared key hence the AuthAlgorithm.SHARED is used
+                // to identify it.
+                if (wifiConfiguration.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)
+                        && wifiConfiguration.allowedAuthAlgorithms.get(
+                        WifiConfiguration.AuthAlgorithm.SHARED)) {
+                    return privilegedWifiConfiguration
+                            .wepKeys[privilegedWifiConfiguration.wepTxKeyIndex];
+                } else {
+                    return privilegedWifiConfiguration.preSharedKey;
+                }
+            }
+        }
+        return wifiConfiguration.preSharedKey;
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/dpp/WifiDppIntentHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/dpp/WifiDppIntentHelperTest.java
new file mode 100644
index 0000000..d73df2d
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/dpp/WifiDppIntentHelperTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.wifi.dpp;
+
+import static com.android.settingslib.wifi.dpp.WifiDppIntentHelper.EXTRA_WIFI_HIDDEN_SSID;
+import static com.android.settingslib.wifi.dpp.WifiDppIntentHelper.EXTRA_WIFI_PRE_SHARED_KEY;
+import static com.android.settingslib.wifi.dpp.WifiDppIntentHelper.EXTRA_WIFI_SECURITY;
+import static com.android.settingslib.wifi.dpp.WifiDppIntentHelper.EXTRA_WIFI_SSID;
+import static com.android.settingslib.wifi.dpp.WifiDppIntentHelper.SECURITY_NO_PASSWORD;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import android.content.Intent;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+
+@RunWith(RobolectricTestRunner.class)
+public class WifiDppIntentHelperTest {
+    @Mock
+    private WifiManager mWifiManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mWifiManager.getPrivilegedConfiguredNetworks()).thenReturn(new ArrayList<>());
+    }
+
+    @Test
+    public void setConfiguratorIntentExtra_returnsCorrectValues() {
+        WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.SSID = EXTRA_WIFI_SSID;
+        wifiConfiguration.preSharedKey = EXTRA_WIFI_PRE_SHARED_KEY;
+        wifiConfiguration.hiddenSSID = true;
+
+        Intent expected = new Intent();
+        WifiDppIntentHelper.setConfiguratorIntentExtra(expected, mWifiManager, wifiConfiguration);
+
+        assertThat(expected.getStringExtra(EXTRA_WIFI_SSID)).isEqualTo(EXTRA_WIFI_SSID);
+        assertThat(expected.getStringExtra(EXTRA_WIFI_SECURITY)).isEqualTo(SECURITY_NO_PASSWORD);
+        assertThat(expected.getStringExtra(EXTRA_WIFI_PRE_SHARED_KEY)).isEqualTo(
+                EXTRA_WIFI_PRE_SHARED_KEY);
+        assertThat(expected.getBooleanExtra(EXTRA_WIFI_HIDDEN_SSID, false))
+                .isEqualTo(true);
+    }
+}
diff --git a/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
new file mode 100644
index 0000000..6c0c1a7
--- /dev/null
+++ b/packages/SettingsLib/tests/unit/src/com/android/settingslib/fuelgague/BatteryStatusTest.kt
@@ -0,0 +1,330 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.fuelgague
+
+import android.content.Context
+import android.content.Intent
+import android.os.BatteryManager
+import android.os.BatteryManager.BATTERY_PLUGGED_AC
+import android.os.BatteryManager.BATTERY_PLUGGED_DOCK
+import android.os.BatteryManager.BATTERY_PLUGGED_USB
+import android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS
+import android.os.BatteryManager.BATTERY_STATUS_FULL
+import android.os.BatteryManager.BATTERY_STATUS_UNKNOWN
+import android.os.BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE
+import android.os.BatteryManager.CHARGING_POLICY_DEFAULT
+import android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT
+import android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE
+import android.os.OsProtoEnums.BATTERY_PLUGGED_NONE
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.fuelgauge.BatteryStatus
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_FAST
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_REGULAR
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_SLOWLY
+import com.android.settingslib.fuelgauge.BatteryStatus.CHARGING_UNKNOWN
+import com.android.settingslib.fuelgauge.BatteryStatus.isBatteryDefender
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import java.util.Optional
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.junit.runners.Suite
+import org.junit.runners.Suite.SuiteClasses
+
+@RunWith(Suite::class)
+@SuiteClasses(
+    BatteryStatusTest.NonParameterizedTest::class,
+    BatteryStatusTest.IsPluggedInTest::class,
+    BatteryStatusTest.IsChargedTest::class,
+    BatteryStatusTest.GetChargingSpeedTest::class,
+    BatteryStatusTest.IsPluggedInDockTest::class,
+)
+open class BatteryStatusTest {
+
+    @RunWith(AndroidJUnit4::class)
+    class NonParameterizedTest : BatteryStatusTest() {
+        @Test
+        fun isLowBattery_20Percent_returnsTrue() {
+            val level = 20
+            val intent = createIntent(batteryLevel = level)
+
+            assertWithMessage("failed by isLowBattery(Intent), level=$level")
+                .that(BatteryStatus.isLowBattery(intent))
+                .isTrue()
+            assertWithMessage("failed by isLowBattery($level)")
+                .that(BatteryStatus.isLowBattery(level))
+                .isTrue()
+        }
+
+        @Test
+        fun isLowBattery_21Percent_returnsFalse() {
+            val level = 21
+            val intent = createIntent(batteryLevel = level)
+
+            assertWithMessage("failed by isLowBattery(intent), level=$level")
+                .that(BatteryStatus.isLowBattery(intent))
+                .isFalse()
+            assertWithMessage("failed by isLowBattery($level)")
+                .that(BatteryStatus.isLowBattery(intent))
+                .isFalse()
+        }
+
+        @Test
+        fun isSevereLowBattery_10Percent_returnsTrue() {
+            val batteryChangedIntent = createIntent(batteryLevel = 10)
+
+            assertThat(BatteryStatus.isSevereLowBattery(batteryChangedIntent)).isTrue()
+        }
+
+        @Test
+        fun isSevereLowBattery_11Percent_returnFalse() {
+            val batteryChangedIntent = createIntent(batteryLevel = 11)
+
+            assertThat(BatteryStatus.isSevereLowBattery(batteryChangedIntent)).isFalse()
+        }
+
+        @Test
+        fun isExtremeLowBattery_3Percent_returnsTrue() {
+            val batteryChangedIntent = createIntent(batteryLevel = 3)
+
+            assertThat(BatteryStatus.isExtremeLowBattery(batteryChangedIntent)).isTrue()
+        }
+
+        @Test
+        fun isExtremeLowBattery_4Percent_returnsFalse() {
+            val batteryChangedIntent = createIntent(batteryLevel = 4)
+
+            assertThat(BatteryStatus.isExtremeLowBattery(batteryChangedIntent)).isFalse()
+        }
+
+        @Test
+        fun isBatteryDefender_chargingLongLife_returnsTrue() {
+            val chargingStatus = CHARGING_POLICY_ADAPTIVE_LONGLIFE
+            val batteryChangedIntent = createIntent(chargingStatus = chargingStatus)
+
+            assertIsBatteryDefender(chargingStatus, batteryChangedIntent).isTrue()
+        }
+
+        @Test
+        fun isBatteryDefender_nonChargingLongLife_returnsFalse() {
+            val chargingStatus = CHARGING_POLICY_DEFAULT
+            val batteryChangedIntent = createIntent(chargingStatus = chargingStatus)
+
+            assertIsBatteryDefender(chargingStatus, batteryChangedIntent).isFalse()
+        }
+
+        private fun assertIsBatteryDefender(chargingStatus: Int, batteryChangedIntent: Intent) =
+            object {
+                val assertions =
+                    listOf(
+                        "failed by isBatteryDefender(Intent), chargingStatus=$chargingStatus".let {
+                            assertWithMessage(it).that(isBatteryDefender(batteryChangedIntent))
+                        },
+                        "failed by isBatteryDefender($chargingStatus)".let {
+                            assertWithMessage(it).that(isBatteryDefender(chargingStatus))
+                        },
+                    )
+
+                fun isTrue() = assertions.forEach { it.isTrue() }
+
+                fun isFalse() = assertions.forEach { it.isFalse() }
+            }
+    }
+
+    @RunWith(Parameterized::class)
+    class IsPluggedInTest(
+        private val name: String,
+        private val plugged: Int,
+        val expected: Boolean
+    ) : BatteryStatusTest() {
+
+        @Test
+        fun isPluggedIn_() {
+            val batteryChangedIntent = createIntent(plugged = plugged)
+
+            assertWithMessage("failed by isPluggedIn(plugged=$plugged)")
+                .that(BatteryStatus.isPluggedIn(plugged))
+                .isEqualTo(expected)
+            assertWithMessage("failed by isPlugged(Intent), which plugged=$plugged")
+                .that(BatteryStatus.isPluggedIn(batteryChangedIntent))
+                .isEqualTo(expected)
+        }
+
+        companion object {
+            @Parameterized.Parameters(name = "{0}")
+            @JvmStatic
+            fun parameters() =
+                arrayListOf(
+                    arrayOf("withAC_returnsTrue", BATTERY_PLUGGED_AC, true),
+                    arrayOf("withDock_returnsTrue", BATTERY_PLUGGED_DOCK, true),
+                    arrayOf("withUSB_returnsTrue", BATTERY_PLUGGED_USB, true),
+                    arrayOf("withWireless_returnsTrue", BATTERY_PLUGGED_WIRELESS, true),
+                    arrayOf("pluggedNone_returnsTrue", BATTERY_PLUGGED_NONE, false),
+                )
+        }
+    }
+
+    @RunWith(Parameterized::class)
+    class IsPluggedInDockTest(
+        private val name: String,
+        private val plugged: Int,
+        val expected: Boolean
+    ) : BatteryStatusTest() {
+
+        @Test
+        fun isPluggedDockIn_() {
+            val batteryChangedIntent = createIntent(plugged = plugged)
+
+            assertWithMessage("failed by isPluggedInDock(plugged=$plugged)")
+                .that(BatteryStatus.isPluggedInDock(plugged))
+                .isEqualTo(expected)
+            assertWithMessage("failed by isPluggedInDock(Intent), which plugged=$plugged")
+                .that(BatteryStatus.isPluggedInDock(batteryChangedIntent))
+                .isEqualTo(expected)
+        }
+
+        companion object {
+            @Parameterized.Parameters(name = "{0}")
+            @JvmStatic
+            fun parameters() =
+                arrayListOf(
+                    arrayOf("withAC_returnsTrue", BATTERY_PLUGGED_AC, false),
+                    arrayOf("withDock_returnsTrue", BATTERY_PLUGGED_DOCK, true),
+                    arrayOf("withUSB_returnsTrue", BATTERY_PLUGGED_USB, false),
+                    arrayOf("withWireless_returnsTrue", BATTERY_PLUGGED_WIRELESS, false),
+                    arrayOf("pluggedNone_returnsTrue", BATTERY_PLUGGED_NONE, false),
+                )
+        }
+    }
+
+    @RunWith(Parameterized::class)
+    class IsChargedTest(
+        private val status: Int,
+        private val batteryLevel: Int,
+        private val expected: Boolean
+    ) : BatteryStatusTest() {
+
+        @Test
+        fun isCharged_() {
+            val batteryChangedIntent = createIntent(batteryLevel = batteryLevel, status = status)
+
+            assertWithMessage(
+                    "failed by isCharged(Intent), status=$status, batteryLevel=$batteryLevel"
+                )
+                .that(BatteryStatus.isCharged(batteryChangedIntent))
+                .isEqualTo(expected)
+            assertWithMessage("failed by isCharged($status, $batteryLevel)")
+                .that(BatteryStatus.isCharged(status, batteryLevel))
+                .isEqualTo(expected)
+        }
+
+        companion object {
+            @Parameterized.Parameters(name = "status{0}_level{1}_returns-{2}")
+            @JvmStatic
+            fun parameters() =
+                arrayListOf(
+                    arrayOf(BATTERY_STATUS_FULL, 99, true),
+                    arrayOf(BATTERY_STATUS_UNKNOWN, 100, true),
+                    arrayOf(BATTERY_STATUS_FULL, 100, true),
+                    arrayOf(BATTERY_STATUS_UNKNOWN, 99, false),
+                )
+        }
+    }
+
+    @RunWith(Parameterized::class)
+    class GetChargingSpeedTest(
+        private val name: String,
+        private val maxChargingCurrent: Optional<Int>,
+        private val maxChargingVoltage: Optional<Int>,
+        private val expectedChargingSpeed: Int,
+    ) {
+
+        val context: Context = ApplicationProvider.getApplicationContext()
+
+        @Test
+        fun getChargingSpeed_() {
+            val batteryChangedIntent =
+                Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+                    maxChargingCurrent.ifPresent { putExtra(EXTRA_MAX_CHARGING_CURRENT, it) }
+                    maxChargingVoltage.ifPresent { putExtra(EXTRA_MAX_CHARGING_VOLTAGE, it) }
+                }
+
+            assertThat(BatteryStatus.getChargingSpeed(context, batteryChangedIntent))
+                .isEqualTo(expectedChargingSpeed)
+        }
+
+        companion object {
+            @Parameterized.Parameters(name = "{0}")
+            @JvmStatic
+            fun parameters() =
+                arrayListOf(
+                    arrayOf(
+                        "maxCurrent=n/a, maxVoltage=n/a -> UNKNOWN",
+                        Optional.empty<Int>(),
+                        Optional.empty<Int>(),
+                        CHARGING_UNKNOWN
+                    ),
+                    arrayOf(
+                        "maxCurrent=0, maxVoltage=9000000 -> UNKNOWN",
+                        Optional.of(0),
+                        Optional.of(0),
+                        CHARGING_UNKNOWN
+                    ),
+                    arrayOf(
+                        "maxCurrent=1500000, maxVoltage=5000000 -> CHARGING_REGULAR",
+                        Optional.of(1500000),
+                        Optional.of(5000000),
+                        CHARGING_REGULAR
+                    ),
+                    arrayOf(
+                        "maxCurrent=1000000, maxVoltage=5000000 -> CHARGING_REGULAR",
+                        Optional.of(1000000),
+                        Optional.of(5000000),
+                        CHARGING_REGULAR
+                    ),
+                    arrayOf(
+                        "maxCurrent=1500001, maxVoltage=5000000 -> CHARGING_FAST",
+                        Optional.of(1501000),
+                        Optional.of(5000000),
+                        CHARGING_FAST
+                    ),
+                    arrayOf(
+                        "maxCurrent=999999, maxVoltage=5000000 -> CHARGING_SLOWLY",
+                        Optional.of(999999),
+                        Optional.of(5000000),
+                        CHARGING_SLOWLY
+                    ),
+                )
+        }
+    }
+
+    protected fun createIntent(
+        batteryLevel: Int = 50,
+        chargingStatus: Int = CHARGING_POLICY_DEFAULT,
+        plugged: Int = BATTERY_PLUGGED_NONE,
+        status: Int = BatteryManager.BATTERY_STATUS_CHARGING,
+    ): Intent =
+        Intent(Intent.ACTION_BATTERY_CHANGED).apply {
+            putExtra(BatteryManager.EXTRA_STATUS, status)
+            putExtra(BatteryManager.EXTRA_LEVEL, batteryLevel)
+            putExtra(BatteryManager.EXTRA_SCALE, 100)
+            putExtra(BatteryManager.EXTRA_CHARGING_STATUS, chargingStatus)
+            putExtra(BatteryManager.EXTRA_PLUGGED, plugged)
+        }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 46e73d0..58106c0 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -442,5 +442,6 @@
                         }));
         VALIDATORS.put(Global.Wearable.PHONE_SWITCHING_SUPPORTED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Global.Wearable.WEAR_LAUNCHER_UI_MODE, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index f1efe9e..bbfdc38 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -385,5 +385,6 @@
         VALIDATORS.put(Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
                 BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.DND_CONFIGS_MIGRATED, BOOLEAN_VALIDATOR);
     }
 }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 9da1ab8..27a45df 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -44,6 +44,7 @@
 import com.android.internal.app.LocalePicker;
 import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
 
+import java.io.FileNotFoundException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Locale;
@@ -332,21 +333,30 @@
      * @param value can be a canonicalized uri or "_silent" to indicate a silent (null) ringtone.
      */
     private void setRingtone(String name, String value) {
-        // If it's null, don't change the default
-        if (value == null) return;
-        final Uri ringtoneUri;
-        if (SILENT_RINGTONE.equals(value)) {
-            ringtoneUri = null;
-        } else {
-            Uri canonicalUri = Uri.parse(value);
-            ringtoneUri = mContext.getContentResolver().uncanonicalize(canonicalUri);
-            if (ringtoneUri == null) {
-                // Unrecognized or invalid Uri, don't restore
-                return;
-            }
-        }
-        final int ringtoneType = getRingtoneType(name);
+        Log.v(TAG, "Set ringtone for name: " + name + " value: " + value);
 
+        // If it's null, don't change the default.
+        if (value == null) return;
+        final int ringtoneType = getRingtoneType(name);
+        if (SILENT_RINGTONE.equals(value)) {
+            // SILENT_RINGTONE is a special constant generated by onBackupValue in the source
+            // device.
+            RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, null);
+            return;
+        }
+
+        Uri ringtoneUri = null;
+        try {
+            ringtoneUri =
+                    RingtoneManager.getRingtoneUriForRestore(
+                            mContext.getContentResolver(), value, ringtoneType);
+        } catch (FileNotFoundException | IllegalArgumentException e) {
+            Log.w(TAG, "Failed to resolve " + value + ": " + e);
+            // Unrecognized or invalid Uri, don't restore
+            return;
+        }
+
+        Log.v(TAG, "setActualDefaultRingtoneUri type: " + ringtoneType + ", uri: " + ringtoneUri);
         RingtoneManager.setActualDefaultRingtoneUri(mContext, ringtoneType, ringtoneUri);
     }
 
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 9c9dd8a..5475fad 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -689,7 +689,8 @@
                     Settings.Global.Wearable.TETHER_CONFIG_STATE,
                     Settings.Global.Wearable.PHONE_SWITCHING_SUPPORTED,
                     Settings.Global.Wearable.WEAR_MEDIA_CONTROLS_PACKAGE,
-                    Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE);
+                    Settings.Global.Wearable.WEAR_MEDIA_SESSIONS_PACKAGE,
+                    Settings.Global.Wearable.WEAR_POWER_ANOMALY_SERVICE_ENABLED);
 
     private static final Set<String> BACKUP_DENY_LIST_SECURE_SETTINGS =
              newHashSet(
@@ -857,7 +858,8 @@
                  Settings.Secure.UI_TRANSLATION_ENABLED,
                  Settings.Secure.CREDENTIAL_SERVICE,
                  Settings.Secure.CREDENTIAL_SERVICE_PRIMARY,
-                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED);
+                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_EDGE_HAPTIC_ENABLED,
+                 Settings.Secure.DND_CONFIGS_MIGRATED);
 
     @Test
     public void systemSettingsBackedUpOrDenied() {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index bc81c44..ef062df 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -26,15 +26,24 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.MatrixCursor;
 import android.media.AudioManager;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.LocaleList;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
+import android.test.mock.MockContentProvider;
+import android.test.mock.MockContentResolver;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -57,6 +66,13 @@
     private static final String SETTING_VALUE = "setting_value";
     private static final String SETTING_REAL_VALUE = "setting_real_value";
 
+    private static final String DEFAULT_RINGTONE_VALUE =
+            "content://media/internal/audio/media/10?title=DefaultRingtone&canonical=1";
+    private static final String DEFAULT_NOTIFICATION_VALUE =
+            "content://media/internal/audio/media/20?title=DefaultNotification&canonical=1";
+    private static final String DEFAULT_ALARM_VALUE =
+            "content://media/internal/audio/media/30?title=DefaultAlarm&canonical=1";
+
     private SettingsHelper mSettingsHelper;
 
     @Mock private Context mContext;
@@ -74,6 +90,7 @@
                 mTelephonyManager);
         when(mContext.getResources()).thenReturn(mResources);
         when(mContext.getApplicationContext()).thenReturn(mContext);
+        when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
         when(mContext.getContentResolver()).thenReturn(getContentResolver());
 
         mSettingsHelper = spy(new SettingsHelper(mContext));
@@ -338,6 +355,377 @@
     }
 
     @Test
+    public void testRestoreValue_customRingtone_regularUncanonicalize_Success() {
+        final String sourceRingtoneValue =
+                "content://media/internal/audio/media/1?title=Song&canonical=1";
+        final String newRingtoneValueUncanonicalized =
+                "content://media/internal/audio/media/100";
+        final String newRingtoneValueCanonicalized =
+                "content://media/internal/audio/media/100?title=Song&canonical=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(sourceRingtoneValue));
+                        return Uri.parse(newRingtoneValueUncanonicalized);
+                    }
+
+                    @Override
+                    public Uri canonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+                        return Uri.parse(newRingtoneValueCanonicalized);
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(DEFAULT_RINGTONE_VALUE);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.RINGTONE,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(newRingtoneValueCanonicalized);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_useCustomLookup_success() {
+        final String sourceRingtoneValue =
+                "content://0@media/external/audio/media/1?title=Song&canonical=1";
+        final String newRingtoneValueUncanonicalized =
+                "content://0@media/external/audio/media/100";
+        final String newRingtoneValueCanonicalized =
+                "content://0@media/external/audio/media/100?title=Song&canonical=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+        cursor.addRow(new Object[] {100L});
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public Uri canonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+                        return Uri.parse(newRingtoneValueCanonicalized);
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+
+                    @Override
+                    public Cursor query(
+                            Uri uri,
+                            String[] projection,
+                            String selection,
+                            String[] selectionArgs,
+                            String sortOrder) {
+                        assertThat(uri)
+                                .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+                        assertThat(projection).isEqualTo(new String[] {"_id"});
+                        assertThat(selection).isEqualTo("is_ringtone=1 AND title=?");
+                        assertThat(selectionArgs).isEqualTo(new String[] {"Song"});
+                        return cursor;
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.RINGTONE,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(newRingtoneValueCanonicalized);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_notificationSound_useCustomLookup_success() {
+        final String sourceRingtoneValue =
+                "content://0@media/external/audio/media/2?title=notificationPing&canonical=1";
+        final String newRingtoneValueUncanonicalized =
+                "content://0@media/external/audio/media/200";
+        final String newRingtoneValueCanonicalized =
+                "content://0@media/external/audio/media/200?title=notificationPing&canonicalize=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+        cursor.addRow(new Object[] {200L});
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public Uri canonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+                        return Uri.parse(newRingtoneValueCanonicalized);
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+
+                    @Override
+                    public Cursor query(
+                            Uri uri,
+                            String[] projection,
+                            String selection,
+                            String[] selectionArgs,
+                            String sortOrder) {
+                        assertThat(uri)
+                                .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+                        assertThat(projection).isEqualTo(new String[] {"_id"});
+                        assertThat(selection).isEqualTo("is_notification=1 AND title=?");
+                        assertThat(selectionArgs).isEqualTo(new String[] {"notificationPing"});
+                        return cursor;
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.NOTIFICATION_SOUND,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(
+                        Settings.System.getString(
+                                mMockContentResolver, Settings.System.NOTIFICATION_SOUND))
+                .isEqualTo(newRingtoneValueCanonicalized);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_alarmSound_useCustomLookup_success() {
+        final String sourceRingtoneValue =
+                "content://0@media/external/audio/media/3?title=alarmSound&canonical=1";
+        final String newRingtoneValueUncanonicalized =
+                "content://0@media/external/audio/media/300";
+        final String newRingtoneValueCanonicalized =
+                "content://0@media/external/audio/media/300?title=alarmSound&canonical=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+        cursor.addRow(new Object[] {300L});
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public Uri canonicalize(Uri url) {
+                        assertThat(url).isEqualTo(Uri.parse(newRingtoneValueUncanonicalized));
+                        return Uri.parse(newRingtoneValueCanonicalized);
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+
+                    @Override
+                    public Cursor query(
+                            Uri uri,
+                            String[] projection,
+                            String selection,
+                            String[] selectionArgs,
+                            String sortOrder) {
+                        assertThat(uri)
+                                .isEqualTo(Uri.parse("content://0@media/external/audio/media"));
+                        assertThat(projection).isEqualTo(new String[] {"_id"});
+                        assertThat(selection).isEqualTo("is_alarm=1 AND title=?");
+                        assertThat(selectionArgs).isEqualTo(new String[] {"alarmSound"});
+                        return cursor;
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.ALARM_ALERT,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.ALARM_ALERT))
+                .isEqualTo(newRingtoneValueCanonicalized);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_useCustomLookup_multipleResults_notRestore() {
+        final String sourceRingtoneValue =
+                "content://0@media/external/audio/media/1?title=Song&canonical=1";
+
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        // This is to mock the case that there are multiple results by querying title +
+        // ringtone_type.
+        MatrixCursor cursor = new MatrixCursor(new String[] {BaseColumns._ID});
+        cursor.addRow(new Object[] {100L});
+        cursor.addRow(new Object[] {110L});
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider("0@" + MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.RINGTONE,
+                sourceRingtoneValue,
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(DEFAULT_RINGTONE_VALUE);
+    }
+
+    @Test
+    public void testRestoreValue_customRingtone_restoreSilentValue() {
+        MockContentResolver mMockContentResolver = new MockContentResolver();
+        when(mContext.getContentResolver()).thenReturn(mMockContentResolver);
+
+        ContentProvider mockMediaContentProvider =
+                new MockContentProvider(mContext) {
+                    @Override
+                    public Uri uncanonicalize(Uri url) {
+                        // mock the lookup failure in regular MediaProvider.uncanonicalize.
+                        return null;
+                    }
+
+                    @Override
+                    public String getType(Uri url) {
+                        return "audio/ogg";
+                    }
+                };
+
+        ContentProvider mockSettingsContentProvider =
+                new MockSettingsProvider(mContext, getContentResolver());
+        mMockContentResolver.addProvider(MediaStore.AUTHORITY, mockMediaContentProvider);
+        mMockContentResolver.addProvider(Settings.AUTHORITY, mockSettingsContentProvider);
+
+        resetRingtoneSettingsToDefault(mMockContentResolver);
+
+        mSettingsHelper.restoreValue(
+                mContext,
+                mMockContentResolver,
+                new ContentValues(),
+                Uri.EMPTY,
+                Settings.System.RINGTONE,
+                "_silent",
+                0);
+
+        assertThat(Settings.System.getString(mMockContentResolver, Settings.System.RINGTONE))
+                .isEqualTo(null);
+    }
+
+    public static class MockSettingsProvider extends MockContentProvider {
+        ContentResolver mBaseContentResolver;
+
+        public MockSettingsProvider(Context context, ContentResolver baseContentResolver) {
+            super(context);
+            this.mBaseContentResolver = baseContentResolver;
+        }
+
+        @Override
+        public Bundle call(String method, String request, Bundle args) {
+            return mBaseContentResolver.call(Settings.AUTHORITY, method, request, args);
+        }
+    }
+
+    @Test
     public void restoreValue_autoRotation_deviceStateAutoRotationDisabled_restoresValue() {
         when(mResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults))
                 .thenReturn(new String[]{});
@@ -400,4 +788,20 @@
         Settings.Global.putString(cr, Settings.Global.POWER_BUTTON_LONG_PRESS, null);
         Settings.Global.putString(cr, Settings.Global.KEY_CHORD_POWER_VOLUME_UP, null);
     }
+
+    private void resetRingtoneSettingsToDefault(ContentResolver contentResolver) {
+        Settings.System.putString(
+                contentResolver, Settings.System.RINGTONE, DEFAULT_RINGTONE_VALUE);
+        Settings.System.putString(
+                contentResolver, Settings.System.NOTIFICATION_SOUND, DEFAULT_NOTIFICATION_VALUE);
+        Settings.System.putString(
+                contentResolver, Settings.System.ALARM_ALERT, DEFAULT_ALARM_VALUE);
+
+        assertThat(Settings.System.getString(contentResolver, Settings.System.RINGTONE))
+                .isEqualTo(DEFAULT_RINGTONE_VALUE);
+        assertThat(Settings.System.getString(contentResolver, Settings.System.NOTIFICATION_SOUND))
+                .isEqualTo(DEFAULT_NOTIFICATION_VALUE);
+        assertThat(Settings.System.getString(contentResolver, Settings.System.ALARM_ALERT))
+                .isEqualTo(DEFAULT_ALARM_VALUE);
+    }
 }
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 29999c2..b472982 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -423,6 +423,7 @@
         "mockito-target-extended-minus-junit4",
         "androidx.test.ext.junit",
         "androidx.test.ext.truth",
+        "kotlin-test",
     ],
     libs: [
         "android.test.runner",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 58c9f77..6778d5a 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -562,7 +562,7 @@
                   android:exported="true"
                   android:launchMode="singleTop"
                   android:permission="android.permission.MANAGE_SENSOR_PRIVACY"
-                  android:theme="@style/Theme.SystemUI.Dialog.Alert"
+                  android:theme="@style/Theme.SystemUI.Dialog.Alert.SensorPrivacy"
                   android:finishOnCloseSystemDialogs="true"
                   android:showForAllUsers="true">
         </activity>
diff --git a/packages/SystemUI/OWNERS b/packages/SystemUI/OWNERS
index 0f1f168..a892269 100644
--- a/packages/SystemUI/OWNERS
+++ b/packages/SystemUI/OWNERS
@@ -98,7 +98,6 @@
 yuandizhou@google.com
 yurilin@google.com
 zakcohen@google.com
-zoepage@google.com
 
 #Android TV
 rgl@google.com
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 37b1ee5..187d073 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -249,7 +249,7 @@
         // intent is to launch a dialog from another dialog.
         val animatedParent =
             openedDialogs.firstOrNull {
-                it.dialog.window.decorView.viewRootImpl == controller.viewRoot
+                it.dialog.window?.decorView?.viewRootImpl == controller.viewRoot
             }
         val controller =
             animatedParent?.dialogContentWithBackground?.let {
@@ -336,7 +336,7 @@
     ): ActivityLaunchAnimator.Controller? {
         val animatedDialog =
             openedDialogs.firstOrNull {
-                it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
+                it.dialog.window?.decorView?.viewRootImpl == view.viewRootImpl
             }
                 ?: return null
         return createActivityLaunchController(animatedDialog, cujType)
@@ -417,7 +417,7 @@
                 animatedDialog.prepareForStackDismiss()
 
                 // Remove the dim.
-                dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+                dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
             }
 
             override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
@@ -783,7 +783,7 @@
         }
 
         // Show the background dim.
-        dialog.window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+        dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
 
         startAnimation(
             isLaunching = true,
@@ -863,7 +863,7 @@
             isLaunching = false,
             onLaunchAnimationStart = {
                 // Remove the dim background as soon as we start the animation.
-                dialog.window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+                dialog.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
             },
             onLaunchAnimationEnd = {
                 val dialogContentWithBackground = this.dialogContentWithBackground!!
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 1a03ede..6c4b695 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -206,8 +206,9 @@
             return
         }
 
-        backgroundView = FrameLayout(launchContainer.context)
-        launchContainerOverlay.add(backgroundView)
+        backgroundView = FrameLayout(launchContainer.context).also {
+            launchContainerOverlay.add(it)
+        }
 
         // We wrap the ghosted view background and use it to draw the expandable background. Its
         // alpha will be set to 0 as soon as we start drawing the expanding background.
@@ -319,7 +320,7 @@
         backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
 
         GhostView.removeGhost(ghostedView)
-        launchContainerOverlay.remove(backgroundView)
+        backgroundView?.let { launchContainerOverlay.remove(it) }
 
         if (ghostedView is LaunchableView) {
             // Restore the ghosted view visibility.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index 142fd21..d6eba2e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -283,7 +283,7 @@
 
         animator.addListener(
             object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
+                override fun onAnimationStart(animation: Animator, isReverse: Boolean) {
                     if (DEBUG) {
                         Log.d(TAG, "Animation started")
                     }
@@ -295,7 +295,7 @@
                     launchContainerOverlay.add(windowBackgroundLayer)
                 }
 
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     if (DEBUG) {
                         Log.d(TAG, "Animation ended")
                     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index b555fa5..8dc7495 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -42,7 +42,9 @@
                 return baseTypeface
             }
 
-            val axes = FontVariationAxis.fromFontVariationSettings(fVar).toMutableList()
+            val axes = FontVariationAxis.fromFontVariationSettings(fVar)
+                ?.toMutableList()
+                ?: mutableListOf()
             axes.removeIf { !baseTypeface.isSupportedAxes(it.getOpenTypeTagValue()) }
             if (axes.isEmpty()) {
                 return baseTypeface
@@ -120,8 +122,8 @@
             }
             addListener(
                 object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) = textInterpolator.rebase()
-                    override fun onAnimationCancel(animation: Animator?) = textInterpolator.rebase()
+                    override fun onAnimationEnd(animation: Animator) = textInterpolator.rebase()
+                    override fun onAnimationCancel(animation: Animator) = textInterpolator.rebase()
                 }
             )
         }
@@ -302,11 +304,11 @@
             if (onAnimationEnd != null) {
                 val listener =
                     object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
+                        override fun onAnimationEnd(animation: Animator) {
                             onAnimationEnd.run()
                             animator.removeListener(this)
                         }
-                        override fun onAnimationCancel(animation: Animator?) {
+                        override fun onAnimationCancel(animation: Animator) {
                             animator.removeListener(this)
                         }
                     }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 38b99cc..bd3706e 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -1046,7 +1046,7 @@
                         }
                     }
 
-                    override fun onAnimationCancel(animation: Animator?) {
+                    override fun onAnimationCancel(animation: Animator) {
                         cancelled = true
                     }
                 }
diff --git a/packages/SystemUI/compose/core/OWNERS b/packages/SystemUI/compose/core/OWNERS
new file mode 100644
index 0000000..7e37f4f
--- /dev/null
+++ b/packages/SystemUI/compose/core/OWNERS
@@ -0,0 +1,12 @@
+set noparent
+
+# Bug component: 1184816
+
+jdemeulenaere@google.com
+nijamkin@google.com
+
+# Don't send reviews here.
+dsandler@android.com
+cinek@google.com
+juliacr@google.com
+pixel@google.com
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
index c3f44f8..f7ebe2f 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/Key.kt
@@ -37,7 +37,14 @@
 }
 
 /** Key for a scene. */
-class SceneKey(name: String, identity: Any = Object()) : Key(name, identity) {
+class SceneKey(
+    name: String,
+    identity: Any = Object(),
+) : Key(name, identity) {
+
+    /** The unique [ElementKey] identifying this scene's root element. */
+    val rootElementKey = ElementKey(name, identity)
+
     override fun toString(): String {
         return "SceneKey(name=$name)"
     }
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
index 9752f53..f4e3902 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -35,7 +35,7 @@
 
 /** The transitions configuration of a [SceneTransitionLayout]. */
 class SceneTransitions(
-    val transitionSpecs: List<TransitionSpec>,
+    private val transitionSpecs: List<TransitionSpec>,
 ) {
     private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
 
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index afd49b4..48d5638e8b 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -75,7 +75,7 @@
     }
 }
 
-private class TransitionBuilderImpl : TransitionBuilder {
+internal class TransitionBuilderImpl : TransitionBuilder {
     val transformations = mutableListOf<Transformation>()
     override var spec: AnimationSpec<Float> = spring(stiffness = Spring.StiffnessLow)
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt b/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
deleted file mode 100644
index c58c162..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/ExampleFeature.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui
-
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.BoxWithConstraints
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Surface
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import kotlin.math.roundToInt
-
-/**
- * This is an example Compose feature, which shows a text and a count that is incremented when
- * clicked. We also show the max width available to this component, which is displayed either next
- * to or below the text depending on that max width.
- */
-@Composable
-fun ExampleFeature(text: String, modifier: Modifier = Modifier) {
-    BoxWithConstraints(modifier) {
-        val maxWidth = maxWidth
-        if (maxWidth < 600.dp) {
-            Column {
-                CounterTile(text)
-                Spacer(Modifier.size(16.dp))
-                MaxWidthTile(maxWidth)
-            }
-        } else {
-            Row {
-                CounterTile(text)
-                Spacer(Modifier.size(16.dp))
-                MaxWidthTile(maxWidth)
-            }
-        }
-    }
-}
-
-@Composable
-private fun CounterTile(text: String, modifier: Modifier = Modifier) {
-    Surface(
-        modifier,
-        color = MaterialTheme.colorScheme.primaryContainer,
-        shape = RoundedCornerShape(28.dp),
-    ) {
-        var count by remember { mutableStateOf(0) }
-        Column(
-            Modifier.clickable { count++ }.padding(16.dp),
-        ) {
-            Text(text)
-            Text("I was clicked $count times.")
-        }
-    }
-}
-
-@Composable
-private fun MaxWidthTile(maxWidth: Dp, modifier: Modifier = Modifier) {
-    Surface(
-        modifier,
-        color = MaterialTheme.colorScheme.tertiaryContainer,
-        shape = RoundedCornerShape(28.dp),
-    ) {
-        Text(
-            "The max available width to me is: ${maxWidth.value.roundToInt()}dp",
-            Modifier.padding(16.dp)
-        )
-    }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index b9baa793..81b9eb0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -24,7 +24,7 @@
 import androidx.compose.animation.Crossfade
 import androidx.compose.animation.core.snap
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.background
+import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
@@ -46,6 +46,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.R
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
@@ -63,6 +64,13 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
+object Bouncer {
+    object Elements {
+        val Background = ElementKey("BouncerBackground")
+        val Content = ElementKey("BouncerContent")
+    }
+}
+
 /** The bouncer scene displays authentication challenges like PIN, password, or pattern. */
 @SysUISingleton
 class BouncerScene
@@ -88,7 +96,7 @@
 }
 
 @Composable
-private fun BouncerScene(
+private fun SceneScope.BouncerScene(
     viewModel: BouncerViewModel,
     dialogFactory: BouncerSceneDialogFactory,
     modifier: Modifier = Modifier,
@@ -97,84 +105,90 @@
     val authMethodViewModel: AuthMethodBouncerViewModel? by viewModel.authMethod.collectAsState()
     val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState()
     var dialog: Dialog? by remember { mutableStateOf(null) }
+    val backgroundColor = MaterialTheme.colorScheme.surface
 
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        verticalArrangement = Arrangement.spacedBy(60.dp),
-        modifier =
-            modifier
-                .fillMaxSize()
-                .background(MaterialTheme.colorScheme.surface)
-                .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
-    ) {
-        Crossfade(
-            targetState = message,
-            label = "Bouncer message",
-            animationSpec = if (message.isUpdateAnimated) tween() else snap(),
-        ) { message ->
-            Text(
-                text = message.text,
-                color = MaterialTheme.colorScheme.onSurface,
-                style = MaterialTheme.typography.bodyLarge,
-            )
+    Box(modifier) {
+        Canvas(Modifier.element(Bouncer.Elements.Background).fillMaxSize()) {
+            drawRect(color = backgroundColor)
         }
 
-        Box(Modifier.weight(1f)) {
-            when (val nonNullViewModel = authMethodViewModel) {
-                is PinBouncerViewModel ->
-                    PinBouncer(
-                        viewModel = nonNullViewModel,
-                        modifier = Modifier.align(Alignment.Center),
-                    )
-                is PasswordBouncerViewModel ->
-                    PasswordBouncer(
-                        viewModel = nonNullViewModel,
-                        modifier = Modifier.align(Alignment.Center),
-                    )
-                is PatternBouncerViewModel ->
-                    PatternBouncer(
-                        viewModel = nonNullViewModel,
-                        modifier =
-                            Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
-                                .align(Alignment.BottomCenter),
-                    )
-                else -> Unit
-            }
-        }
-
-        Button(
-            onClick = viewModel::onEmergencyServicesButtonClicked,
-            colors =
-                ButtonDefaults.buttonColors(
-                    containerColor = MaterialTheme.colorScheme.tertiaryContainer,
-                    contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
-                ),
+        Column(
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.spacedBy(60.dp),
+            modifier =
+                Modifier.element(Bouncer.Elements.Content)
+                    .fillMaxSize()
+                    .padding(start = 32.dp, top = 92.dp, end = 32.dp, bottom = 32.dp)
         ) {
-            Text(
-                text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
-                style = MaterialTheme.typography.bodyMedium,
-            )
-        }
-
-        if (dialogMessage != null) {
-            if (dialog == null) {
-                dialog =
-                    dialogFactory().apply {
-                        setMessage(dialogMessage)
-                        setButton(
-                            DialogInterface.BUTTON_NEUTRAL,
-                            context.getString(R.string.ok),
-                        ) { _, _ ->
-                            viewModel.onThrottlingDialogDismissed()
-                        }
-                        setCancelable(false)
-                        setCanceledOnTouchOutside(false)
-                        show()
-                    }
+            Crossfade(
+                targetState = message,
+                label = "Bouncer message",
+                animationSpec = if (message.isUpdateAnimated) tween() else snap(),
+            ) { message ->
+                Text(
+                    text = message.text,
+                    color = MaterialTheme.colorScheme.onSurface,
+                    style = MaterialTheme.typography.bodyLarge,
+                )
             }
-        } else {
-            dialog?.dismiss()
-            dialog = null
+
+            Box(Modifier.weight(1f)) {
+                when (val nonNullViewModel = authMethodViewModel) {
+                    is PinBouncerViewModel ->
+                        PinBouncer(
+                            viewModel = nonNullViewModel,
+                            modifier = Modifier.align(Alignment.Center),
+                        )
+                    is PasswordBouncerViewModel ->
+                        PasswordBouncer(
+                            viewModel = nonNullViewModel,
+                            modifier = Modifier.align(Alignment.Center),
+                        )
+                    is PatternBouncerViewModel ->
+                        PatternBouncer(
+                            viewModel = nonNullViewModel,
+                            modifier =
+                                Modifier.aspectRatio(1f, matchHeightConstraintsFirst = false)
+                                    .align(Alignment.BottomCenter),
+                        )
+                    else -> Unit
+                }
+            }
+
+            Button(
+                onClick = viewModel::onEmergencyServicesButtonClicked,
+                colors =
+                    ButtonDefaults.buttonColors(
+                        containerColor = MaterialTheme.colorScheme.tertiaryContainer,
+                        contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
+                    ),
+            ) {
+                Text(
+                    text = stringResource(com.android.internal.R.string.lockscreen_emergency_call),
+                    style = MaterialTheme.typography.bodyMedium,
+                )
+            }
+
+            if (dialogMessage != null) {
+                if (dialog == null) {
+                    dialog =
+                        dialogFactory().apply {
+                            setMessage(dialogMessage)
+                            setButton(
+                                DialogInterface.BUTTON_NEUTRAL,
+                                context.getString(R.string.ok),
+                            ) { _, _ ->
+                                viewModel.onThrottlingDialogDismissed()
+                            }
+                            setCancelable(false)
+                            setCanceledOnTouchOutside(false)
+                            show()
+                        }
+                }
+            } else {
+                dialog?.dismiss()
+                dialog = null
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
index ca7352e..da48762 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenScene.kt
@@ -63,7 +63,7 @@
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = destinationScenes(up = viewModel.upDestinationSceneKey.value)
+                initialValue = destinationScenes(up = null)
             )
 
     @Composable
@@ -77,12 +77,12 @@
     }
 
     private fun destinationScenes(
-        up: SceneKey,
+        up: SceneKey?,
     ): Map<UserAction, SceneModel> {
-        return mapOf(
-            UserAction.Swipe(Direction.UP) to SceneModel(up),
-            UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Shade)
-        )
+        return buildMap {
+            up?.let { this[UserAction.Swipe(Direction.UP)] = SceneModel(up) }
+            this[UserAction.Swipe(Direction.DOWN)] = SceneModel(SceneKey.Shade)
+        }
     }
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 38b751c..889c026 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -31,9 +31,17 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+
+object Notifications {
+    object Elements {
+        val Notifications = ElementKey("Notifications")
+    }
+}
 
 @Composable
-fun Notifications(
+fun SceneScope.Notifications(
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/272779828): implement.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
index d84e676..68f010e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/people/ui/compose/PeopleScreen.kt
@@ -42,13 +42,10 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
 import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.R
 import com.android.systemui.compose.modifiers.sysuiResTag
@@ -70,15 +67,6 @@
     val priorityTiles by viewModel.priorityTiles.collectAsState()
     val recentTiles by viewModel.recentTiles.collectAsState()
 
-    // Make sure to refresh the tiles/conversations when the lifecycle is resumed, so that it
-    // updates them when going back to the Activity after leaving it.
-    val lifecycleOwner = LocalLifecycleOwner.current
-    LaunchedEffect(lifecycleOwner, viewModel) {
-        lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
-            viewModel.onTileRefreshRequested()
-        }
-    }
-
     // Call [onResult] this activity when the ViewModel tells us so.
     LaunchedEffect(viewModel.result) {
         viewModel.result.collect { result ->
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
index 1bb341c..c84a5e9 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/footer/ui/compose/QuickSettings.kt
@@ -31,15 +31,27 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
+import com.android.compose.animation.scene.SceneScope
+
+object QuickSettings {
+    object Elements {
+        // TODO RENAME
+        val Content = ElementKey("QuickSettingsContent")
+        val CollapsedGrid = ElementKey("QuickSettingsCollapsedGrid")
+        val FooterActions = ElementKey("QuickSettingsFooterActions")
+    }
+}
 
 @Composable
-fun QuickSettings(
+fun SceneScope.QuickSettings(
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/272780058): implement.
     Column(
         modifier =
             modifier
+                .element(QuickSettings.Elements.Content)
                 .fillMaxWidth()
                 .defaultMinSize(minHeight = 300.dp)
                 .clip(RoundedCornerShape(32.dp))
@@ -47,15 +59,19 @@
                 .padding(16.dp),
     ) {
         Text(
-            text = "Quick settings",
-            modifier = Modifier.align(Alignment.CenterHorizontally),
+            text = "Quick settings grid",
+            modifier =
+                Modifier.element(QuickSettings.Elements.CollapsedGrid)
+                    .align(Alignment.CenterHorizontally),
             style = MaterialTheme.typography.titleLarge,
             color = MaterialTheme.colorScheme.onPrimary,
         )
         Spacer(modifier = Modifier.weight(1f))
         Text(
             text = "QS footer actions",
-            modifier = Modifier.align(Alignment.CenterHorizontally),
+            modifier =
+                Modifier.element(QuickSettings.Elements.FooterActions)
+                    .align(Alignment.CenterHorizontally),
             style = MaterialTheme.typography.titleSmall,
             color = MaterialTheme.colorScheme.onPrimary,
         )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 29763c2..e5cd439 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -16,19 +16,17 @@
 
 package com.android.systemui.qs.ui.composable
 
-import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.Row
-import androidx.compose.material3.Button
-import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
 import com.android.systemui.qs.ui.viewmodel.QuickSettingsSceneViewModel
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
@@ -69,23 +67,18 @@
 }
 
 @Composable
-private fun QuickSettingsScene(
+private fun SceneScope.QuickSettingsScene(
     viewModel: QuickSettingsSceneViewModel,
     modifier: Modifier = Modifier,
 ) {
     // TODO(b/280887232): implement the real UI.
 
-    Box(modifier = modifier) {
-        Column(
-            horizontalAlignment = Alignment.CenterHorizontally,
-            modifier = Modifier.align(Alignment.Center)
-        ) {
-            Text("Quick settings", style = MaterialTheme.typography.headlineMedium)
-            Row(
-                horizontalArrangement = Arrangement.spacedBy(8.dp),
-            ) {
-                Button(onClick = { viewModel.onContentClicked() }) { Text("Open some content") }
-            }
-        }
+    Box(
+        modifier
+            .fillMaxSize()
+            .clickable(onClick = { viewModel.onContentClicked() })
+            .padding(horizontal = 16.dp, vertical = 48.dp)
+    ) {
+        QuickSettings(modifier = Modifier.fillMaxHeight())
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
index 3dfdbba..c865070 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt
@@ -31,7 +31,6 @@
 import com.android.compose.animation.scene.Swipe
 import com.android.compose.animation.scene.UserAction as SceneTransitionUserAction
 import com.android.compose.animation.scene.observableTransitionState
-import com.android.compose.animation.scene.transitions
 import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
@@ -77,8 +76,8 @@
 
     SceneTransitionLayout(
         currentScene = currentSceneKey.toTransitionSceneKey(),
-        onChangeScene = { sceneKey -> viewModel.setCurrentScene(sceneKey.toModel()) },
-        transitions = transitions {},
+        onChangeScene = viewModel::onSceneChanged,
+        transitions = SceneContainerTransitions,
         state = state,
         modifier = modifier.fillMaxSize(),
     ) {
@@ -98,7 +97,9 @@
             ) {
                 with(composableScene) {
                     this@scene.Content(
-                        modifier = Modifier.fillMaxSize(),
+                        modifier =
+                            Modifier.element(sceneKey.toTransitionSceneKey().rootElementKey)
+                                .fillMaxSize(),
                     )
                 }
             }
@@ -129,14 +130,6 @@
 }
 
 // TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
-private fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
-    return SceneTransitionSceneKey(
-        name = toString(),
-        identity = this,
-    )
-}
-
-// TODO(b/293899074): remove this once we can use the one from SceneTransitionLayout.
 private fun SceneTransitionSceneKey.toModel(): SceneModel {
     return SceneModel(key = identity as SceneKey)
 }
@@ -154,3 +147,7 @@
         is UserAction.Back -> Back
     }
 }
+
+private fun SceneContainerViewModel.onSceneChanged(sceneKey: SceneTransitionSceneKey) {
+    onSceneChanged(sceneKey.toModel())
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
new file mode 100644
index 0000000..404bf81
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -0,0 +1,34 @@
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.transitions
+import com.android.systemui.scene.ui.composable.transitions.bouncerToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToQuickSettingsTransition
+import com.android.systemui.scene.ui.composable.transitions.goneToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToBouncerTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToGoneTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToQuickSettingsTransition
+import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
+import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
+
+/**
+ * Comprehensive definition of all transitions between scenes in [SceneContainer].
+ *
+ * Transitions are automatically reversible, so define only one transition per scene pair. By
+ * convention, use the more common transition direction when defining the pair order, e.g.
+ * Lockscreen to Bouncer rather than Bouncer to Lockscreen.
+ *
+ * The actual transition DSL must be placed in a separate file under the package
+ * [com.android.systemui.scene.ui.composable.transitions].
+ *
+ * Please keep the list sorted alphabetically.
+ */
+val SceneContainerTransitions = transitions {
+    from(Bouncer, to = Gone) { bouncerToGoneTransition() }
+    from(Gone, to = Shade) { goneToShadeTransition() }
+    from(Gone, to = QuickSettings) { goneToQuickSettingsTransition() }
+    from(Lockscreen, to = Bouncer) { lockscreenToBouncerTransition() }
+    from(Lockscreen, to = Shade) { lockscreenToShadeTransition() }
+    from(Lockscreen, to = QuickSettings) { lockscreenToQuickSettingsTransition() }
+    from(Lockscreen, to = Gone) { lockscreenToGoneTransition() }
+    from(Shade, to = QuickSettings) { shadeToQuickSettingsTransition() }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
new file mode 100644
index 0000000..8d0d705
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/TransitionSceneKeys.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.scene.ui.composable
+
+import com.android.compose.animation.scene.SceneKey as SceneTransitionSceneKey
+import com.android.systemui.scene.shared.model.SceneKey
+
+val Lockscreen = SceneKey.Lockscreen.toTransitionSceneKey()
+val Bouncer = SceneKey.Bouncer.toTransitionSceneKey()
+val Shade = SceneKey.Shade.toTransitionSceneKey()
+val QuickSettings = SceneKey.QuickSettings.toTransitionSceneKey()
+val Gone = SceneKey.Gone.toTransitionSceneKey()
+
+// TODO(b/293899074): Remove this file once we can use the scene keys from SceneTransitionLayout.
+fun SceneKey.toTransitionSceneKey(): SceneTransitionSceneKey {
+    return SceneTransitionSceneKey(name = toString(), identity = this)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
new file mode 100644
index 0000000..1a9face
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromBouncerToGoneTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Bouncer
+
+fun TransitionBuilder.bouncerToGoneTransition() {
+    spec = tween(durationMillis = 500)
+
+    fade(Bouncer.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
new file mode 100644
index 0000000..38712b0
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToQuickSettingsTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.QuickSettings
+
+fun TransitionBuilder.goneToQuickSettingsTransition() {
+    spec = tween(durationMillis = 500)
+
+    fade(QuickSettings.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
new file mode 100644
index 0000000..1d57c1a
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromGoneToShadeTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Shade
+
+fun TransitionBuilder.goneToShadeTransition() {
+    spec = tween(durationMillis = 500)
+
+    fade(Shade.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
new file mode 100644
index 0000000..1fee874
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToBouncerTransition.kt
@@ -0,0 +1,14 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.bouncer.ui.composable.Bouncer
+
+fun TransitionBuilder.lockscreenToBouncerTransition() {
+    spec = tween(durationMillis = 500)
+
+    translate(Bouncer.Elements.Content, y = 300.dp)
+    fractionRange(end = 0.5f) { fade(Bouncer.Elements.Background) }
+    fractionRange(start = 0.5f) { fade(Bouncer.Elements.Content) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
new file mode 100644
index 0000000..da6306d
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToGoneTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.Lockscreen
+
+fun TransitionBuilder.lockscreenToGoneTransition() {
+    spec = tween(durationMillis = 500)
+
+    fade(Lockscreen.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
new file mode 100644
index 0000000..9a8a3e2
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToQuickSettingsTransition.kt
@@ -0,0 +1,11 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.scene.ui.composable.QuickSettings
+
+fun TransitionBuilder.lockscreenToQuickSettingsTransition() {
+    spec = tween(durationMillis = 500)
+
+    fade(QuickSettings.rootElementKey)
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
new file mode 100644
index 0000000..7ecfb62
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromLockscreenToShadeTransition.kt
@@ -0,0 +1,24 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+import com.android.systemui.shade.ui.composable.Shade
+
+fun TransitionBuilder.lockscreenToShadeTransition() {
+    spec = tween(durationMillis = 500)
+
+    punchHole(Shade.Elements.QuickSettings, bounds = Shade.Elements.Scrim, Shade.Shapes.Scrim)
+    translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
+    fractionRange(end = 0.5f) {
+        fade(Shade.Elements.ScrimBackground)
+        translate(
+            QuickSettings.Elements.CollapsedGrid,
+            Edge.Top,
+            startsOutsideLayoutBounds = false,
+        )
+    }
+    fractionRange(start = 0.5f) { fade(Notifications.Elements.Notifications) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
new file mode 100644
index 0000000..6c7964b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/FromShadeToQuickSettingsTransition.kt
@@ -0,0 +1,15 @@
+package com.android.systemui.scene.ui.composable.transitions
+
+import androidx.compose.animation.core.tween
+import com.android.compose.animation.scene.Edge
+import com.android.compose.animation.scene.TransitionBuilder
+import com.android.systemui.notifications.ui.composable.Notifications
+import com.android.systemui.qs.footer.ui.compose.QuickSettings
+
+fun TransitionBuilder.shadeToQuickSettingsTransition() {
+    spec = tween(durationMillis = 500)
+
+    translate(Notifications.Elements.Notifications, Edge.Bottom)
+    fade(Notifications.Elements.Notifications)
+    timestampRange(endMillis = 83) { fade(QuickSettings.Elements.FooterActions) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index ff1cb5f..f985aa2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -16,16 +16,22 @@
 
 package com.android.systemui.shade.ui.composable
 
+import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -44,6 +50,26 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
+object Shade {
+    object Elements {
+        val QuickSettings = ElementKey("ShadeQuickSettings")
+        val Scrim = ElementKey("ShadeScrim")
+        val ScrimBackground = ElementKey("ShadeScrimBackground")
+    }
+
+    object Dimensions {
+        val ScrimCornerSize = 32.dp
+    }
+
+    object Shapes {
+        val Scrim =
+            RoundedCornerShape(
+                topStart = Dimensions.ScrimCornerSize,
+                topEnd = Dimensions.ScrimCornerSize,
+            )
+    }
+}
+
 /** The shade scene shows scrolling list of notifications and some of the quick setting tiles. */
 @SysUISingleton
 class ShadeScene
@@ -79,20 +105,28 @@
 }
 
 @Composable
-private fun ShadeScene(
+private fun SceneScope.ShadeScene(
     viewModel: ShadeSceneViewModel,
     modifier: Modifier = Modifier,
 ) {
-    Column(
-        horizontalAlignment = Alignment.CenterHorizontally,
-        verticalArrangement = Arrangement.spacedBy(16.dp),
-        modifier =
-            modifier
-                .fillMaxSize()
-                .clickable(onClick = { viewModel.onContentClicked() })
-                .padding(horizontal = 16.dp, vertical = 48.dp)
-    ) {
-        QuickSettings(modifier = Modifier.height(160.dp))
-        Notifications(modifier = Modifier.weight(1f))
+    Box(modifier.element(Shade.Elements.Scrim)) {
+        Spacer(
+            modifier =
+                Modifier.element(Shade.Elements.ScrimBackground)
+                    .fillMaxSize()
+                    .background(MaterialTheme.colorScheme.scrim, shape = Shade.Shapes.Scrim)
+        )
+
+        Column(
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.spacedBy(16.dp),
+            modifier =
+                Modifier.fillMaxSize()
+                    .clickable(onClick = { viewModel.onContentClicked() })
+                    .padding(horizontal = 16.dp, vertical = 48.dp)
+        ) {
+            QuickSettings(modifier = Modifier.height(160.dp))
+            Notifications(modifier = Modifier.weight(1f))
+        }
     }
 }
diff --git a/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt b/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt
deleted file mode 100644
index 1c2e8fa..0000000
--- a/packages/SystemUI/compose/features/tests/src/com/android/systemui/ExampleFeatureTest.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui
-
-import androidx.compose.ui.test.assertIsDisplayed
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithText
-import androidx.compose.ui.test.performClick
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@RunWith(AndroidJUnit4::class)
-class ExampleFeatureTest {
-    @get:Rule val composeRule = createComposeRule()
-
-    @Test
-    fun testProvidedTextIsDisplayed() {
-        composeRule.setContent { ExampleFeature("foo") }
-
-        composeRule.onNodeWithText("foo").assertIsDisplayed()
-    }
-
-    @Test
-    fun testCountIsIncreasedWhenClicking() {
-        composeRule.setContent { ExampleFeature("foo") }
-
-        composeRule.onNodeWithText("I was clicked 0 times.").assertIsDisplayed().performClick()
-        composeRule.onNodeWithText("I was clicked 1 times.").assertIsDisplayed()
-    }
-}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
index 46f5971..92d2bd2 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt
@@ -190,6 +190,9 @@
         /** Flag denoting transit clock are enabled in wallpaper picker. */
         const val FLAG_NAME_PAGE_TRANSITIONS = "wallpaper_picker_page_transitions"
 
+        /** Flag denoting adding apply button to wallpaper picker's grid preview page. */
+        const val FLAG_NAME_GRID_APPLY_BUTTON = "wallpaper_picker_grid_apply_button"
+
         /** Flag denoting whether preview loading animation is enabled. */
         const val FLAG_NAME_WALLPAPER_PICKER_PREVIEW_ANIMATION =
             "wallpaper_picker_preview_animation"
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 006fc09..92083b0 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -438,7 +438,6 @@
 -packages/SystemUI/src/com/android/systemui/statusbar/policy/WalletControllerImpl.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt
--packages/SystemUI/src/com/android/systemui/statusbar/tv/VpnStatusObserver.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt
 -packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
 -packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -446,7 +445,6 @@
 -packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt
 -packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt
 -packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt
--packages/SystemUI/src/com/android/systemui/tv/TVSystemUICoreStartableModule.kt
 -packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt
 -packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt
 -packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
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 f83fa33..affb76b 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt
@@ -57,7 +57,7 @@
 
         private fun readIntFromBundle(extras: Bundle, key: String): Int? =
             try {
-                extras.getString(key).toInt()
+                extras.getString(key)?.toInt()
             } catch (e: Exception) {
                 null
             }
diff --git a/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
new file mode 100644
index 0000000..cd7ab98
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-land/keyguard_pin_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include layout="@layout/keyguard_pin_view_landscape" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml b/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml
new file mode 100644
index 0000000..80cc8c0
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout-sw600dp-land/keyguard_pin_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <include layout="@layout/keyguard_pin_view_portrait" />
+
+</FrameLayout>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml
new file mode 100644
index 0000000..e00742d
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_view_landscape.xml
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<com.android.keyguard.KeyguardPINView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/keyguard_pin_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_horizontal|bottom"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="horizontal">
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="2"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <include layout="@layout/keyguard_bouncer_message_area" />
+
+        <com.android.systemui.bouncer.ui.BouncerMessageView
+            android:id="@+id/bouncer_message_view"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toTopOf="@+id/row0"
+            androidprv:layout_constraintTop_toTopOf="parent"
+            androidprv:layout_constraintVertical_chainStyle="packed" />
+
+        <!-- Set this to be just above key1. It would be better to introduce a barrier above
+         key1/key2/key3, then place this View above that. Sadly, that doesn't work (the Barrier
+         drops to the bottom of the page, and key1/2/3 all shoot up to the top-left). In any
+         case, the Flow should ensure that key1/2/3 all have the same top, so this should be
+         fine. -->
+        <com.android.keyguard.AlphaOptimizedRelativeLayout
+            android:id="@+id/row0"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+            androidprv:layout_constraintTop_toBottomOf="@+id/bouncer_message_view"
+            tools:layout_editor_absoluteX="-16dp">
+
+            <com.android.keyguard.PasswordTextView
+                android:id="@+id/pinEntry"
+                style="@style/Widget.TextView.Password"
+                android:layout_width="@dimen/keyguard_security_width"
+                android:layout_height="@dimen/keyguard_password_height"
+                android:layout_centerHorizontal="true"
+                android:layout_marginRight="72dp"
+                android:contentDescription="@string/keyguard_accessibility_pin_area"
+                androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+        </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+        <include
+            android:id="@+id/keyguard_selector_fade_container"
+            layout="@layout/keyguard_eca"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom|center_horizontal"
+            android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+            android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+            android:gravity="center_horizontal"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toBottomOf="parent" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+    <androidx.constraintlayout.widget.ConstraintLayout
+        android:id="@+id/pin_container"
+        android:layout_width="0dp"
+        android:layout_height="match_parent"
+        android:layout_weight="3"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical">
+
+        <!-- Guideline used to place the top row of keys relative to the screen height. This will be
+        updated in KeyguardPINView to reduce the height of the PIN pad. -->
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/pin_pad_top_guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            androidprv:layout_constraintGuide_percent="0" />
+
+        <com.android.keyguard.KeyguardPinFlowView
+            android:id="@+id/flow1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:orientation="horizontal"
+
+            androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+
+            androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+
+            androidprv:flow_horizontalStyle="packed"
+            androidprv:flow_maxElementsWrap="3"
+
+            androidprv:flow_verticalBias="0.5"
+            androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:flow_verticalStyle="packed"
+
+            androidprv:flow_wrapMode="aligned"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@id/pin_pad_top_guideline" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/delete_button"
+            style="@style/NumPadKey.Delete"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key0"
+            android:contentDescription="@string/keyboardview_keycode_delete" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/key_enter"
+            style="@style/NumPadKey.Enter"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:contentDescription="@string/keyboardview_keycode_enter" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key2"
+            androidprv:digit="1"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key2"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key3"
+            androidprv:digit="2"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key3"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key4"
+            androidprv:digit="3"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key4"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key5"
+            androidprv:digit="4"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key5"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key6"
+            androidprv:digit="5"
+            androidprv:textView="@+id/pinEntry" />
+
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key6"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key7"
+            androidprv:digit="6"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key7"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key8"
+            androidprv:digit="7"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key8"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key9"
+            androidprv:digit="8"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key9"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/delete_button"
+            androidprv:digit="9"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key0"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key_enter"
+            androidprv:digit="0"
+            androidprv:textView="@+id/pinEntry" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+
+</com.android.keyguard.KeyguardPINView>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 7e892f7..d85e012 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -21,9 +21,11 @@
     <dimen name="status_bar_header_height_keyguard">@dimen/status_bar_height</dimen>
 
     <!-- padding for container with status icons and battery -->
-    <dimen name="status_bar_icons_padding_end">12dp</dimen>
+    <dimen name="status_bar_icons_padding_end">4dp</dimen>
     <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
-    <dimen name="status_bar_icons_padding_start">11dp</dimen>
+    <dimen name="status_bar_icons_padding_start">3dp</dimen>
+    <dimen name="status_bar_icons_padding_bottom">2dp</dimen>
+    <dimen name="status_bar_icons_padding_top">2dp</dimen>
 
     <dimen name="status_bar_padding_end">0dp</dimen>
 
@@ -78,8 +80,8 @@
 
     <dimen name="large_screen_shade_header_height">42dp</dimen>
     <!-- start padding is smaller to account for status icon margins coming from drawable itself -->
-    <dimen name="shade_header_system_icons_padding_start">11dp</dimen>
-    <dimen name="shade_header_system_icons_padding_end">12dp</dimen>
+    <dimen name="shade_header_system_icons_padding_start">3dp</dimen>
+    <dimen name="shade_header_system_icons_padding_end">4dp</dimen>
 
     <!-- Lockscreen shade transition values -->
     <dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index d74eca6..dc1f0e4 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -16,9 +16,6 @@
 */
 -->
 <resources>
-    <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
-    <dimen name="status_bar_icons_padding_start">10dp</dimen>
-
     <!-- gap on either side of status bar notification icons -->
     <dimen name="status_bar_icon_horizontal_margin">1sp</dimen>
 
@@ -30,9 +27,6 @@
 
     <dimen name="large_screen_shade_header_height">56dp</dimen>
 
-    <!-- it's a bit smaller on 720dp to account for status_bar_icon_horizontal_margin -->
-    <dimen name="shade_header_system_icons_padding_start">10dp</dimen>
-
     <!-- Biometric Auth pattern view size, better to align keyguard_security_width -->
     <dimen name="biometric_auth_pattern_view_size">348dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 12b3ec3..5c42e45 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -780,6 +780,9 @@
     <!-- Flag to enable privacy dot views, it shall be true for normal case -->
     <bool name="config_enablePrivacyDot">true</bool>
 
+    <!-- Flag to enable privacy chip animation, it shall be true for normal case -->
+    <bool name="config_enablePrivacyChipAnimation">true</bool>
+
     <!-- Class for the communal source connector to be used -->
     <string name="config_communalSourceConnector" translatable="false"></string>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 572a7fe7..8310b95 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -351,9 +351,9 @@
 
     <!-- paddings for container with status icons and battery -->
     <!-- padding start is a bit smaller than end to account for status icon margin-->
-    <dimen name="status_bar_icons_padding_start">11dp</dimen>
+    <dimen name="status_bar_icons_padding_start">3dp</dimen>
 
-    <dimen name="status_bar_icons_padding_end">0dp</dimen>
+    <dimen name="status_bar_icons_padding_end">4dp</dimen>
     <dimen name="status_bar_icons_padding_bottom">0dp</dimen>
     <dimen name="status_bar_icons_padding_top">0dp</dimen>
 
@@ -364,7 +364,7 @@
     <dimen name="status_bar_padding_start">8dp</dimen>
 
     <!-- the padding on the end of the statusbar -->
-    <dimen name="status_bar_padding_end">8dp</dimen>
+    <dimen name="status_bar_padding_end">4dp</dimen>
 
     <!-- the padding on the top of the statusbar (usually 0) -->
     <dimen name="status_bar_padding_top">0dp</dimen>
@@ -1606,7 +1606,7 @@
     <!-- Status bar user chip -->
     <dimen name="status_bar_user_chip_avatar_size">16dp</dimen>
     <!-- below also works as break between user chip and hover state of status icons -->
-    <dimen name="status_bar_user_chip_end_margin">4dp</dimen>
+    <dimen name="status_bar_user_chip_end_margin">8dp</dimen>
     <dimen name="status_bar_user_chip_text_size">12sp</dimen>
 
     <!-- System UI Dialog -->
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 763930d..261b08d 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -38,4 +38,9 @@
          protected. -->
     <bool name="flag_battery_shield_icon">false</bool>
 
+    <!-- Whether face auth will immediately stop when the display state is OFF -->
+    <bool name="flag_stop_face_auth_on_display_off">false</bool>
+
+    <!-- Whether we want to stop pulsing while running the face scanning animation -->
+    <bool name="flag_stop_pulsing_face_scanning_animation">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 15ca9d4..d2cb475 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -212,6 +212,7 @@
     <item type="id" name="keyguard_indication_text" />
     <item type="id" name="keyguard_indication_text_bottom" />
     <item type="id" name="nssl_guideline" />
+    <item type="id" name="split_shade_guideline" />
     <item type="id" name="lock_icon" />
     <item type="id" name="lock_icon_bg" />
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ee9b132..cddfda2 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -747,6 +747,9 @@
     off. This means a separate profile on a user's phone that's specifically for their
     work apps and managed by their company. "Work" is used as an adjective. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_work_mode_label">Work apps</string>
+    <!-- QuickSettings: Subtitle for the Work profile Quick Settings tile when it's in the off
+    state. This corresponds to the work profile not being currently accessible. [CHAR LIMIT=20] -->
+    <string name="quick_settings_work_mode_paused_state">Paused</string>
     <!-- QuickSettings: Label for the toggle to activate Night display (renamed "Night Light" with title caps). [CHAR LIMIT=20] -->
     <string name="quick_settings_night_display_label">Night Light</string>
     <!-- QuickSettings: Secondary text for when the Night Light will be enabled at sunset. [CHAR LIMIT=20] -->
@@ -2403,6 +2406,8 @@
     <string name="magnification_open_settings_click_label">Open magnification settings</string>
     <!-- Click action label for magnification settings panel. [CHAR LIMIT=NONE] -->
     <string name="magnification_close_settings_click_label">Close magnification settings</string>
+    <!-- Click action label for exiting magnifier edit mode. [CHAR LIMIT=NONE] -->
+    <string name="magnification_exit_edit_mode_click_label">Exit edit mode</string>
     <!-- Label of the corner of a rectangle that you can tap and drag to resize the magnification area. [CHAR LIMIT=NONE] -->
     <string name="magnification_drag_corner_to_resize">Drag corner to resize</string>
 
@@ -3053,13 +3058,6 @@
     <string name="wallet_quick_affordance_unavailable_configure_the_app">To add the Wallet app as a shortcut, make sure at least one card has been added</string>
 
     <!--
-    Requirement for the QR code scanner functionality to be available for the user to use. This is
-    shown as part of a bulleted list of requirements. When all requirements are met, the piece of
-    functionality can be accessed through a shortcut button on the lock screen. [CHAR LIMIT=NONE].
-    -->
-    <string name="qr_scanner_quick_affordance_unavailable_explanation">To add the QR code scanner as a shortcut, make sure a camera app is installed</string>
-
-    <!--
     Explains that the lock screen shortcut for the "home" app is not available because the app isn't
     installed. This is shown as part of a dialog that explains to the user why they cannot select
     this shortcut for their lock screen right now. [CHAR LIMIT=NONE].
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index d520670..10340c6 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -451,6 +451,11 @@
 
     <style name="Theme.SystemUI.Dialog.Alert" parent="@*android:style/Theme.DeviceDefault.Light.Dialog.Alert" />
 
+    <style name="Theme.SystemUI.Dialog.Alert.SensorPrivacy" parent="Theme.SystemUI.Dialog.Alert">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+    </style>
+
     <style name="Theme.SystemUI.Dialog.GlobalActions" parent="@android:style/Theme.DeviceDefault.Light.NoActionBar.Fullscreen">
         <item name="android:colorError">@*android:color/error_color_material_dark</item>
         <item name="android:windowIsFloating">true</item>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index cb2c3a1..2ec6180 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -60,7 +60,7 @@
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toStartOf="@id/privacy_container"
             app:layout_constraintTop_toTopOf="parent"
-            app:layout_constraintEnd_toEndOf="@id/carrier_group"/>
+            app:layout_constraintStart_toEndOf="@id/carrier_group"/>
         <PropertySet android:alpha="1" />
     </Constraint>
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index b6aae69..f1a4007 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -24,14 +24,12 @@
 
 /**
  * Base interface for flags that can change value on a running device.
- * @property id unique id to help identify this flag. Must be unique. This will be removed soon.
  * @property teamfood Set to true to include this flag as part of the teamfood flag. This will
  *                    be removed soon.
  * @property name Used for server-side flagging where appropriate. Also used for display. No spaces.
  * @property namespace The server-side namespace that this flag lives under.
  */
 interface Flag<T> {
-    val id: Int
     val teamfood: Boolean
     val name: String
     val namespace: String
@@ -58,7 +56,6 @@
  */
 // Consider using the "parcelize" kotlin library.
 abstract class BooleanFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val default: Boolean = false,
@@ -74,8 +71,17 @@
         }
     }
 
+    private constructor(
+            id: Int,
+            name: String,
+            namespace: String,
+            default: Boolean,
+            teamfood: Boolean,
+            overridden: Boolean,
+            ) : this(name, namespace, default, teamfood, overridden)
+
     private constructor(parcel: Parcel) : this(
-        id = parcel.readInt(),
+        parcel.readInt(),
         name = parcel.readString() ?: "",
         namespace = parcel.readString() ?: "",
         default = parcel.readBoolean(),
@@ -84,7 +90,7 @@
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeInt(id)
+        parcel.writeInt(0)
         parcel.writeString(name)
         parcel.writeString(namespace)
         parcel.writeBoolean(default)
@@ -99,12 +105,11 @@
  * It can be changed or overridden in debug builds but not in release builds.
  */
 data class UnreleasedFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
-) : BooleanFlag(id, name, namespace, false, teamfood, overridden)
+) : BooleanFlag(name, namespace, false, teamfood, overridden)
 
 /**
  * A Flag that is true by default.
@@ -112,12 +117,11 @@
  * It can be changed or overridden in any build, meaning it can be turned off if needed.
  */
 data class ReleasedFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val teamfood: Boolean = false,
     override val overridden: Boolean = false
-) : BooleanFlag(id, name, namespace, true, teamfood, overridden)
+) : BooleanFlag(name, namespace, true, teamfood, overridden)
 
 /**
  * A Flag that reads its default values from a resource overlay instead of code.
@@ -125,7 +129,6 @@
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
 data class ResourceBooleanFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     @BoolRes override val resourceId: Int,
@@ -140,7 +143,6 @@
  * Prefer [UnreleasedFlag] and [ReleasedFlag].
  */
 data class SysPropBooleanFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val default: Boolean = false,
@@ -150,7 +152,6 @@
 }
 
 data class StringFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val default: String = "",
@@ -165,15 +166,21 @@
         }
     }
 
+    private constructor(id: Int, name: String, namespace: String, default: String) : this(
+            name,
+            namespace,
+            default
+    )
+
     private constructor(parcel: Parcel) : this(
-        id = parcel.readInt(),
+        parcel.readInt(),
         name = parcel.readString() ?: "",
         namespace = parcel.readString() ?: "",
         default = parcel.readString() ?: ""
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeInt(id)
+        parcel.writeInt(0)
         parcel.writeString(name)
         parcel.writeString(namespace)
         parcel.writeString(default)
@@ -181,7 +188,6 @@
 }
 
 data class ResourceStringFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     @StringRes override val resourceId: Int,
@@ -189,7 +195,6 @@
 ) : ResourceFlag<String>
 
 data class IntFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val default: Int = 0,
@@ -205,15 +210,21 @@
         }
     }
 
+    private constructor(id: Int, name: String, namespace: String, default: Int) : this(
+            name,
+            namespace,
+            default
+    )
+
     private constructor(parcel: Parcel) : this(
-        id = parcel.readInt(),
+        parcel.readInt(),
         name = parcel.readString() ?: "",
         namespace = parcel.readString() ?: "",
         default = parcel.readInt()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeInt(id)
+        parcel.writeInt(0)
         parcel.writeString(name)
         parcel.writeString(namespace)
         parcel.writeInt(default)
@@ -221,7 +232,6 @@
 }
 
 data class ResourceIntFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     @IntegerRes override val resourceId: Int,
@@ -229,11 +239,10 @@
 ) : ResourceFlag<Int>
 
 data class LongFlag constructor(
-    override val id: Int,
-    override val default: Long = 0,
-    override val teamfood: Boolean = false,
     override val name: String,
     override val namespace: String,
+    override val default: Long = 0,
+    override val teamfood: Boolean = false,
     override val overridden: Boolean = false
 ) : ParcelableFlag<Long> {
 
@@ -245,15 +254,21 @@
         }
     }
 
+    private constructor(id: Int, name: String, namespace: String, default: Long) : this(
+            name,
+            namespace,
+            default
+    )
+
     private constructor(parcel: Parcel) : this(
-        id = parcel.readInt(),
+        parcel.readInt(),
         name = parcel.readString() ?: "",
         namespace = parcel.readString() ?: "",
         default = parcel.readLong()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeInt(id)
+        parcel.writeInt(0)
         parcel.writeString(name)
         parcel.writeString(namespace)
         parcel.writeLong(default)
@@ -261,7 +276,6 @@
 }
 
 data class FloatFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val default: Float = 0f,
@@ -277,15 +291,21 @@
         }
     }
 
+    private constructor(id: Int, name: String, namespace: String, default: Float) : this(
+            name,
+            namespace,
+            default
+    )
+
     private constructor(parcel: Parcel) : this(
-        id = parcel.readInt(),
+        parcel.readInt(),
         name = parcel.readString() ?: "",
         namespace = parcel.readString() ?: "",
         default = parcel.readFloat()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeInt(id)
+        parcel.writeInt(0)
         parcel.writeString(name)
         parcel.writeString(namespace)
         parcel.writeFloat(default)
@@ -293,7 +313,6 @@
 }
 
 data class ResourceFloatFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val resourceId: Int,
@@ -301,7 +320,6 @@
 ) : ResourceFlag<Int>
 
 data class DoubleFlag constructor(
-    override val id: Int,
     override val name: String,
     override val namespace: String,
     override val default: Double = 0.0,
@@ -317,15 +335,21 @@
         }
     }
 
+    private constructor(id: Int, name: String, namespace: String, default: Double) : this(
+            name,
+            namespace,
+            default
+    )
+
     private constructor(parcel: Parcel) : this(
-        id = parcel.readInt(),
+        parcel.readInt(),
         name = parcel.readString() ?: "",
         namespace = parcel.readString() ?: "",
         default = parcel.readDouble()
     )
 
     override fun writeToParcel(parcel: Parcel, flags: Int) {
-        parcel.writeInt(id)
+        parcel.writeInt(0)
         parcel.writeString(name)
         parcel.writeString(namespace)
         parcel.writeDouble(default)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index da1641c..1366226 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -116,13 +116,6 @@
     }
 
     /** Returns the stored value or null if not set.  */
-    // TODO(b/265188950): Remove method this once ids are fully deprecated.
-    fun <T> readFlagValue(id: Int, serializer: FlagSerializer<T>): T? {
-        val data = settings.getStringFromSecure(idToSettingsKey(id))
-        return serializer.fromSettingsData(data)
-    }
-
-    /** Returns the stored value or null if not set.  */
     fun <T> readFlagValue(name: String, serializer: FlagSerializer<T>): T? {
         val data = settings.getString(nameToSettingsKey(name))
         return serializer.fromSettingsData(data)
@@ -158,11 +151,6 @@
         return intent
     }
 
-    // TODO(b/265188950): Remove method this once ids are fully deprecated.
-    fun idToSettingsKey(id: Int): String {
-        return "$SETTINGS_PREFIX/$id"
-    }
-
     fun nameToSettingsKey(name: String): String {
         return "$SETTINGS_PREFIX/$name"
     }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
index 6beb851..e0a7fea 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt
@@ -22,7 +22,6 @@
 
 class FlagSettingsHelper(private val contentResolver: ContentResolver) {
 
-    // TODO(b/265188950): Remove method this once ids are fully deprecated.
     fun getStringFromSecure(key: String): String? = Settings.Secure.getString(contentResolver, key)
 
     fun getString(key: String): String? = Settings.Global.getString(contentResolver, key)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index fac2f91..3605ac2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -336,6 +336,14 @@
 
     @Override
     public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+
+        if (!(o instanceof Task)) {
+            return false;
+        }
+
         // Check that the id matches
         Task t = (Task) o;
         return key.equals(t.key);
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
index c142933..5edd283 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
@@ -27,6 +27,6 @@
      */
     fun ActivityManager.isInForeground(packageName: String): Boolean {
         val tasks: List<ActivityManager.RunningTaskInfo> = getRunningTasks(1)
-        return tasks.isNotEmpty() && packageName == tasks[0].topActivity.packageName
+        return tasks.isNotEmpty() && packageName == tasks[0].topActivity?.packageName
     }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
index d7e61d6..ebc57d2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
@@ -31,15 +31,15 @@
     var visibleOnScreen = false
 
     constructor(parcel: Parcel) : this() {
-        this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader)
+        this.boundsOnScreen = parcel.readParcelable(Rect::javaClass.javaClass.classLoader) ?: Rect()
         this.selectedPage = parcel.readInt()
         this.visibleOnScreen = parcel.readBoolean()
     }
 
-    override fun writeToParcel(dest: Parcel?, flags: Int) {
-        dest?.writeParcelable(boundsOnScreen, 0)
-        dest?.writeInt(selectedPage)
-        dest?.writeBoolean(visibleOnScreen)
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(boundsOnScreen, 0)
+        dest.writeInt(selectedPage)
+        dest.writeBoolean(visibleOnScreen)
     }
 
     override fun describeContents(): Int {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index aca9907..dac130d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -39,7 +39,7 @@
 
     fun init() {
         rotationChangeProvider.addCallback(rotationListener)
-        rotationListener.onRotationChanged(context.display.rotation)
+        context.display?.rotation?.let { rotationListener.onRotationChanged(it) }
     }
 
     private val rotationListener = RotationListener { rotation ->
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
index c22d689..3360c96 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt
@@ -29,35 +29,31 @@
         }
 
     fun unreleasedFlag(
-        id: Int,
         name: String,
         namespace: String = "systemui",
         teamfood: Boolean = false
     ): UnreleasedFlag {
-        val flag = UnreleasedFlag(id = id, name = name, namespace = namespace, teamfood = teamfood)
+        val flag = UnreleasedFlag(name = name, namespace = namespace, teamfood = teamfood)
         checkForDupesAndAdd(flag)
         return flag
     }
 
     fun releasedFlag(
-        id: Int,
         name: String,
         namespace: String = "systemui",
     ): ReleasedFlag {
-        val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = false)
+        val flag = ReleasedFlag(name = name, namespace = namespace, teamfood = false)
         checkForDupesAndAdd(flag)
         return flag
     }
 
     fun resourceBooleanFlag(
-        id: Int,
         @BoolRes resourceId: Int,
         name: String,
         namespace: String = "systemui",
     ): ResourceBooleanFlag {
         val flag =
             ResourceBooleanFlag(
-                id = id,
                 name = name,
                 namespace = namespace,
                 resourceId = resourceId,
@@ -68,13 +64,11 @@
     }
 
     fun sysPropBooleanFlag(
-        id: Int,
         name: String,
         namespace: String = "systemui",
         default: Boolean = false
     ): SysPropBooleanFlag {
-        val flag =
-            SysPropBooleanFlag(id = id, name = name, namespace = "systemui", default = default)
+        val flag = SysPropBooleanFlag(name = name, namespace = "systemui", default = default)
         checkForDupesAndAdd(flag)
         return flag
     }
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
index 5502da1..75465c2 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt
@@ -29,35 +29,31 @@
         }
 
     fun unreleasedFlag(
-        id: Int,
         name: String,
         namespace: String = "systemui",
         teamfood: Boolean = false
     ): UnreleasedFlag {
         // Unreleased flags are always false in this build.
-        val flag = UnreleasedFlag(id = id, name = "", namespace = "", teamfood = false)
+        val flag = UnreleasedFlag(name = name, namespace = namespace, teamfood = false)
         return flag
     }
 
     fun releasedFlag(
-        id: Int,
         name: String,
         namespace: String = "systemui",
     ): ReleasedFlag {
-        val flag = ReleasedFlag(id = id, name = name, namespace = namespace, teamfood = false)
+        val flag = ReleasedFlag(name = name, namespace = namespace, teamfood = false)
         flagMap[name] = flag
         return flag
     }
 
     fun resourceBooleanFlag(
-        id: Int,
         @BoolRes resourceId: Int,
         name: String,
         namespace: String = "systemui",
     ): ResourceBooleanFlag {
         val flag =
             ResourceBooleanFlag(
-                id = id,
                 name = name,
                 namespace = namespace,
                 resourceId = resourceId,
@@ -68,13 +64,11 @@
     }
 
     fun sysPropBooleanFlag(
-        id: Int,
         name: String,
         namespace: String = "systemui",
         default: Boolean = false
     ): SysPropBooleanFlag {
-        val flag =
-            SysPropBooleanFlag(id = id, name = name, namespace = namespace, default = default)
+        val flag = SysPropBooleanFlag(name = name, namespace = namespace, default = default)
         flagMap[name] = flag
         return flag
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
index 78a5c98..495367b 100644
--- a/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
+++ b/packages/SystemUI/src/com/android/keyguard/BouncerKeyguardMessageArea.kt
@@ -105,7 +105,7 @@
 
         hideAnimator.addListener(
             object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     super@BouncerKeyguardMessageArea.setMessage(msg, animate)
                 }
             }
@@ -118,7 +118,7 @@
 
         showAnimator.addListener(
             object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     textAboutToShow = null
                 }
             }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 0b9e6e9..2f3c1f2 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -144,7 +144,7 @@
                 smallClockOnAttachStateChangeListener =
                     object : OnAttachStateChangeListener {
                         var pastVisibility: Int? = null
-                        override fun onViewAttachedToWindow(view: View?) {
+                        override fun onViewAttachedToWindow(view: View) {
                             value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
                             if (view != null) {
                                 smallClockFrame = view.parent as FrameLayout
@@ -168,7 +168,7 @@
                             }
                         }
 
-                        override fun onViewDetachedFromWindow(p0: View?) {
+                        override fun onViewDetachedFromWindow(p0: View) {
                             smallClockFrame?.viewTreeObserver
                                     ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
                         }
@@ -178,10 +178,10 @@
 
                 largeClockOnAttachStateChangeListener =
                     object : OnAttachStateChangeListener {
-                        override fun onViewAttachedToWindow(p0: View?) {
+                        override fun onViewAttachedToWindow(p0: View) {
                             value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
                         }
-                        override fun onViewDetachedFromWindow(p0: View?) {
+                        override fun onViewDetachedFromWindow(p0: View) {
                         }
                 }
                 value.largeClock.view
diff --git a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
index 22cdb30..2abb7a4 100644
--- a/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
+++ b/packages/SystemUI/src/com/android/keyguard/FaceAuthReason.kt
@@ -33,6 +33,7 @@
 import com.android.keyguard.InternalFaceAuthReasons.BIOMETRIC_ENABLED
 import com.android.keyguard.InternalFaceAuthReasons.CAMERA_LAUNCHED
 import com.android.keyguard.InternalFaceAuthReasons.DEVICE_WOKEN_UP_ON_REACH_GESTURE
+import com.android.keyguard.InternalFaceAuthReasons.DISPLAY_OFF
 import com.android.keyguard.InternalFaceAuthReasons.DREAM_STARTED
 import com.android.keyguard.InternalFaceAuthReasons.DREAM_STOPPED
 import com.android.keyguard.InternalFaceAuthReasons.ENROLLMENTS_CHANGED
@@ -131,6 +132,7 @@
     const val NON_STRONG_BIOMETRIC_ALLOWED_CHANGED =
         "Face auth stopped because non strong biometric allowed changed"
     const val POSTURE_CHANGED = "Face auth started/stopped due to device posture changed."
+    const val DISPLAY_OFF = "Face auth stopped due to display state OFF."
 }
 
 /**
@@ -221,7 +223,8 @@
     FACE_AUTH_UPDATED_STRONG_AUTH_CHANGED(1255, STRONG_AUTH_ALLOWED_CHANGED),
     @UiEvent(doc = NON_STRONG_BIOMETRIC_ALLOWED_CHANGED)
     FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED(1256, NON_STRONG_BIOMETRIC_ALLOWED_CHANGED),
-    @UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION);
+    @UiEvent(doc = ACCESSIBILITY_ACTION) FACE_AUTH_ACCESSIBILITY_ACTION(1454, ACCESSIBILITY_ACTION),
+    @UiEvent(doc = DISPLAY_OFF) FACE_AUTH_DISPLAY_OFF(1461, DISPLAY_OFF);
 
     override fun getId(): Int = this.id
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 2c224f62..3d48f3c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -244,24 +244,8 @@
             return;
         }
         updateAodIcons();
-
         mStatusArea = mView.findViewById(R.id.keyguard_status_area);
 
-        if (mSmartspaceController.isEnabled()) {
-            View ksv = mView.findViewById(R.id.keyguard_slice_view);
-            int viewIndex = mStatusArea.indexOfChild(ksv);
-            ksv.setVisibility(View.GONE);
-
-            // TODO(b/261757708): add content observer for the Settings toggle and add/remove
-            //  weather according to the Settings.
-            if (mSmartspaceController.isDateWeatherDecoupled()) {
-                addDateWeatherView(viewIndex);
-                viewIndex += 1;
-            }
-
-            addSmartspaceView(viewIndex);
-        }
-
         mSecureSettings.registerContentObserverForUser(
                 Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK,
                 false, /* notifyForDescendants */
@@ -275,13 +259,27 @@
                 mShowWeatherObserver,
                 UserHandle.USER_ALL
         );
-
         updateDoubleLineClock();
-        setDateWeatherVisibility();
-        setWeatherVisibility();
 
         mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
                 mKeyguardUnlockAnimationListener);
+
+        if (mSmartspaceController.isEnabled()) {
+            View ksv = mView.findViewById(R.id.keyguard_slice_view);
+            int viewIndex = mStatusArea.indexOfChild(ksv);
+            ksv.setVisibility(View.GONE);
+
+            mSmartspaceController.removeViewsFromParent(mStatusArea);
+            addSmartspaceView();
+            // TODO(b/261757708): add content observer for the Settings toggle and add/remove
+            //  weather according to the Settings.
+            if (mSmartspaceController.isDateWeatherDecoupled()) {
+                addDateWeatherView();
+            }
+        }
+
+        setDateWeatherVisibility();
+        setWeatherVisibility();
     }
 
     int getNotificationIconAreaHeight() {
@@ -302,29 +300,22 @@
 
     void onLocaleListChanged() {
         if (mSmartspaceController.isEnabled()) {
+            mSmartspaceController.removeViewsFromParent(mStatusArea);
+            addSmartspaceView();
             if (mSmartspaceController.isDateWeatherDecoupled()) {
                 mDateWeatherView.removeView(mWeatherView);
-                int index = mStatusArea.indexOfChild(mDateWeatherView);
-                if (index >= 0) {
-                    mStatusArea.removeView(mDateWeatherView);
-                    addDateWeatherView(index);
-                }
+                addDateWeatherView();
                 setDateWeatherVisibility();
                 setWeatherVisibility();
             }
-            int index = mStatusArea.indexOfChild(mSmartspaceView);
-            if (index >= 0) {
-                mStatusArea.removeView(mSmartspaceView);
-                addSmartspaceView(index);
-            }
         }
     }
 
-    private void addDateWeatherView(int index) {
+    private void addDateWeatherView() {
         mDateWeatherView = (ViewGroup) mSmartspaceController.buildAndConnectDateView(mView);
         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                 MATCH_PARENT, WRAP_CONTENT);
-        mStatusArea.addView(mDateWeatherView, index, lp);
+        mStatusArea.addView(mDateWeatherView, 0, lp);
         int startPadding = getContext().getResources().getDimensionPixelSize(
                 R.dimen.below_clock_padding_start);
         int endPadding = getContext().getResources().getDimensionPixelSize(
@@ -344,11 +335,11 @@
         mWeatherView.setPaddingRelative(0, 0, 4, 0);
     }
 
-    private void addSmartspaceView(int index) {
+    private void addSmartspaceView() {
         mSmartspaceView = mSmartspaceController.buildAndConnectView(mView);
         LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
                 MATCH_PARENT, WRAP_CONTENT);
-        mStatusArea.addView(mSmartspaceView, index, lp);
+        mStatusArea.addView(mSmartspaceView, 0, lp);
         int startPadding = getContext().getResources().getDimensionPixelSize(
                 R.dimen.below_clock_padding_start);
         int endPadding = getContext().getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
index 461d390..bb799fc 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardFaceListenModel.kt
@@ -27,6 +27,7 @@
     override var userId: Int = 0,
     override var listening: Boolean = false,
     // keep sorted
+    var allowedDisplayState: Boolean = false,
     var alternateBouncerShowing: Boolean = false,
     var authInterruptActive: Boolean = false,
     var biometricSettingEnabledForUser: Boolean = false,
@@ -57,6 +58,8 @@
             userId.toString(),
             listening.toString(),
             // keep sorted
+            allowedDisplayState.toString(),
+            alternateBouncerShowing.toString(),
             authInterruptActive.toString(),
             biometricSettingEnabledForUser.toString(),
             bouncerFullyShown.toString(),
@@ -74,7 +77,6 @@
             supportsDetect.toString(),
             switchingUser.toString(),
             systemUser.toString(),
-            alternateBouncerShowing.toString(),
             udfpsFingerDown.toString(),
             userNotTrustedOrDetectionIsNeeded.toString(),
         )
@@ -96,7 +98,9 @@
                 userId = model.userId
                 listening = model.listening
                 // keep sorted
+                allowedDisplayState = model.allowedDisplayState
                 alternateBouncerShowing = model.alternateBouncerShowing
+                authInterruptActive = model.authInterruptActive
                 biometricSettingEnabledForUser = model.biometricSettingEnabledForUser
                 bouncerFullyShown = model.bouncerFullyShown
                 faceAndFpNotAuthenticated = model.faceAndFpNotAuthenticated
@@ -105,7 +109,6 @@
                 faceLockedOut = model.faceLockedOut
                 goingToSleep = model.goingToSleep
                 keyguardAwake = model.keyguardAwake
-                goingToSleep = model.goingToSleep
                 keyguardGoingAway = model.keyguardGoingAway
                 listeningForFaceAssistant = model.listeningForFaceAssistant
                 occludingAppRequestingFaceAuth = model.occludingAppRequestingFaceAuth
@@ -140,6 +143,8 @@
                 "userId",
                 "listening",
                 // keep sorted
+                "allowedDisplayState",
+                "alternateBouncerShowing",
                 "authInterruptActive",
                 "biometricSettingEnabledForUser",
                 "bouncerFullyShown",
@@ -157,7 +162,6 @@
                 "supportsDetect",
                 "switchingUser",
                 "systemUser",
-                "udfpsBouncerShowing",
                 "udfpsFingerDown",
                 "userNotTrustedOrDetectionIsNeeded",
             )
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index 03d9eb3..59ee0d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -32,6 +32,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.content.Context;
+import android.content.res.ColorStateList;
 import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.Trace;
@@ -71,6 +72,8 @@
     private Interpolator mLinearOutSlowInInterpolator;
     private Interpolator mFastOutLinearInInterpolator;
     private DisappearAnimationListener mDisappearAnimationListener;
+    private static final int[] DISABLE_STATE_SET = {-android.R.attr.state_enabled};
+    private static final int[] ENABLE_STATE_SET = {android.R.attr.state_enabled};
 
     public KeyguardPasswordView(Context context) {
         this(context, null);
@@ -148,7 +151,10 @@
 
     @Override
     protected void setPasswordEntryEnabled(boolean enabled) {
-        mPasswordEntry.setEnabled(enabled);
+        int color = mPasswordEntry.getTextColors().getColorForState(
+                enabled ? ENABLE_STATE_SET : DISABLE_STATE_SET, 0);
+        mPasswordEntry.setBackgroundTintList(ColorStateList.valueOf(color));
+        mPasswordEntry.setCursorVisible(enabled);
     }
 
     @Override
@@ -189,17 +195,18 @@
                             if (controller.isCancelled()) {
                                 return;
                             }
+                            float value = (float) animation.getAnimatedValue();
+                            float fraction = anim.getAnimatedFraction();
                             Insets shownInsets = controller.getShownStateInsets();
                             int dist = (int) (-shownInsets.bottom / 4
-                                    * anim.getAnimatedFraction());
+                                    * fraction);
                             Insets insets = Insets.add(shownInsets, Insets.of(0, 0, 0, dist));
                             if (mDisappearAnimationListener != null) {
                                 mDisappearAnimationListener.setTranslationY(-dist);
                             }
 
-                            controller.setInsetsAndAlpha(insets,
-                                    (float) animation.getAnimatedValue(),
-                                    anim.getAnimatedFraction());
+                            controller.setInsetsAndAlpha(insets, value, fraction);
+                            setAlpha(value);
                         });
                         anim.addListener(new AnimatorListenerAdapter() {
                             @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 1f6b09b..5dbd014 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -265,7 +265,8 @@
     private boolean hasMultipleEnabledIMEsOrSubtypes(InputMethodManager imm,
             final boolean shouldIncludeAuxiliarySubtypes) {
         final List<InputMethodInfo> enabledImis =
-                imm.getEnabledInputMethodListAsUser(KeyguardUpdateMonitor.getCurrentUser());
+                imm.getEnabledInputMethodListAsUser(
+                        UserHandle.of(KeyguardUpdateMonitor.getCurrentUser()));
 
         // Number of the filtered IMEs
         int filteredImisCount = 0;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 8a0c9ef..3f3efe9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -100,6 +100,7 @@
 import com.android.systemui.R;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
@@ -666,6 +667,11 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+    }
+
+    @Override
     protected void dispatchDraw(Canvas canvas) {
         super.dispatchDraw(canvas);
         if (mViewMediatorCallback != null) {
@@ -1198,8 +1204,6 @@
                 });
                 mPopup.show();
             });
-
-            mUserSwitcherViewGroup.setAlpha(0f);
         }
 
         @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index aff2591..d9a1dc6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -33,7 +33,6 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Intent;
 import android.content.res.ColorStateList;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.media.AudioManager;
@@ -69,6 +68,7 @@
 import com.android.settingslib.utils.ThreadUtils;
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor;
 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
 import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.biometrics.SideFpsUiRequestSource;
@@ -78,11 +78,10 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.scene.domain.interactor.SceneInteractor;
-import com.android.systemui.scene.shared.model.SceneKey;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -128,6 +127,7 @@
     private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
     private final BouncerMessageInteractor mBouncerMessageInteractor;
     private int mTranslationY;
+    private final KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     // 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
     // the audio service will bring up the volume dialog.
@@ -143,7 +143,7 @@
     private Runnable mCancelAction;
     private boolean mWillRunDismissFromKeyguard;
 
-    private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
+    private int mLastOrientation;
 
     private SecurityMode mCurrentSecurityMode = SecurityMode.Invalid;
     private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
@@ -301,6 +301,10 @@
                     mViewMediatorCallback.keyguardDone(fromPrimaryAuth, targetUserId);
                 }
             }
+
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+            }
         }
 
         @Override
@@ -349,7 +353,14 @@
 
                 @Override
                 public void onDensityOrFontScaleChanged() {
-                    KeyguardSecurityContainerController.this.onDensityOrFontScaleChanged();
+                    KeyguardSecurityContainerController.this
+                            .onDensityOrFontScaleOrOrientationChanged();
+                }
+
+                @Override
+                public void onOrientationChanged(int orientation) {
+                    KeyguardSecurityContainerController.this
+                            .onDensityOrFontScaleOrOrientationChanged();
                 }
             };
     private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
@@ -388,7 +399,7 @@
                 }
             };
     private final UserInteractor mUserInteractor;
-    private final Provider<SceneInteractor> mSceneInteractor;
+    private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
     private final Provider<JavaAdapter> mJavaAdapter;
     @Nullable private Job mSceneTransitionCollectionJob;
 
@@ -419,7 +430,8 @@
             Provider<JavaAdapter> javaAdapter,
             UserInteractor userInteractor,
             FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
-            Provider<SceneInteractor> sceneInteractor
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
+            Provider<AuthenticationInteractor> authenticationInteractor
     ) {
         super(view);
         view.setAccessibilityDelegate(faceAuthAccessibilityDelegate);
@@ -448,8 +460,9 @@
         mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
         mBouncerMessageInteractor = bouncerMessageInteractor;
         mUserInteractor = userInteractor;
-        mSceneInteractor = sceneInteractor;
+        mAuthenticationInteractor = authenticationInteractor;
         mJavaAdapter = javaAdapter;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
     }
 
     @Override
@@ -474,21 +487,21 @@
         showPrimarySecurityScreen(false);
 
         if (mFeatureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
-            // When the scene framework transitions from bouncer to gone, we dismiss the keyguard.
+            // When the scene framework says that the lockscreen has been dismissed, dismiss the
+            // keyguard here, revealing the underlying app or launcher:
             mSceneTransitionCollectionJob = mJavaAdapter.get().alwaysCollectFlow(
-                mSceneInteractor.get().getTransitions(),
-                sceneTransitionModel -> {
-                    if (sceneTransitionModel != null
-                            && sceneTransitionModel.getFrom() == SceneKey.Bouncer.INSTANCE
-                            && sceneTransitionModel.getTo() == SceneKey.Gone.INSTANCE) {
+                mAuthenticationInteractor.get().isLockscreenDismissed(),
+                isLockscreenDismissed -> {
+                    if (isLockscreenDismissed) {
                         final int selectedUserId = mUserInteractor.getSelectedUserId();
                         showNextSecurityScreenOrFinish(
-                                /* authenticated= */ true,
-                                selectedUserId,
-                                /* bypassSecondaryLockScreen= */ true,
-                                mSecurityModel.getSecurityMode(selectedUserId));
+                            /* authenticated= */ true,
+                            selectedUserId,
+                            /* bypassSecondaryLockScreen= */ true,
+                            mSecurityModel.getSecurityMode(selectedUserId));
                     }
-                });
+                }
+            );
         }
     }
 
@@ -677,6 +690,14 @@
         mSecurityViewFlipperController.reset();
     }
 
+    /** Prepares views in the bouncer before starting appear animation. */
+    public void prepareToShow() {
+        View bouncerUserSwitcher = mView.findViewById(R.id.keyguard_bouncer_user_switcher);
+        if (bouncerUserSwitcher != null) {
+            bouncerUserSwitcher.setAlpha(0f);
+        }
+    }
+
     @Override
     public void onResume(int reason) {
         if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
@@ -1147,7 +1168,7 @@
     }
 
     /** Handles density or font scale changes. */
-    private void onDensityOrFontScaleChanged() {
+    private void onDensityOrFontScaleOrOrientationChanged() {
         reinflateViewFlipper(controller -> mView.onDensityOrFontScaleChanged());
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
index 96ac8ad..e1c060f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewTransition.kt
@@ -66,11 +66,11 @@
     }
 
     override fun createAnimator(
-        sceneRoot: ViewGroup?,
+        sceneRoot: ViewGroup,
         startValues: TransitionValues?,
         endValues: TransitionValues?
     ): Animator? {
-        if (sceneRoot == null || startValues == null || endValues == null) {
+        if (startValues == null || endValues == null) {
             return null
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 7585279..5774e42 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -23,12 +23,14 @@
 import android.os.Build;
 import android.os.Trace;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewPropertyAnimator;
 import android.widget.GridLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.statusbar.CrossFadeHelper;
 
 import java.io.PrintWriter;
@@ -110,6 +112,11 @@
         }
     }
 
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+    }
+
     public void dump(PrintWriter pw, String[] args) {
         pw.println("KeyguardStatusView:");
         pw.println("  mDarkAmount: " + mDarkAmount);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 8e92941..757022d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -150,7 +150,8 @@
     @Override
     public void onInit() {
         mKeyguardClockSwitchController.init();
-        mDumpManager.registerDumpable(this);
+
+        mDumpManager.registerDumpable(getInstanceName(), this);
         if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
             startCoroutines(EmptyCoroutineContext.INSTANCE);
         }
@@ -190,7 +191,7 @@
      * Called in notificationPanelViewController to avoid leak
      */
     public void onDestroy() {
-        mDumpManager.unregisterDumpable(TAG);
+        mDumpManager.unregisterDumpable(getInstanceName());
     }
 
     /**
@@ -385,7 +386,7 @@
      * Updates the alignment of the KeyguardStatusView and animates the transition if requested.
      */
     public void updateAlignment(
-            ConstraintLayout notifContainerParent,
+            ConstraintLayout layout,
             boolean splitShadeEnabled,
             boolean shouldBeCentered,
             boolean animate) {
@@ -395,16 +396,23 @@
         }
 
         mStatusViewCentered = shouldBeCentered;
-        if (notifContainerParent == null) {
+        if (layout == null) {
             return;
         }
 
         ConstraintSet constraintSet = new ConstraintSet();
-        constraintSet.clone(notifContainerParent);
-        int statusConstraint = shouldBeCentered ? PARENT_ID : R.id.qs_edge_guideline;
+        constraintSet.clone(layout);
+        int guideline;
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            guideline = R.id.split_shade_guideline;
+        } else {
+            guideline = R.id.qs_edge_guideline;
+        }
+
+        int statusConstraint = shouldBeCentered ? PARENT_ID : guideline;
         constraintSet.connect(R.id.keyguard_status_view, END, statusConstraint, END);
         if (!animate) {
-            constraintSet.applyTo(notifContainerParent);
+            constraintSet.applyTo(layout);
             return;
         }
 
@@ -447,7 +455,7 @@
             // old animation rather than setting up the custom animations.
             if (clockContainerView == null || clockContainerView.getChildCount() == 0) {
                 transition.addListener(mKeyguardStatusAlignmentTransitionListener);
-                TransitionManager.beginDelayedTransition(notifContainerParent, transition);
+                TransitionManager.beginDelayedTransition(layout, transition);
             } else {
                 View clockView = clockContainerView.getChildAt(0);
 
@@ -481,14 +489,14 @@
                 }
 
                 set.addListener(mKeyguardStatusAlignmentTransitionListener);
-                TransitionManager.beginDelayedTransition(notifContainerParent, set);
+                TransitionManager.beginDelayedTransition(layout, set);
             }
         } else {
             transition.addListener(mKeyguardStatusAlignmentTransitionListener);
-            TransitionManager.beginDelayedTransition(notifContainerParent, transition);
+            TransitionManager.beginDelayedTransition(layout, transition);
         }
 
-        constraintSet.applyTo(notifContainerParent);
+        constraintSet.applyTo(layout);
     }
 
     @Override
@@ -496,6 +504,10 @@
         mView.dump(pw, args);
     }
 
+    String getInstanceName() {
+        return TAG + "#" + hashCode();
+    }
+
     @VisibleForTesting
     static class SplitShadeTransitionAdapter extends Transition {
         private static final String PROP_BOUNDS_LEFT = "splitShadeTransitionAdapter:boundsLeft";
@@ -531,7 +543,8 @@
 
         @Nullable
         @Override
-        public Animator createAnimator(ViewGroup sceneRoot, @Nullable TransitionValues startValues,
+        public Animator createAnimator(@NonNull ViewGroup sceneRoot,
+                @Nullable TransitionValues startValues,
                 @Nullable TransitionValues endValues) {
             if (startValues == null || endValues == null) {
                 return null;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index c03053d..853a62a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -41,6 +41,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.keyguard.FaceAuthReasonKt.apiRequestReasonToUiEvent;
+import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_DISPLAY_OFF;
 import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_NON_STRONG_BIOMETRIC_ALLOWED_CHANGED;
 import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_DREAM_STARTED;
 import static com.android.keyguard.FaceAuthUiEvent.FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED;
@@ -135,6 +136,7 @@
 import android.text.TextUtils;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
+import android.view.Display;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -175,6 +177,7 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.WeatherData;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -335,6 +338,25 @@
             }
         }
     };
+    private final DisplayTracker.Callback mDisplayCallback = new DisplayTracker.Callback() {
+        @Override
+        public void onDisplayChanged(int displayId) {
+            if (displayId != Display.DEFAULT_DISPLAY) {
+                return;
+            }
+
+            if (mDisplayTracker.getDisplay(mDisplayTracker.getDefaultDisplayId()).getState()
+                    == Display.STATE_OFF) {
+                mAllowedDisplayStateForFaceAuth = false;
+                updateFaceListeningState(
+                        BIOMETRIC_ACTION_STOP,
+                        FACE_AUTH_DISPLAY_OFF
+                );
+            } else {
+                mAllowedDisplayStateForFaceAuth = true;
+            }
+        }
+    };
     private final FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
 
     HashMap<Integer, SimData> mSimDatas = new HashMap<>();
@@ -355,6 +377,7 @@
     private boolean mOccludingAppRequestingFp;
     private boolean mOccludingAppRequestingFace;
     private boolean mSecureCameraLaunched;
+    private boolean mAllowedDisplayStateForFaceAuth = true;
     @VisibleForTesting
     protected boolean mTelephonyCapable;
     private boolean mAllowFingerprintOnCurrentOccludingActivity;
@@ -403,6 +426,7 @@
     private KeyguardFaceAuthInteractor mFaceAuthInteractor;
     private final TaskStackChangeListeners mTaskStackChangeListeners;
     private final IActivityTaskManager mActivityTaskManager;
+    private final DisplayTracker mDisplayTracker;
     private final LockPatternUtils mLockPatternUtils;
     @VisibleForTesting
     @DevicePostureInt
@@ -2187,6 +2211,7 @@
         Trace.beginSection("KeyguardUpdateMonitor#handleStartedWakingUp");
         Assert.isMainThread();
 
+        mAllowedDisplayStateForFaceAuth = true;
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
         if (mFaceWakeUpTriggersConfig.shouldTriggerFaceAuthOnWakeUpFrom(pmWakeReason)) {
             FACE_AUTH_UPDATED_STARTED_WAKING_UP.setExtraInfo(pmWakeReason);
@@ -2342,7 +2367,8 @@
             Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider,
             FeatureFlags featureFlags,
             TaskStackChangeListeners taskStackChangeListeners,
-            IActivityTaskManager activityTaskManagerService) {
+            IActivityTaskManager activityTaskManagerService,
+            DisplayTracker displayTracker) {
         mContext = context;
         mSubscriptionManager = subscriptionManager;
         mUserTracker = userTracker;
@@ -2390,6 +2416,10 @@
                 .collect(Collectors.toSet());
         mTaskStackChangeListeners = taskStackChangeListeners;
         mActivityTaskManager = activityTaskManagerService;
+        mDisplayTracker = displayTracker;
+        if (mFeatureFlags.isEnabled(Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF)) {
+            mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
+        }
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -3199,7 +3229,8 @@
                 && (!mSecureCameraLaunched || mAlternateBouncerShowing)
                 && faceAndFpNotAuthenticated
                 && !mGoingToSleep
-                && isPostureAllowedForFaceAuth;
+                && isPostureAllowedForFaceAuth
+                && mAllowedDisplayStateForFaceAuth;
 
         // Aggregate relevant fields for debug logging.
         logListenerModelData(
@@ -3207,6 +3238,7 @@
                     System.currentTimeMillis(),
                     user,
                     shouldListen,
+                    mAllowedDisplayStateForFaceAuth,
                     mAlternateBouncerShowing,
                     mAuthInterruptActive,
                     biometricEnabledForUser,
@@ -4400,6 +4432,7 @@
 
         mLockPatternUtils.unregisterStrongAuthTracker(mStrongAuthTracker);
         mTrustManager.unregisterTrustListener(this);
+        mDisplayTracker.removeCallback(mDisplayCallback);
 
         mHandler.removeCallbacksAndMessages(null);
     }
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
index 1a0c7f9..8611dbbb 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java
@@ -22,6 +22,7 @@
 import android.content.res.ColorStateList;
 import android.graphics.Color;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
@@ -154,11 +155,16 @@
     }
 
     float getLocationTop() {
-        return mLockIconCenter.y - mRadius;
+        Rect r = new Rect();
+        mLockIcon.getGlobalVisibleRect(r);
+        return r.top;
     }
 
     float getLocationBottom() {
-        return mLockIconCenter.y + mRadius;
+        Rect r = new Rect();
+        mLockIcon.getGlobalVisibleRect(r);
+        return r.bottom;
+
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 4845a61..951a6ae 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -25,6 +25,7 @@
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.content.res.Configuration;
@@ -39,6 +40,7 @@
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.MathUtils;
+import android.view.HapticFeedbackConstants;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -613,12 +615,7 @@
             case MotionEvent.ACTION_DOWN:
             case MotionEvent.ACTION_HOVER_ENTER:
                 if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) {
-                    mVibrator.vibrate(
-                            Process.myUid(),
-                            getContext().getOpPackageName(),
-                            UdfpsController.EFFECT_CLICK,
-                            "lock-icon-down",
-                            TOUCH_VIBRATION_ATTRIBUTES);
+                    vibrateOnTouchExploration();
                 }
 
                 // The pointer that causes ACTION_DOWN is always at index 0.
@@ -699,13 +696,8 @@
             mOnGestureDetectedRunnable.run();
         }
 
-        // play device entry haptic (same as biometric success haptic)
-        mVibrator.vibrate(
-                Process.myUid(),
-                getContext().getOpPackageName(),
-                UdfpsController.EFFECT_CLICK,
-                "lock-screen-lock-icon-longpress",
-                TOUCH_VIBRATION_ATTRIBUTES);
+        // play device entry haptic (consistent with UDFPS controller longpress)
+        vibrateOnLongPress();
 
         mKeyguardViewController.showPrimaryBouncer(/* scrim */ true);
     }
@@ -753,6 +745,37 @@
         });
     }
 
+    @VisibleForTesting
+    void vibrateOnTouchExploration() {
+        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            mVibrator.performHapticFeedback(
+                    mView,
+                    HapticFeedbackConstants.CONTEXT_CLICK
+            );
+        } else {
+            mVibrator.vibrate(
+                    Process.myUid(),
+                    getContext().getOpPackageName(),
+                    UdfpsController.EFFECT_CLICK,
+                    "lock-icon-down",
+                    TOUCH_VIBRATION_ATTRIBUTES);
+        }
+    }
+
+    @VisibleForTesting
+    void vibrateOnLongPress() {
+        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            mVibrator.performHapticFeedback(mView, UdfpsController.LONG_PRESS);
+        } else {
+            mVibrator.vibrate(
+                    Process.myUid(),
+                    getContext().getOpPackageName(),
+                    UdfpsController.EFFECT_CLICK,
+                    "lock-screen-lock-icon-longpress",
+                    TOUCH_VIBRATION_ATTRIBUTES);
+        }
+    }
+
     private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() {
         @Override
         public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) {
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
index a04a48d..e773416 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java
@@ -58,6 +58,7 @@
     private float mStartRadius;
     private float mEndRadius;
     private int mHeight;
+    private int mWidth;
 
     private static final int EXPAND_ANIMATION_MS = 100;
     private static final int EXPAND_COLOR_ANIMATION_MS = 50;
@@ -95,11 +96,17 @@
         mBackground.setCornerRadius(mEndRadius + (mStartRadius - mEndRadius) * progress);
         int height = (int) (mHeight * 0.7f + mHeight * 0.3 * progress);
         int difference = mHeight - height;
-        mBackground.setBounds(0, difference / 2, mHeight, mHeight - difference / 2);
+
+        int left = 0;
+        int top = difference / 2;
+        int right = mWidth;
+        int bottom = mHeight - difference / 2;
+        mBackground.setBounds(left, top, right, bottom);
     }
 
-    void onLayout(int height) {
+    void onLayout(int width, int height) {
         boolean shouldUpdateHeight = height != mHeight;
+        mWidth = width;
         mHeight = height;
         mStartRadius = height / 2f;
         mEndRadius = height / 4f;
@@ -121,7 +128,7 @@
         ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle);
         @SuppressLint("ResourceType") TypedArray a = ctw.obtainStyledAttributes(customAttrs);
         mNormalBackgroundColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0,
-                       NUM_PAD_BACKGROUND);
+                NUM_PAD_BACKGROUND);
         a.recycle();
 
         mPressedBackgroundColor = getColorAttrDefaultColor(context, NUM_PAD_BACKGROUND_PRESSED);
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
index 3f1741a6..5c2f3b3 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java
@@ -74,8 +74,9 @@
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
-
-        if (mAnimator != null) mAnimator.onLayout(b - t);
+        int width = r - l;
+        int height = b - t;
+        if (mAnimator != null) mAnimator.onLayout(width, height);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
index edc298c..466d154 100644
--- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
+++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java
@@ -211,7 +211,9 @@
         left = centerX - mKlondikeText.getMeasuredWidth() / 2;
         mKlondikeText.layout(left, top, left + mKlondikeText.getMeasuredWidth(), bottom);
 
-        if (mAnimator != null) mAnimator.onLayout(b - t);
+        int width = r - l;
+        int height = b - t;
+        if (mAnimator != null) mAnimator.onLayout(width, height);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index b01e136..0180384 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -130,14 +130,14 @@
 import com.android.systemui.util.leak.LeakReporter;
 import com.android.systemui.util.sensors.AsyncSensorManager;
 
-import dagger.Lazy;
-
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
 import javax.inject.Named;
 
+import dagger.Lazy;
+
 /**
  * Class to handle ugly dependencies throughout sysui until we determine the
  * long-term dependency injection solution.
@@ -278,7 +278,6 @@
     @Inject Lazy<AccessibilityManagerWrapper> mAccessibilityManagerWrapper;
     @Inject Lazy<SysuiColorExtractor> mSysuiColorExtractor;
     @Inject Lazy<TunablePaddingService> mTunablePaddingService;
-    @Inject Lazy<ForegroundServiceController> mForegroundServiceController;
     @Inject Lazy<UiOffloadThread> mUiOffloadThread;
     @Inject Lazy<PowerUI.WarningsUI> mWarningsUI;
     @Inject Lazy<LightBarController> mLightBarController;
@@ -456,8 +455,6 @@
 
         mProviders.put(TunablePaddingService.class, mTunablePaddingService::get);
 
-        mProviders.put(ForegroundServiceController.class, mForegroundServiceController::get);
-
         mProviders.put(UiOffloadThread.class, mUiOffloadThread::get);
 
         mProviders.put(PowerUI.WarningsUI.class, mWarningsUI::get);
diff --git a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
index 403c809..95e2dba 100644
--- a/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt
@@ -36,6 +36,8 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.settingslib.Utils
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.asIndenting
@@ -54,6 +56,7 @@
     val mainExecutor: Executor,
     val logger: ScreenDecorationsLogger,
     val authController: AuthController,
+    val featureFlags: FeatureFlags,
 ) : ScreenDecorations.DisplayCutoutView(context, pos) {
     private var showScanningAnim = false
     private val rimPaint = Paint()
@@ -294,6 +297,15 @@
     }
 
     private fun createFaceScanningRimAnimator(): AnimatorSet {
+        val dontPulse = featureFlags.isEnabled(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION)
+        if (dontPulse) {
+            return AnimatorSet().apply {
+                playSequentially(
+                        cameraProtectionAnimator,
+                        createRimAppearAnimator(),
+                )
+            }
+        }
         return AnimatorSet().apply {
             playSequentially(
                 cameraProtectionAnimator,
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
deleted file mode 100644
index 15e8c4e..0000000
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceController.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2017 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.annotation.Nullable;
-import android.app.AppOpsManager;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
-import android.util.SparseArray;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.util.Assert;
-
-import javax.inject.Inject;
-
-/**
- * Tracks state of foreground services and notifications related to foreground services per user.
- */
-@SysUISingleton
-public class ForegroundServiceController {
-    public static final int[] APP_OPS = new int[] {AppOpsManager.OP_SYSTEM_ALERT_WINDOW};
-
-    private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>();
-    private final Object mMutex = new Object();
-    private final Handler mMainHandler;
-
-    @Inject
-    public ForegroundServiceController(
-            AppOpsController appOpsController,
-            @Main Handler mainHandler) {
-        mMainHandler = mainHandler;
-        appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> {
-            mMainHandler.post(() -> {
-                onAppOpChanged(code, uid, packageName, active);
-            });
-        });
-    }
-
-    /**
-     * @return true if this user has services missing notifications and therefore needs a
-     * disclosure notification for running a foreground service.
-     */
-    public boolean isDisclosureNeededForUser(int userId) {
-        synchronized (mMutex) {
-            final ForegroundServicesUserState services = mUserServices.get(userId);
-            if (services == null) return false;
-            return services.isDisclosureNeeded();
-        }
-    }
-
-    /**
-     * @return true if this user/pkg has a missing or custom layout notification and therefore needs
-     * a disclosure notification showing the user which appsOps the app is using.
-     */
-    public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
-        synchronized (mMutex) {
-            final ForegroundServicesUserState services = mUserServices.get(userId);
-            if (services == null) return false;
-            return services.getStandardLayoutKeys(pkg) == null;
-        }
-    }
-
-    /**
-     * Gets active app ops for this user and package
-     */
-    @Nullable
-    public ArraySet<Integer> getAppOps(int userId, String pkg) {
-        synchronized (mMutex) {
-            final ForegroundServicesUserState services = mUserServices.get(userId);
-            if (services == null) {
-                return null;
-            }
-            return services.getFeatures(pkg);
-        }
-    }
-
-    /**
-     * Records active app ops and updates the app op for the pending or visible notifications
-     * with the given parameters.
-     * App Ops are stored in FSC in addition to NotificationEntry in case they change before we
-     * have a notification to tag.
-     * @param appOpCode code for appOp to add/remove
-     * @param uid of user the notification is sent to
-     * @param packageName package that created the notification
-     * @param active whether the appOpCode is active or not
-     */
-    void onAppOpChanged(int appOpCode, int uid, String packageName, boolean active) {
-        Assert.isMainThread();
-
-        int userId = UserHandle.getUserId(uid);
-        // Record active app ops
-        synchronized (mMutex) {
-            ForegroundServicesUserState userServices = mUserServices.get(userId);
-            if (userServices == null) {
-                userServices = new ForegroundServicesUserState();
-                mUserServices.put(userId, userServices);
-            }
-            if (active) {
-                userServices.addOp(packageName, appOpCode);
-            } else {
-                userServices.removeOp(packageName, appOpCode);
-            }
-        }
-    }
-
-    /**
-     * Looks up the {@link ForegroundServicesUserState} for the given {@code userId}, then performs
-     * the given {@link UserStateUpdateCallback} on it.  If no state exists for the user ID, creates
-     * a new one if {@code createIfNotFound} is true, then performs the update on the new state.
-     * If {@code createIfNotFound} is false, no update is performed.
-     *
-     * @return false if no user state was found and none was created; true otherwise.
-     */
-    boolean updateUserState(int userId,
-            UserStateUpdateCallback updateCallback,
-            boolean createIfNotFound) {
-        synchronized (mMutex) {
-            ForegroundServicesUserState userState = mUserServices.get(userId);
-            if (userState == null) {
-                if (createIfNotFound) {
-                    userState = new ForegroundServicesUserState();
-                    mUserServices.put(userId, userState);
-                } else {
-                    return false;
-                }
-            }
-            return updateCallback.updateUserState(userState);
-        }
-    }
-
-    /**
-     * @return true if {@code sbn} is the system-provided disclosure notification containing the
-     * list of running foreground services.
-     */
-    public boolean isDisclosureNotification(StatusBarNotification sbn) {
-        return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
-                && sbn.getTag() == null
-                && sbn.getPackageName().equals("android");
-    }
-
-    /**
-     * @return true if sbn is one of the window manager "drawing over other apps" notifications
-     */
-    public boolean isSystemAlertNotification(StatusBarNotification sbn) {
-        return sbn.getPackageName().equals("android")
-                && sbn.getTag() != null
-                && sbn.getTag().contains("AlertWindowNotification");
-    }
-
-    /**
-     * Callback provided to {@link #updateUserState(int, UserStateUpdateCallback, boolean)}
-     * to perform the update.
-     */
-    interface UserStateUpdateCallback {
-        /**
-         * Perform update operations on the provided {@code userState}.
-         *
-         * @return true if the update succeeded.
-         */
-        boolean updateUserState(ForegroundServicesUserState userState);
-
-        /** Called if the state was not found and was not created. */
-        default void userStateNotFound(int userId) {
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
deleted file mode 100644
index a1a3b72..0000000
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2018 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.app.Notification;
-import android.app.NotificationManager;
-import android.content.Context;
-import android.os.Bundle;
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-
-import javax.inject.Inject;
-
-/** Updates foreground service notification state in response to notification data events. */
-@SysUISingleton
-public class ForegroundServiceNotificationListener {
-
-    private static final String TAG = "FgServiceController";
-    private static final boolean DBG = false;
-
-    private final Context mContext;
-    private final ForegroundServiceController mForegroundServiceController;
-    private final NotifPipeline mNotifPipeline;
-
-    @Inject
-    public ForegroundServiceNotificationListener(Context context,
-            ForegroundServiceController foregroundServiceController,
-            NotifPipeline notifPipeline) {
-        mContext = context;
-        mForegroundServiceController = foregroundServiceController;
-        mNotifPipeline = notifPipeline;
-    }
-
-    /** Initializes this listener by connecting it to the notification pipeline. */
-    public void init() {
-        mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
-            @Override
-            public void onEntryAdded(NotificationEntry entry) {
-                addNotification(entry, entry.getImportance());
-            }
-
-            @Override
-            public void onEntryUpdated(NotificationEntry entry) {
-                updateNotification(entry, entry.getImportance());
-            }
-
-            @Override
-            public void onEntryRemoved(NotificationEntry entry, int reason) {
-                removeNotification(entry.getSbn());
-            }
-        });
-    }
-
-    /**
-     * @param entry notification that was just posted
-     */
-    private void addNotification(NotificationEntry entry, int importance) {
-        updateNotification(entry, importance);
-    }
-
-    /**
-     * @param sbn notification that was just removed
-     */
-    private void removeNotification(StatusBarNotification sbn) {
-        mForegroundServiceController.updateUserState(
-                sbn.getUserId(),
-                new ForegroundServiceController.UserStateUpdateCallback() {
-                    @Override
-                    public boolean updateUserState(ForegroundServicesUserState userState) {
-                        if (mForegroundServiceController.isDisclosureNotification(sbn)) {
-                            // if you remove the dungeon entirely, we take that to mean there are
-                            // no running services
-                            userState.setRunningServices(null, 0);
-                            return true;
-                        } else {
-                            // this is safe to call on any notification, not just
-                            // FLAG_FOREGROUND_SERVICE
-                            return userState.removeNotification(sbn.getPackageName(), sbn.getKey());
-                        }
-                    }
-
-                    @Override
-                    public void userStateNotFound(int userId) {
-                        if (DBG) {
-                            Log.w(TAG, String.format(
-                                    "user %d with no known notifications got removeNotification "
-                                            + "for %s",
-                                    sbn.getUserId(), sbn));
-                        }
-                    }
-                },
-                false /* don't create */);
-    }
-
-    /**
-     * @param entry notification that was just changed in some way
-     */
-    private void updateNotification(NotificationEntry entry, int newImportance) {
-        final StatusBarNotification sbn = entry.getSbn();
-        mForegroundServiceController.updateUserState(
-                sbn.getUserId(),
-                userState -> {
-                    if (mForegroundServiceController.isDisclosureNotification(sbn)) {
-                        final Bundle extras = sbn.getNotification().extras;
-                        if (extras != null) {
-                            final String[] svcs = extras.getStringArray(
-                                    Notification.EXTRA_FOREGROUND_APPS);
-                            userState.setRunningServices(svcs, sbn.getNotification().when);
-                        }
-                    } else {
-                        userState.removeNotification(sbn.getPackageName(), sbn.getKey());
-                        if (0 != (sbn.getNotification().flags
-                                & Notification.FLAG_FOREGROUND_SERVICE)) {
-                            if (newImportance > NotificationManager.IMPORTANCE_MIN) {
-                                userState.addImportantNotification(sbn.getPackageName(),
-                                        sbn.getKey());
-                            }
-                        }
-                        final Notification.Builder builder =
-                                Notification.Builder.recoverBuilder(
-                                        mContext, sbn.getNotification());
-                        if (builder.usesStandardHeader()) {
-                            userState.addStandardLayoutNotification(
-                                    sbn.getPackageName(), sbn.getKey());
-                        }
-                    }
-                    return true;
-                },
-                true /* create if not found */);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/LatencyTester.java b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
index 6ea0fc3..b33d501 100644
--- a/packages/SystemUI/src/com/android/systemui/LatencyTester.java
+++ b/packages/SystemUI/src/com/android/systemui/LatencyTester.java
@@ -80,7 +80,6 @@
 
     @Override
     public void start() {
-        updateEnabled();
     }
 
     private void fakeWakeAndUnlock(BiometricSourceType type) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index 4a31f3d..f1cebba 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -1476,6 +1476,12 @@
     private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate {
 
         private CharSequence getClickAccessibilityActionLabel() {
+            if (mEditSizeEnable) {
+                // Perform click action to exit edit mode
+                return mContext.getResources().getString(
+                        R.string.magnification_exit_edit_mode_click_label);
+            }
+
             return mSettingsPanelVisibility
                     ? mContext.getResources().getString(
                             R.string.magnification_close_settings_click_label)
@@ -1518,8 +1524,14 @@
 
         private boolean performA11yAction(int action) {
             if (action == AccessibilityAction.ACTION_CLICK.getId()) {
-                // Simulate tapping the drag view so it opens the Settings.
-                handleSingleTap(mDragView);
+                if (mEditSizeEnable) {
+                    // When edit mode is enabled, click the magnifier to exit edit mode.
+                    setEditMagnifierSizeMode(false);
+                } else {
+                    // Simulate tapping the drag view so it opens the Settings.
+                    handleSingleTap(mDragView);
+                }
+
             } else if (action == R.id.accessibility_action_zoom_in) {
                 performScale(mScale + A11Y_CHANGE_SCALE_DIFFERENCE);
             } else if (action == R.id.accessibility_action_zoom_out) {
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 9708d9a..c28a9eb 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -20,6 +20,7 @@
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
 import static android.media.AudioManager.ACTION_MICROPHONE_MUTE_CHANGED;
 
+import android.annotation.Nullable;
 import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -33,6 +34,7 @@
 import android.permission.PermissionManager;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Slog;
 import android.util.SparseArray;
 
 import androidx.annotation.WorkerThread;
@@ -96,19 +98,58 @@
     private final SparseArray<ArrayList<AudioRecordingConfiguration>> mRecordingsByUid =
             new SparseArray<>();
 
-    protected static final int[] OPS = new int[] {
-            AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
-            AppOpsManager.OP_CAMERA,
-            AppOpsManager.OP_PHONE_CALL_CAMERA,
-            AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
+    @VisibleForTesting
+    protected static final int[] OPS_MIC = new int[] {
             AppOpsManager.OP_RECORD_AUDIO,
-            AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
-            AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
             AppOpsManager.OP_PHONE_CALL_MICROPHONE,
-            AppOpsManager.OP_COARSE_LOCATION,
-            AppOpsManager.OP_FINE_LOCATION
+            AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
+            AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO
     };
 
+    protected static final int[] OPS_CAMERA = new int[] {
+            AppOpsManager.OP_CAMERA,
+            AppOpsManager.OP_PHONE_CALL_CAMERA
+    };
+
+    protected static final int[] OPS_LOC = new int[] {
+            AppOpsManager.OP_FINE_LOCATION,
+            AppOpsManager.OP_COARSE_LOCATION,
+            AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION
+    };
+
+    protected static final int[] OPS_OTHERS = new int[] {
+            AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
+            AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO
+    };
+
+
+   protected static final int[] OPS = concatOps(OPS_MIC, OPS_CAMERA, OPS_LOC, OPS_OTHERS);
+
+    /**
+     * @param opArrays the given op arrays.
+     * @return the concatenations of the given op arrays. Null arrays are treated as empty.
+     */
+    private static int[] concatOps(@Nullable int[]...opArrays) {
+        if (opArrays == null) {
+            return new int[0];
+        }
+        int totalLength = 0;
+        for (int[] opArray : opArrays) {
+            if (opArray == null || opArray.length == 0) {
+                continue;
+            }
+            totalLength += opArray.length;
+        }
+        final int[] concatOps = new int[totalLength];
+        int index = 0;
+        for (int[] opArray : opArrays) {
+            if (opArray == null || opArray.length == 0) continue;
+            System.arraycopy(opArray, 0, concatOps, index, opArray.length);
+            index += opArray.length;
+        }
+        return concatOps;
+    }
+
     @Inject
     public AppOpsControllerImpl(
             Context context,
@@ -533,12 +574,17 @@
     }
 
     private boolean isOpCamera(int op) {
-        return op == AppOpsManager.OP_CAMERA || op == AppOpsManager.OP_PHONE_CALL_CAMERA;
+        for (int i = 0; i < OPS_CAMERA.length; i++) {
+            if (op == OPS_CAMERA[i]) return true;
+        }
+        return false;
     }
 
     private boolean isOpMicrophone(int op) {
-        return op == AppOpsManager.OP_RECORD_AUDIO || op == AppOpsManager.OP_PHONE_CALL_MICROPHONE
-                || op == AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO;
+        for (int i = 0; i < OPS_MIC.length; i++) {
+            if (op == OPS_MIC[i]) return true;
+        }
+        return false;
     }
 
     protected class H extends Handler {
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt
similarity index 73%
copy from packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
copy to packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt
index 97c6697..6d23b11 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/model/AuthenticationMethodModel.kt
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.authentication.shared.model
+package com.android.systemui.authentication.data.model
 
 /** Enumerates all known authentication methods. */
 sealed class AuthenticationMethodModel(
@@ -29,17 +29,9 @@
     /** There is no authentication method on the device. We shouldn't even show the lock screen. */
     object None : AuthenticationMethodModel(isSecure = false)
 
-    /** The most basic authentication method. The lock screen can be swiped away when displayed. */
-    object Swipe : AuthenticationMethodModel(isSecure = false)
-
     object Pin : AuthenticationMethodModel(isSecure = true)
 
     object Password : AuthenticationMethodModel(isSecure = true)
 
-    object Pattern : AuthenticationMethodModel(isSecure = true) {
-        data class PatternCoordinate(
-            val x: Int,
-            val y: Int,
-        )
-    }
+    object Pattern : AuthenticationMethodModel(isSecure = true)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index deb3d03..8d1fc5d 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -14,15 +14,21 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.authentication.data.repository
 
+import android.app.admin.DevicePolicyManager
+import android.content.IntentFilter
+import android.os.UserHandle
 import com.android.internal.widget.LockPatternChecker
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.KeyguardSecurityModel
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationResultModel
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
+import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
@@ -37,13 +43,17 @@
 import kotlin.coroutines.suspendCoroutine
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
@@ -54,9 +64,10 @@
      * Whether the device is unlocked.
      *
      * A device that is not yet unlocked requires unlocking by completing an authentication
-     * challenge according to the current authentication method.
-     *
-     * Note that this state has no real bearing on whether the lockscreen is showing or dismissed.
+     * challenge according to the current authentication method, unless in cases when the current
+     * authentication method is not "secure" (for example, None); in such cases, the value of this
+     * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed
+     * by the user to proceed.
      */
     val isUnlocked: StateFlow<Boolean>
 
@@ -86,8 +97,29 @@
     val throttling: StateFlow<AuthenticationThrottlingModel>
 
     /**
+     * The currently-configured authentication method. This determines how the authentication
+     * challenge needs to be completed in order to unlock an otherwise locked device.
+     *
+     * Note: there may be other ways to unlock the device that "bypass" the need for this
+     * authentication challenge (notably, biometrics like fingerprint or face unlock).
+     *
+     * Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a
+     * snapshot of the current authentication method without establishing a collector of the flow
+     * can do so by invoking [getAuthenticationMethod].
+     */
+    val authenticationMethod: Flow<AuthenticationMethodModel>
+
+    /**
      * Returns the currently-configured authentication method. This determines how the
-     * authentication challenge is completed in order to unlock an otherwise locked device.
+     * authentication challenge needs to be completed in order to unlock an otherwise locked device.
+     *
+     * Note: there may be other ways to unlock the device that "bypass" the need for this
+     * authentication challenge (notably, biometrics like fingerprint or face unlock).
+     *
+     * Note: by design, this is offered as a convenience method alongside [authenticationMethod].
+     * The flow should be used for code that wishes to stay up-to-date its logic as the
+     * authentication changes over time and this method should be used for simple code that only
+     * needs to check the current value.
      */
     suspend fun getAuthenticationMethod(): AuthenticationMethodModel
 
@@ -141,6 +173,7 @@
     private val userRepository: UserRepository,
     keyguardRepository: KeyguardRepository,
     private val lockPatternUtils: LockPatternUtils,
+    broadcastDispatcher: BroadcastDispatcher,
 ) : AuthenticationRepository {
 
     override val isUnlocked = keyguardRepository.isKeyguardUnlocked
@@ -148,7 +181,7 @@
     override suspend fun isLockscreenEnabled(): Boolean {
         return withContext(backgroundDispatcher) {
             val selectedUserId = userRepository.selectedUserId
-            !lockPatternUtils.isLockPatternEnabled(selectedUserId)
+            !lockPatternUtils.isLockScreenDisabled(selectedUserId)
         }
     }
 
@@ -172,18 +205,31 @@
     private val UserRepository.selectedUserId: Int
         get() = getSelectedUserInfo().id
 
+    override val authenticationMethod: Flow<AuthenticationMethodModel> =
+        userRepository.selectedUserInfo
+            .map { it.id }
+            .distinctUntilChanged()
+            .flatMapLatest { selectedUserId ->
+                broadcastDispatcher
+                    .broadcastFlow(
+                        filter =
+                            IntentFilter(
+                                DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
+                            ),
+                        user = UserHandle.of(selectedUserId),
+                    )
+                    .onStart { emit(Unit) }
+                    .map { selectedUserId }
+            }
+            .map { selectedUserId ->
+                withContext(backgroundDispatcher) {
+                    blockingAuthenticationMethodInternal(selectedUserId)
+                }
+            }
+
     override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
         return withContext(backgroundDispatcher) {
-            val selectedUserId = userRepository.selectedUserId
-            when (getSecurityMode.apply(selectedUserId)) {
-                KeyguardSecurityModel.SecurityMode.PIN,
-                KeyguardSecurityModel.SecurityMode.SimPin,
-                KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
-                KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
-                KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
-                KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
-                KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!")
-            }
+            blockingAuthenticationMethodInternal(userRepository.selectedUserId)
         }
     }
 
@@ -301,6 +347,27 @@
 
         return flow.asStateFlow()
     }
+
+    /**
+     * Returns the authentication method for the given user ID.
+     *
+     * WARNING: this is actually a blocking IPC/"binder" call that's expensive to do on the main
+     * thread. We keep it not marked as `suspend` because we want to be able to run this without a
+     * `runBlocking` which has a ton of performance/blocking problems.
+     */
+    private fun blockingAuthenticationMethodInternal(
+        userId: Int,
+    ): AuthenticationMethodModel {
+        return when (getSecurityMode.apply(userId)) {
+            KeyguardSecurityModel.SecurityMode.PIN,
+            KeyguardSecurityModel.SecurityMode.SimPin,
+            KeyguardSecurityModel.SecurityMode.SimPuk -> AuthenticationMethodModel.Pin
+            KeyguardSecurityModel.SecurityMode.Password -> AuthenticationMethodModel.Password
+            KeyguardSecurityModel.SecurityMode.Pattern -> AuthenticationMethodModel.Pattern
+            KeyguardSecurityModel.SecurityMode.None -> AuthenticationMethodModel.None
+            KeyguardSecurityModel.SecurityMode.Invalid -> error("Invalid security mode!")
+        }
+    }
 }
 
 @Module
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index d4371bf..ecd7bae 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -18,13 +18,17 @@
 
 import com.android.internal.widget.LockPatternView
 import com.android.internal.widget.LockscreenCredential
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
@@ -35,9 +39,12 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.async
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
@@ -53,29 +60,91 @@
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val userRepository: UserRepository,
     private val keyguardRepository: KeyguardRepository,
+    sceneInteractor: SceneInteractor,
     private val clock: SystemClock,
 ) {
     /**
+     * The currently-configured authentication method. This determines how the authentication
+     * challenge needs to be completed in order to unlock an otherwise locked device.
+     *
+     * Note: there may be other ways to unlock the device that "bypass" the need for this
+     * authentication challenge (notably, biometrics like fingerprint or face unlock).
+     *
+     * Note: by design, this is a [Flow] and not a [StateFlow]; a consumer who wishes to get a
+     * snapshot of the current authentication method without establishing a collector of the flow
+     * can do so by invoking [getAuthenticationMethod].
+     *
+     * Note: this layer adds the synthetic authentication method of "swipe" which is special. When
+     * the current authentication method is "swipe", the user does not need to complete any
+     * authentication challenge to unlock the device; they just need to dismiss the lockscreen to
+     * get past it. This also means that the value of [isUnlocked] remains `false` even when the
+     * lockscreen is showing and still needs to be dismissed by the user to proceed.
+     */
+    val authenticationMethod: Flow<DomainLayerAuthenticationMethodModel> =
+        repository.authenticationMethod.map { rawModel -> rawModel.toDomainLayer() }
+
+    /**
      * Whether the device is unlocked.
      *
      * A device that is not yet unlocked requires unlocking by completing an authentication
-     * challenge according to the current authentication method.
-     *
-     * Note that this state has no real bearing on whether the lock screen is showing or dismissed.
+     * challenge according to the current authentication method, unless in cases when the current
+     * authentication method is not "secure" (for example, None and Swipe); in such cases, the value
+     * of this flow will always be `true`, even if the lockscreen is showing and still needs to be
+     * dismissed by the user to proceed.
      */
     val isUnlocked: StateFlow<Boolean> =
-        repository.isUnlocked
-            .map { isUnlocked ->
-                if (getAuthenticationMethod() is AuthenticationMethodModel.None) {
-                    true
-                } else {
-                    isUnlocked
-                }
+        combine(
+                repository.isUnlocked,
+                authenticationMethod,
+            ) { isUnlocked, authenticationMethod ->
+                !authenticationMethod.isSecure || isUnlocked
             }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
-                initialValue = true,
+                initialValue = false,
+            )
+
+    /**
+     * Whether the lockscreen has been dismissed (by any method). This can be false even when the
+     * device is unlocked, e.g. when swipe to unlock is enabled.
+     *
+     * Note:
+     * - `false` doesn't mean the lockscreen is visible (it may be occluded or covered by other UI).
+     * - `true` doesn't mean the lockscreen is invisible (since this state changes before the
+     *   transition occurs).
+     */
+    val isLockscreenDismissed: StateFlow<Boolean> =
+        sceneInteractor.desiredScene
+            .map { it.key }
+            .filter { currentScene ->
+                currentScene == SceneKey.Gone || currentScene == SceneKey.Lockscreen
+            }
+            .map { it == SceneKey.Gone }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    /**
+     * Whether it's currently possible to swipe up to dismiss the lockscreen without requiring
+     * authentication. This returns false whenever the lockscreen has been dismissed.
+     *
+     * Note: `true` doesn't mean the lockscreen is visible. It may be occluded or covered by other
+     * UI.
+     */
+    val canSwipeToDismiss =
+        combine(authenticationMethod, isLockscreenDismissed) {
+                authenticationMethod,
+                isLockscreenDismissed ->
+                authenticationMethod is DomainLayerAuthenticationMethodModel.Swipe &&
+                    !isLockscreenDismissed
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
             )
 
     /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */
@@ -129,18 +198,24 @@
 
     /**
      * Returns the currently-configured authentication method. This determines how the
-     * authentication challenge is completed in order to unlock an otherwise locked device.
+     * authentication challenge needs to be completed in order to unlock an otherwise locked device.
+     *
+     * Note: there may be other ways to unlock the device that "bypass" the need for this
+     * authentication challenge (notably, biometrics like fingerprint or face unlock).
+     *
+     * Note: by design, this is offered as a convenience method alongside [authenticationMethod].
+     * The flow should be used for code that wishes to stay up-to-date its logic as the
+     * authentication changes over time and this method should be used for simple code that only
+     * needs to check the current value.
+     *
+     * Note: this layer adds the synthetic authentication method of "swipe" which is special. When
+     * the current authentication method is "swipe", the user does not need to complete any
+     * authentication challenge to unlock the device; they just need to dismiss the lockscreen to
+     * get past it. This also means that the value of [isUnlocked] remains `false` even when the
+     * lockscreen is showing and still needs to be dismissed by the user to proceed.
      */
-    suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
-        val authMethod = repository.getAuthenticationMethod()
-        return if (
-            authMethod is AuthenticationMethodModel.None && repository.isLockscreenEnabled()
-        ) {
-            // We treat "None" as "Swipe" when the lockscreen is enabled.
-            AuthenticationMethodModel.Swipe
-        } else {
-            authMethod
-        }
+    suspend fun getAuthenticationMethod(): DomainLayerAuthenticationMethodModel {
+        return repository.getAuthenticationMethod().toDomainLayer()
     }
 
     /**
@@ -270,21 +345,38 @@
         }
     }
 
-    private fun AuthenticationMethodModel.createCredential(
+    private fun DomainLayerAuthenticationMethodModel.createCredential(
         input: List<Any>
     ): LockscreenCredential? {
         return when (this) {
-            is AuthenticationMethodModel.Pin ->
+            is DomainLayerAuthenticationMethodModel.Pin ->
                 LockscreenCredential.createPin(input.joinToString(""))
-            is AuthenticationMethodModel.Password ->
+            is DomainLayerAuthenticationMethodModel.Password ->
                 LockscreenCredential.createPassword(input.joinToString(""))
-            is AuthenticationMethodModel.Pattern ->
+            is DomainLayerAuthenticationMethodModel.Pattern ->
                 LockscreenCredential.createPattern(
                     input
-                        .map { it as AuthenticationMethodModel.Pattern.PatternCoordinate }
+                        .map { it as AuthenticationPatternCoordinate }
                         .map { LockPatternView.Cell.of(it.y, it.x) }
                 )
             else -> null
         }
     }
+
+    private suspend fun DataLayerAuthenticationMethodModel.toDomainLayer():
+        DomainLayerAuthenticationMethodModel {
+        return when (this) {
+            is DataLayerAuthenticationMethodModel.None ->
+                if (repository.isLockscreenEnabled()) {
+                    DomainLayerAuthenticationMethodModel.Swipe
+                } else {
+                    DomainLayerAuthenticationMethodModel.None
+                }
+            is DataLayerAuthenticationMethodModel.Pin -> DomainLayerAuthenticationMethodModel.Pin
+            is DataLayerAuthenticationMethodModel.Password ->
+                DomainLayerAuthenticationMethodModel.Password
+            is DataLayerAuthenticationMethodModel.Pattern ->
+                DomainLayerAuthenticationMethodModel.Pattern
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt
similarity index 86%
rename from packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
rename to packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt
index 97c6697..d7e6099 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/model/AuthenticationMethodModel.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.authentication.shared.model
+package com.android.systemui.authentication.domain.model
 
 /** Enumerates all known authentication methods. */
 sealed class AuthenticationMethodModel(
@@ -36,10 +36,5 @@
 
     object Password : AuthenticationMethodModel(isSecure = true)
 
-    object Pattern : AuthenticationMethodModel(isSecure = true) {
-        data class PatternCoordinate(
-            val x: Int,
-            val y: Int,
-        )
-    }
+    object Pattern : AuthenticationMethodModel(isSecure = true)
 }
diff --git a/core/java/android/view/selectiontoolbar/WidgetInfo.aidl b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationPatternCoordinate.kt
similarity index 74%
rename from core/java/android/view/selectiontoolbar/WidgetInfo.aidl
rename to packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationPatternCoordinate.kt
index 1057c51..8a3f780 100644
--- a/core/java/android/view/selectiontoolbar/WidgetInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationPatternCoordinate.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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.
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package android.view.selectiontoolbar;
+package com.android.systemui.authentication.shared.model
 
-/**
- * @hide
- */
-parcelable WidgetInfo;
+data class AuthenticationPatternCoordinate(
+    val x: Int,
+    val y: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
index b34f1b4..b81d7fc 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/battery/AccessorizedBatteryDrawable.kt
@@ -178,6 +178,11 @@
         mainBatteryDrawable.charging = charging
     }
 
+    /** Returns whether the battery is currently charging. */
+    fun getCharging(): Boolean {
+        return mainBatteryDrawable.charging
+    }
+
     /** Sets the current level (out of 100) of the battery. */
     fun setBatteryLevel(level: Int) {
         mainBatteryDrawable.setBatteryLevel(level)
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
index 46fcdbc..87ea411 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java
@@ -77,8 +77,9 @@
     private int mShowPercentMode = MODE_DEFAULT;
     private boolean mShowPercentAvailable;
     private String mEstimateText = null;
-    private boolean mCharging;
+    private boolean mPluggedIn;
     private boolean mIsBatteryDefender;
+    private boolean mIsIncompatibleCharging;
     private boolean mDisplayShieldEnabled;
     // Error state where we know nothing about the current battery state
     private boolean mBatteryStateUnknown;
@@ -202,10 +203,10 @@
      * @param pluggedIn whether the device is plugged in or not
      */
     public void onBatteryLevelChanged(@IntRange(from = 0, to = 100) int level, boolean pluggedIn) {
-        mDrawable.setCharging(pluggedIn);
-        mDrawable.setBatteryLevel(level);
-        mCharging = pluggedIn;
+        mPluggedIn = pluggedIn;
         mLevel = level;
+        mDrawable.setCharging(isCharging());
+        mDrawable.setBatteryLevel(level);
         updatePercentText();
     }
 
@@ -224,6 +225,15 @@
         }
     }
 
+    void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) {
+        boolean valueChanged = mIsIncompatibleCharging != isIncompatibleCharging;
+        mIsIncompatibleCharging = isIncompatibleCharging;
+        if (valueChanged) {
+            mDrawable.setCharging(isCharging());
+            updateContentDescription();
+        }
+    }
+
     private TextView loadPercentView() {
         return (TextView) LayoutInflater.from(getContext())
                 .inflate(R.layout.battery_percentage_view, null);
@@ -263,7 +273,7 @@
         }
 
         if (mBatteryPercentView != null) {
-            if (mShowPercentMode == MODE_ESTIMATE && !mCharging) {
+            if (mShowPercentMode == MODE_ESTIMATE && !isCharging()) {
                 mBatteryEstimateFetcher.fetchBatteryTimeRemainingEstimate(
                         (String estimate) -> {
                     if (mBatteryPercentView == null) {
@@ -316,7 +326,7 @@
         } else if (mIsBatteryDefender) {
             contentDescription =
                     context.getString(R.string.accessibility_battery_level_charging_paused, mLevel);
-        } else if (mCharging) {
+        } else if (isCharging()) {
             contentDescription =
                     context.getString(R.string.accessibility_battery_level_charging, mLevel);
         } else {
@@ -464,16 +474,24 @@
         }
     }
 
+    private boolean isCharging() {
+        return mPluggedIn && !mIsIncompatibleCharging;
+    }
+
     public void dump(PrintWriter pw, String[] args) {
         String powerSave = mDrawable == null ? null : mDrawable.getPowerSaveEnabled() + "";
         String displayShield = mDrawable == null ? null : mDrawable.getDisplayShield() + "";
+        String charging = mDrawable == null ? null : mDrawable.getCharging() + "";
         CharSequence percent = mBatteryPercentView == null ? null : mBatteryPercentView.getText();
         pw.println("  BatteryMeterView:");
         pw.println("    mDrawable.getPowerSave: " + powerSave);
         pw.println("    mDrawable.getDisplayShield: " + displayShield);
+        pw.println("    mDrawable.getCharging: " + charging);
         pw.println("    mBatteryPercentView.getText(): " + percent);
         pw.println("    mTextColor: #" + Integer.toHexString(mTextColor));
         pw.println("    mBatteryStateUnknown: " + mBatteryStateUnknown);
+        pw.println("    mIsIncompatibleCharging: " + mIsIncompatibleCharging);
+        pw.println("    mPluggedIn: " + mPluggedIn);
         pw.println("    mLevel: " + mLevel);
         pw.println("    mMode: " + mShowPercentMode);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
index 6a5749c..0ca3883 100644
--- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterViewController.java
@@ -32,6 +32,8 @@
 
 import com.android.systemui.R;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 import com.android.systemui.statusbar.phone.StatusBarLocation;
@@ -50,6 +52,7 @@
     private final TunerService mTunerService;
     private final Handler mMainHandler;
     private final ContentResolver mContentResolver;
+    private final FeatureFlags mFeatureFlags;
     private final BatteryController mBatteryController;
 
     private final String mSlotBattery;
@@ -99,6 +102,13 @@
                 }
 
                 @Override
+                public void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) {
+                    if (mFeatureFlags.isEnabled(Flags.INCOMPATIBLE_CHARGING_BATTERY_ICON)) {
+                        mView.onIsIncompatibleChargingChanged(isIncompatibleCharging);
+                    }
+                }
+
+                @Override
                 public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
                     pw.print(super.toString());
                     pw.println(" location=" + mLocation);
@@ -129,6 +139,7 @@
             TunerService tunerService,
             @Main Handler mainHandler,
             ContentResolver contentResolver,
+            FeatureFlags featureFlags,
             BatteryController batteryController) {
         super(view);
         mLocation = location;
@@ -137,6 +148,7 @@
         mTunerService = tunerService;
         mMainHandler = mainHandler;
         mContentResolver = contentResolver;
+        mFeatureFlags = featureFlags;
         mBatteryController = batteryController;
 
         mView.setBatteryEstimateFetcher(mBatteryController::getEstimatedTimeRemainingString);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 58adfa1..58c8000 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -82,6 +82,7 @@
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import java.io.PrintWriter;
@@ -288,12 +289,13 @@
             @NonNull Provider<PromptSelectorInteractor> promptSelectorInteractor,
             @NonNull PromptViewModel promptViewModel,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
-            @NonNull @Background DelayableExecutor bgExecutor) {
+            @NonNull @Background DelayableExecutor bgExecutor,
+            @NonNull VibratorHelper vibratorHelper) {
         this(config, featureFlags, applicationCoroutineScope, fpProps, faceProps,
                 wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
                 jankMonitor, authBiometricFingerprintViewModelProvider, promptSelectorInteractor,
                 promptCredentialInteractor, promptViewModel, credentialViewModelProvider,
-                new Handler(Looper.getMainLooper()), bgExecutor);
+                new Handler(Looper.getMainLooper()), bgExecutor, vibratorHelper);
     }
 
     @VisibleForTesting
@@ -314,7 +316,8 @@
             @NonNull PromptViewModel promptViewModel,
             @NonNull Provider<CredentialViewModel> credentialViewModelProvider,
             @NonNull Handler mainHandler,
-            @NonNull @Background DelayableExecutor bgExecutor) {
+            @NonNull @Background DelayableExecutor bgExecutor,
+            @NonNull VibratorHelper vibratorHelper) {
         super(config.mContext);
 
         mConfig = config;
@@ -364,7 +367,8 @@
         if (featureFlags.isEnabled(Flags.BIOMETRIC_BP_STRONG)) {
             showPrompt(config, layoutInflater, promptViewModel,
                     Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
-                    Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds));
+                    Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds),
+                    vibratorHelper, featureFlags);
         } else {
             showLegacyPrompt(config, layoutInflater, fpProps, faceProps);
         }
@@ -388,7 +392,10 @@
     private void showPrompt(@NonNull Config config, @NonNull LayoutInflater layoutInflater,
             @NonNull PromptViewModel viewModel,
             @Nullable FingerprintSensorPropertiesInternal fpProps,
-            @Nullable FaceSensorPropertiesInternal faceProps) {
+            @Nullable FaceSensorPropertiesInternal faceProps,
+            @NonNull VibratorHelper vibratorHelper,
+            @NonNull FeatureFlags featureFlags
+    ) {
         if (Utils.isBiometricAllowed(config.mPromptInfo)) {
             mPromptSelectorInteractorProvider.get().useBiometricsForAuthentication(
                     config.mPromptInfo,
@@ -401,7 +408,8 @@
             mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
                     // TODO(b/201510778): This uses the wrong timeout in some cases
                     getJankListener(view, TRANSIT, AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
-                    mBackgroundView, mBiometricCallback, mApplicationCoroutineScope);
+                    mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
+                    vibratorHelper, featureFlags);
 
             // TODO(b/251476085): migrate these dependencies
             if (fpProps != null && fpProps.isAnyUdfpsType()) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 3df7ca5..7b288a8 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -85,9 +85,12 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.data.repository.BiometricType;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -101,7 +104,6 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
-import kotlin.Unit;
 import kotlinx.coroutines.CoroutineScope;
 
 /**
@@ -183,6 +185,7 @@
     @NonNull private final UdfpsUtils mUdfpsUtils;
     private final @Background DelayableExecutor mBackgroundExecutor;
     private final DisplayInfo mCachedDisplayInfo = new DisplayInfo();
+    @NonNull private final VibratorHelper mVibratorHelper;
 
     @VisibleForTesting
     final TaskStackListener mTaskStackListener = new TaskStackListener() {
@@ -771,7 +774,8 @@
             @NonNull InteractionJankMonitor jankMonitor,
             @Main Handler handler,
             @Background DelayableExecutor bgExecutor,
-            @NonNull UdfpsUtils udfpsUtils) {
+            @NonNull UdfpsUtils udfpsUtils,
+            @NonNull VibratorHelper vibratorHelper) {
         mContext = context;
         mFeatureFlags = featureFlags;
         mExecution = execution;
@@ -794,6 +798,7 @@
         mFaceEnrolledForUser = new SparseBooleanArray();
         mUdfpsUtils = udfpsUtils;
         mApplicationCoroutineScope = applicationCoroutineScope;
+        mVibratorHelper = vibratorHelper;
 
         mLogContextInteractor = logContextInteractor;
         mAuthBiometricFingerprintViewModelProvider = authBiometricFingerprintViewModelProvider;
@@ -1044,7 +1049,7 @@
         final int userId = mCurrentDialogArgs.argi1;
         if (isFaceAuthEnrolled(userId) && isFingerprintEnrolled(userId)) {
             messageRes = modality == TYPE_FACE
-                    ? R.string.biometric_face_not_recognized
+                    ? R.string.fingerprint_dialog_use_fingerprint_instead
                     : R.string.fingerprint_error_not_match;
         } else {
             messageRes = R.string.biometric_not_recognized;
@@ -1341,7 +1346,7 @@
                 wakefulnessLifecycle, panelInteractionDetector, userManager, lockPatternUtils,
                 mInteractionJankMonitor, mAuthBiometricFingerprintViewModelProvider,
                 mPromptCredentialInteractor, mPromptSelectorInteractor, viewModel,
-                mCredentialViewModelProvider, bgExecutor);
+                mCredentialViewModelProvider, bgExecutor, mVibratorHelper);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index 946ddba..ea9fe5f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -230,7 +230,7 @@
                         lightRevealScrim.revealAmount = animator.animatedValue as Float
                     }
                     addListener(object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
+                        override fun onAnimationEnd(animation: Animator) {
                             // Reset light reveal scrim to the default, so the CentralSurfaces
                             // can handle any subsequent light reveal changes
                             // (ie: from dozing changes)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 5ede16d..4c2dc41 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -147,12 +147,12 @@
             retractDwellAnimator = AnimatorSet().apply {
                 playTogether(retractDwellRippleAnimator, retractAlphaAnimator)
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationStart(animation: Animator?) {
+                    override fun onAnimationStart(animation: Animator) {
                         dwellPulseOutAnimator?.cancel()
                         drawDwell = true
                     }
 
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         drawDwell = false
                         resetDwellAlpha()
                     }
@@ -182,13 +182,13 @@
                     invalidate()
                 }
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationStart(animation: Animator?) {
+                    override fun onAnimationStart(animation: Animator) {
                         retractDwellAnimator?.cancel()
                         dwellPulseOutAnimator?.cancel()
                         drawDwell = true
                     }
 
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         drawDwell = false
                         resetDwellAlpha()
                     }
@@ -239,14 +239,14 @@
                     expandDwellRippleAnimator
             )
             addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator?) {
+                override fun onAnimationStart(animation: Animator) {
                     retractDwellAnimator?.cancel()
                     fadeDwellAnimator?.cancel()
                     visibility = VISIBLE
                     drawDwell = true
                 }
 
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     drawDwell = false
                 }
             })
@@ -273,12 +273,12 @@
 
         unlockedRippleAnimator = rippleAnimator.apply {
             addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationStart(animation: Animator?) {
+                override fun onAnimationStart(animation: Animator) {
                     drawRipple = true
                     visibility = VISIBLE
                 }
 
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     onAnimationEnd?.run()
                     drawRipple = false
                     visibility = GONE
@@ -327,7 +327,7 @@
         }
     }
 
-    override fun onDraw(canvas: Canvas?) {
+    override fun onDraw(canvas: Canvas) {
         // To reduce overdraw, we mask the effect to a circle whose radius is big enough to cover
         // the active effect area. Values here should be kept in sync with the
         // animation implementation in the ripple shader. (Twice bigger)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
index b9fa240..a24a47b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceAuthAccessibilityDelegate.kt
@@ -40,7 +40,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val faceAuthInteractor: KeyguardFaceAuthInteractor,
 ) : View.AccessibilityDelegate() {
-    override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
+    override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
         super.onInitializeAccessibilityNodeInfo(host, info)
         if (keyguardUpdateMonitor.shouldListenForFace()) {
             val clickActionToRetryFace =
@@ -52,7 +52,7 @@
         }
     }
 
-    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+    override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
         return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
             keyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.ACCESSIBILITY_ACTION)
             faceAuthInteractor.onAccessibilityAction()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 063f62e..0d7d9cc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -119,7 +119,7 @@
     private var overlayView: View? = null
         set(value) {
             field?.let { oldView ->
-                val lottie = oldView.findViewById(R.id.sidefps_animation) as LottieAnimationView
+                val lottie = oldView.requireViewById(R.id.sidefps_animation) as LottieAnimationView
                 lottie.pauseAnimation()
                 windowManager.removeView(oldView)
                 orientationListener.disable()
@@ -186,7 +186,7 @@
     }
 
     private fun listenForAlternateBouncerVisibility() {
-        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, "SideFpsController")
         scope.launch {
             alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
                 if (isVisible) {
@@ -274,7 +274,7 @@
             }
         overlayOffsets = offsets
 
-        val lottie = view.findViewById(R.id.sidefps_animation) as LottieAnimationView
+        val lottie = view.requireViewById(R.id.sidefps_animation) as LottieAnimationView
         view.rotation =
             display.asSideFpsAnimationRotation(
                 offsets.isYAligned(),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 39a45f7..b23e085 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -646,8 +646,9 @@
             shouldPilfer = true;
         }
 
-        // Pilfer only once per gesture
-        if (shouldPilfer && !mPointerPilfered) {
+        // Pilfer only once per gesture, don't pilfer for BP
+        if (shouldPilfer && !mPointerPilfered
+                && getBiometricSessionType() != SESSION_BIOMETRIC_PROMPT) {
             mInputManager.pilferPointers(
                     mOverlay.getOverlayView().getViewRootImpl().getInputToken());
             mPointerPilfered = true;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
index 8352d0a..5dafa61 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt
@@ -38,7 +38,8 @@
     override fun getDrawable(): UdfpsDrawable = fingerprintDrawable
 
     fun updateAccessibilityViewLocation(sensorBounds: Rect) {
-        val fingerprintAccessibilityView: View = findViewById(R.id.udfps_enroll_accessibility_view)
+        val fingerprintAccessibilityView: View =
+            requireViewById(R.id.udfps_enroll_accessibility_view)
         val params: ViewGroup.LayoutParams = fingerprintAccessibilityView.layoutParams
         params.width = sensorBounds.width()
         params.height = sensorBounds.height()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
index fb7b56e..8497879 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardAccessibilityDelegate.kt
@@ -33,7 +33,7 @@
     @Main private val resources: Resources,
     private val keyguardViewManager: StatusBarKeyguardViewManager,
 ) : View.AccessibilityDelegate() {
-    override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo) {
+    override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfo) {
         super.onInitializeAccessibilityNodeInfo(host, info)
         val clickAction =
             AccessibilityNodeInfo.AccessibilityAction(
@@ -43,7 +43,7 @@
         info.addAction(clickAction)
     }
 
-    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+    override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
         // when an a11y service is enabled, double tapping on the fingerprint sensor should
         // show the primary bouncer
         return if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK.id) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index 15bd731..e3fd3ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -83,6 +83,7 @@
         dumpManager,
     ),
     UdfpsKeyguardViewControllerAdapter {
+    private val uniqueIdentifier = this.toString()
     private val useExpandedOverlay: Boolean =
         featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)
     private var showingUdfpsBouncer = false
@@ -282,7 +283,7 @@
 
     public override fun onViewAttached() {
         super.onViewAttached()
-        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier)
         val dozeAmount = statusBarStateController.dozeAmount
         lastDozeAmount = dozeAmount
         stateListener.onDozeAmountChanged(dozeAmount, dozeAmount)
@@ -305,14 +306,15 @@
         activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
         view.mUseExpandedOverlay = useExpandedOverlay
         view.startIconAsyncInflate {
-            (view.findViewById(R.id.udfps_animation_view_internal) as View).accessibilityDelegate =
-                udfpsKeyguardAccessibilityDelegate
+            val animationViewInternal: View =
+                view.requireViewById(R.id.udfps_animation_view_internal)
+            animationViewInternal.accessibilityDelegate = udfpsKeyguardAccessibilityDelegate
         }
     }
 
     override fun onViewDetached() {
         super.onViewDetached()
-        alternateBouncerInteractor.setAlternateBouncerUIAvailable(false)
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier)
         faceDetectRunning = false
         keyguardStateController.removeCallback(keyguardStateControllerCallback)
         statusBarStateController.removeCallback(stateListener)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
index b538085..1ca57e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
@@ -60,6 +60,13 @@
         return dp * (density / DisplayMetrics.DENSITY_DEFAULT)
     }
 
+    /**
+     * Note: Talkback 14.0 has new rate-limitation design to reduce frequency
+     * of TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds.
+     * (context: b/281765653#comment18)
+     * Using {@link View#announceForAccessibility} instead as workaround when sending events
+     * exceeding this frequency is required.
+     */
     @JvmStatic
     fun notifyAccessibilityContentChanged(am: AccessibilityManager, view: ViewGroup) {
         if (!am.isEnabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
index 1f1a1b5..2a02667 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/CredentialInteractor.kt
@@ -89,7 +89,7 @@
                 )
             val hat = gkResponse.gatekeeperHAT
             lockPatternUtils.removeGatekeeperPasswordHandle(pwHandle)
-            emit(CredentialStatus.Success.Verified(hat))
+            emit(CredentialStatus.Success.Verified(checkNotNull(hat)))
         } else if (response.timeout > 0) {
             // if requests are being throttled, update the error message every
             // second until the temporary lock has expired
@@ -226,8 +226,7 @@
             is BiometricPromptRequest.Credential.Password ->
                 DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_PASSWORD_LAST_ATTEMPT
         }
-    return devicePolicyManager.resources.getString(id) {
-        // use fallback a string if not found
+    val getFallbackString = {
         val defaultId =
             when (request) {
                 is BiometricPromptRequest.Credential.Pin ->
@@ -239,6 +238,8 @@
             }
         getString(defaultId)
     }
+
+    return devicePolicyManager.resources?.getString(id, getFallbackString) ?: getFallbackString()
 }
 
 private fun Context.getLastAttemptBeforeWipeUserMessage(
@@ -266,8 +267,8 @@
                 DevicePolicyResources.Strings.SystemUi.BIOMETRIC_DIALOG_WORK_LOCK_FAILED_ATTEMPTS
             else -> DevicePolicyResources.UNDEFINED
         }
-    return devicePolicyManager.resources.getString(id) {
-        // use fallback a string if not found
+
+    val getFallbackString = {
         val defaultId =
             when (userType) {
                 UserType.PRIMARY ->
@@ -279,4 +280,6 @@
             }
         getString(defaultId)
     }
+
+    return devicePolicyManager.resources?.getString(id, getFallbackString) ?: getFallbackString()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
index eca0ada..709fe85 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/CredentialPasswordView.kt
@@ -121,7 +121,7 @@
                 titleView.ellipsize = TextUtils.TruncateAt.MARQUEE
                 titleView.marqueeRepeatLimit = -1
                 // select to enable marquee unless a screen reader is enabled
-                titleView.isSelected = accessibilityManager.shouldMarquee()
+                titleView.isSelected = accessibilityManager?.shouldMarquee() ?: false
             } else {
                 titleView.isSingleLine = false
                 titleView.ellipsize = null
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 7b78761..d054751 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.ui.binder
 
 import android.animation.Animator
+import android.annotation.SuppressLint
 import android.content.Context
 import android.hardware.biometrics.BiometricAuthenticator
 import android.hardware.biometrics.BiometricConstants
@@ -25,6 +26,8 @@
 import android.os.Bundle
 import android.text.method.ScrollingMovementMethod
 import android.util.Log
+import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
 import android.view.View
 import android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO
 import android.view.accessibility.AccessibilityManager
@@ -45,7 +48,6 @@
 import com.android.systemui.biometrics.AuthBiometricViewAdapter
 import com.android.systemui.biometrics.AuthIconController
 import com.android.systemui.biometrics.AuthPanelController
-import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.domain.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.PromptKind
@@ -55,9 +57,13 @@
 import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
 import com.android.systemui.biometrics.ui.viewmodel.PromptSize
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.VibratorHelper
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
@@ -69,6 +75,7 @@
 object BiometricViewBinder {
 
     /** Binds a [BiometricPromptLayout] to a [PromptViewModel]. */
+    @SuppressLint("ClickableViewAccessibility")
     @JvmStatic
     fun bind(
         view: BiometricPromptLayout,
@@ -78,20 +85,19 @@
         backgroundView: View,
         legacyCallback: Callback,
         applicationScope: CoroutineScope,
+        vibratorHelper: VibratorHelper,
+        featureFlags: FeatureFlags,
     ): AuthBiometricViewAdapter {
         val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
-        fun notifyAccessibilityChanged() {
-            Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
-        }
 
         val textColorError =
             view.resources.getColor(R.color.biometric_dialog_error, view.context.theme)
         val textColorHint =
             view.resources.getColor(R.color.biometric_dialog_gray, view.context.theme)
 
-        val titleView = view.findViewById<TextView>(R.id.title)
-        val subtitleView = view.findViewById<TextView>(R.id.subtitle)
-        val descriptionView = view.findViewById<TextView>(R.id.description)
+        val titleView = view.requireViewById<TextView>(R.id.title)
+        val subtitleView = view.requireViewById<TextView>(R.id.subtitle)
+        val descriptionView = view.requireViewById<TextView>(R.id.description)
 
         // set selected to enable marquee unless a screen reader is enabled
         titleView.isSelected =
@@ -100,18 +106,18 @@
             !accessibilityManager.isEnabled || !accessibilityManager.isTouchExplorationEnabled
         descriptionView.movementMethod = ScrollingMovementMethod()
 
-        val iconViewOverlay = view.findViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
-        val iconView = view.findViewById<LottieAnimationView>(R.id.biometric_icon)
-        val indicatorMessageView = view.findViewById<TextView>(R.id.indicator)
+        val iconViewOverlay = view.requireViewById<LottieAnimationView>(R.id.biometric_icon_overlay)
+        val iconView = view.requireViewById<LottieAnimationView>(R.id.biometric_icon)
+        val indicatorMessageView = view.requireViewById<TextView>(R.id.indicator)
 
         // Negative-side (left) buttons
-        val negativeButton = view.findViewById<Button>(R.id.button_negative)
-        val cancelButton = view.findViewById<Button>(R.id.button_cancel)
-        val credentialFallbackButton = view.findViewById<Button>(R.id.button_use_credential)
+        val negativeButton = view.requireViewById<Button>(R.id.button_negative)
+        val cancelButton = view.requireViewById<Button>(R.id.button_cancel)
+        val credentialFallbackButton = view.requireViewById<Button>(R.id.button_use_credential)
 
         // Positive-side (right) buttons
-        val confirmationButton = view.findViewById<Button>(R.id.button_confirm)
-        val retryButton = view.findViewById<Button>(R.id.button_try_again)
+        val confirmationButton = view.requireViewById<Button>(R.id.button_confirm)
+        val retryButton = view.requireViewById<Button>(R.id.button_try_again)
 
         // TODO(b/251476085): temporary workaround for the unsafe callbacks & legacy controllers
         val adapter =
@@ -297,21 +303,19 @@
 
                 // reuse the icon as a confirm button
                 launch {
-                    viewModel.isConfirmButtonVisible
+                    viewModel.isIconConfirmButton
                         .map { isPending ->
                             when {
                                 isPending && iconController.actsAsConfirmButton ->
-                                    View.OnClickListener { viewModel.confirmAuthenticated() }
+                                    View.OnTouchListener { _: View, event: MotionEvent ->
+                                        viewModel.onOverlayTouch(event)
+                                    }
                                 else -> null
                             }
                         }
-                        .collect { onClick ->
-                            iconViewOverlay.setOnClickListener(onClick)
-                            iconView.setOnClickListener(onClick)
-                            if (onClick == null) {
-                                iconViewOverlay.isClickable = false
-                                iconView.isClickable = false
-                            }
+                        .collect { onTouch ->
+                            iconViewOverlay.setOnTouchListener(onTouch)
+                            iconView.setOnTouchListener(onTouch)
                         }
                 }
 
@@ -326,30 +330,30 @@
                     }
                 }
 
-                // not sure why this is here, but the legacy code did it probably needed?
-                launch {
-                    viewModel.isAuthenticating.collect { isAuthenticating ->
-                        if (isAuthenticating) {
-                            notifyAccessibilityChanged()
-                        }
-                    }
-                }
-
                 // dismiss prompt when authenticated and confirmed
                 launch {
                     viewModel.isAuthenticated.collect { authState ->
                         // Disable background view for cancelling authentication once authenticated,
                         // and remove from talkback
                         if (authState.isAuthenticated) {
+                            // Prevents Talkback from speaking subtitle after already authenticated
+                            subtitleView.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
                             backgroundView.setOnClickListener(null)
                             backgroundView.importantForAccessibility =
                                 IMPORTANT_FOR_ACCESSIBILITY_NO
+
+                            // Allow icon to be used as confirmation button with a11y enabled
+                            if (accessibilityManager.isTouchExplorationEnabled) {
+                                iconViewOverlay.setOnClickListener {
+                                    viewModel.confirmAuthenticated()
+                                }
+                                iconView.setOnClickListener { viewModel.confirmAuthenticated() }
+                            }
                         }
                         if (authState.isAuthenticatedAndConfirmed) {
                             view.announceForAccessibility(
                                 view.resources.getString(R.string.biometric_dialog_authenticated)
                             )
-                            notifyAccessibilityChanged()
 
                             launch {
                                 delay(authState.delay)
@@ -381,7 +385,30 @@
                             !accessibilityManager.isEnabled ||
                                 !accessibilityManager.isTouchExplorationEnabled
 
-                        notifyAccessibilityChanged()
+                        /**
+                         * Note: Talkback 14.0 has new rate-limitation design to reduce frequency of
+                         * TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds. (context:
+                         * b/281765653#comment18) Using {@link View#announceForAccessibility}
+                         * instead as workaround since sending events exceeding this frequency is
+                         * required.
+                         */
+                        indicatorMessageView?.text?.let {
+                            if (it.isNotBlank()) {
+                                view.announceForAccessibility(it)
+                            }
+                        }
+                    }
+                }
+
+                // Play haptics
+                if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+                    launch {
+                        viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
+                            if (hapticFeedbackConstant != HapticFeedbackConstants.NO_HAPTICS) {
+                                vibratorHelper.performHapticFeedback(view, hapticFeedbackConstant)
+                                viewModel.clearHaptics()
+                            }
+                        }
                     }
                 }
             }
@@ -477,7 +504,7 @@
             modalities.hasFaceAndFingerprint &&
                 (viewModel.fingerprintStartMode.first() != FingerprintStartMode.Pending) &&
                 (authenticatedModality == BiometricModality.Face) ->
-                R.string.biometric_dialog_tap_confirm_with_face
+                R.string.biometric_dialog_tap_confirm_with_face_alt_1
             else -> null
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 1a286cf..370b36b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -72,7 +72,7 @@
             }
         }
 
-        val iconHolderView = view.findViewById<View>(R.id.biometric_icon_frame)
+        val iconHolderView = view.requireViewById<View>(R.id.biometric_icon_frame)
         val iconPadding = view.resources.getDimension(R.dimen.biometric_dialog_icon_padding)
         val fullSizeYOffset =
             view.resources.getDimension(R.dimen.biometric_dialog_medium_to_large_translation_offset)
@@ -205,7 +205,7 @@
 }
 
 private fun View.isLandscape(): Boolean {
-    val r = context.display.rotation
+    val r = context.display?.rotation
     return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
index 2a9f3ea..c9b1624 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
@@ -46,6 +46,7 @@
         dumpManager,
     ),
     UdfpsKeyguardViewControllerAdapter {
+    private val uniqueIdentifier = this.toString()
     override val tag: String
         get() = TAG
 
@@ -55,12 +56,12 @@
 
     public override fun onViewAttached() {
         super.onViewAttached()
-        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true)
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier)
     }
 
     public override fun onViewDetached() {
         super.onViewDetached()
-        alternateBouncerInteractor.setAlternateBouncerUIAvailable(false)
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier)
     }
 
     override fun shouldPauseAuth(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index dca19c5..89561a5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -17,11 +17,15 @@
 
 import android.hardware.biometrics.BiometricPrompt
 import android.util.Log
+import android.view.HapticFeedbackConstants
+import android.view.MotionEvent
 import com.android.systemui.biometrics.AuthBiometricView
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.statusbar.VibratorHelper
 import javax.inject.Inject
 import kotlinx.coroutines.Job
@@ -43,6 +47,7 @@
 constructor(
     private val interactor: PromptSelectorInteractor,
     private val vibrator: VibratorHelper,
+    private val featureFlags: FeatureFlags,
 ) {
     /** The set of modalities available for this prompt */
     val modalities: Flow<BiometricModalities> =
@@ -63,11 +68,18 @@
     /** If the user has successfully authenticated and confirmed (when explicitly required). */
     val isAuthenticated: Flow<PromptAuthState> = _isAuthenticated.asStateFlow()
 
+    private val _isOverlayTouched: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
     /**
      * If the API caller or the user's personal preferences require explicit confirmation after
      * successful authentication.
      */
-    val isConfirmationRequired: Flow<Boolean> = interactor.isConfirmationRequired
+    val isConfirmationRequired: Flow<Boolean> =
+        combine(_isOverlayTouched, interactor.isConfirmationRequired) {
+            isOverlayTouched,
+            isConfirmationRequired ->
+            !isOverlayTouched && isConfirmationRequired
+        }
 
     /** The kind of credential the user has. */
     val credentialKind: Flow<PromptKind> = interactor.credentialKind
@@ -90,6 +102,11 @@
     private val _forceLargeSize = MutableStateFlow(false)
     private val _forceMediumSize = MutableStateFlow(false)
 
+    private val _hapticsToPlay = MutableStateFlow(HapticFeedbackConstants.NO_HAPTICS)
+
+    /** Event fired to the view indicating a [HapticFeedbackConstants] to be played */
+    val hapticsToPlay = _hapticsToPlay.asStateFlow()
+
     /** The size of the prompt. */
     val size: Flow<PromptSize> =
         combine(
@@ -141,6 +158,12 @@
             }
             .distinctUntilChanged()
 
+    /** If the icon can be used as a confirmation button. */
+    val isIconConfirmButton: Flow<Boolean> =
+        combine(size, interactor.isConfirmationRequired) { size, isConfirmationRequired ->
+            size.isNotSmall && isConfirmationRequired
+        }
+
     /** If the negative button should be shown. */
     val isNegativeButtonVisible: Flow<Boolean> =
         combine(
@@ -289,8 +312,10 @@
             if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
         _forceMediumSize.value = true
         _legacyState.value =
-            if (alreadyAuthenticated) {
+            if (alreadyAuthenticated && isConfirmationRequired.first()) {
                 AuthBiometricView.STATE_PENDING_CONFIRMATION
+            } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
+                AuthBiometricView.STATE_AUTHENTICATED
             } else {
                 AuthBiometricView.STATE_HELP
             }
@@ -388,18 +413,10 @@
     }
 
     private suspend fun needsExplicitConfirmation(modality: BiometricModality): Boolean {
-        val availableModalities = modalities.first()
         val confirmationRequired = isConfirmationRequired.first()
 
-        if (availableModalities.hasFaceAndFingerprint) {
-            // coex only needs confirmation when face is successful, unless it happens on the
-            // first attempt (i.e. without failure) before fingerprint scanning starts
-            val fingerprintStarted = fingerprintStartMode.first() != FingerprintStartMode.Pending
-            if (modality == BiometricModality.Face) {
-                return fingerprintStarted || confirmationRequired
-            }
-        }
-        if (availableModalities.hasFaceOnly) {
+        // Only worry about confirmationRequired if face was used to unlock
+        if (modality == BiometricModality.Face) {
             return confirmationRequired
         }
         // fingerprint only never requires confirmation
@@ -430,6 +447,26 @@
     }
 
     /**
+     * Touch event occurred on the overlay
+     *
+     * Tracks whether a finger is currently down to set [_isOverlayTouched] to be used as user
+     * confirmation
+     */
+    fun onOverlayTouch(event: MotionEvent): Boolean {
+        if (event.actionMasked == MotionEvent.ACTION_DOWN) {
+            _isOverlayTouched.value = true
+
+            if (_isAuthenticated.value.needsUserConfirmation) {
+                confirmAuthenticated()
+            }
+            return true
+        } else if (event.actionMasked == MotionEvent.ACTION_UP) {
+            _isOverlayTouched.value = false
+        }
+        return false
+    }
+
+    /**
      * Switch to the credential view.
      *
      * TODO(b/251476085): this should be decoupled from the shared panel controller
@@ -438,11 +475,26 @@
         _forceLargeSize.value = true
     }
 
-    private fun VibratorHelper.success(modality: BiometricModality) =
-        vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+    private fun VibratorHelper.success(modality: BiometricModality) {
+        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            _hapticsToPlay.value = HapticFeedbackConstants.CONFIRM
+        } else {
+            vibrateAuthSuccess("$TAG, modality = $modality BP::success")
+        }
+    }
 
-    private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) =
-        vibrateAuthError("$TAG, modality = $modality BP::error")
+    private fun VibratorHelper.error(modality: BiometricModality = BiometricModality.None) {
+        if (featureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
+            _hapticsToPlay.value = HapticFeedbackConstants.REJECT
+        } else {
+            vibrateAuthError("$TAG, modality = $modality BP::error")
+        }
+    }
+
+    /** Clears the [hapticsToPlay] variable by setting it to the NO_HAPTICS default. */
+    fun clearHaptics() {
+        _hapticsToPlay.value = HapticFeedbackConstants.NO_HAPTICS
+    }
 
     companion object {
         private const val TAG = "PromptViewModel"
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
index e3e9b3a..98ae54b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt
@@ -40,6 +40,7 @@
 ) {
     var receivedDownTouch = false
     val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
+    private val alternateBouncerUiAvailableFromSource: HashSet<String> = HashSet()
 
     /**
      * Sets the correct bouncer states to show the alternate bouncer if it can show.
@@ -69,8 +70,15 @@
         return bouncerRepository.alternateBouncerVisible.value
     }
 
-    fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
-        bouncerRepository.setAlternateBouncerUIAvailable(isAvailable)
+    fun setAlternateBouncerUIAvailable(isAvailable: Boolean, token: String) {
+        if (isAvailable) {
+            alternateBouncerUiAvailableFromSource.add(token)
+        } else {
+            alternateBouncerUiAvailableFromSource.remove(token)
+        }
+        bouncerRepository.setAlternateBouncerUIAvailable(
+            alternateBouncerUiAvailableFromSource.isNotEmpty()
+        )
     }
 
     fun canShowAlternateBouncerForFingerprint(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 8ed964d..1bf3a9e 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -14,14 +14,12 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
 package com.android.systemui.bouncer.domain.interactor
 
 import android.content.Context
 import com.android.systemui.R
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.bouncer.data.repository.BouncerRepository
 import com.android.systemui.dagger.SysUISingleton
@@ -35,7 +33,6 @@
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
@@ -107,14 +104,6 @@
     }
 
     /**
-     * Returns the currently-configured authentication method. This determines how the
-     * authentication challenge is completed in order to unlock an otherwise locked device.
-     */
-    suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
-        return authenticationInteractor.getAuthenticationMethod()
-    }
-
-    /**
      * Either shows the bouncer or unlocks the device, if the bouncer doesn't need to be shown.
      *
      * @param message An optional message to show to the user in the bouncer.
@@ -124,13 +113,15 @@
     ) {
         applicationScope.launch {
             if (authenticationInteractor.isAuthenticationRequired()) {
-                repository.setMessage(message ?: promptMessage(getAuthenticationMethod()))
-                sceneInteractor.setCurrentScene(
+                repository.setMessage(
+                    message ?: promptMessage(authenticationInteractor.getAuthenticationMethod())
+                )
+                sceneInteractor.changeScene(
                     scene = SceneModel(SceneKey.Bouncer),
                     loggingReason = "request to unlock device while authentication required",
                 )
             } else {
-                sceneInteractor.setCurrentScene(
+                sceneInteractor.changeScene(
                     scene = SceneModel(SceneKey.Gone),
                     loggingReason = "request to unlock device while authentication isn't required",
                 )
@@ -143,7 +134,9 @@
      * method.
      */
     fun resetMessage() {
-        applicationScope.launch { repository.setMessage(promptMessage(getAuthenticationMethod())) }
+        applicationScope.launch {
+            repository.setMessage(promptMessage(authenticationInteractor.getAuthenticationMethod()))
+        }
     }
 
     /** Removes the user-facing message. */
@@ -176,12 +169,12 @@
             authenticationInteractor.authenticate(input, tryAutoConfirm) ?: return null
 
         if (isAuthenticated) {
-            sceneInteractor.setCurrentScene(
+            sceneInteractor.changeScene(
                 scene = SceneModel(SceneKey.Gone),
                 loggingReason = "successful authentication",
             )
         } else {
-            repository.setMessage(errorMessage(getAuthenticationMethod()))
+            repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod()))
         }
 
         return isAuthenticated
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index d9ec5d0..56f1cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -121,7 +121,7 @@
                             view.visibility = if (isShowing) View.VISIBLE else View.INVISIBLE
                             if (isShowing) {
                                 // Reset security container because these views are not reinflated.
-                                securityContainerController.reset()
+                                securityContainerController.prepareToShow()
                                 securityContainerController.reinflateViewFlipper {
                                     // Reset Security Container entirely.
                                     securityContainerController.onBouncerVisibilityChanged(
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 68e1a29..5b1998d 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -14,25 +14,20 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.content.Context
 import com.android.systemui.R
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import kotlin.math.ceil
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.channels.BufferOverflow
-import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
@@ -50,23 +45,24 @@
 constructor(
     @Application private val applicationContext: Context,
     @Application private val applicationScope: CoroutineScope,
-    private val interactor: BouncerInteractor,
+    private val bouncerInteractor: BouncerInteractor,
+    private val authenticationInteractor: AuthenticationInteractor,
     featureFlags: FeatureFlags,
 ) {
     private val isInputEnabled: StateFlow<Boolean> =
-        interactor.isThrottled
+        bouncerInteractor.isThrottled
             .map { !it }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = !interactor.isThrottled.value,
+                initialValue = !bouncerInteractor.isThrottled.value,
             )
 
     private val pin: PinBouncerViewModel by lazy {
         PinBouncerViewModel(
             applicationContext = applicationContext,
             applicationScope = applicationScope,
-            interactor = interactor,
+            interactor = bouncerInteractor,
             isInputEnabled = isInputEnabled,
         )
     }
@@ -74,7 +70,7 @@
     private val password: PasswordBouncerViewModel by lazy {
         PasswordBouncerViewModel(
             applicationScope = applicationScope,
-            interactor = interactor,
+            interactor = bouncerInteractor,
             isInputEnabled = isInputEnabled,
         )
     }
@@ -83,31 +79,35 @@
         PatternBouncerViewModel(
             applicationContext = applicationContext,
             applicationScope = applicationScope,
-            interactor = interactor,
+            interactor = bouncerInteractor,
             isInputEnabled = isInputEnabled,
         )
     }
 
     /** View-model for the current UI, based on the current authentication method. */
-    private val _authMethod =
-        MutableSharedFlow<AuthMethodBouncerViewModel?>(
-            replay = 1,
-            onBufferOverflow = BufferOverflow.DROP_OLDEST,
-        )
     val authMethod: StateFlow<AuthMethodBouncerViewModel?> =
-        _authMethod.stateIn(
-            scope = applicationScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = null,
-        )
+        authenticationInteractor.authenticationMethod
+            .map { authenticationMethod ->
+                when (authenticationMethod) {
+                    is AuthenticationMethodModel.Pin -> pin
+                    is AuthenticationMethodModel.Password -> password
+                    is AuthenticationMethodModel.Pattern -> pattern
+                    else -> null
+                }
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null,
+            )
 
     init {
         if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
             applicationScope.launch {
-                interactor.isThrottled
+                bouncerInteractor.isThrottled
                     .map { isThrottled ->
                         if (isThrottled) {
-                            when (interactor.getAuthenticationMethod()) {
+                            when (authenticationInteractor.getAuthenticationMethod()) {
                                 is AuthenticationMethodModel.Pin ->
                                     R.string.kg_too_many_failed_pin_attempts_dialog_message
                                 is AuthenticationMethodModel.Password ->
@@ -118,8 +118,9 @@
                             }?.let { stringResourceId ->
                                 applicationContext.getString(
                                     stringResourceId,
-                                    interactor.throttling.value.failedAttemptCount,
-                                    ceil(interactor.throttling.value.remainingMs / 1000f).toInt(),
+                                    bouncerInteractor.throttling.value.failedAttemptCount,
+                                    ceil(bouncerInteractor.throttling.value.remainingMs / 1000f)
+                                        .toInt(),
                                 )
                             }
                         } else {
@@ -133,25 +134,14 @@
                         }
                     }
             }
-
-            applicationScope.launch {
-                _authMethod.subscriptionCount
-                    .pairwise()
-                    .map { (previousCount, currentCount) -> currentCount > previousCount }
-                    .collect { subscriberAdded ->
-                        if (subscriberAdded) {
-                            reloadAuthMethod()
-                        }
-                    }
-            }
         }
     }
 
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<MessageViewModel> =
         combine(
-                interactor.message,
-                interactor.isThrottled,
+                bouncerInteractor.message,
+                bouncerInteractor.isThrottled,
             ) { message, isThrottled ->
                 toMessageViewModel(message, isThrottled)
             }
@@ -160,8 +150,8 @@
                 started = SharingStarted.WhileSubscribed(),
                 initialValue =
                     toMessageViewModel(
-                        message = interactor.message.value,
-                        isThrottled = interactor.isThrottled.value,
+                        message = bouncerInteractor.message.value,
+                        isThrottled = bouncerInteractor.isThrottled.value,
                     ),
             )
 
@@ -197,17 +187,6 @@
         )
     }
 
-    private suspend fun reloadAuthMethod() {
-        _authMethod.tryEmit(
-            when (interactor.getAuthenticationMethod()) {
-                is AuthenticationMethodModel.Pin -> pin
-                is AuthenticationMethodModel.Password -> password
-                is AuthenticationMethodModel.Pattern -> pattern
-                else -> null
-            }
-        )
-    }
-
     data class MessageViewModel(
         val text: String,
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 4be539d..4425f9f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -18,7 +18,7 @@
 
 import android.content.Context
 import android.util.TypedValue
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import kotlin.math.max
 import kotlin.math.min
@@ -193,8 +193,8 @@
     val x: Int,
     val y: Int,
 ) {
-    fun toCoordinate(): AuthenticationMethodModel.Pattern.PatternCoordinate {
-        return AuthenticationMethodModel.Pattern.PatternCoordinate(
+    fun toCoordinate(): AuthenticationPatternCoordinate {
+        return AuthenticationPatternCoordinate(
             x = x,
             y = y,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 5ca36ab..ddb09749 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -156,9 +156,9 @@
         }
         windowLayoutParams.packageName = context.opPackageName
         rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
-            override fun onViewDetachedFromWindow(view: View?) {}
+            override fun onViewDetachedFromWindow(view: View) {}
 
-            override fun onViewAttachedToWindow(view: View?) {
+            override fun onViewAttachedToWindow(view: View) {
                 layoutRipple()
                 rippleView.startRipple(Runnable {
                     windowManager.removeView(rippleView)
@@ -176,7 +176,7 @@
         val height = bounds.height()
         val maxDiameter = Integer.max(width, height) * 2f
         rippleView.setMaxSize(maxDiameter, maxDiameter)
-        when (context.display.rotation) {
+        when (context.display?.rotation) {
             Surface.ROTATION_0 -> {
                 rippleView.setCenter(
                         width * normalizedPortPosX, height * normalizedPortPosY)
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
index 63d57cc..a9f3b77 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
@@ -29,7 +29,7 @@
  */
 class FalsingA11yDelegate @Inject constructor(private val falsingCollector: FalsingCollector) :
     View.AccessibilityDelegate() {
-    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+    override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean {
         if (action == ACTION_CLICK) {
             falsingCollector.onA11yAction()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt
index 0b8e83e..1b45ecd 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardTransitionExecutor.kt
@@ -59,7 +59,7 @@
         context.startActivity(intent, transition.first.toBundle())
         val runner = RemoteAnimationAdapter(NULL_ACTIVITY_TRANSITION, 0, 0)
         try {
-            WindowManagerGlobal.getWindowManagerService()
+            checkNotNull(WindowManagerGlobal.getWindowManagerService())
                 .overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId)
         } catch (e: Exception) {
             Log.e(TAG, "Error overriding clipboard app transition", e)
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
index 3e6ac86..c0d1951 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/data/repository/ConfigurationRepository.kt
@@ -100,7 +100,7 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), getResolutionScale())
 
     override fun getResolutionScale(): Float {
-        context.display.getDisplayInfo(displayInfo.value)
+        context.display?.getDisplayInfo(displayInfo.value)
         val maxDisplayMode =
             displayUtils.getMaximumResolutionDisplayMode(displayInfo.value.supportedModes)
         maxDisplayMode?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
index 2dd98dc..323070a 100644
--- a/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/ui/view/LongPressHandlingView.kt
@@ -22,6 +22,7 @@
 import android.util.AttributeSet
 import android.view.MotionEvent
 import android.view.View
+import com.android.systemui.shade.TouchLogger
 import kotlin.math.pow
 import kotlin.math.sqrt
 import kotlinx.coroutines.DisposableHandle
@@ -83,6 +84,10 @@
         interactionHandler.isLongPressHandlingEnabled = isEnabled
     }
 
+    override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+        return TouchLogger.logDispatchTouch("long_press", event, super.dispatchTouchEvent(event))
+    }
+
     @SuppressLint("ClickableViewAccessibility")
     override fun onTouchEvent(event: MotionEvent?): Boolean {
         return interactionHandler.onTouchEvent(event?.toModel())
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
index 6b1c85f..e627b68 100644
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt
@@ -66,9 +66,9 @@
 
         contrastButtons =
             mapOf(
-                CONTRAST_LEVEL_STANDARD to findViewById(R.id.contrast_button_standard),
-                CONTRAST_LEVEL_MEDIUM to findViewById(R.id.contrast_button_medium),
-                CONTRAST_LEVEL_HIGH to findViewById(R.id.contrast_button_high)
+                CONTRAST_LEVEL_STANDARD to requireViewById(R.id.contrast_button_standard),
+                CONTRAST_LEVEL_MEDIUM to requireViewById(R.id.contrast_button_medium),
+                CONTRAST_LEVEL_HIGH to requireViewById(R.id.contrast_button_high)
             )
 
         contrastButtons.forEach { (contrastLevel, contrastButton) ->
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 7db5968..638da86 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -190,7 +190,7 @@
                     PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
                 val servicePackageSet = serviceInfoSet.map { it.packageName }
                 prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED,
-                    completedSeedingPackageSet.intersect(servicePackageSet)).apply()
+                    completedSeedingPackageSet?.intersect(servicePackageSet) ?: emptySet()).apply()
 
                 var changed = false
                 favoriteComponentSet.subtract(serviceInfoSet).forEach {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 23721c9..8bae667 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -193,7 +193,7 @@
 
                         ControlsAnimations.enterAnimation(pageIndicator).apply {
                             addListener(object : AnimatorListenerAdapter() {
-                                override fun onAnimationEnd(animation: Animator?) {
+                                override fun onAnimationEnd(animation: Animator) {
                                     // Position the tooltip if necessary after animations are complete
                                     // so we can get the position on screen. The tooltip is not
                                     // rooted in the layout root.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
index ff55b76d..a13f717 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt
@@ -106,10 +106,8 @@
                 }
             )
 
-            getWindow().apply {
-                setType(WINDOW_TYPE)
-                setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
-            }
+            window?.setType(WINDOW_TYPE)
+            window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)
             setOnShowListener(DialogInterface.OnShowListener { _ ->
                 val editText = requireViewById<EditText>(R.id.controls_pin_input)
                 editText.setHint(instructions)
@@ -153,9 +151,7 @@
             )
         }
         return builder.create().apply {
-            getWindow().apply {
-                setType(WINDOW_TYPE)
-            }
+            window?.setType(WINDOW_TYPE)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
index c04bc87..abe3423 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt
@@ -384,7 +384,7 @@
                 )
             }
             addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     stateAnimator = null
                 }
             })
@@ -438,7 +438,7 @@
                 duration = 200L
                 interpolator = Interpolators.LINEAR
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         statusRowUpdater.invoke()
                     }
                 })
@@ -450,7 +450,7 @@
             statusAnimator = AnimatorSet().apply {
                 playSequentially(fadeOut, fadeIn)
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         status.alpha = STATUS_ALPHA_ENABLED
                         statusAnimator = null
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
index be50a14..98f17f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt
@@ -132,8 +132,8 @@
 
     init {
         // To pass touches to the task inside TaskView.
-        window.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
-        window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+        window?.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
+        window?.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
 
         setContentView(R.layout.controls_detail_dialog)
 
@@ -182,7 +182,7 @@
         }
 
         // consume all insets to achieve slide under effect
-        window.getDecorView().setOnApplyWindowInsetsListener {
+        checkNotNull(window).decorView.setOnApplyWindowInsetsListener {
             v: View, insets: WindowInsets ->
                 val l = v.getPaddingLeft()
                 val r = v.getPaddingRight()
@@ -202,7 +202,7 @@
     }
 
     fun getTaskViewBounds(): Rect {
-        val wm = context.getSystemService(WindowManager::class.java)
+        val wm = checkNotNull(context.getSystemService(WindowManager::class.java))
         val windowMetrics = wm.getCurrentWindowMetrics()
         val rect = windowMetrics.bounds
         val metricInsets = windowMetrics.windowInsets
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
index ad2b785..dbbda9a 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt
@@ -67,7 +67,8 @@
                     iconMap.put(resourceId, icon)
                 }
             }
-            return RenderInfo(icon!!.constantState.newDrawable(context.resources), fg, bg)
+            return RenderInfo(
+                checkNotNull(icon?.constantState).newDrawable(context.resources), fg, bg)
         }
 
         fun registerComponentIcon(componentName: ComponentName, icon: Drawable) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
index 84cda5a..3c2bfa0 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt
@@ -94,10 +94,8 @@
             )
         }
         cvh.visibleDialog = builder.create().apply {
-            getWindow().apply {
-                setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
-                show()
-            }
+            window?.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY)
+            show()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
index 1461135..b2c95a6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt
@@ -244,7 +244,7 @@
                     cvh.clipLayer.level = it.animatedValue as Int
                 }
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         rangeAnimator = null
                     }
                 })
@@ -335,7 +335,7 @@
         }
 
         override fun onScroll(
-            e1: MotionEvent,
+            e1: MotionEvent?,
             e2: MotionEvent,
             xDiff: Float,
             yDiff: Float
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java b/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java
index 0992882..585c390 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/AndroidInternalsModule.java
@@ -24,11 +24,11 @@
 import com.android.internal.util.NotificationMessagingUtil;
 import com.android.internal.widget.LockPatternUtils;
 
-import javax.inject.Singleton;
-
 import dagger.Module;
 import dagger.Provides;
 
+import javax.inject.Singleton;
+
 /**
  * Provides items imported from com.android.internal.
  */
@@ -51,7 +51,7 @@
     /** */
     @Provides
     public NotificationMessagingUtil provideNotificationMessagingUtil(Context context) {
-        return new NotificationMessagingUtil(context);
+        return new NotificationMessagingUtil(context, null);
     }
 
     /** Provides an instance of {@link com.android.internal.logging.UiEventLogger} */
diff --git a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
index 4e62104..ac0d3c8 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import java.util.concurrent.Executor
@@ -47,6 +48,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     @Main private val mainExecutor: Executor,
     private val logger: ScreenDecorationsLogger,
+    private val featureFlags: FeatureFlags,
 ) : DecorProviderFactory() {
     private val display = context.display
     private val displayInfo = DisplayInfo()
@@ -86,6 +88,7 @@
                                         keyguardUpdateMonitor,
                                         mainExecutor,
                                         logger,
+                                        featureFlags,
                                 )
                         )
                     }
@@ -110,6 +113,7 @@
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val mainExecutor: Executor,
     private val logger: ScreenDecorationsLogger,
+    private val featureFlags: FeatureFlags,
 ) : BoundDecorProvider() {
     override val viewId: Int = com.android.systemui.R.id.face_scanning_anim
 
@@ -144,6 +148,7 @@
                 mainExecutor,
                 logger,
                 authController,
+                featureFlags
         )
         view.id = viewId
         view.setColor(tintColor)
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
index bcfeeb9e..cef45dc 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayMetricsRepository.kt
@@ -51,7 +51,7 @@
                 val callback =
                     object : ConfigurationController.ConfigurationListener {
                         override fun onConfigChanged(newConfig: Configuration?) {
-                            context.display.getMetrics(displayMetricsHolder)
+                            context.display?.getMetrics(displayMetricsHolder)
                             trySend(displayMetricsHolder)
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index 7c816ce..34a80e8 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -26,9 +26,9 @@
 import android.text.format.Formatter;
 import android.util.Log;
 
+import com.android.systemui.DejankUtils;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.dagger.DozeScope;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.util.AlarmTimeout;
 import com.android.systemui.util.wakelock.WakeLock;
@@ -52,15 +52,21 @@
     private final boolean mCanAnimateTransition;
     private final DozeParameters mDozeParameters;
     private final DozeLog mDozeLog;
-    private final StatusBarStateController mStatusBarStateController;
 
     private long mLastTimeTickElapsed = 0;
+    // If time tick is scheduled and there's not a pending runnable to cancel:
+    private boolean mTimeTickScheduled;
+    private final Runnable mCancelTimeTickerRunnable =  new Runnable() {
+        @Override
+        public void run() {
+            mTimeTicker.cancel();
+        }
+    };
 
     @Inject
     public DozeUi(Context context, AlarmManager alarmManager,
             WakeLock wakeLock, DozeHost host, @Main Handler handler,
             DozeParameters params,
-            StatusBarStateController statusBarStateController,
             DozeLog dozeLog) {
         mContext = context;
         mWakeLock = wakeLock;
@@ -70,7 +76,6 @@
         mDozeParameters = params;
         mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
         mDozeLog = dozeLog;
-        mStatusBarStateController = statusBarStateController;
     }
 
     @Override
@@ -157,13 +162,15 @@
     }
 
     private void scheduleTimeTick() {
-        if (mTimeTicker.isScheduled()) {
+        if (mTimeTickScheduled) {
             return;
         }
+        mTimeTickScheduled = true;
+        DejankUtils.removeCallbacks(mCancelTimeTickerRunnable);
 
         long time = System.currentTimeMillis();
         long delta = roundToNextMinute(time) - System.currentTimeMillis();
-        boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+        boolean scheduled = mTimeTicker.schedule(delta, AlarmTimeout.MODE_RESCHEDULE_IF_SCHEDULED);
         if (scheduled) {
             mDozeLog.traceTimeTickScheduled(time, time + delta);
         }
@@ -171,11 +178,11 @@
     }
 
     private void unscheduleTimeTick() {
-        if (!mTimeTicker.isScheduled()) {
+        if (!mTimeTickScheduled) {
             return;
         }
-        verifyLastTimeTick();
-        mTimeTicker.cancel();
+        mTimeTickScheduled = false;
+        DejankUtils.postAfterTraversal(mCancelTimeTickerRunnable);
     }
 
     private void verifyLastTimeTick() {
@@ -205,6 +212,7 @@
         // Keep wakelock until a frame has been pushed.
         mHandler.post(mWakeLock.wrap(() -> {}));
 
+        mTimeTickScheduled = false;
         scheduleTimeTick();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index ae40f7e8..7150d69e 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -432,9 +432,11 @@
         }
 
         private inline fun PrintWriter.wrapSection(entry: DumpsysEntry, block: () -> Unit) {
+            Trace.beginSection(entry.name)
             preamble(entry)
             val dumpTime = measureTimeMillis(block)
             footer(entry, dumpTime)
+            Trace.endSection()
         }
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
index f7e6b98..2e9d04b 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.dump
 
+import android.os.Trace
 import java.io.PrintWriter
 
 /**
@@ -83,31 +84,33 @@
 ) {
 
     fun printTableData(pw: PrintWriter) {
+        Trace.beginSection("DumpsysTableLogger#printTableData")
         printSectionStart(pw)
         printSchema(pw)
         printData(pw)
         printSectionEnd(pw)
+        Trace.endSection()
     }
 
     private fun printSectionStart(pw: PrintWriter) {
-        pw.println(HEADER_PREFIX + sectionName)
-        pw.println("version $VERSION")
+        pw.append(HEADER_PREFIX).println(sectionName)
+        pw.append("version ").println(VERSION)
     }
 
     private fun printSectionEnd(pw: PrintWriter) {
-        pw.println(FOOTER_PREFIX + sectionName)
+        pw.append(FOOTER_PREFIX).println(sectionName)
     }
 
     private fun printSchema(pw: PrintWriter) {
-        pw.println(columns.joinToString(separator = SEPARATOR))
+        columns.joinTo(pw, separator = SEPARATOR).println()
     }
 
     private fun printData(pw: PrintWriter) {
         val count = columns.size
-        rows
-            .filter { it.size == count }
-            .forEach { dataLine ->
-                pw.println(dataLine.joinToString(separator = SEPARATOR))
+        rows.forEach { dataLine ->
+            if (dataLine.size == count) {
+                dataLine.joinTo(pw, separator = SEPARATOR).println()
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 367a9b9..4c78e4c 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -93,8 +93,7 @@
                             shouldRestart = true;
                         }
                     } else if (mStringFlagCache.containsKey(flag.getName())) {
-                        String newValue = value == null ? "" : value;
-                        if (mStringFlagCache.get(flag.getName()) != value) {
+                        if (!mStringFlagCache.get(flag.getName()).equals(value)) {
                             shouldRestart = true;
                         }
                     } else if (mIntFlagCache.containsKey(flag.getName())) {
@@ -202,8 +201,7 @@
         String name = flag.getName();
         if (!mStringFlagCache.containsKey(name)) {
             mStringFlagCache.put(name,
-                    readFlagValueInternal(
-                            flag.getId(), name, flag.getDefault(), StringFlagSerializer.INSTANCE));
+                    readFlagValueInternal(name, flag.getDefault(), StringFlagSerializer.INSTANCE));
         }
 
         return mStringFlagCache.get(name);
@@ -215,36 +213,30 @@
         String name = flag.getName();
         if (!mStringFlagCache.containsKey(name)) {
             mStringFlagCache.put(name,
-                    readFlagValueInternal(
-                            flag.getId(), name, mResources.getString(flag.getResourceId()),
+                    readFlagValueInternal(name, mResources.getString(flag.getResourceId()),
                             StringFlagSerializer.INSTANCE));
         }
 
         return mStringFlagCache.get(name);
     }
 
-
-    @NonNull
     @Override
     public int getInt(@NonNull IntFlag flag) {
         String name = flag.getName();
         if (!mIntFlagCache.containsKey(name)) {
             mIntFlagCache.put(name,
-                    readFlagValueInternal(
-                            flag.getId(), name, flag.getDefault(), IntFlagSerializer.INSTANCE));
+                    readFlagValueInternal(name, flag.getDefault(), IntFlagSerializer.INSTANCE));
         }
 
         return mIntFlagCache.get(name);
     }
 
-    @NonNull
     @Override
     public int getInt(@NonNull ResourceIntFlag flag) {
         String name = flag.getName();
         if (!mIntFlagCache.containsKey(name)) {
             mIntFlagCache.put(name,
-                    readFlagValueInternal(
-                            flag.getId(), name, mResources.getInteger(flag.getResourceId()),
+                    readFlagValueInternal(name, mResources.getInteger(flag.getResourceId()),
                             IntFlagSerializer.INSTANCE));
         }
 
@@ -254,13 +246,6 @@
     /** Specific override for Boolean flags that checks against the teamfood list.*/
     private boolean readBooleanFlagInternal(Flag<Boolean> flag, boolean defaultValue) {
         Boolean result = readBooleanFlagOverride(flag.getName());
-        if (result == null) {
-            result = readBooleanFlagOverride(flag.getId());
-            if (result != null) {
-                // Move overrides from id to name
-                setFlagValueInternal(flag.getName(), result, BooleanFlagSerializer.INSTANCE);
-            }
-        }
         boolean hasServerOverride = mServerFlagReader.hasOverride(
                 flag.getNamespace(), flag.getName());
 
@@ -278,45 +263,22 @@
                 flag.getNamespace(), flag.getName(), defaultValue) : result;
     }
 
-    private Boolean readBooleanFlagOverride(int id) {
-        return readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE);
-    }
 
     private Boolean readBooleanFlagOverride(String name) {
         return readFlagValueInternal(name, BooleanFlagSerializer.INSTANCE);
     }
 
-    // TODO(b/265188950): Remove id from this method once ids are fully deprecated.
     @NonNull
     private <T> T readFlagValueInternal(
-            int id, String name, @NonNull T defaultValue, FlagSerializer<T> serializer) {
+            String name, @NonNull T defaultValue, FlagSerializer<T> serializer) {
         requireNonNull(defaultValue, "defaultValue");
         T resultForName = readFlagValueInternal(name, serializer);
         if (resultForName == null) {
-            T resultForId = readFlagValueInternal(id, serializer);
-            if (resultForId == null) {
-                return defaultValue;
-            } else {
-                setFlagValue(name, resultForId, serializer);
-                return resultForId;
-            }
+            return defaultValue;
         }
         return resultForName;
     }
 
-
-    /** Returns the stored value or null if not set. */
-    // TODO(b/265188950): Remove method this once ids are fully deprecated.
-    @Nullable
-    private <T> T readFlagValueInternal(int id, FlagSerializer<T> serializer) {
-        try {
-            return mFlagManager.readFlagValue(id, serializer);
-        } catch (Exception e) {
-            eraseInternal(id);
-        }
-        return null;
-    }
-
     /** Returns the stored value or null if not set. */
     @Nullable
     private <T> T readFlagValueInternal(String name, FlagSerializer<T> serializer) {
@@ -384,15 +346,6 @@
     }
 
     /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */
-    // TODO(b/265188950): Remove method this once ids are fully deprecated.
-    private void eraseInternal(int id) {
-        // We can't actually "erase" things from settings, but we can set them to empty!
-        mGlobalSettings.putStringForUser(mFlagManager.idToSettingsKey(id), "",
-                UserHandle.USER_CURRENT);
-        Log.i(TAG, "Erase name " + id);
-    }
-
-    /** Works just like {@link #eraseFlag(String)} except that it doesn't restart SystemUI. */
     private void eraseInternal(String name) {
         // We can't actually "erase" things from settings, but we can set them to empty!
         mGlobalSettings.putStringForUser(mFlagManager.nameToSettingsKey(name), "",
@@ -433,7 +386,7 @@
             setFlagValue(flag.getName(), value, BooleanFlagSerializer.INSTANCE);
         } else if (flag instanceof SysPropBooleanFlag) {
             // Store SysProp flags in SystemProperties where they can read by outside parties.
-            mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value);
+            mSystemProperties.setBoolean(flag.getName(), value);
             dispatchListenersAndMaybeRestart(
                     flag.getName(),
                     suppressRestart -> restartSystemUI(
@@ -526,7 +479,7 @@
                 }
             } catch (IllegalArgumentException e) {
                 Log.w(TAG,
-                        "Unable to set " + flag.getId() + " of type " + flag.getClass()
+                        "Unable to set " + flag.getName() + " of type " + flag.getClass()
                                 + " to value of type " + (value == null ? null : value.getClass()));
             }
         }
@@ -544,21 +497,18 @@
 
             if (f instanceof ReleasedFlag) {
                 enabled = isEnabled((ReleasedFlag) f);
-                overridden = readBooleanFlagOverride(f.getName()) != null
-                            || readBooleanFlagOverride(f.getId()) != null;
+                overridden = readBooleanFlagOverride(f.getName()) != null;
             } else if (f instanceof UnreleasedFlag) {
                 enabled = isEnabled((UnreleasedFlag) f);
-                overridden = readBooleanFlagOverride(f.getName()) != null
-                            || readBooleanFlagOverride(f.getId()) != null;
+                overridden = readBooleanFlagOverride(f.getName()) != null;
             } else if (f instanceof ResourceBooleanFlag) {
                 enabled = isEnabled((ResourceBooleanFlag) f);
-                overridden = readBooleanFlagOverride(f.getName()) != null
-                            || readBooleanFlagOverride(f.getId()) != null;
+                overridden = readBooleanFlagOverride(f.getName()) != null;
             } else if (f instanceof SysPropBooleanFlag) {
                 // TODO(b/223379190): Teamfood not supported for sysprop flags yet.
                 enabled = isEnabled((SysPropBooleanFlag) f);
                 teamfood = false;
-                overridden = !mSystemProperties.get(((SysPropBooleanFlag) f).getName()).isEmpty();
+                overridden = !mSystemProperties.get(f.getName()).isEmpty();
             } else {
                 // TODO: add support for other flag types.
                 Log.w(TAG, "Unsupported Flag Type. Please file a bug.");
@@ -566,11 +516,9 @@
             }
 
             if (enabled) {
-                return new ReleasedFlag(
-                        f.getId(), f.getName(), f.getNamespace(), teamfood, overridden);
+                return new ReleasedFlag(f.getName(), f.getNamespace(), teamfood, overridden);
             } else {
-                return new UnreleasedFlag(
-                        f.getId(), f.getName(), f.getNamespace(), teamfood, overridden);
+                return new UnreleasedFlag(f.getName(), f.getNamespace(), teamfood, overridden);
             }
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 9d19a7d..e03b438 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -67,7 +67,7 @@
                         }
                     } else if (mStringCache.containsKey(flag.getName())) {
                         String newValue = value == null ? "" : value;
-                        if (mStringCache.get(flag.getName()) != newValue) {
+                        if (!mStringCache.get(flag.getName()).equals(newValue)) {
                             shouldRestart = true;
                         }
                     } else if (mIntCache.containsKey(flag.getName())) {
@@ -185,14 +185,12 @@
         return mStringCache.get(name);
     }
 
-    @NonNull
     @Override
     public int getInt(@NonNull IntFlag flag) {
         // Fill the cache.
         return getIntInternal(flag.getName(), flag.getDefault());
     }
 
-    @NonNull
     @Override
     public int getInt(@NonNull ResourceIntFlag flag) {
         // Fill the cache.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index daf9429..bd0ed48 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -219,17 +219,6 @@
         return 0;
     }
 
-    private int flagNameToId(String flagName) {
-        Map<String, Flag<?>> flagFields = FlagsFactory.INSTANCE.getKnownFlags();
-        for (String fieldName : flagFields.keySet()) {
-            if (flagName.equals(fieldName)) {
-                return flagFields.get(fieldName).getId();
-            }
-        }
-
-        return 0;
-    }
-
     private void printKnownFlags(PrintWriter pw) {
         Map<String, Flag<?>> fields = FlagsFactory.INSTANCE.getKnownFlags();
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index a2fbd9c..4a22a67 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -36,30 +36,29 @@
  * See [FeatureFlagsDebug] for instructions on flipping the flags via adb.
  */
 object Flags {
-    @JvmField val TEAMFOOD = unreleasedFlag(1, "teamfood")
+    @JvmField val TEAMFOOD = unreleasedFlag("teamfood")
 
     // 100 - notification
     // TODO(b/254512751): Tracking Bug
     val NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
-        unreleasedFlag(103, "notification_pipeline_developer_logging")
+        unreleasedFlag("notification_pipeline_developer_logging")
 
     // TODO(b/254512732): Tracking Bug
-    @JvmField val NSSL_DEBUG_LINES = unreleasedFlag(105, "nssl_debug_lines")
+    @JvmField val NSSL_DEBUG_LINES = unreleasedFlag("nssl_debug_lines")
 
     // TODO(b/254512505): Tracking Bug
-    @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = unreleasedFlag(106, "nssl_debug_remove_animation")
+    @JvmField val NSSL_DEBUG_REMOVE_ANIMATION = unreleasedFlag("nssl_debug_remove_animation")
 
     // TODO(b/254512624): Tracking Bug
     @JvmField
     val NOTIFICATION_DRAG_TO_CONTENTS =
         resourceBooleanFlag(
-            108,
             R.bool.config_notificationToContents,
             "notification_drag_to_contents"
         )
 
     // TODO(b/254512538): Tracking Bug
-    val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
+    val INSTANT_VOICE_REPLY = unreleasedFlag("instant_voice_reply")
 
     /**
      * This flag controls whether we register a listener for StatsD notification memory reports.
@@ -67,48 +66,47 @@
      * enabled as well.
      */
     val NOTIFICATION_MEMORY_LOGGING_ENABLED =
-            releasedFlag(119, "notification_memory_logging_enabled")
+            releasedFlag("notification_memory_logging_enabled")
 
     // TODO(b/260335638): Tracking Bug
     @JvmField
     val NOTIFICATION_INLINE_REPLY_ANIMATION =
-        unreleasedFlag(174148361, "notification_inline_reply_animation")
+        unreleasedFlag("notification_inline_reply_animation")
 
     /** Makes sure notification panel is updated before the user switch is complete. */
     // TODO(b/278873737): Tracking Bug
     @JvmField
     val LOAD_NOTIFICATIONS_BEFORE_THE_USER_SWITCH_IS_COMPLETE =
-        releasedFlag(278873737, "load_notifications_before_the_user_switch_is_complete")
+        releasedFlag("load_notifications_before_the_user_switch_is_complete")
 
     // TODO(b/277338665): Tracking Bug
     @JvmField
     val NOTIFICATION_SHELF_REFACTOR =
-        unreleasedFlag(271161129, "notification_shelf_refactor", teamfood = true)
+        unreleasedFlag("notification_shelf_refactor", teamfood = true)
 
     // TODO(b/290787599): Tracking Bug
     @JvmField
     val NOTIFICATION_ICON_CONTAINER_REFACTOR =
-        unreleasedFlag(278765923, "notification_icon_container_refactor")
+        unreleasedFlag("notification_icon_container_refactor")
 
     // TODO(b/288326013): Tracking Bug
     @JvmField
     val NOTIFICATION_ASYNC_HYBRID_VIEW_INFLATION =
-        unreleasedFlag(288326013, "notification_async_hybrid_view_inflation", teamfood = false)
+        unreleasedFlag("notification_async_hybrid_view_inflation", teamfood = false)
 
     @JvmField
     val ANIMATED_NOTIFICATION_SHADE_INSETS =
-        releasedFlag(270682168, "animated_notification_shade_insets")
+        releasedFlag("animated_notification_shade_insets")
 
     // TODO(b/268005230): Tracking Bug
     @JvmField
-    val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim", teamfood = true)
+    val SENSITIVE_REVEAL_ANIM = unreleasedFlag("sensitive_reveal_anim", teamfood = true)
 
     // TODO(b/280783617): Tracking Bug
     @Keep
     @JvmField
     val BUILDER_EXTRAS_OVERRIDE =
         sysPropBooleanFlag(
-            128,
             "persist.sysui.notification.builder_extras_override",
             default = true
         )
@@ -117,27 +115,26 @@
     // TODO(b/292213543): Tracking Bug
     @JvmField
     val NOTIFICATION_GROUP_EXPANSION_CHANGE =
-            unreleasedFlag(292213543, "notification_group_expansion_change", teamfood = false)
+            unreleasedFlag("notification_group_expansion_change", teamfood = false)
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
     // public static final BooleanFlag KEYGUARD_LAYOUT =
-    //         new BooleanFlag(200, true);
+    //         new BooleanFlag(true);
 
     // TODO(b/254512750): Tracking Bug
-    val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag(202, "new_unlock_swipe_animation")
-    val CHARGING_RIPPLE = resourceBooleanFlag(203, R.bool.flag_charging_ripple, "charging_ripple")
+    val NEW_UNLOCK_SWIPE_ANIMATION = releasedFlag("new_unlock_swipe_animation")
+    val CHARGING_RIPPLE = resourceBooleanFlag(R.bool.flag_charging_ripple, "charging_ripple")
 
     // TODO(b/254512281): Tracking Bug
     @JvmField
     val BOUNCER_USER_SWITCHER =
-        resourceBooleanFlag(204, R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher")
+        resourceBooleanFlag(R.bool.config_enableBouncerUserSwitcher, "bouncer_user_switcher")
 
     // TODO(b/254512676): Tracking Bug
     @JvmField
     val LOCKSCREEN_CUSTOM_CLOCKS =
         resourceBooleanFlag(
-            207,
             R.bool.config_enableLockScreenCustomClocks,
             "lockscreen_custom_clocks"
         )
@@ -145,28 +142,28 @@
     // TODO(b/275694445): Tracking Bug
     @JvmField
     val LOCKSCREEN_WITHOUT_SECURE_LOCK_WHEN_DREAMING =
-        releasedFlag(208, "lockscreen_without_secure_lock_when_dreaming")
+        releasedFlag("lockscreen_without_secure_lock_when_dreaming")
 
     // TODO(b/286092087): Tracking Bug
     @JvmField
-    val ENABLE_SYSTEM_UI_DREAM_CONTROLLER = unreleasedFlag(301, "enable_system_ui_dream_controller")
+    val ENABLE_SYSTEM_UI_DREAM_CONTROLLER = unreleasedFlag("enable_system_ui_dream_controller")
 
     // TODO(b/288287730): Tracking Bug
     @JvmField
-    val ENABLE_SYSTEM_UI_DREAM_HOSTING = unreleasedFlag(302, "enable_system_ui_dream_hosting")
+    val ENABLE_SYSTEM_UI_DREAM_HOSTING = unreleasedFlag("enable_system_ui_dream_hosting")
 
     /**
      * Whether the clock on a wide lock screen should use the new "stepping" animation for moving
      * the digits when the clock moves.
      */
-    @JvmField val STEP_CLOCK_ANIMATION = releasedFlag(212, "step_clock_animation")
+    @JvmField val STEP_CLOCK_ANIMATION = releasedFlag("step_clock_animation")
 
     /**
      * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
      * will occur in stages. This is one stage of many to come.
      */
     // TODO(b/255607168): Tracking Bug
-    @JvmField val DOZING_MIGRATION_1 = unreleasedFlag(213, "dozing_migration_1")
+    @JvmField val DOZING_MIGRATION_1 = unreleasedFlag("dozing_migration_1")
 
     /**
      * Migrates control of the LightRevealScrim's reveal effect and amount from legacy code to the
@@ -174,80 +171,82 @@
      */
     // TODO(b/281655028): Tracking bug
     @JvmField
-    val LIGHT_REVEAL_MIGRATION = unreleasedFlag(218, "light_reveal_migration", teamfood = false)
+    val LIGHT_REVEAL_MIGRATION = unreleasedFlag("light_reveal_migration", teamfood = false)
 
     /** Flag to control the migration of face auth to modern architecture. */
     // TODO(b/262838215): Tracking bug
-    @JvmField val FACE_AUTH_REFACTOR = unreleasedFlag(220, "face_auth_refactor")
+    @JvmField val FACE_AUTH_REFACTOR = unreleasedFlag("face_auth_refactor")
 
     /** Flag to control the revamp of keyguard biometrics progress animation */
     // TODO(b/244313043): Tracking bug
-    @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag(221, "biometrics_animation_revamp")
+    @JvmField val BIOMETRICS_ANIMATION_REVAMP = unreleasedFlag("biometrics_animation_revamp")
 
     // TODO(b/262780002): Tracking Bug
-    @JvmField val REVAMPED_WALLPAPER_UI = releasedFlag(222, "revamped_wallpaper_ui")
+    @JvmField val REVAMPED_WALLPAPER_UI = releasedFlag("revamped_wallpaper_ui")
 
     // flag for controlling auto pin confirmation and material u shapes in bouncer
     @JvmField
-    val AUTO_PIN_CONFIRMATION = releasedFlag(224, "auto_pin_confirmation", "auto_pin_confirmation")
+    val AUTO_PIN_CONFIRMATION = releasedFlag("auto_pin_confirmation", "auto_pin_confirmation")
 
     // TODO(b/262859270): Tracking Bug
-    @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag(225, "falsing_off_for_unfolded")
+    @JvmField val FALSING_OFF_FOR_UNFOLDED = releasedFlag("falsing_off_for_unfolded")
 
     /** Enables code to show contextual loyalty cards in wallet entrypoints */
     // TODO(b/294110497): Tracking Bug
     @JvmField
     val ENABLE_WALLET_CONTEXTUAL_LOYALTY_CARDS =
-        unreleasedFlag(226, "enable_wallet_contextual_loyalty_cards", teamfood = true)
+        unreleasedFlag("enable_wallet_contextual_loyalty_cards", teamfood = true)
 
     // TODO(b/242908637): Tracking Bug
-    @JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag(227, "wallpaper_fullscreen_preview")
+    @JvmField val WALLPAPER_FULLSCREEN_PREVIEW = releasedFlag("wallpaper_fullscreen_preview")
 
     /** Whether the long-press gesture to open wallpaper picker is enabled. */
     // TODO(b/266242192): Tracking Bug
     @JvmField
-    val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag(228, "lock_screen_long_press_enabled")
+    val LOCK_SCREEN_LONG_PRESS_ENABLED = releasedFlag("lock_screen_long_press_enabled")
 
     /** Enables UI updates for AI wallpapers in the wallpaper picker. */
     // TODO(b/267722622): Tracking Bug
-    @JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag(229, "wallpaper_picker_ui_for_aiwp")
+    @JvmField val WALLPAPER_PICKER_UI_FOR_AIWP = releasedFlag("wallpaper_picker_ui_for_aiwp")
 
     /** Whether to use a new data source for intents to run on keyguard dismissal. */
     // TODO(b/275069969): Tracking bug.
     @JvmField
-    val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag(231, "refactor_keyguard_dismiss_intent")
+    val REFACTOR_KEYGUARD_DISMISS_INTENT = unreleasedFlag("refactor_keyguard_dismiss_intent")
 
     /** Whether to allow long-press on the lock screen to directly open wallpaper picker. */
     // TODO(b/277220285): Tracking bug.
     @JvmField
     val LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP =
-        unreleasedFlag(232, "lock_screen_long_press_directly_opens_wallpaper_picker")
+        unreleasedFlag("lock_screen_long_press_directly_opens_wallpaper_picker")
 
     /** Whether page transition animations in the wallpaper picker are enabled */
     // TODO(b/291710220): Tracking bug.
     @JvmField
     val WALLPAPER_PICKER_PAGE_TRANSITIONS =
-        unreleasedFlag(291710220, "wallpaper_picker_page_transitions")
+        unreleasedFlag("wallpaper_picker_page_transitions")
+
+    /** Add "Apply" button to wall paper picker's grid preview page. */
+    // TODO(b/294866904): Tracking bug.
+    @JvmField
+    val WALLPAPER_PICKER_GRID_APPLY_BUTTON =
+            unreleasedFlag("wallpaper_picker_grid_apply_button")
 
     /** Whether to run the new udfps keyguard refactor code. */
     // TODO(b/279440316): Tracking bug.
     @JvmField
-    val REFACTOR_UDFPS_KEYGUARD_VIEWS = unreleasedFlag(233, "refactor_udfps_keyguard_views")
+    val REFACTOR_UDFPS_KEYGUARD_VIEWS = unreleasedFlag("refactor_udfps_keyguard_views")
 
     /** Provide new auth messages on the bouncer. */
     // TODO(b/277961132): Tracking bug.
-    @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag(234, "revamped_bouncer_messages")
+    @JvmField val REVAMPED_BOUNCER_MESSAGES = unreleasedFlag("revamped_bouncer_messages")
 
     /** Whether to delay showing bouncer UI when face auth or active unlock are enrolled. */
     // TODO(b/279794160): Tracking bug.
-    @JvmField val DELAY_BOUNCER = releasedFlag(235, "delay_bouncer")
+    @JvmField val DELAY_BOUNCER = releasedFlag("delay_bouncer")
 
     /** Keyguard Migration */
 
-    /** Migrate the indication area to the new keyguard root view. */
-    // TODO(b/280067944): Tracking bug.
-    @JvmField val MIGRATE_INDICATION_AREA = releasedFlag(236, "migrate_indication_area")
-
     /**
      * Migrate the bottom area to the new keyguard root view. Because there is no such thing as a
      * "bottom area" after this, this also breaks it up into many smaller, modular pieces.
@@ -255,208 +254,234 @@
     // TODO(b/290652751): Tracking bug.
     @JvmField
     val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA =
-        unreleasedFlag(290652751, "migrate_split_keyguard_bottom_area")
+        unreleasedFlag("migrate_split_keyguard_bottom_area")
 
     /** Whether to listen for fingerprint authentication over keyguard occluding activities. */
     // TODO(b/283260512): Tracking bug.
-    @JvmField val FP_LISTEN_OCCLUDING_APPS = unreleasedFlag(237, "fp_listen_occluding_apps",
-            teamfood = true)
+    @JvmField val FP_LISTEN_OCCLUDING_APPS = releasedFlag("fp_listen_occluding_apps")
 
     /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */
     // TODO(b/286563884): Tracking bug
-    @JvmField val KEYGUARD_TALKBACK_FIX = releasedFlag(238, "keyguard_talkback_fix")
+    @JvmField val KEYGUARD_TALKBACK_FIX = releasedFlag("keyguard_talkback_fix")
 
     // TODO(b/287268101): Tracking bug.
-    @JvmField val TRANSIT_CLOCK = releasedFlag(239, "lockscreen_custom_transit_clock")
+    @JvmField val TRANSIT_CLOCK = releasedFlag("lockscreen_custom_transit_clock")
 
     /** Migrate the lock icon view to the new keyguard root view. */
     // TODO(b/286552209): Tracking bug.
-    @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag(240, "migrate_lock_icon")
+    @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag("migrate_lock_icon")
 
     // TODO(b/288276738): Tracking bug.
-    @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag(241, "widget_on_keyguard")
+    @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard")
 
     /** Migrate the NSSL to the a sibling to both the panel and keyguard root view. */
     // TODO(b/288074305): Tracking bug.
-    @JvmField val MIGRATE_NSSL = unreleasedFlag(242, "migrate_nssl")
+    @JvmField val MIGRATE_NSSL = unreleasedFlag("migrate_nssl")
 
     /** Migrate the status view from the notification panel to keyguard root view. */
     // TODO(b/291767565): Tracking bug.
-    @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag(243, "migrate_keyguard_status_view")
+    @JvmField val MIGRATE_KEYGUARD_STATUS_VIEW = unreleasedFlag("migrate_keyguard_status_view")
 
     /** Enables preview loading animation in the wallpaper picker. */
     // TODO(b/274443705): Tracking Bug
     @JvmField
     val WALLPAPER_PICKER_PREVIEW_ANIMATION =
             unreleasedFlag(
-                    244,
-                    "wallpaper_picker_preview_animation"
+                    "wallpaper_picker_preview_animation",
+                teamfood = true
             )
 
+    /**
+     * TODO(b/278086361): Tracking bug
+     * Complete rewrite of the interactions between System UI and Window Manager involving keyguard
+     * state. When enabled, calls to ActivityTaskManagerService from System UI will exclusively
+     * occur from [WmLockscreenVisibilityManager] rather than the legacy KeyguardViewMediator.
+     *
+     * This flag is under development; some types of unlock may not animate properly if you enable
+     * it.
+     */
+    @JvmField
+    val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
+            unreleasedFlag("keyguard_wm_state_refactor")
+
+    /** Stop running face auth when the display state changes to OFF. */
+    // TODO(b/294221702): Tracking bug.
+    @JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag(
+            R.bool.flag_stop_face_auth_on_display_off, "stop_face_auth_on_display_off")
+
+    /** Flag to disable the face scanning animation pulsing. */
+    // TODO(b/295245791): Tracking bug.
+    @JvmField val STOP_PULSING_FACE_SCANNING_ANIMATION = resourceBooleanFlag(
+            R.bool.flag_stop_pulsing_face_scanning_animation,
+            "stop_pulsing_face_scanning_animation")
+
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
-    @JvmField val POWER_MENU_LITE = releasedFlag(300, "power_menu_lite")
+    @JvmField val POWER_MENU_LITE = releasedFlag("power_menu_lite")
 
     // 400 - smartspace
 
     // TODO(b/254513100): Tracking Bug
     val SMARTSPACE_SHARED_ELEMENT_TRANSITION_ENABLED =
-        releasedFlag(401, "smartspace_shared_element_transition_enabled")
+        releasedFlag("smartspace_shared_element_transition_enabled")
 
     // TODO(b/258517050): Clean up after the feature is launched.
     @JvmField
     val SMARTSPACE_DATE_WEATHER_DECOUPLED =
-        sysPropBooleanFlag(403, "persist.sysui.ss.dw_decoupled", default = true)
+        sysPropBooleanFlag("persist.sysui.ss.dw_decoupled", default = true)
 
     // TODO(b/270223352): Tracking Bug
     @JvmField
-    val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = releasedFlag(404, "hide_smartspace_on_dream_overlay")
+    val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = releasedFlag("hide_smartspace_on_dream_overlay")
 
     // TODO(b/271460958): Tracking Bug
     @JvmField
     val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY =
-        releasedFlag(405, "show_weather_complication_on_dream_overlay")
+        releasedFlag("show_weather_complication_on_dream_overlay")
 
     // 500 - quick settings
 
-    val PEOPLE_TILE = resourceBooleanFlag(502, R.bool.flag_conversations, "people_tile")
+    val PEOPLE_TILE = resourceBooleanFlag(R.bool.flag_conversations, "people_tile")
 
     @JvmField
     val QS_USER_DETAIL_SHORTCUT =
         resourceBooleanFlag(
-            503,
             R.bool.flag_lockscreen_qs_user_detail_shortcut,
             "qs_user_detail_shortcut"
         )
 
     @JvmField
-    val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = true)
+    val QS_PIPELINE_NEW_HOST = unreleasedFlag("qs_pipeline_new_host", teamfood = true)
 
     // TODO(b/278068252): Tracking Bug
     @JvmField
-    val QS_PIPELINE_AUTO_ADD = unreleasedFlag(505, "qs_pipeline_auto_add", teamfood = false)
+    val QS_PIPELINE_AUTO_ADD = unreleasedFlag("qs_pipeline_auto_add", teamfood = false)
 
     // TODO(b/254512383): Tracking Bug
     @JvmField
     val FULL_SCREEN_USER_SWITCHER =
         resourceBooleanFlag(
-            506,
             R.bool.config_enableFullscreenUserSwitcher,
             "full_screen_user_switcher"
         )
 
     // TODO(b/244064524): Tracking Bug
-    @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag(508, "qs_secondary_data_sub_info")
+    @JvmField val QS_SECONDARY_DATA_SUB_INFO = releasedFlag("qs_secondary_data_sub_info")
 
     /** Enables Font Scaling Quick Settings tile */
     // TODO(b/269341316): Tracking Bug
-    @JvmField val ENABLE_FONT_SCALING_TILE = releasedFlag(509, "enable_font_scaling_tile")
+    @JvmField val ENABLE_FONT_SCALING_TILE = releasedFlag("enable_font_scaling_tile")
 
     /** Enables new QS Edit Mode visual refresh */
     // TODO(b/269787742): Tracking Bug
     @JvmField
-    val ENABLE_NEW_QS_EDIT_MODE = unreleasedFlag(510, "enable_new_qs_edit_mode", teamfood = false)
+    val ENABLE_NEW_QS_EDIT_MODE = unreleasedFlag("enable_new_qs_edit_mode", teamfood = false)
 
     // 600- status bar
 
     // TODO(b/265892345): Tracking Bug
-    val PLUG_IN_STATUS_BAR_CHIP = releasedFlag(265892345, "plug_in_status_bar_chip")
+    val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip")
 
     // TODO(b/280426085): Tracking Bug
-    @JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag(612, "new_bluetooth_repository")
+    @JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag("new_bluetooth_repository")
 
     // TODO(b/292533677): Tracking Bug
     val WIFI_TRACKER_LIB_FOR_WIFI_ICON =
-        unreleasedFlag(613, "wifi_tracker_lib_for_wifi_icon")
+        unreleasedFlag("wifi_tracker_lib_for_wifi_icon", teamfood = true)
 
     // TODO(b/293863612): Tracking Bug
     @JvmField val INCOMPATIBLE_CHARGING_BATTERY_ICON =
-        unreleasedFlag(614, "incompatible_charging_battery_icon")
+        releasedFlag("incompatible_charging_battery_icon")
+
+    // TODO(b/293585143): Tracking Bug
+    val INSTANT_TETHER = unreleasedFlag("instant_tether")
+
+    // TODO(b/294588085): Tracking Bug
+    val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
 
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
-    val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag(700, "ongoing_call_status_bar_chip")
+    val ONGOING_CALL_STATUS_BAR_CHIP = releasedFlag("ongoing_call_status_bar_chip")
 
     // TODO(b/254512681): Tracking Bug
-    val ONGOING_CALL_IN_IMMERSIVE = releasedFlag(701, "ongoing_call_in_immersive")
+    val ONGOING_CALL_IN_IMMERSIVE = releasedFlag("ongoing_call_in_immersive")
 
     // TODO(b/254512753): Tracking Bug
-    val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = releasedFlag(702, "ongoing_call_in_immersive_chip_tap")
+    val ONGOING_CALL_IN_IMMERSIVE_CHIP_TAP = releasedFlag("ongoing_call_in_immersive_chip_tap")
 
     // 800 - general visual/theme
-    @JvmField val MONET = resourceBooleanFlag(800, R.bool.flag_monet, "monet")
+    @JvmField val MONET = resourceBooleanFlag(R.bool.flag_monet, "monet")
 
     // 801 - region sampling
     // TODO(b/254512848): Tracking Bug
-    val REGION_SAMPLING = unreleasedFlag(801, "region_sampling")
+    val REGION_SAMPLING = unreleasedFlag("region_sampling")
 
     // 803 - screen contents translation
     // TODO(b/254513187): Tracking Bug
-    val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag(803, "screen_contents_translation")
+    val SCREEN_CONTENTS_TRANSLATION = unreleasedFlag("screen_contents_translation")
 
     // 804 - monochromatic themes
-    @JvmField val MONOCHROMATIC_THEME = releasedFlag(804, "monochromatic")
+    @JvmField val MONOCHROMATIC_THEME = releasedFlag("monochromatic")
 
     // TODO(b/293380347): Tracking Bug
-    @JvmField val COLOR_FIDELITY = unreleasedFlag(805, "color_fidelity")
+    @JvmField val COLOR_FIDELITY = unreleasedFlag("color_fidelity")
 
     // 900 - media
     // TODO(b/254512697): Tracking Bug
-    val MEDIA_TAP_TO_TRANSFER = releasedFlag(900, "media_tap_to_transfer")
+    val MEDIA_TAP_TO_TRANSFER = releasedFlag("media_tap_to_transfer")
 
     // TODO(b/254512502): Tracking Bug
-    val MEDIA_SESSION_ACTIONS = unreleasedFlag(901, "media_session_actions")
+    val MEDIA_SESSION_ACTIONS = unreleasedFlag("media_session_actions")
 
     // TODO(b/254512654): Tracking Bug
-    @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag(905, "dream_media_complication")
+    @JvmField val DREAM_MEDIA_COMPLICATION = unreleasedFlag("dream_media_complication")
 
     // TODO(b/254512673): Tracking Bug
-    @JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag(906, "dream_media_tap_to_open")
+    @JvmField val DREAM_MEDIA_TAP_TO_OPEN = unreleasedFlag("dream_media_tap_to_open")
 
     // TODO(b/254513168): Tracking Bug
-    @JvmField val UMO_SURFACE_RIPPLE = releasedFlag(907, "umo_surface_ripple")
+    @JvmField val UMO_SURFACE_RIPPLE = releasedFlag("umo_surface_ripple")
 
     // TODO(b/261734857): Tracking Bug
-    @JvmField val UMO_TURBULENCE_NOISE = releasedFlag(909, "umo_turbulence_noise")
+    @JvmField val UMO_TURBULENCE_NOISE = releasedFlag("umo_turbulence_noise")
 
     // TODO(b/263272731): Tracking Bug
-    val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag(910, "media_ttt_receiver_success_ripple")
+    val MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE = releasedFlag("media_ttt_receiver_success_ripple")
 
     // TODO(b/266157412): Tracking Bug
-    val MEDIA_RETAIN_SESSIONS = unreleasedFlag(913, "media_retain_sessions")
+    val MEDIA_RETAIN_SESSIONS = unreleasedFlag("media_retain_sessions")
 
     // TODO(b/267007629): Tracking Bug
-    val MEDIA_RESUME_PROGRESS = releasedFlag(915, "media_resume_progress")
+    val MEDIA_RESUME_PROGRESS = releasedFlag("media_resume_progress")
 
     // TODO(b/267166152) : Tracking Bug
-    val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag(916, "media_retain_recommendations")
+    val MEDIA_RETAIN_RECOMMENDATIONS = unreleasedFlag("media_retain_recommendations")
 
     // TODO(b/270437894): Tracking Bug
-    val MEDIA_REMOTE_RESUME = unreleasedFlag(917, "media_remote_resume")
+    val MEDIA_REMOTE_RESUME = unreleasedFlag("media_remote_resume")
 
     // 1000 - dock
-    val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
+    val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag("simulate_dock_through_charging")
 
     // TODO(b/254512758): Tracking Bug
-    @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
+    @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag("rounded_box_ripple")
 
     // TODO(b/273509374): Tracking Bug
     @JvmField
     val ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS =
-        releasedFlag(1006, "always_show_home_controls_on_dreams")
+        releasedFlag("always_show_home_controls_on_dreams")
 
     // 1100 - windowing
     @Keep
     @JvmField
     val WM_ENABLE_SHELL_TRANSITIONS =
-        sysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", default = true)
+        sysPropBooleanFlag("persist.wm.debug.shell_transit", default = true)
 
     // TODO(b/254513207): Tracking Bug
     @Keep
     @JvmField
     val WM_ENABLE_PARTIAL_SCREEN_SHARING =
         unreleasedFlag(
-            1102,
             name = "record_task_content",
             namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
             teamfood = true
@@ -466,50 +491,49 @@
     @Keep
     @JvmField
     val HIDE_NAVBAR_WINDOW =
-        sysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", default = false)
+        sysPropBooleanFlag("persist.wm.debug.hide_navbar_window", default = false)
 
     @Keep
     @JvmField
     val WM_DESKTOP_WINDOWING =
-        sysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", default = false)
+        sysPropBooleanFlag("persist.wm.debug.desktop_mode", default = false)
 
     @Keep
     @JvmField
     val WM_CAPTION_ON_SHELL =
-        sysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", default = true)
+        sysPropBooleanFlag("persist.wm.debug.caption_on_shell", default = true)
 
     @Keep
     @JvmField
     val ENABLE_FLING_TO_DISMISS_BUBBLE =
-        sysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", default = true)
+        sysPropBooleanFlag("persist.wm.debug.fling_to_dismiss_bubble", default = true)
 
     @Keep
     @JvmField
     val ENABLE_FLING_TO_DISMISS_PIP =
-        sysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", default = true)
+        sysPropBooleanFlag("persist.wm.debug.fling_to_dismiss_pip", default = true)
 
     @Keep
     @JvmField
     val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
-        sysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", default = true)
+        sysPropBooleanFlag("persist.wm.debug.enable_pip_keep_clear_algorithm", default = true)
 
     // TODO(b/256873975): Tracking Bug
     @JvmField
     @Keep
-    val WM_BUBBLE_BAR = sysPropBooleanFlag(1111, "persist.wm.debug.bubble_bar", default = false)
+    val WM_BUBBLE_BAR = sysPropBooleanFlag("persist.wm.debug.bubble_bar", default = false)
 
     // TODO(b/260271148): Tracking bug
     @Keep
     @JvmField
     val WM_DESKTOP_WINDOWING_2 =
-        sysPropBooleanFlag(1112, "persist.wm.debug.desktop_mode_2", default = false)
+        sysPropBooleanFlag("persist.wm.debug.desktop_mode_2", default = false)
 
     // TODO(b/254513207): Tracking Bug to delete
     @Keep
     @JvmField
     val WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES =
         unreleasedFlag(
-            1113,
             name = "screen_record_enterprise_policies",
             namespace = DeviceConfig.NAMESPACE_WINDOW_MANAGER,
             teamfood = false
@@ -519,238 +543,248 @@
     @Keep
     @JvmField
     val ENABLE_PIP_SIZE_LARGE_SCREEN =
-        sysPropBooleanFlag(1114, "persist.wm.debug.enable_pip_size_large_screen", default = true)
+        sysPropBooleanFlag("persist.wm.debug.enable_pip_size_large_screen", default = true)
 
     // TODO(b/265998256): Tracking bug
     @Keep
     @JvmField
     val ENABLE_PIP_APP_ICON_OVERLAY =
-        sysPropBooleanFlag(1115, "persist.wm.debug.enable_pip_app_icon_overlay", default = true)
+        sysPropBooleanFlag("persist.wm.debug.enable_pip_app_icon_overlay", default = true)
+
+
+    // TODO(b/293252410) : Tracking Bug
+    @JvmField
+    val LOCKSCREEN_ENABLE_LANDSCAPE =
+            unreleasedFlag("lockscreen.enable_landscape")
 
     // TODO(b/273443374): Tracking Bug
     @Keep
     @JvmField
     val LOCKSCREEN_LIVE_WALLPAPER =
-        sysPropBooleanFlag(1117, "persist.wm.debug.lockscreen_live_wallpaper", default = true)
+        sysPropBooleanFlag("persist.wm.debug.lockscreen_live_wallpaper", default = true)
 
     // TODO(b/281648899): Tracking bug
     @Keep
     @JvmField
     val WALLPAPER_MULTI_CROP =
-        sysPropBooleanFlag(1118, "persist.wm.debug.wallpaper_multi_crop", default = false)
+        sysPropBooleanFlag("persist.wm.debug.wallpaper_multi_crop", default = false)
 
     // TODO(b/290220798): Tracking Bug
     @Keep
     @JvmField
     val ENABLE_PIP2_IMPLEMENTATION =
-        sysPropBooleanFlag(1119, "persist.wm.debug.enable_pip2_implementation", default = false)
+        sysPropBooleanFlag("persist.wm.debug.enable_pip2_implementation", default = false)
 
     // 1200 - predictive back
     @Keep
     @JvmField
     val WM_ENABLE_PREDICTIVE_BACK =
-        sysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", default = true)
+        sysPropBooleanFlag("persist.wm.debug.predictive_back", default = true)
 
     @Keep
     @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_ANIM =
-        sysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", default = true)
+        sysPropBooleanFlag("persist.wm.debug.predictive_back_anim", default = true)
 
     @Keep
     @JvmField
     val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
-        sysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", default = false)
+        sysPropBooleanFlag("persist.wm.debug.predictive_back_always_enforce", default = false)
 
     // TODO(b/254512728): Tracking Bug
-    @JvmField val NEW_BACK_AFFORDANCE = releasedFlag(1203, "new_back_affordance")
+    @JvmField val NEW_BACK_AFFORDANCE = releasedFlag("new_back_affordance")
 
     // TODO(b/255854141): Tracking Bug
     @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_SYSUI =
-        unreleasedFlag(1204, "persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
+        unreleasedFlag("persist.wm.debug.predictive_back_sysui_enable", teamfood = true)
 
     // TODO(b/270987164): Tracking Bug
-    @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag(1205, "trackpad_gesture_features")
+    @JvmField val TRACKPAD_GESTURE_FEATURES = releasedFlag("trackpad_gesture_features")
 
     // TODO(b/263826204): Tracking Bug
     @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM =
-        unreleasedFlag(1206, "persist.wm.debug.predictive_back_bouncer_anim", teamfood = true)
+        unreleasedFlag("persist.wm.debug.predictive_back_bouncer_anim", teamfood = true)
 
     // TODO(b/238475428): Tracking Bug
     @JvmField
     val WM_SHADE_ALLOW_BACK_GESTURE =
-        sysPropBooleanFlag(1207, "persist.wm.debug.shade_allow_back_gesture", default = false)
+        sysPropBooleanFlag("persist.wm.debug.shade_allow_back_gesture", default = false)
 
     // TODO(b/238475428): Tracking Bug
     @JvmField
     val WM_SHADE_ANIMATE_BACK_GESTURE =
-        unreleasedFlag(1208, "persist.wm.debug.shade_animate_back_gesture", teamfood = false)
+        unreleasedFlag("persist.wm.debug.shade_animate_back_gesture", teamfood = false)
 
     // TODO(b/265639042): Tracking Bug
     @JvmField
     val WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM =
-        unreleasedFlag(1209, "persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true)
+        unreleasedFlag("persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true)
 
     // TODO(b/273800936): Tracking Bug
-    @JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag(1210, "trackpad_gesture_common")
+    @JvmField val TRACKPAD_GESTURE_COMMON = releasedFlag("trackpad_gesture_common")
 
     // 1300 - screenshots
     // TODO(b/264916608): Tracking Bug
-    @JvmField val SCREENSHOT_METADATA = unreleasedFlag(1302, "screenshot_metadata")
+    @JvmField val SCREENSHOT_METADATA = unreleasedFlag("screenshot_metadata")
 
     // TODO(b/266955521): Tracking bug
-    @JvmField val SCREENSHOT_DETECTION = releasedFlag(1303, "screenshot_detection")
+    @JvmField val SCREENSHOT_DETECTION = releasedFlag("screenshot_detection")
 
     // TODO(b/251205791): Tracking Bug
-    @JvmField val SCREENSHOT_APP_CLIPS = releasedFlag(1304, "screenshot_app_clips")
+    @JvmField val SCREENSHOT_APP_CLIPS = releasedFlag("screenshot_app_clips")
 
     // 1400 - columbus
     // TODO(b/254512756): Tracking Bug
-    val QUICK_TAP_IN_PCC = releasedFlag(1400, "quick_tap_in_pcc")
+    val QUICK_TAP_IN_PCC = releasedFlag("quick_tap_in_pcc")
 
     // TODO(b/261979569): Tracking Bug
     val QUICK_TAP_FLOW_FRAMEWORK =
-        unreleasedFlag(1401, "quick_tap_flow_framework", teamfood = false)
+        unreleasedFlag("quick_tap_flow_framework", teamfood = false)
 
     // 1500 - chooser aka sharesheet
 
     // 1700 - clipboard
-    @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag(1701, "clipboard_remote_behavior")
+    @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = releasedFlag("clipboard_remote_behavior")
     // TODO(b/278714186) Tracking Bug
     @JvmField
-    val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag(1702, "clipboard_image_timeout", teamfood = true)
+    val CLIPBOARD_IMAGE_TIMEOUT = unreleasedFlag("clipboard_image_timeout", teamfood = true)
     // TODO(b/279405451): Tracking Bug
     @JvmField
     val CLIPBOARD_SHARED_TRANSITIONS =
-            unreleasedFlag(1703, "clipboard_shared_transitions", teamfood = true)
+            unreleasedFlag("clipboard_shared_transitions", teamfood = true)
 
     // TODO(b/283300105): Tracking Bug
-    @JvmField val SCENE_CONTAINER = unreleasedFlag(1802, "scene_container")
+    @JvmField val SCENE_CONTAINER = unreleasedFlag("scene_container")
 
     // 1900
-    @JvmField val NOTE_TASKS = releasedFlag(1900, "keycode_flag")
+    @JvmField val NOTE_TASKS = releasedFlag("keycode_flag")
 
     // 2000 - device controls
-    @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag(2001, "app_panels_all_apps_allowed")
+    @JvmField val APP_PANELS_ALL_APPS_ALLOWED = releasedFlag("app_panels_all_apps_allowed")
 
     // 2200 - biometrics (udfps, sfps, BiometricPrompt, etc.)
     // TODO(b/259264861): Tracking Bug
-    @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag(2200, "udfps_new_touch_detection")
-    @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag(2201, "udfps_ellipse_detection")
+    @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection")
+    @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag("udfps_ellipse_detection")
     // TODO(b/278622168): Tracking Bug
-    @JvmField val BIOMETRIC_BP_STRONG = releasedFlag(2202, "biometric_bp_strong")
+    @JvmField val BIOMETRIC_BP_STRONG = releasedFlag("biometric_bp_strong")
 
     // 2300 - stylus
-    @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag(2300, "track_stylus_ever_used")
-    @JvmField val ENABLE_STYLUS_CHARGING_UI = releasedFlag(2301, "enable_stylus_charging_ui")
+    @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag("track_stylus_ever_used")
+    @JvmField val ENABLE_STYLUS_CHARGING_UI = releasedFlag("enable_stylus_charging_ui")
     @JvmField
-    val ENABLE_USI_BATTERY_NOTIFICATIONS = releasedFlag(2302, "enable_usi_battery_notifications")
-    @JvmField val ENABLE_STYLUS_EDUCATION = releasedFlag(2303, "enable_stylus_education")
+    val ENABLE_USI_BATTERY_NOTIFICATIONS = releasedFlag("enable_usi_battery_notifications")
+    @JvmField val ENABLE_STYLUS_EDUCATION = releasedFlag("enable_stylus_education")
 
     // 2400 - performance tools and debugging info
     // TODO(b/238923086): Tracking Bug
     @JvmField
     val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
-        unreleasedFlag(2400, "warn_on_blocking_binder_transactions")
+        unreleasedFlag("warn_on_blocking_binder_transactions")
 
     // TODO(b/283071711): Tracking bug
     @JvmField
     val TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK =
-        unreleasedFlag(2401, "trim_resources_with_background_trim_on_lock")
+        unreleasedFlag("trim_resources_with_background_trim_on_lock")
 
     // TODO:(b/283203305): Tracking bug
-    @JvmField val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag(2402, "trim_font_caches_on_unlock")
+    @JvmField val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag("trim_font_caches_on_unlock")
 
     // 2700 - unfold transitions
     // TODO(b/265764985): Tracking Bug
     @Keep
     @JvmField
     val ENABLE_DARK_VIGNETTE_WHEN_FOLDING =
-        unreleasedFlag(2700, "enable_dark_vignette_when_folding")
+        unreleasedFlag("enable_dark_vignette_when_folding")
 
     // TODO(b/265764985): Tracking Bug
     @Keep
     @JvmField
     val ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS =
-        unreleasedFlag(2701, "enable_unfold_status_bar_animations")
+        unreleasedFlag("enable_unfold_status_bar_animations")
 
     // TODO(b259590361): Tracking bug
-    val EXPERIMENTAL_FLAG = unreleasedFlag(2, "exp_flag_release")
+    val EXPERIMENTAL_FLAG = unreleasedFlag("exp_flag_release")
 
     // 2600 - keyboard
     // TODO(b/259352579): Tracking Bug
-    @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = releasedFlag(2600, "shortcut_list_search_layout")
+    @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = releasedFlag("shortcut_list_search_layout")
 
     // TODO(b/259428678): Tracking Bug
-    @JvmField val KEYBOARD_BACKLIGHT_INDICATOR = releasedFlag(2601, "keyboard_backlight_indicator")
+    @JvmField val KEYBOARD_BACKLIGHT_INDICATOR = releasedFlag("keyboard_backlight_indicator")
 
     // TODO(b/277192623): Tracking Bug
-    @JvmField val KEYBOARD_EDUCATION = unreleasedFlag(2603, "keyboard_education", teamfood = false)
+    @JvmField val KEYBOARD_EDUCATION = unreleasedFlag("keyboard_education", teamfood = false)
 
     // TODO(b/277201412): Tracking Bug
     @JvmField
-    val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = releasedFlag(2805, "split_shade_subpixel_optimization")
+    val SPLIT_SHADE_SUBPIXEL_OPTIMIZATION = releasedFlag("split_shade_subpixel_optimization")
 
     // TODO(b/288868056): Tracking Bug
     @JvmField
-    val PARTIAL_SCREEN_SHARING_TASK_SWITCHER = unreleasedFlag(288868056, "pss_task_switcher")
+    val PARTIAL_SCREEN_SHARING_TASK_SWITCHER = unreleasedFlag("pss_task_switcher")
 
     // TODO(b/278761837): Tracking Bug
-    @JvmField val USE_NEW_ACTIVITY_STARTER = releasedFlag(2801, name = "use_new_activity_starter")
+    @JvmField val USE_NEW_ACTIVITY_STARTER = releasedFlag(name = "use_new_activity_starter")
 
     // 2900 - Zero Jank fixes. Naming convention is: zj_<bug number>_<cuj name>
 
     // TODO:(b/285623104): Tracking bug
     @JvmField
     val ZJ_285570694_LOCKSCREEN_TRANSITION_FROM_AOD =
-        releasedFlag(2900, "zj_285570694_lockscreen_transition_from_aod")
+        releasedFlag("zj_285570694_lockscreen_transition_from_aod")
 
     // 3000 - dream
     // TODO(b/285059790) : Tracking Bug
     @JvmField
     val LOCKSCREEN_WALLPAPER_DREAM_ENABLED =
-        unreleasedFlag(3000, name = "enable_lockscreen_wallpaper_dream", teamfood = true)
+        unreleasedFlag(name = "enable_lockscreen_wallpaper_dream", teamfood = true)
 
     // TODO(b/283084712): Tracking Bug
-    @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag(283084712, "improved_hun_animations")
+    @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag("improved_hun_animations")
 
     // TODO(b/283447257): Tracking bug
     @JvmField
     val BIGPICTURE_NOTIFICATION_LAZY_LOADING =
-        unreleasedFlag(283447257, "bigpicture_notification_lazy_loading")
+        unreleasedFlag("bigpicture_notification_lazy_loading")
 
     // TODO(b/292062937): Tracking bug
     @JvmField
     val NOTIFICATION_CLEARABLE_REFACTOR =
-            unreleasedFlag(292062937, "notification_clearable_refactor")
+            unreleasedFlag("notification_clearable_refactor")
 
     // TODO(b/283740863): Tracking Bug
     @JvmField
     val ENABLE_NEW_PRIVACY_DIALOG =
-        unreleasedFlag(283740863, "enable_new_privacy_dialog", teamfood = true)
+        unreleasedFlag("enable_new_privacy_dialog", teamfood = true)
 
     // TODO(b/289573946): Tracking Bug
-    @JvmField val PRECOMPUTED_TEXT = unreleasedFlag(289573946, "precomputed_text")
+    @JvmField val PRECOMPUTED_TEXT = unreleasedFlag("precomputed_text")
 
     // 2900 - CentralSurfaces-related flags
 
     // TODO(b/285174336): Tracking Bug
     @JvmField
     val USE_REPOS_FOR_BOUNCER_SHOWING =
-        unreleasedFlag(2900, "use_repos_for_bouncer_showing", teamfood = true)
+        unreleasedFlag("use_repos_for_bouncer_showing", teamfood = true)
 
     // 3100 - Haptic interactions
 
     // TODO(b/290213663): Tracking Bug
     @JvmField
-    val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag(3100, "oneway_haptics_api_migration")
+    val ONE_WAY_HAPTICS_API_MIGRATION = unreleasedFlag("oneway_haptics_api_migration")
 
     /** Enable the Compose implementation of the PeopleSpaceActivity. */
     @JvmField
-    val COMPOSE_PEOPLE_SPACE = unreleasedFlag(293570761, "compose_people_space")
+    val COMPOSE_PEOPLE_SPACE = unreleasedFlag("compose_people_space")
 
     /** Enable the Compose implementation of the Quick Settings footer actions. */
     @JvmField
-    val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag(293569320, "compose_qs_footer_actions")
+    val COMPOSE_QS_FOOTER_ACTIONS = unreleasedFlag("compose_qs_footer_actions")
+
+    /** Enable the share wifi button in Quick Settings internet dialog. */
+    @JvmField
+    val SHARE_WIFI_QS_BUTTON = unreleasedFlag("share_wifi_qs_button")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
index 7078341..b5b56b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -161,7 +161,7 @@
     }
 
     private fun updateIconTile() {
-        val iconTile = rootView.findViewById(BACKLIGHT_ICON_ID) as ImageView
+        val iconTile = rootView.requireViewById(BACKLIGHT_ICON_ID) as ImageView
         val backgroundDrawable = iconTile.background as ShapeDrawable
         if (currentLevel == 0) {
             iconTile.setColorFilter(dimmedIconColor)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index e6053fb..9d2771e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -73,6 +73,14 @@
 import com.android.internal.policy.IKeyguardStateCallback;
 import com.android.keyguard.mediator.ScreenOnCoordinator;
 import com.android.systemui.SystemUIApplication;
+import com.android.systemui.dagger.qualifiers.Application;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier;
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindViewBinder;
+import com.android.systemui.keyguard.ui.binder.WindowManagerLockscreenVisibilityViewBinder;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.wm.shell.transition.ShellTransitions;
 import com.android.wm.shell.transition.Transitions;
@@ -85,10 +93,13 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.CoroutineScope;
+
 public class KeyguardService extends Service {
     static final String TAG = "KeyguardService";
     static final String PERMISSION = android.Manifest.permission.CONTROL_KEYGUARD;
 
+    private final FeatureFlags mFlags;
     private final KeyguardViewMediator mKeyguardViewMediator;
     private final KeyguardLifecyclesDispatcher mKeyguardLifecyclesDispatcher;
     private final ScreenOnCoordinator mScreenOnCoordinator;
@@ -291,13 +302,33 @@
                            KeyguardLifecyclesDispatcher keyguardLifecyclesDispatcher,
                            ScreenOnCoordinator screenOnCoordinator,
                            ShellTransitions shellTransitions,
-                           DisplayTracker displayTracker) {
+                           DisplayTracker displayTracker,
+                           WindowManagerLockscreenVisibilityViewModel
+                                   wmLockscreenVisibilityViewModel,
+                           WindowManagerLockscreenVisibilityManager wmLockscreenVisibilityManager,
+                           KeyguardSurfaceBehindViewModel keyguardSurfaceBehindViewModel,
+                           KeyguardSurfaceBehindParamsApplier keyguardSurfaceBehindAnimator,
+                           @Application CoroutineScope scope,
+                           FeatureFlags featureFlags) {
         super();
         mKeyguardViewMediator = keyguardViewMediator;
         mKeyguardLifecyclesDispatcher = keyguardLifecyclesDispatcher;
         mScreenOnCoordinator = screenOnCoordinator;
         mShellTransitions = shellTransitions;
         mDisplayTracker = displayTracker;
+        mFlags = featureFlags;
+
+        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            WindowManagerLockscreenVisibilityViewBinder.bind(
+                    wmLockscreenVisibilityViewModel,
+                    wmLockscreenVisibilityManager,
+                    scope);
+
+            KeyguardSurfaceBehindViewBinder.bind(
+                    keyguardSurfaceBehindViewModel,
+                    keyguardSurfaceBehindAnimator,
+                    scope);
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 489d2ab..ff74050 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -403,7 +403,9 @@
      * the device.
      */
     fun canPerformInWindowLauncherAnimations(): Boolean {
-        return isNexusLauncherUnderneath() &&
+        // TODO(b/278086361): Refactor in-window animations.
+        return !featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR) &&
+                isNexusLauncherUnderneath() &&
                 // If the launcher is underneath, but we're about to launch an activity, don't do
                 // the animations since they won't be visible.
                 !notificationShadeWindowController.isLaunchingActivity &&
@@ -847,54 +849,57 @@
         }
 
         surfaceBehindRemoteAnimationTargets?.forEach { surfaceBehindRemoteAnimationTarget ->
-            val surfaceHeight: Int = surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
+            if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                val surfaceHeight: Int =
+                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.height()
 
-            var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
-                    (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
-                    MathUtils.clamp(amount, 0f, 1f))
+                var scaleFactor = (SURFACE_BEHIND_START_SCALE_FACTOR +
+                        (1f - SURFACE_BEHIND_START_SCALE_FACTOR) *
+                        MathUtils.clamp(amount, 0f, 1f))
 
-            // If we're dismissing via swipe to the Launcher, we'll play in-window scale animations,
-            // so don't also scale the window.
-            if (keyguardStateController.isDismissingFromSwipe &&
-                    willUnlockWithInWindowLauncherAnimations) {
-                scaleFactor = 1f
-            }
-
-            // Translate up from the bottom.
-            surfaceBehindMatrix.setTranslate(
-                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
-                    surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
-                            surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
-            )
-
-            // Scale up from a point at the center-bottom of the surface.
-            surfaceBehindMatrix.postScale(
-                    scaleFactor,
-                    scaleFactor,
-                    keyguardViewController.viewRootImpl.width / 2f,
-                    surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
-            )
-
-            // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
-            // unable to draw
-            val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
-            if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
-                    sc?.isValid == true) {
-                with(SurfaceControl.Transaction()) {
-                    setMatrix(sc, surfaceBehindMatrix, tmpFloat)
-                    setCornerRadius(sc, roundedCornerRadius)
-                    setAlpha(sc, animationAlpha)
-                    apply()
+                // If we're dismissing via swipe to the Launcher, we'll play in-window scale
+                // animations, so don't also scale the window.
+                if (keyguardStateController.isDismissingFromSwipe &&
+                        willUnlockWithInWindowLauncherAnimations) {
+                    scaleFactor = 1f
                 }
-            } else {
-                applyParamsToSurface(
-                        SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
-                            surfaceBehindRemoteAnimationTarget.leash)
-                            .withMatrix(surfaceBehindMatrix)
-                            .withCornerRadius(roundedCornerRadius)
-                            .withAlpha(animationAlpha)
-                            .build()
+
+                // Translate up from the bottom.
+                surfaceBehindMatrix.setTranslate(
+                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.left.toFloat(),
+                        surfaceBehindRemoteAnimationTarget.screenSpaceBounds.top.toFloat() +
+                                surfaceHeight * SURFACE_BEHIND_START_TRANSLATION_Y * (1f - amount)
                 )
+
+                // Scale up from a point at the center-bottom of the surface.
+                surfaceBehindMatrix.postScale(
+                        scaleFactor,
+                        scaleFactor,
+                        keyguardViewController.viewRootImpl.width / 2f,
+                        surfaceHeight * SURFACE_BEHIND_SCALE_PIVOT_Y
+                )
+
+                // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
+                // unable to draw
+                val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget.leash
+                if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+                        sc?.isValid == true) {
+                    with(SurfaceControl.Transaction()) {
+                        setMatrix(sc, surfaceBehindMatrix, tmpFloat)
+                        setCornerRadius(sc, roundedCornerRadius)
+                        setAlpha(sc, animationAlpha)
+                        apply()
+                    }
+                } else {
+                    applyParamsToSurface(
+                            SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+                                    surfaceBehindRemoteAnimationTarget.leash)
+                                    .withMatrix(surfaceBehindMatrix)
+                                    .withCornerRadius(roundedCornerRadius)
+                                    .withAlpha(animationAlpha)
+                                    .build()
+                    )
+                }
             }
         }
 
@@ -929,28 +934,41 @@
 
     /**
      * Called by [KeyguardViewMediator] to let us know that the remote animation has finished, and
-     * we should clean up all of our state.
+     * we should clean up all of our state. [showKeyguard] will tell us which surface should be
+     * visible after the animation has been completed or canceled.
      *
      * This is generally triggered by us, calling
      * [KeyguardViewMediator.finishSurfaceBehindRemoteAnimation].
      */
-    fun notifyFinishedKeyguardExitAnimation(cancelled: Boolean) {
+    fun notifyFinishedKeyguardExitAnimation(showKeyguard: Boolean) {
         // Cancel any pending actions.
         handler.removeCallbacksAndMessages(null)
 
-        // Make sure we made the surface behind fully visible, just in case. It should already be
-        // fully visible. The exit animation is finished, and we should not hold the leash anymore,
-        // so forcing it to 1f.
-        surfaceBehindAlpha = 1f
-        setSurfaceBehindAppearAmount(1f)
+        // The lockscreen surface is gone, so it is now safe to re-show the smartspace.
+        if (lockscreenSmartspace?.visibility == View.INVISIBLE) {
+            lockscreenSmartspace?.visibility = View.VISIBLE
+        }
+
+        if (!showKeyguard) {
+            // Make sure we made the surface behind fully visible, just in case. It should already be
+            // fully visible. The exit animation is finished, and we should not hold the leash anymore,
+            // so forcing it to 1f.
+            surfaceBehindAlpha = 1f
+            setSurfaceBehindAppearAmount(1f)
+
+            try {
+                launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
+            } catch (e: RemoteException) {
+                Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e)
+            }
+        }
+
+        listeners.forEach { it.onUnlockAnimationFinished() }
+
+        // Reset all state
         surfaceBehindAlphaAnimator.cancel()
         surfaceBehindEntryAnimator.cancel()
         wallpaperCannedUnlockAnimator.cancel()
-        try {
-            launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
-        } catch (e: RemoteException) {
-            Log.e(TAG, "Remote exception in notifyFinishedKeyguardExitAnimation", e)
-        }
 
         // That target is no longer valid since the animation finished, null it out.
         surfaceBehindRemoteAnimationTargets = null
@@ -960,13 +978,6 @@
         dismissAmountThresholdsReached = false
         willUnlockWithInWindowLauncherAnimations = false
         willUnlockWithSmartspaceTransition = false
-
-        // The lockscreen surface is gone, so it is now safe to re-show the smartspace.
-        if (lockscreenSmartspace?.visibility == View.INVISIBLE) {
-            lockscreenSmartspace?.visibility = View.VISIBLE
-        }
-
-        listeners.forEach { it.onUnlockAnimationFinished() }
     }
 
     /**
@@ -977,10 +988,12 @@
         if (keyguardStateController.isShowing) {
             // Hide the keyguard, with no fade out since we animated it away during the unlock.
 
-            keyguardViewController.hide(
-                surfaceBehindRemoteAnimationStartTime,
-                0 /* fadeOutDuration */
-            )
+            if (!featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                keyguardViewController.hide(
+                        surfaceBehindRemoteAnimationStartTime,
+                        0 /* fadeOutDuration */
+                )
+            }
         } else {
             Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
                     "showing. Ignoring...")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index 6213265c..8e323d8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -20,6 +20,8 @@
 import android.content.res.Configuration
 import android.view.View
 import android.view.ViewGroup
+import com.android.keyguard.KeyguardStatusViewController
+import com.android.keyguard.dagger.KeyguardStatusViewComponent
 import com.android.systemui.CoreStartable
 import com.android.systemui.R
 import com.android.systemui.dagger.SysUISingleton
@@ -80,6 +82,7 @@
     private val chipbarCoordinator: ChipbarCoordinator,
     private val keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener,
     private val keyguardBlueprintViewModel: KeyguardBlueprintViewModel,
+    private val keyguardStatusViewComponentFactory: KeyguardStatusViewComponent.Factory,
 ) : CoreStartable {
 
     private var rootViewHandle: DisposableHandle? = null
@@ -88,6 +91,7 @@
     private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
     private var ambientIndicationAreaHandle: KeyguardAmbientIndicationAreaViewBinder.Binding? = null
     private var settingsPopupMenuHandle: DisposableHandle? = null
+    private var keyguardStatusViewController: KeyguardStatusViewController? = null
 
     override fun start() {
         bindKeyguardRootView()
@@ -96,6 +100,7 @@
         unbindKeyguardBottomArea(notificationPanel)
         bindIndicationArea()
         bindLockIconView(notificationPanel)
+        bindKeyguardStatusView(notificationPanel)
         setupNotificationStackScrollLayout(notificationPanel)
         bindLeftShortcut()
         bindRightShortcut()
@@ -117,7 +122,7 @@
             sharedNotificationContainer.addNotificationStackScrollLayout(nssl)
             SharedNotificationContainerBinder.bind(
                 sharedNotificationContainer,
-                sharedNotificationContainerViewModel
+                sharedNotificationContainerViewModel,
             )
         }
     }
@@ -132,9 +137,7 @@
     fun bindIndicationArea() {
         indicationAreaHandle?.dispose()
 
-        // At startup, 2 views with the ID `R.id.keyguard_indication_area` will be available.
-        // Disable one of them
-        if (!featureFlags.isEnabled(Flags.MIGRATE_INDICATION_AREA)) {
+        if (!featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             keyguardRootView.findViewById<View?>(R.id.keyguard_indication_area)?.let {
                 keyguardRootView.removeView(it)
             }
@@ -255,4 +258,31 @@
             }
         }
     }
+
+    fun bindKeyguardStatusView(legacyParent: ViewGroup) {
+        // At startup, 2 views with the ID `R.id.keyguard_status_view` will be available.
+        // Disable one of them
+        if (featureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            legacyParent.findViewById<View>(R.id.keyguard_status_view)?.let {
+                legacyParent.removeView(it)
+            }
+
+            val keyguardStatusView = keyguardRootView.addStatusView()
+            val statusViewComponent = keyguardStatusViewComponentFactory.build(keyguardStatusView)
+            val controller = statusViewComponent.getKeyguardStatusViewController()
+            controller.init()
+            keyguardStatusViewController = controller
+        } else {
+            keyguardRootView.findViewById<View?>(R.id.keyguard_status_view)?.let {
+                keyguardRootView.removeView(it)
+            }
+        }
+    }
+
+    /**
+     * Temporary, to allow NotificationPanelViewController to use the same instance while code is
+     * migrated: b/288242803
+     */
+    fun getKeyguardStatusViewController() = keyguardStatusViewController
+    fun getKeyguardRootView() = keyguardRootView
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 66de371..fd15853 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -171,8 +171,6 @@
 import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -182,6 +180,7 @@
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 
+import dagger.Lazy;
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -1035,12 +1034,19 @@
                 IRemoteAnimationFinishedCallback finishedCallback) {
             Trace.beginSection("mExitAnimationRunner.onAnimationStart#startKeyguardExitAnimation");
             startKeyguardExitAnimation(transit, apps, wallpapers, nonApps, finishedCallback);
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationStart(
+                        transit, apps, wallpapers, nonApps, finishedCallback);
+            }
             Trace.endSection();
         }
 
         @Override // Binder interface
         public void onAnimationCancelled() {
             cancelKeyguardExitAnimation();
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                mWmLockscreenVisibilityManager.get().onKeyguardGoingAwayRemoteAnimationCancelled();
+            }
         }
     };
 
@@ -1106,7 +1112,7 @@
 
                         mOccludeByDreamAnimator = ValueAnimator.ofFloat(0f, 1f);
                         mOccludeByDreamAnimator.setDuration(mDreamOpenAnimationDuration);
-                        mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
+                        //mOccludeByDreamAnimator.setInterpolator(Interpolators.LINEAR);
                         mOccludeByDreamAnimator.addUpdateListener(
                                 animation -> {
                                     SyncRtSurfaceTransactionApplier.SurfaceParams.Builder
@@ -1336,6 +1342,8 @@
             mDreamingToLockscreenTransitionViewModel;
     private RemoteAnimationTarget mRemoteAnimationTarget;
 
+    private Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager;
+
     /**
      * Injected constructor. See {@link KeyguardModule}.
      */
@@ -1379,7 +1387,8 @@
             SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
-            SystemPropertiesHelper systemPropertiesHelper) {
+            SystemPropertiesHelper systemPropertiesHelper,
+            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
         mContext = context;
         mUserTracker = userTracker;
         mFalsingCollector = falsingCollector;
@@ -1446,8 +1455,9 @@
         mUiEventLogger = uiEventLogger;
         mSessionTracker = sessionTracker;
 
-        mMainDispatcher = mainDispatcher;
         mDreamingToLockscreenTransitionViewModel = dreamingToLockscreenTransitionViewModel;
+        mWmLockscreenVisibilityManager = wmLockscreenVisibilityManager;
+        mMainDispatcher = mainDispatcher;
     }
 
     public void userActivity() {
@@ -2561,7 +2571,7 @@
         } else if (mSurfaceBehindRemoteAnimationRunning) {
             // We're already running the keyguard exit animation, likely due to an in-progress swipe
             // to unlock.
-           exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* cancelled */);
+            exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* showKeyguard */);
         } else if (!mHideAnimationRun) {
             if (DEBUG) Log.d(TAG, "tryKeyguardDone: starting pre-hide animation");
             mHideAnimationRun = true;
@@ -2685,6 +2695,12 @@
             if (DEBUG) {
                 Log.d(TAG, "updateActivityLockScreenState(" + showing + ", " + aodShowing + ")");
             }
+
+            if (mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Handled in WmLockscreenVisibilityManager if flag is enabled.
+                return;
+            }
+
             try {
                 mActivityTaskManagerService.setLockScreenShown(showing, aodShowing);
             } catch (RemoteException e) {
@@ -2724,7 +2740,11 @@
             }
             mHiding = false;
 
-            mKeyguardViewControllerLazy.get().show(options);
+            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Handled directly in StatusBarKeyguardViewManager if enabled.
+                mKeyguardViewControllerLazy.get().show(options);
+            }
+
             resetKeyguardDonePendingLocked();
             mHideAnimationRun = false;
             adjustStatusBarLocked();
@@ -2795,19 +2815,22 @@
             mUpdateMonitor.setKeyguardGoingAway(true);
             mKeyguardViewControllerLazy.get().setKeyguardGoingAwayState(true);
 
-            // Don't actually hide the Keyguard at the moment, wait for window
-            // manager until it tells us it's safe to do so with
-            // startKeyguardExitAnimation.
-            // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager will be in
-            // order.
-            final int keyguardFlag = flags;
-            mUiBgExecutor.execute(() -> {
-                try {
-                    mActivityTaskManagerService.keyguardGoingAway(keyguardFlag);
-                } catch (RemoteException e) {
-                    Log.e(TAG, "Error while calling WindowManager", e);
-                }
-            });
+            // Handled in WmLockscreenVisibilityManager if flag is enabled.
+            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    // Don't actually hide the Keyguard at the moment, wait for window manager 
+                    // until it tells us it's safe to do so with startKeyguardExitAnimation.
+		    // Posting to mUiOffloadThread to ensure that calls to ActivityTaskManager 
+		    // will be in order.
+		    final int keyguardFlag = flags;
+		    mUiBgExecutor.execute(() -> {
+		        try {
+		            mActivityTaskManagerService.keyguardGoingAway(keyguardFlag);
+		        } catch (RemoteException e) {
+		            Log.e(TAG, "Error while calling WindowManager", e);
+		        }
+		    });
+            }
+
             Trace.endSection();
         }
     };
@@ -2919,7 +2942,10 @@
             if (!mHiding
                     && !mSurfaceBehindRemoteAnimationRequested
                     && !mKeyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture()) {
-                if (finishedCallback != null) {
+                // If the flag is enabled, remote animation state is handled in
+                // WmLockscreenVisibilityManager.
+                if (finishedCallback != null
+                        && !mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
                     // There will not execute animation, send a finish callback to ensure the remote
                     // animation won't hang there.
                     try {
@@ -2945,10 +2971,12 @@
                         new IRemoteAnimationFinishedCallback() {
                             @Override
                             public void onAnimationFinished() throws RemoteException {
-                                try {
-                                    finishedCallback.onAnimationFinished();
-                                } catch (RemoteException e) {
-                                    Slog.w(TAG, "Failed to call onAnimationFinished", e);
+                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                    try {
+                                        finishedCallback.onAnimationFinished();
+                                    } catch (RemoteException e) {
+                                        Slog.w(TAG, "Failed to call onAnimationFinished", e);
+                                    }
                                 }
                                 onKeyguardExitFinished();
                                 mKeyguardViewControllerLazy.get().hide(0 /* startTime */,
@@ -2975,7 +3003,11 @@
             // it will dismiss the panel in that case.
             } else if (!mStatusBarStateController.leaveOpenOnKeyguardHide()
                     && apps != null && apps.length > 0) {
-                mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    // Handled in WmLockscreenVisibilityManager. Other logic in this class will
+                    // short circuit when this is null.
+                    mSurfaceBehindRemoteAnimationFinishedCallback = finishedCallback;
+                }
                 mSurfaceBehindRemoteAnimationRunning = true;
 
                 mInteractionJankMonitor.begin(
@@ -2995,7 +3027,10 @@
                         createInteractionJankMonitorConf(
                                 CUJ_LOCKSCREEN_UNLOCK_ANIMATION, "RemoteAnimationDisabled"));
 
-                mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    // Handled directly in StatusBarKeyguardViewManager if enabled.
+                    mKeyguardViewControllerLazy.get().hide(startTime, fadeoutDuration);
+                }
 
                 // TODO(bc-animation): When remote animation is enabled for keyguard exit animation,
                 // apps, wallpapers and finishedCallback are set to non-null. nonApps is not yet
@@ -3003,19 +3038,23 @@
                 mContext.getMainExecutor().execute(() -> {
                     if (finishedCallback == null) {
                         mKeyguardUnlockAnimationControllerLazy.get()
-                                .notifyFinishedKeyguardExitAnimation(false /* cancelled */);
+                                .notifyFinishedKeyguardExitAnimation(false /* showKeyguard */);
                         mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                         return;
                     }
                     if (apps == null || apps.length == 0) {
                         Slog.e(TAG, "Keyguard exit without a corresponding app to show.");
+
                         try {
-                            finishedCallback.onAnimationFinished();
+                            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                finishedCallback.onAnimationFinished();
+                            }
                         } catch (RemoteException e) {
                             Slog.e(TAG, "RemoteException");
                         } finally {
                             mInteractionJankMonitor.end(CUJ_LOCKSCREEN_UNLOCK_ANIMATION);
                         }
+
                         return;
                     }
 
@@ -3039,7 +3078,9 @@
                         @Override
                         public void onAnimationEnd(Animator animation) {
                             try {
-                                finishedCallback.onAnimationFinished();
+                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                    finishedCallback.onAnimationFinished();
+                                }
                             } catch (RemoteException e) {
                                 Slog.e(TAG, "RemoteException");
                             } finally {
@@ -3050,7 +3091,9 @@
                         @Override
                         public void onAnimationCancel(Animator animation) {
                             try {
-                                finishedCallback.onAnimationFinished();
+                                if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                                    finishedCallback.onAnimationFinished();
+                                }
                             } catch (RemoteException e) {
                                 Slog.e(TAG, "RemoteException");
                             } finally {
@@ -3120,7 +3163,7 @@
             // A lock is pending, meaning the keyguard exit animation was cancelled because we're
             // re-locking. We should just end the surface-behind animation without exiting the
             // keyguard. The pending lock will be handled by onFinishedGoingToSleep().
-            finishSurfaceBehindRemoteAnimation(true);
+            finishSurfaceBehindRemoteAnimation(true /* showKeyguard */);
             maybeHandlePendingLock();
         } else {
             Log.d(TAG, "#handleCancelKeyguardExitAnimation: keyguard exit animation cancelled. "
@@ -3129,7 +3172,7 @@
             // No lock is pending, so the animation was cancelled during the unlock sequence, but
             // we should end up unlocked. Show the surface and exit the keyguard.
             showSurfaceBehindKeyguard();
-            exitKeyguardAndFinishSurfaceBehindRemoteAnimation(true /* cancelled */);
+            exitKeyguardAndFinishSurfaceBehindRemoteAnimation(false /* showKeyguard */);
         }
     }
 
@@ -3140,12 +3183,13 @@
      * with the RemoteAnimation, actually hide the keyguard, and clean up state related to the
      * keyguard exit animation.
      *
-     * @param cancelled {@code true} if the animation was cancelled before it finishes.
+     * @param showKeyguard {@code true} if the animation was cancelled and keyguard should remain
+     *                        visible
      */
-    public void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean cancelled) {
+    public void exitKeyguardAndFinishSurfaceBehindRemoteAnimation(boolean showKeyguard) {
         Log.d(TAG, "onKeyguardExitRemoteAnimationFinished");
         if (!mSurfaceBehindRemoteAnimationRunning && !mSurfaceBehindRemoteAnimationRequested) {
-            Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished cancelled=" + cancelled
+            Log.d(TAG, "skip onKeyguardExitRemoteAnimationFinished showKeyguard=" + showKeyguard
                     + " surfaceAnimationRunning=" + mSurfaceBehindRemoteAnimationRunning
                     + " surfaceAnimationRequested=" + mSurfaceBehindRemoteAnimationRequested);
             return;
@@ -3170,9 +3214,7 @@
                         + " wasShowing=" + wasShowing);
             }
 
-            mKeyguardUnlockAnimationControllerLazy.get()
-                    .notifyFinishedKeyguardExitAnimation(cancelled);
-            finishSurfaceBehindRemoteAnimation(cancelled);
+            finishSurfaceBehindRemoteAnimation(showKeyguard);
 
             // Dispatch the callback on animation finishes.
             mUpdateMonitor.dispatchKeyguardDismissAnimationFinished();
@@ -3200,7 +3242,11 @@
                 flags |= KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
             }
 
-            mActivityTaskManagerService.keyguardGoingAway(flags);
+            if (!mFeatureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                // Handled in WmLockscreenVisibilityManager.
+                mActivityTaskManagerService.keyguardGoingAway(flags);
+            }
+
             mKeyguardStateController.notifyKeyguardGoingAway(true);
         } catch (RemoteException e) {
             mSurfaceBehindRemoteAnimationRequested = false;
@@ -3236,7 +3282,10 @@
      * This does not set keyguard state to either locked or unlocked, it simply ends the remote
      * animation on the surface behind the keyguard. This can be called by
      */
-    void finishSurfaceBehindRemoteAnimation(boolean cancelled) {
+    void finishSurfaceBehindRemoteAnimation(boolean showKeyguard) {
+        mKeyguardUnlockAnimationControllerLazy.get()
+                .notifyFinishedKeyguardExitAnimation(showKeyguard);
+
         mSurfaceBehindRemoteAnimationRequested = false;
         mSurfaceBehindRemoteAnimationRunning = false;
         mKeyguardStateController.notifyKeyguardGoingAway(false);
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
new file mode 100644
index 0000000..75677f2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WindowManagerLockscreenVisibilityManager.kt
@@ -0,0 +1,206 @@
+/*
+ * 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
+
+import android.app.IActivityTaskManager
+import android.util.Log
+import android.view.IRemoteAnimationFinishedCallback
+import android.view.RemoteAnimationTarget
+import android.view.WindowManager
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.ui.binder.KeyguardSurfaceBehindParamsApplier
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Manages lockscreen and AOD visibility state via the [IActivityTaskManager], and keeps track of
+ * remote animations related to changes in lockscreen visibility.
+ */
+@SysUISingleton
+class WindowManagerLockscreenVisibilityManager
+@Inject
+constructor(
+    @Main private val executor: Executor,
+    private val activityTaskManagerService: IActivityTaskManager,
+    private val keyguardStateController: KeyguardStateController,
+    private val keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier,
+) {
+
+    /**
+     * Whether the lockscreen is showing, which we pass to [IActivityTaskManager.setLockScreenShown]
+     * in order to show the lockscreen and hide the surface behind the keyguard (or the inverse).
+     */
+    private var isLockscreenShowing = true
+
+    /**
+     * Whether AOD is showing, which we pass to [IActivityTaskManager.setLockScreenShown] in order
+     * to show AOD when the lockscreen is visible.
+     */
+    private var isAodVisible = false
+
+    /**
+     * Whether the keyguard is currently "going away", which we triggered via a call to
+     * [IActivityTaskManager.keyguardGoingAway]. When we tell WM that the keyguard is going away,
+     * the app/launcher surface behind the keyguard is made visible, and WM calls
+     * [onKeyguardGoingAwayRemoteAnimationStart] with a RemoteAnimationTarget so that we can animate
+     * it.
+     *
+     * Going away does not inherently result in [isLockscreenShowing] being set to false; we need to
+     * do that ourselves once we are done animating the surface.
+     *
+     * THIS IS THE ONLY PLACE 'GOING AWAY' TERMINOLOGY SHOULD BE USED. 'Going away' is a WM concept
+     * and we have gotten into trouble using it to mean various different things in the past. Unlock
+     * animations may still be visible when the keyguard is NOT 'going away', for example, when we
+     * play in-window animations, we set the surface to alpha=1f and end the animation immediately.
+     * The remainder of the animation occurs in-window, so while you might expect that the keyguard
+     * is still 'going away' because unlock animations are playing, it's actually not.
+     *
+     * If you want to know if the keyguard is 'going away', you probably want to check if we have
+     * STARTED but not FINISHED a transition to GONE.
+     *
+     * The going away animation will run until:
+     * - We manually call [endKeyguardGoingAwayAnimation] after we're done animating.
+     * - We call [setLockscreenShown] = true, which cancels the going away animation.
+     * - WM calls [onKeyguardGoingAwayRemoteAnimationCancelled] for another reason (such as the 10
+     *   second timeout).
+     */
+    private var isKeyguardGoingAway = false
+        private set(value) {
+            // TODO(b/278086361): Extricate the keyguard state controller.
+            keyguardStateController.notifyKeyguardGoingAway(value)
+            field = value
+        }
+
+    /** Callback provided by WM to call once we're done with the going away animation. */
+    private var goingAwayRemoteAnimationFinishedCallback: IRemoteAnimationFinishedCallback? = null
+
+    /**
+     * Set the visibility of the surface behind the keyguard, making the appropriate calls to Window
+     * Manager to effect the change.
+     */
+    fun setSurfaceBehindVisibility(visible: Boolean) {
+        if (isKeyguardGoingAway == visible) {
+            Log.d(TAG, "WmLockscreenVisibilityManager#setVisibility -> already visible=$visible")
+            return
+        }
+
+        // The surface behind is always visible if the lockscreen is not showing, so we're already
+        // visible.
+        if (visible && !isLockscreenShowing) {
+            Log.d(TAG, "#setVisibility -> already visible since the lockscreen isn't showing")
+            return
+        }
+
+        if (visible) {
+            // Make the surface visible behind the keyguard by calling keyguardGoingAway. The
+            // lockscreen is still showing as well, allowing us to animate unlocked.
+            Log.d(TAG, "ActivityTaskManagerService#keyguardGoingAway()")
+            activityTaskManagerService.keyguardGoingAway(0)
+            isKeyguardGoingAway = true
+        } else {
+            // Hide the surface by setting the lockscreen showing.
+            setLockscreenShown(true)
+        }
+    }
+
+    fun setAodVisible(aodVisible: Boolean) {
+        setWmLockscreenState(aodVisible = aodVisible)
+    }
+
+    /** Sets the visibility of the lockscreen. */
+    fun setLockscreenShown(lockscreenShown: Boolean) {
+        setWmLockscreenState(lockscreenShowing = lockscreenShown)
+    }
+
+    fun onKeyguardGoingAwayRemoteAnimationStart(
+        @WindowManager.TransitionOldType transit: Int,
+        apps: Array<RemoteAnimationTarget>,
+        wallpapers: Array<RemoteAnimationTarget>,
+        nonApps: Array<RemoteAnimationTarget>,
+        finishedCallback: IRemoteAnimationFinishedCallback
+    ) {
+        goingAwayRemoteAnimationFinishedCallback = finishedCallback
+        keyguardSurfaceBehindAnimator.applyParamsToSurface(apps[0])
+    }
+
+    fun onKeyguardGoingAwayRemoteAnimationCancelled() {
+        // If WM cancelled the animation, we need to end immediately even if we're still using the
+        // animation.
+        endKeyguardGoingAwayAnimation()
+    }
+
+    /**
+     * Whether the going away remote animation target is in-use, which means we're animating it or
+     * intend to animate it.
+     *
+     * Some unlock animations (such as the translation spring animation) are non-deterministic and
+     * might end after the transition to GONE ends. In that case, we want to keep the remote
+     * animation running until the spring ends.
+     */
+    fun setUsingGoingAwayRemoteAnimation(usingTarget: Boolean) {
+        if (!usingTarget) {
+            endKeyguardGoingAwayAnimation()
+        }
+    }
+
+    private fun setWmLockscreenState(
+        lockscreenShowing: Boolean = this.isLockscreenShowing,
+        aodVisible: Boolean = this.isAodVisible
+    ) {
+        Log.d(
+            TAG,
+            "#setWmLockscreenState(" +
+                "isLockscreenShowing=$lockscreenShowing, " +
+                "aodVisible=$aodVisible)."
+        )
+
+        if (this.isLockscreenShowing == lockscreenShowing && this.isAodVisible == aodVisible) {
+            return
+        }
+
+        activityTaskManagerService.setLockScreenShown(lockscreenShowing, aodVisible)
+        this.isLockscreenShowing = lockscreenShowing
+        this.isAodVisible = aodVisible
+    }
+
+    private fun endKeyguardGoingAwayAnimation() {
+        if (!isKeyguardGoingAway) {
+            Log.d(
+                TAG,
+                "#endKeyguardGoingAwayAnimation() called when isKeyguardGoingAway=false. " +
+                    "Short-circuiting."
+            )
+            return
+        }
+
+        executor.execute {
+            Log.d(TAG, "Finishing remote animation.")
+            goingAwayRemoteAnimationFinishedCallback?.onAnimationFinished()
+            goingAwayRemoteAnimationFinishedCallback = null
+
+            isKeyguardGoingAway = false
+
+            keyguardSurfaceBehindAnimator.notifySurfaceReleased()
+        }
+    }
+
+    companion object {
+        private val TAG = this::class.java.simpleName
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index a5ac7c7..9a44230 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -47,6 +47,7 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager;
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
 import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
@@ -74,12 +75,11 @@
 import com.android.systemui.wallpapers.data.repository.WallpaperRepository;
 import com.android.wm.shell.keyguard.KeyguardTransitions;
 
+import java.util.concurrent.Executor;
+
 import dagger.Lazy;
 import dagger.Module;
 import dagger.Provides;
-
-import java.util.concurrent.Executor;
-
 import kotlinx.coroutines.CoroutineDispatcher;
 
 /**
@@ -146,7 +146,8 @@
             SystemClock systemClock,
             @Main CoroutineDispatcher mainDispatcher,
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
-            SystemPropertiesHelper systemPropertiesHelper) {
+            SystemPropertiesHelper systemPropertiesHelper,
+            Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager) {
         return new KeyguardViewMediator(
                 context,
                 uiEventLogger,
@@ -189,7 +190,8 @@
                 systemClock,
                 mainDispatcher,
                 dreamingToLockscreenTransitionViewModel,
-                systemPropertiesHelper);
+                systemPropertiesHelper,
+                wmLockscreenVisibilityManager);
     }
 
     /** */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index 20ed549..45277b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -78,16 +78,8 @@
 
     override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
         return when {
-            !controller.isAvailableOnDevice ->
+            !isEnabledForPickerStateOption() ->
                 KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
-            !controller.isAbleToOpenCameraApp -> {
-                KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
-                    explanation =
-                        context.getString(
-                            R.string.qr_scanner_quick_affordance_unavailable_explanation
-                        ),
-                )
-            }
             else -> KeyguardQuickAffordanceConfig.PickerScreenState.Default()
         }
     }
@@ -118,6 +110,11 @@
         }
     }
 
+    /** Returns whether QR scanner be shown as one of available lockscreen shortcut option. */
+    private fun isEnabledForPickerStateOption(): Boolean {
+        return controller.isAbleToLaunchScannerActivity && controller.isAllowedOnLockScreen
+    }
+
     companion object {
         private const val TAG = "QrCodeScannerKeyguardQuickAffordanceConfig"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index c019d21..5d7a3d4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -59,7 +59,7 @@
         conflatedCallbackFlow {
             val callback =
                 object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
-                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
                         val hasCards = response?.walletCards?.isNotEmpty() == true
                         trySendWithFailureLogging(
                             state(
@@ -71,7 +71,7 @@
                         )
                     }
 
-                    override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+                    override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
                         Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
                         trySendWithFailureLogging(
                             KeyguardQuickAffordanceConfig.LockScreenState.Hidden,
@@ -133,13 +133,13 @@
         return suspendCancellableCoroutine { continuation ->
             val callback =
                 object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
-                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse) {
                         continuation.resumeWith(
                             Result.success(response?.walletCards ?: emptyList())
                         )
                     }
 
-                    override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+                    override fun onWalletCardRetrievalError(error: GetWalletCardsError) {
                         continuation.resumeWith(Result.success(emptyList()))
                     }
                 }
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 30f8f3e..6894147 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
@@ -46,6 +46,7 @@
 import com.android.systemui.keyguard.shared.model.FaceDetectionStatus
 import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.HelpFaceAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.SuccessFaceAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.log.FaceAuthenticationLogger
@@ -160,7 +161,7 @@
     @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
     @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
-    featureFlags: FeatureFlags,
+    private val featureFlags: FeatureFlags,
     facePropertyRepository: FacePropertyRepository,
     dumpManager: DumpManager,
 ) : DeviceEntryFaceAuthRepository, Dumpable {
@@ -286,8 +287,12 @@
         // starts going to sleep.
         merge(
                 keyguardRepository.wakefulness.map { it.isStartingToSleepOrAsleep() },
-                keyguardRepository.isKeyguardGoingAway,
-                userRepository.userSwitchingInProgress
+                if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+                    keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
+                } else {
+                    keyguardRepository.isKeyguardGoingAway
+                },
+                userRepository.userSwitchingInProgress,
             )
             .onEach { anyOfThemIsTrue ->
                 if (anyOfThemIsTrue) {
@@ -581,7 +586,7 @@
             // We always want to invoke face detect in the main thread.
             faceAuthLogger.faceDetectionStarted()
             faceManager?.detectFace(
-                detectCancellationSignal,
+                checkNotNull(detectCancellationSignal),
                 detectionCallback,
                 FaceAuthenticateOptions.Builder().setUserId(currentUserId).build()
             )
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index f1b3441..42cd3a5 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -99,7 +99,16 @@
     /** Is an activity showing over the keyguard? */
     val isKeyguardOccluded: Flow<Boolean>
 
-    /** Observable for the signal that keyguard is about to go away. */
+    /**
+     * Observable for the signal that keyguard is about to go away.
+     *
+     * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
+     */
+    @Deprecated(
+        "Use KeyguardTransitionInteractor flows instead. The closest match for 'going " +
+            "away' is isInTransitionToState(GONE), but consider using more specific flows " +
+            "whenever possible."
+    )
     val isKeyguardGoingAway: Flow<Boolean>
 
     /** Is the always-on display available to be used? */
@@ -199,13 +208,16 @@
     fun setAnimateDozingTransitions(animate: Boolean)
 
     /** Sets the current amount of alpha that should be used for rendering the bottom area. */
-    @Deprecated("Deprecated as part of b/278057014")
-    fun setBottomAreaAlpha(alpha: Float)
+    @Deprecated("Deprecated as part of b/278057014") fun setBottomAreaAlpha(alpha: Float)
 
     /** Sets the current amount of alpha that should be used for rendering the keyguard. */
     fun setKeyguardAlpha(alpha: Float)
 
-    fun setKeyguardVisibility(statusBarState: Int, goingToFullShade: Boolean, occlusionTransitionRunning: Boolean)
+    fun setKeyguardVisibility(
+        statusBarState: Int,
+        goingToFullShade: Boolean,
+        occlusionTransitionRunning: Boolean
+    )
 
     /**
      * Sets the relative offset of the lock-screen clock from its natural position on the screen.
@@ -362,10 +374,11 @@
 
                 awaitClose { keyguardStateController.removeCallback(callback) }
             }
+            .distinctUntilChanged()
             .stateIn(
-                scope = scope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = keyguardStateController.isUnlocked,
+                scope,
+                SharingStarted.Eagerly,
+                initialValue = false,
             )
 
     override val isKeyguardGoingAway: Flow<Boolean> = conflatedCallbackFlow {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index 246ee33..2f80106 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -32,6 +32,11 @@
     @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
 
     @Binds
+    fun keyguardSurfaceBehindRepository(
+        impl: KeyguardSurfaceBehindRepositoryImpl
+    ): KeyguardSurfaceBehindRepository
+
+    @Binds
     fun keyguardTransitionRepository(
         impl: KeyguardTransitionRepositoryImpl
     ): KeyguardTransitionRepository
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..014b7fa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepository.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * State related to SysUI's handling of the surface behind the keyguard (typically an app or the
+ * launcher). We manipulate this surface during unlock animations.
+ */
+interface KeyguardSurfaceBehindRepository {
+
+    /** Whether we're running animations on the surface. */
+    val isAnimatingSurface: Flow<Boolean>
+
+    /** Set whether we're running animations on the surface. */
+    fun setAnimatingSurface(animating: Boolean)
+}
+
+@SysUISingleton
+class KeyguardSurfaceBehindRepositoryImpl @Inject constructor() : KeyguardSurfaceBehindRepository {
+    private val _isAnimatingSurface = MutableStateFlow(false)
+    override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+    override fun setAnimatingSurface(animating: Boolean) {
+        _isAnimatingSurface.value = animating
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
index 6a2511f..1c0b73f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt
@@ -163,12 +163,13 @@
 
     private fun constructCircleRevealFromPoint(point: Point): LightRevealEffect {
         return with(point) {
+            val display = checkNotNull(context.display)
             CircleReveal(
                 x,
                 y,
                 startRadius = 0,
                 endRadius =
-                    max(max(x, context.display.width - x), max(y, context.display.height - y)),
+                    max(max(x, display.width - x), max(y, display.height - y)),
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 8f0b91b..271bc38 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
@@ -26,6 +25,7 @@
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index 6b28b27..aa771fd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -20,8 +20,11 @@
 import com.android.app.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.StatusBarState.KEYGUARD
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -34,7 +37,11 @@
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -45,6 +52,7 @@
     override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
+    private val flags: FeatureFlags,
     private val shadeRepository: ShadeRepository,
 ) :
     TransitionInteractor(
@@ -53,6 +61,7 @@
 
     override fun start() {
         listenForLockscreenToGone()
+        listenForLockscreenToGoneDragging()
         listenForLockscreenToOccluded()
         listenForLockscreenToCamera()
         listenForLockscreenToAodOrDozing()
@@ -62,6 +71,63 @@
         listenForLockscreenToAlternateBouncer()
     }
 
+    /**
+     * Whether we want the surface behind the keyguard visible for the transition from LOCKSCREEN,
+     * or null if we don't care and should just use a reasonable default.
+     *
+     * [KeyguardSurfaceBehindInteractor] will switch to this flow whenever a transition from
+     * LOCKSCREEN is running.
+     */
+    val surfaceBehindVisibility: Flow<Boolean?> =
+        transitionInteractor.startedKeyguardTransitionStep
+            .map { startedStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    // LOCKSCREEN to anything but GONE does not require any special surface
+                    // visibility handling.
+                    return@map null
+                }
+
+                true // TODO(b/278086361): Implement continuous swipe to unlock.
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
+    /**
+     * The surface behind view params to use for the transition from LOCKSCREEN, or null if we don't
+     * care and should use a reasonable default.
+     */
+    val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.LOCKSCREEN)
+            ) { startedStep, fromLockscreenStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    // Only LOCKSCREEN -> GONE has specific surface params (for the unlock
+                    // animation).
+                    return@combine null
+                } else if (fromLockscreenStep.value > 0.5f) {
+                    // Start the animation once we're 50% transitioned to GONE.
+                    KeyguardSurfaceBehindModel(
+                        animateFromAlpha = 0f,
+                        alpha = 1f,
+                        animateFromTranslationY = 500f,
+                        translationY = 0f
+                    )
+                } else {
+                    KeyguardSurfaceBehindModel(
+                        alpha = 0f,
+                    )
+                }
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
     private fun listenForLockscreenToDreaming() {
         val invalidFromStates = setOf(KeyguardState.AOD, KeyguardState.DOZING)
         scope.launch {
@@ -169,7 +235,8 @@
                             }
 
                             // If canceled, just put the state back
-                            // TODO: This logic should happen in FromPrimaryBouncerInteractor.
+                            // TODO(b/278086361): This logic should happen in
+                            //  FromPrimaryBouncerInteractor.
                             if (nextState == TransitionState.CANCELED) {
                                 transitionRepository.startTransition(
                                     TransitionInfo(
@@ -201,7 +268,32 @@
         }
     }
 
+    fun dismissKeyguard() {
+        startTransitionTo(KeyguardState.GONE)
+    }
+
     private fun listenForLockscreenToGone() {
+        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            return
+        }
+
+        scope.launch {
+            keyguardInteractor.isKeyguardGoingAway
+                .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
+                .collect { pair ->
+                    val (isKeyguardGoingAway, lastStartedStep) = pair
+                    if (isKeyguardGoingAway && lastStartedStep.to == KeyguardState.LOCKSCREEN) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
+                }
+        }
+    }
+
+    private fun listenForLockscreenToGoneDragging() {
+        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            return
+        }
+
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -291,7 +383,7 @@
     }
 
     companion object {
-        private val DEFAULT_DURATION = 500.milliseconds
+        private val DEFAULT_DURATION = 400.milliseconds
         val TO_DREAMING_DURATION = 933.milliseconds
         val TO_OCCLUDED_DURATION = 450.milliseconds
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 9142d1f..c9f32da 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -17,23 +17,28 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.animation.ValueAnimator
-import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardSecurityModel
-import com.android.keyguard.KeyguardSecurityModel.SecurityMode.Password
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.util.kotlin.Utils.Companion.toQuad
 import com.android.systemui.util.kotlin.Utils.Companion.toQuint
 import com.android.systemui.util.kotlin.Utils.Companion.toTriple
 import com.android.systemui.util.kotlin.sample
+import com.android.wm.shell.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
 
 @SysUISingleton
@@ -44,6 +49,7 @@
     override val transitionInteractor: KeyguardTransitionInteractor,
     @Application private val scope: CoroutineScope,
     private val keyguardInteractor: KeyguardInteractor,
+    private val flags: FeatureFlags,
     private val keyguardSecurityModel: KeyguardSecurityModel,
 ) :
     TransitionInteractor(
@@ -57,6 +63,57 @@
         listenForPrimaryBouncerToDreamingLockscreenHosted()
     }
 
+    val surfaceBehindVisibility: Flow<Boolean?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+            ) { startedStep, fromBouncerStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    return@combine null
+                }
+
+                fromBouncerStep.value > 0.5f
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
+    val surfaceBehindModel: Flow<KeyguardSurfaceBehindModel?> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.transitionStepsFromState(KeyguardState.PRIMARY_BOUNCER)
+            ) { startedStep, fromBouncerStep ->
+                if (startedStep.to != KeyguardState.GONE) {
+                    // BOUNCER to anything but GONE does not require any special surface
+                    // visibility handling.
+                    return@combine null
+                }
+
+                if (fromBouncerStep.value > 0.5f) {
+                    KeyguardSurfaceBehindModel(
+                        animateFromAlpha = 0f,
+                        alpha = 1f,
+                        animateFromTranslationY = 500f,
+                        translationY = 0f,
+                    )
+                } else {
+                    KeyguardSurfaceBehindModel(
+                        alpha = 0f,
+                    )
+                }
+            }
+            .onStart {
+                // Default to null ("don't care, use a reasonable default").
+                emit(null)
+            }
+            .distinctUntilChanged()
+
+    fun dismissPrimaryBouncer() {
+        startTransitionTo(KeyguardState.GONE)
+    }
+
     private fun listenForPrimaryBouncerToLockscreenOrOccluded() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
@@ -124,28 +181,34 @@
     private fun listenForPrimaryBouncerToDreamingLockscreenHosted() {
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
-                .sample(
-                    combine(
-                        keyguardInteractor.isActiveDreamLockscreenHosted,
-                        transitionInteractor.startedKeyguardTransitionStep,
-                        ::Pair
-                    ),
-                    ::toTriple
-                )
-                .collect {
-                    (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
-                    if (
-                        !isBouncerShowing &&
-                            isActiveDreamLockscreenHosted &&
-                            lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
-                    ) {
-                        startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                    .sample(
+                            combine(
+                                    keyguardInteractor.isActiveDreamLockscreenHosted,
+                                    transitionInteractor.startedKeyguardTransitionStep,
+                                    ::Pair
+                            ),
+                            ::toTriple
+                    )
+                    .collect { (isBouncerShowing, isActiveDreamLockscreenHosted, lastStartedTransitionStep) ->
+                        if (
+                                !isBouncerShowing &&
+                                isActiveDreamLockscreenHosted &&
+                                lastStartedTransitionStep.to == KeyguardState.PRIMARY_BOUNCER
+                        ) {
+                            startTransitionTo(KeyguardState.DREAMING_LOCKSCREEN_HOSTED)
+                        }
                     }
-                }
         }
     }
 
     private fun listenForPrimaryBouncerToGone() {
+        if (flags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            // This is handled in KeyguardSecurityContainerController and
+            // StatusBarKeyguardViewManager, which calls the transition interactor to kick off a
+            // transition vs. listening to legacy state flags.
+            return
+        }
+
         scope.launch {
             keyguardInteractor.isKeyguardGoingAway
                 .sample(transitionInteractor.startedKeyguardTransitionStep, ::Pair)
@@ -160,7 +223,7 @@
                             )
                         // IME for password requires a slightly faster animation
                         val duration =
-                            if (securityMode == Password) {
+                            if (securityMode == KeyguardSecurityModel.SecurityMode.Password) {
                                 TO_GONE_SHORT_DURATION
                             } else {
                                 TO_GONE_DURATION
@@ -188,7 +251,7 @@
 
     companion object {
         private val DEFAULT_DURATION = 300.milliseconds
-        val TO_GONE_DURATION = 250.milliseconds
+        val TO_GONE_DURATION = 500.milliseconds
         val TO_GONE_SHORT_DURATION = 200.milliseconds
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 53d3c07..562c4db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -34,12 +34,12 @@
 import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
 import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.ScreenModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
@@ -53,6 +53,7 @@
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onStart
+import javax.inject.Inject
 
 /**
  * Encapsulates business-logic related to the keyguard but not to a more specific part within it.
@@ -264,7 +265,26 @@
         repository.setAnimateDozingTransitions(animate)
     }
 
+    fun isKeyguardDismissable(): Boolean {
+        return repository.isKeyguardUnlocked.value
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
+
+        fun isKeyguardVisibleInState(state: KeyguardState): Boolean {
+            return when (state) {
+                KeyguardState.OFF -> true
+                KeyguardState.DOZING -> true
+                KeyguardState.DREAMING -> true
+                KeyguardState.AOD -> true
+                KeyguardState.ALTERNATE_BOUNCER -> true
+                KeyguardState.PRIMARY_BOUNCER -> true
+                KeyguardState.LOCKSCREEN -> true
+                KeyguardState.GONE -> false
+                KeyguardState.OCCLUDED -> true
+                KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> false
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
new file mode 100644
index 0000000..bf04f8f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractor.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.data.repository.KeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindInteractor
+@Inject
+constructor(
+    private val repository: KeyguardSurfaceBehindRepository,
+    private val fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+    private val fromPrimaryBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+    transitionInteractor: KeyguardTransitionInteractor,
+) {
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val viewParams: Flow<KeyguardSurfaceBehindModel> =
+        transitionInteractor.isInTransitionToAnyState
+            .flatMapLatest { isInTransition ->
+                if (!isInTransition) {
+                    defaultParams
+                } else {
+                    combine(
+                        transitionSpecificViewParams,
+                        defaultParams,
+                    ) { transitionParams, defaultParams ->
+                        transitionParams ?: defaultParams
+                    }
+                }
+            }
+
+    val isAnimatingSurface = repository.isAnimatingSurface
+
+    private val defaultParams =
+        transitionInteractor.finishedKeyguardState.map { state ->
+            KeyguardSurfaceBehindModel(
+                alpha =
+                    if (WindowManagerLockscreenVisibilityInteractor.isSurfaceVisible(state)) 1f
+                    else 0f
+            )
+        }
+
+    /**
+     * View params provided by the transition interactor for the most recently STARTED transition.
+     * This is used to run transition-specific animations on the surface.
+     *
+     * If null, there are no transition-specific view params needed for this transition and we will
+     * use a reasonable default.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private val transitionSpecificViewParams: Flow<KeyguardSurfaceBehindModel?> =
+        transitionInteractor.startedKeyguardTransitionStep.flatMapLatest { startedStep ->
+            when (startedStep.from) {
+                KeyguardState.LOCKSCREEN -> fromLockscreenInteractor.surfaceBehindModel
+                KeyguardState.PRIMARY_BOUNCER -> fromPrimaryBouncerInteractor.surfaceBehindModel
+                // Return null for other states, where no transition specific params are needed.
+                else -> flowOf(null)
+            }
+        }
+
+    fun setAnimatingSurface(animating: Boolean) {
+        repository.setAnimatingSurface(animating)
+    }
+}
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 8c4c7ae..9382618 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
@@ -17,17 +17,20 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING_LOCKSCREEN_HOSTED
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
 import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
 import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -36,6 +39,7 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
@@ -46,8 +50,12 @@
 class KeyguardTransitionInteractor
 @Inject
 constructor(
-    private val repository: KeyguardTransitionRepository,
     @Application val scope: CoroutineScope,
+    private val repository: KeyguardTransitionRepository,
+    private val keyguardInteractor: dagger.Lazy<KeyguardInteractor>,
+    private val fromLockscreenTransitionInteractor: dagger.Lazy<FromLockscreenTransitionInteractor>,
+    private val fromPrimaryBouncerTransitionInteractor:
+        dagger.Lazy<FromPrimaryBouncerTransitionInteractor>,
 ) {
     private val TAG = this::class.simpleName
 
@@ -128,12 +136,11 @@
         repository.transition(PRIMARY_BOUNCER, GONE)
 
     /** OFF->LOCKSCREEN transition information. */
-    val offToLockscreenTransition: Flow<TransitionStep> =
-        repository.transition(KeyguardState.OFF, LOCKSCREEN)
+    val offToLockscreenTransition: Flow<TransitionStep> = repository.transition(OFF, LOCKSCREEN)
 
     /** DOZING->LOCKSCREEN transition information. */
     val dozingToLockscreenTransition: Flow<TransitionStep> =
-        repository.transition(KeyguardState.DOZING, LOCKSCREEN)
+        repository.transition(DOZING, LOCKSCREEN)
 
     /**
      * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
@@ -157,17 +164,30 @@
     val finishedKeyguardTransitionStep: Flow<TransitionStep> =
         repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
 
-    /** The destination state of the last started transition */
+    /** The destination state of the last started transition. */
     val startedKeyguardState: StateFlow<KeyguardState> =
         startedKeyguardTransitionStep
             .map { step -> step.to }
-            .stateIn(scope, SharingStarted.Eagerly, KeyguardState.OFF)
+            .stateIn(scope, SharingStarted.Eagerly, OFF)
 
     /** The last completed [KeyguardState] transition */
     val finishedKeyguardState: StateFlow<KeyguardState> =
         finishedKeyguardTransitionStep
             .map { step -> step.to }
             .stateIn(scope, SharingStarted.Eagerly, LOCKSCREEN)
+
+    /**
+     * Whether we're currently in a transition to a new [KeyguardState] and haven't yet completed
+     * it.
+     */
+    val isInTransitionToAnyState =
+            combine(
+                    startedKeyguardTransitionStep,
+                    finishedKeyguardState,
+            ) { startedStep, finishedState ->
+                startedStep.to != finishedState
+            }
+
     /**
      * The amount of transition into or out of the given [KeyguardState].
      *
@@ -187,4 +207,41 @@
                 }
             }
     }
+
+    fun transitionStepsFromState(fromState: KeyguardState): Flow<TransitionStep> {
+        return repository.transitions.filter { step -> step.from == fromState }
+    }
+
+    fun transitionStepsToState(toState: KeyguardState): Flow<TransitionStep> {
+        return repository.transitions.filter { step -> step.to == toState }
+    }
+
+    /**
+     * Called to start a transition that will ultimately dismiss the keyguard from the current
+     * state.
+     */
+    fun startDismissKeyguardTransition() {
+        when (startedKeyguardState.value) {
+            LOCKSCREEN -> fromLockscreenTransitionInteractor.get().dismissKeyguard()
+            PRIMARY_BOUNCER -> fromPrimaryBouncerTransitionInteractor.get().dismissPrimaryBouncer()
+            else ->
+                Log.e(
+                    "KeyguardTransitionInteractor",
+                    "We don't know how to dismiss keyguard from state " +
+                        "${startedKeyguardState.value}"
+                )
+        }
+    }
+
+    /** Whether we're in a transition to the given [KeyguardState], but haven't yet completed it. */
+    fun isInTransitionToState(
+            state: KeyguardState,
+    ): Flow<Boolean> {
+        return combine(
+                startedKeyguardTransitionStep,
+                finishedKeyguardState,
+        ) { startedStep, finishedState ->
+            startedStep.to == state && finishedState != state
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
deleted file mode 100644
index 278c68d..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractor.kt
+++ /dev/null
@@ -1,68 +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.systemui.keyguard.domain.interactor
-
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import javax.inject.Inject
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-
-/** Hosts business and application state accessing logic for the lockscreen scene. */
-@SysUISingleton
-class LockscreenSceneInteractor
-@Inject
-constructor(
-    @Application applicationScope: CoroutineScope,
-    private val authenticationInteractor: AuthenticationInteractor,
-    private val bouncerInteractor: BouncerInteractor,
-) {
-    /** Whether the device is currently locked. */
-    val isDeviceLocked: StateFlow<Boolean> =
-        authenticationInteractor.isUnlocked
-            .map { !it }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = !authenticationInteractor.isUnlocked.value,
-            )
-
-    /** Whether it's currently possible to swipe up to dismiss the lockscreen. */
-    val isSwipeToDismissEnabled: StateFlow<Boolean> =
-        authenticationInteractor.isUnlocked
-            .map { isUnlocked ->
-                !isUnlocked &&
-                    authenticationInteractor.getAuthenticationMethod() is
-                        AuthenticationMethodModel.Swipe
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = false,
-            )
-
-    /** Attempts to dismiss the lockscreen. This will cause the bouncer to show, if needed. */
-    fun dismissLockscreen() {
-        bouncerInteractor.showOrUnlockDevice()
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index ff8d5c9..3ec660a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -22,10 +22,14 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -55,6 +59,8 @@
     @Application scope: CoroutineScope,
     private val context: Context,
     activityStarter: ActivityStarter,
+    powerInteractor: PowerInteractor,
+    featureFlags: FeatureFlags,
 ) {
     private val keyguardOccludedByApp: Flow<Boolean> =
         combine(
@@ -87,29 +93,37 @@
             .ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null))
 
     init {
-        scope.launch {
-            // On fingerprint success, go to the home screen
-            fingerprintUnlockSuccessEvents.collect { goToHomeScreen() }
-        }
+        if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
+            scope.launch {
+                // On fingerprint success when the screen is on, go to the home screen
+                fingerprintUnlockSuccessEvents.sample(powerInteractor.isInteractive).collect {
+                    if (it) {
+                        goToHomeScreen()
+                    }
+                    // don't go to the home screen if the authentication is from AOD/dozing/off
+                }
+            }
 
-        scope.launch {
-            // On device fingerprint lockout, request the bouncer with a runnable to
-            // go to the home screen. Without this, the bouncer won't proceed to the home screen.
-            fingerprintLockoutEvents.collect {
-                activityStarter.dismissKeyguardThenExecute(
-                    object : ActivityStarter.OnDismissAction {
-                        override fun onDismiss(): Boolean {
-                            goToHomeScreen()
-                            return false
-                        }
+            scope.launch {
+                // On device fingerprint lockout, request the bouncer with a runnable to
+                // go to the home screen. Without this, the bouncer won't proceed to the home
+                // screen.
+                fingerprintLockoutEvents.collect {
+                    activityStarter.dismissKeyguardThenExecute(
+                        object : ActivityStarter.OnDismissAction {
+                            override fun onDismiss(): Boolean {
+                                goToHomeScreen()
+                                return false
+                            }
 
-                        override fun willRunAnimationOnKeyguard(): Boolean {
-                            return false
-                        }
-                    },
-                    /* cancel= */ null,
-                    /* afterKeyguardGone */ false
-                )
+                            override fun willRunAnimationOnKeyguard(): Boolean {
+                                return false
+                            }
+                        },
+                        /* cancel= */ null,
+                        /* afterKeyguardGone */ false
+                    )
+                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
new file mode 100644
index 0000000..96bfdc6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractor.kt
@@ -0,0 +1,162 @@
+/*
+ * 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.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityInteractor
+@Inject
+constructor(
+    keyguardInteractor: KeyguardInteractor,
+    transitionInteractor: KeyguardTransitionInteractor,
+    surfaceBehindInteractor: KeyguardSurfaceBehindInteractor,
+    fromLockscreenInteractor: FromLockscreenTransitionInteractor,
+    fromBouncerInteractor: FromPrimaryBouncerTransitionInteractor,
+) {
+    private val defaultSurfaceBehindVisibility =
+        transitionInteractor.finishedKeyguardState.map(::isSurfaceVisible)
+
+    /**
+     * Surface visibility provided by the From*TransitionInteractor responsible for the currently
+     * RUNNING transition, or null if the current transition does not require special surface
+     * visibility handling.
+     *
+     * An example of transition-specific visibility is swipe to unlock, where the surface should
+     * only be visible after swiping 20% of the way up the screen, and should become invisible again
+     * if the user swipes back down.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    private val transitionSpecificSurfaceBehindVisibility: Flow<Boolean?> =
+        transitionInteractor.startedKeyguardTransitionStep
+            .flatMapLatest { startedStep ->
+                when (startedStep.from) {
+                    KeyguardState.LOCKSCREEN -> {
+                        fromLockscreenInteractor.surfaceBehindVisibility
+                    }
+                    KeyguardState.PRIMARY_BOUNCER -> {
+                        fromBouncerInteractor.surfaceBehindVisibility
+                    }
+                    else -> flowOf(null)
+                }
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Surface visibility, which is either determined by the default visibility in the FINISHED
+     * KeyguardState, or the transition-specific visibility used during certain RUNNING transitions.
+     */
+    @OptIn(ExperimentalCoroutinesApi::class)
+    val surfaceBehindVisibility: Flow<Boolean> =
+        transitionInteractor
+                .isInTransitionToAnyState
+            .flatMapLatest { isInTransition ->
+                if (!isInTransition) {
+                    defaultSurfaceBehindVisibility
+                } else {
+                    combine(
+                        transitionSpecificSurfaceBehindVisibility,
+                        defaultSurfaceBehindVisibility,
+                    ) { transitionVisibility, defaultVisibility ->
+                        // Defer to the transition-specific visibility since we're RUNNING a
+                        // transition, but fall back to the default visibility if the current
+                        // transition's interactor did not specify a visibility.
+                        transitionVisibility ?: defaultVisibility
+                    }
+                }
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Whether we're animating, or intend to animate, the surface behind the keyguard via remote
+     * animation. This is used to keep the RemoteAnimationTarget alive until we're done using it.
+     */
+    val usingKeyguardGoingAwayAnimation: Flow<Boolean> =
+        combine(
+                transitionInteractor.isInTransitionToState(KeyguardState.GONE),
+                transitionInteractor.finishedKeyguardState,
+                surfaceBehindInteractor.isAnimatingSurface
+            ) { isInTransitionToGone, finishedState, isAnimatingSurface ->
+                // We may still be animating the surface after the keyguard is fully GONE, since
+                // some animations (like the translation spring) are not tied directly to the
+                // transition step amount.
+                isInTransitionToGone || (finishedState == KeyguardState.GONE && isAnimatingSurface)
+            }
+            .distinctUntilChanged()
+
+    /**
+     * Whether the lockscreen is visible, from the Window Manager (WM) perspective.
+     *
+     * Note: This may briefly be true even if the lockscreen UI has animated out (alpha = 0f), as we
+     * only inform WM once we're done with the keyguard and we're fully GONE. Don't use this if you
+     * want to know if the AOD/clock/notifs/etc. are visible.
+     */
+    val lockscreenVisibility: Flow<Boolean> =
+        combine(
+                transitionInteractor.startedKeyguardTransitionStep,
+                transitionInteractor.finishedKeyguardState,
+            ) { startedStep, finishedState ->
+                // If we finished the transition, use the finished state. If we're running a
+                // transition, use the state we're transitioning FROM. This can be different from
+                // the last finished state if a transition is interrupted. For example, if we were
+                // transitioning from GONE to AOD and then started AOD -> LOCKSCREEN mid-transition,
+                // we want to immediately use the visibility for AOD (lockscreenVisibility=true)
+                // even though the lastFinishedState is still GONE (lockscreenVisibility=false).
+                if (finishedState == startedStep.to) finishedState else startedStep.from
+            }
+            .map(::isLockscreenVisible)
+            .distinctUntilChanged()
+
+    /**
+     * Whether always-on-display (AOD) is visible when the lockscreen is visible, from window
+     * manager's perspective.
+     *
+     * Note: This may be true even if AOD is not user-visible, such as when the light sensor
+     * indicates the device is in the user's pocket. Don't use this if you want to know if the AOD
+     * clock/smartspace/notif icons are visible.
+     */
+    val aodVisibility: Flow<Boolean> =
+        combine(
+                keyguardInteractor.isDozing,
+                keyguardInteractor.biometricUnlockState,
+            ) { isDozing, biometricUnlockState ->
+                // AOD is visible if we're dozing, unless we are wake and unlocking (where we go
+                // directly from AOD to unlocked while dozing).
+                isDozing && !BiometricUnlockModel.isWakeAndUnlock(biometricUnlockState)
+            }
+            .distinctUntilChanged()
+
+    companion object {
+        fun isSurfaceVisible(state: KeyguardState): Boolean {
+            return !isLockscreenVisible(state)
+        }
+
+        fun isLockscreenVisible(state: KeyguardState): Boolean {
+            return state != KeyguardState.GONE
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
new file mode 100644
index 0000000..7fb5cfd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSurfaceBehindModel.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.shared.model
+
+/**
+ * Models the appearance of the surface behind the keyguard, and (optionally) how it should be
+ * animating.
+ *
+ * This is intended to be an atomic, high-level description of the surface's appearance and related
+ * animations, which we can derive from the STARTED/FINISHED transition states rather than the
+ * individual TransitionSteps.
+ *
+ * For example, if we're transitioning from LOCKSCREEN to GONE, that means we should be
+ * animatingFromAlpha 0f -> 1f and animatingFromTranslationY 500f -> 0f.
+ * KeyguardSurfaceBehindAnimator can decide how best to implement this, depending on previously
+ * running animations, spring momentum, and other state.
+ */
+data class KeyguardSurfaceBehindModel(
+    val alpha: Float = 1f,
+
+    /**
+     * If provided, animate from this value to [alpha] unless an animation is already running, in
+     * which case we'll animate from the current value to [alpha].
+     */
+    val animateFromAlpha: Float = alpha,
+    val translationY: Float = 0f,
+
+    /**
+     * If provided, animate from this value to [translationY] unless an animation is already
+     * running, in which case we'll animate from the current value to [translationY].
+     */
+    val animateFromTranslationY: Float = translationY,
+) {
+    fun willAnimateAlpha(): Boolean {
+        return animateFromAlpha != alpha
+    }
+
+    fun willAnimateTranslationY(): Boolean {
+        return animateFromTranslationY != translationY
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
index d6883dd..5c072fb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardAmbientIndicationAreaViewBinder.kt
@@ -67,14 +67,15 @@
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
                     launch {
                         keyguardRootViewModel.alpha.collect { alpha ->
-                            view.importantForAccessibility =
-                                if (alpha == 0f) {
-                                    View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                } else {
-                                    View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                                }
-
-                            ambientIndicationArea?.alpha = alpha
+                            ambientIndicationArea?.apply {
+                                this.importantForAccessibility =
+                                    if (alpha == 0f) {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                    } else {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                    }
+                                this.alpha = alpha
+                            }
                         }
                     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
index a0a2abe..44acf4f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt
@@ -176,14 +176,15 @@
 
                     launch {
                         viewModel.alpha.collect { alpha ->
-                            view.importantForAccessibility =
-                                if (alpha == 0f) {
-                                    View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                } else {
-                                    View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                                }
-
-                            ambientIndicationArea?.alpha = alpha
+                            ambientIndicationArea?.apply {
+                                this.importantForAccessibility =
+                                    if (alpha == 0f) {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                    } else {
+                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                    }
+                                this.alpha = alpha
+                            }
                         }
                     }
 
@@ -471,7 +472,7 @@
             return true
         }
 
-        override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
+        override fun onLongClickUseDefaultHapticFeedback(view: View) = false
     }
 
     @Deprecated("Deprecated as part of b/278057014")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index a385a0e..dc51944 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -73,25 +73,27 @@
                     launch {
                         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
                             keyguardRootViewModel.alpha.collect { alpha ->
-                                view.importantForAccessibility =
-                                    if (alpha == 0f) {
-                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                    } else {
-                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                                    }
-
-                                indicationArea.alpha = alpha
+                                indicationArea.apply {
+                                    this.importantForAccessibility =
+                                        if (alpha == 0f) {
+                                            View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                        } else {
+                                            View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                        }
+                                    this.alpha = alpha
+                                }
                             }
                         } else {
                             viewModel.alpha.collect { alpha ->
-                                view.importantForAccessibility =
-                                    if (alpha == 0f) {
-                                        View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
-                                    } else {
-                                        View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                                    }
-
-                                indicationArea.alpha = alpha
+                                indicationArea.apply {
+                                    this.importantForAccessibility =
+                                        if (alpha == 0f) {
+                                            View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+                                        } else {
+                                            View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                        }
+                                    this.alpha = alpha
+                                }
                             }
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 63a6791..83b5463 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -304,7 +304,7 @@
             return true
         }
 
-        override fun onLongClickUseDefaultHapticFeedback(view: View?) = false
+        override fun onLongClickUseDefaultHapticFeedback(view: View) = false
 
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index 162c109..82610e6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -43,7 +43,7 @@
         vibratorHelper: VibratorHelper,
         activityStarter: ActivityStarter
     ): DisposableHandle {
-        val view = parentView.findViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
+        val view = parentView.requireViewById<LaunchableLinearLayout>(R.id.keyguard_settings_button)
 
         val disposableHandle =
             view.repeatWhenAttached {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
new file mode 100644
index 0000000..a5b00e0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
@@ -0,0 +1,219 @@
+/*
+ * 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.ui.binder
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.graphics.Matrix
+import android.util.Log
+import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
+import android.view.SyncRtSurfaceTransactionApplier
+import android.view.View
+import androidx.dynamicanimation.animation.FloatValueHolder
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.keyguard.TAG
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.wm.shell.animation.Interpolators
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Applies [KeyguardSurfaceBehindViewParams] to a RemoteAnimationTarget, starting and managing
+ * animations as needed.
+ */
+@SysUISingleton
+class KeyguardSurfaceBehindParamsApplier
+@Inject
+constructor(
+    @Main private val executor: Executor,
+    private val keyguardViewController: KeyguardViewController,
+    private val interactor: KeyguardSurfaceBehindInteractor,
+) {
+    private var surfaceBehind: RemoteAnimationTarget? = null
+    private val surfaceTransactionApplier: SyncRtSurfaceTransactionApplier
+        get() = SyncRtSurfaceTransactionApplier(keyguardViewController.viewRootImpl.view)
+
+    private val matrix = Matrix()
+    private val tmpFloat = FloatArray(9)
+
+    private var animatedTranslationY = FloatValueHolder()
+    private val translateYSpring =
+        SpringAnimation(animatedTranslationY).apply {
+            spring =
+                SpringForce().apply {
+                    stiffness = 200f
+                    dampingRatio = 1f
+                }
+            addUpdateListener { _, _, _ -> applyToSurfaceBehind() }
+            addEndListener { _, _, _, _ -> 
+                try {
+                    updateIsAnimatingSurface()
+                } catch (e: NullPointerException) {
+                    // TODO(b/291645410): Remove when we can isolate DynamicAnimations.
+                    e.printStackTrace()
+                }
+            }
+        }
+
+    private var animatedAlpha = 0f
+    private var alphaAnimator =
+        ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = 500
+            interpolator = Interpolators.ALPHA_IN
+            addUpdateListener {
+                animatedAlpha = it.animatedValue as Float
+                applyToSurfaceBehind()
+            }
+            addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationEnd(animation: Animator?) {
+                        updateIsAnimatingSurface()
+                    }
+                }
+            )
+        }
+
+    /**
+     * ViewParams to apply to the surface provided to [applyParamsToSurface]. If the surface is null
+     * these will be applied once someone gives us a surface via [applyParamsToSurface].
+     */
+    var viewParams: KeyguardSurfaceBehindModel = KeyguardSurfaceBehindModel()
+        set(newParams) {
+            field = newParams
+            startOrUpdateAnimators()
+            applyToSurfaceBehind()
+        }
+
+    /**
+     * Provides us with a surface to animate. We'll apply the [viewParams] to this surface and start
+     * any necessary animations.
+     */
+    fun applyParamsToSurface(surface: RemoteAnimationTarget) {
+        this.surfaceBehind = surface
+        startOrUpdateAnimators()
+    }
+
+    /**
+     * Notifies us that the [RemoteAnimationTarget] has been released, one way or another.
+     * Attempting to animate a released target will cause a crash.
+     *
+     * This can be called either because we finished animating the surface naturally, or by WM
+     * because external factors cancelled the remote animation (timeout, re-lock, etc). If it's the
+     * latter, cancel any outstanding animations we have.
+     */
+    fun notifySurfaceReleased() {
+        surfaceBehind = null
+
+        if (alphaAnimator.isRunning) {
+            alphaAnimator.cancel()
+        }
+
+        if (translateYSpring.isRunning) {
+            translateYSpring.cancel()
+        }
+    }
+
+    private fun startOrUpdateAnimators() {
+        if (surfaceBehind == null) {
+            return
+        }
+
+        if (viewParams.willAnimateAlpha()) {
+            var fromAlpha = viewParams.animateFromAlpha
+
+            if (alphaAnimator.isRunning) {
+                alphaAnimator.cancel()
+                fromAlpha = animatedAlpha
+            }
+
+            alphaAnimator.setFloatValues(fromAlpha, viewParams.alpha)
+            alphaAnimator.start()
+        }
+
+        if (viewParams.willAnimateTranslationY()) {
+            if (!translateYSpring.isRunning) {
+                // If the spring isn't running yet, set the start value. Otherwise, respect the
+                // current position.
+                animatedTranslationY.value = viewParams.animateFromTranslationY
+            }
+
+            translateYSpring.animateToFinalPosition(viewParams.translationY)
+        }
+
+        updateIsAnimatingSurface()
+    }
+
+    private fun updateIsAnimatingSurface() {
+        interactor.setAnimatingSurface(translateYSpring.isRunning || alphaAnimator.isRunning)
+    }
+
+    private fun applyToSurfaceBehind() {
+        surfaceBehind?.leash?.let { sc ->
+            executor.execute {
+                if (surfaceBehind == null) {
+                    Log.d(
+                        TAG,
+                        "Attempting to modify params of surface that isn't " +
+                            "animating. Ignoring."
+                    )
+                    matrix.set(Matrix.IDENTITY_MATRIX)
+                    return@execute
+                }
+
+                val translationY =
+                    if (translateYSpring.isRunning) animatedTranslationY.value
+                    else viewParams.translationY
+
+                val alpha =
+                    if (alphaAnimator.isRunning) {
+                        animatedAlpha
+                    } else {
+                        viewParams.alpha
+                    }
+
+                if (
+                    keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+                        sc.isValid
+                ) {
+                    with(SurfaceControl.Transaction()) {
+                        setMatrix(
+                            sc,
+                            matrix.apply { setTranslate(/* dx= */ 0f, translationY) },
+                            tmpFloat
+                        )
+                        setAlpha(sc, alpha)
+                        apply()
+                    }
+                } else {
+                    surfaceTransactionApplier.scheduleApply(
+                        SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(sc)
+                            .withMatrix(matrix.apply { setTranslate(/* dx= */ 0f, translationY) })
+                            .withAlpha(alpha)
+                            .build()
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
new file mode 100644
index 0000000..599f69f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object KeyguardSurfaceBehindViewBinder {
+    @JvmStatic
+    fun bind(
+        viewModel: KeyguardSurfaceBehindViewModel,
+        applier: KeyguardSurfaceBehindParamsApplier,
+        scope: CoroutineScope
+    ) {
+        scope.launch { viewModel.surfaceBehindViewParams.collect { applier.viewParams = it } }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
index b568a9a..3bb01f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/UdfpsKeyguardInternalViewBinder.kt
@@ -42,13 +42,13 @@
         view.accessibilityDelegate = viewModel.accessibilityDelegate
 
         // bind child views
-        UdfpsAodFingerprintViewBinder.bind(view.findViewById(R.id.udfps_aod_fp), aodViewModel)
+        UdfpsAodFingerprintViewBinder.bind(view.requireViewById(R.id.udfps_aod_fp), aodViewModel)
         UdfpsFingerprintViewBinder.bind(
-            view.findViewById(R.id.udfps_lockscreen_fp),
+            view.requireViewById(R.id.udfps_lockscreen_fp),
             fingerprintViewModel
         )
         UdfpsBackgroundViewBinder.bind(
-            view.findViewById(R.id.udfps_keyguard_fp_bg),
+            view.requireViewById(R.id.udfps_keyguard_fp_bg),
             backgroundViewModel
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
new file mode 100644
index 0000000..fc0c78a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.ui.binder
+
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Binds the [WindowManagerLockscreenVisibilityManager] "view", which manages the visibility of the
+ * surface behind the keyguard.
+ */
+object WindowManagerLockscreenVisibilityViewBinder {
+    @JvmStatic
+    fun bind(
+        viewModel: WindowManagerLockscreenVisibilityViewModel,
+        lockscreenVisibilityManager: WindowManagerLockscreenVisibilityManager,
+        scope: CoroutineScope
+    ) {
+        scope.launch {
+            viewModel.surfaceBehindVisibility.collect {
+                lockscreenVisibilityManager.setSurfaceBehindVisibility(it)
+            }
+        }
+
+        scope.launch {
+            viewModel.lockscreenVisibility.collect {
+                lockscreenVisibilityManager.setLockscreenShown(it)
+            }
+        }
+
+        scope.launch {
+            viewModel.aodVisibility.collect { lockscreenVisibilityManager.setAodVisible(it) }
+        }
+
+        scope.launch {
+            viewModel.surfaceBehindAnimating.collect {
+                lockscreenVisibilityManager.setUsingGoingAwayRemoteAnimation(it)
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index cf96458..dd3da97 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -117,7 +117,7 @@
     private var host: SurfaceControlViewHost
 
     val surfacePackage: SurfaceControlViewHost.SurfacePackage
-        get() = host.surfacePackage
+        get() = checkNotNull(host.surfacePackage)
 
     private lateinit var largeClockHostView: FrameLayout
     private lateinit var smallClockHostView: FrameLayout
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
index e60901f..a948741 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/KeyguardRootView.kt
@@ -24,6 +24,7 @@
 import android.widget.ImageView
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.core.content.res.ResourcesCompat
+import com.android.keyguard.KeyguardStatusView
 import com.android.keyguard.LockIconView
 import com.android.systemui.R
 import com.android.systemui.animation.view.LaunchableImageView
@@ -38,6 +39,8 @@
         attrs,
     ) {
 
+    private var statusView: KeyguardStatusView? = null
+
     init {
         addIndicationTextArea()
         addLockIconView()
@@ -45,6 +48,7 @@
         addLeftShortcut()
         addRightShortcut()
         addSettingsPopupMenu()
+        addStatusView()
     }
 
     private fun addIndicationTextArea() {
@@ -119,4 +123,19 @@
                 }
         addView(view)
     }
+
+    fun addStatusView(): KeyguardStatusView {
+        // StatusView may need to be rebuilt on config changes. Remove and reinflate
+        statusView?.let { removeView(it) }
+        val view =
+            (LayoutInflater.from(context).inflate(R.layout.keyguard_status_view, this, false)
+                    as KeyguardStatusView)
+                .apply {
+                    setClipChildren(false)
+                    statusView = this
+                }
+
+        addView(view)
+        return view
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
index 5538fe7..518df07 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprint.kt
@@ -25,6 +25,8 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import javax.inject.Inject
 
 /**
@@ -42,6 +44,8 @@
     private val defaultShortcutsSection: DefaultShortcutsSection,
     private val defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection,
     private val defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
+    private val defaultStatusViewSection: DefaultStatusViewSection,
+    private val splitShadeGuidelines: SplitShadeGuidelines,
 ) : KeyguardBlueprint {
     override val id: String = DEFAULT
 
@@ -51,6 +55,8 @@
         defaultShortcutsSection.apply(constraintSet)
         defaultAmbientIndicationAreaSection.apply(constraintSet)
         defaultSettingsPopupMenuSection.apply(constraintSet)
+        defaultStatusViewSection.apply(constraintSet)
+        splitShadeGuidelines.apply(constraintSet)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index 19410e4..54c2796 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import javax.inject.Inject
 
 /** Vertically aligns the shortcuts with the udfps. */
@@ -41,6 +43,8 @@
     private val defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection,
     private val alignShortcutsToUdfpsSection: AlignShortcutsToUdfpsSection,
     private val defaultShortcutsSection: DefaultShortcutsSection,
+    private val defaultStatusViewSection: DefaultStatusViewSection,
+    private val splitShadeGuidelines: SplitShadeGuidelines,
 ) : KeyguardBlueprint {
     override val id: String = SHORTCUTS_BESIDE_UDFPS
 
@@ -54,6 +58,8 @@
         } else {
             defaultShortcutsSection.apply(constraintSet)
         }
+        defaultStatusViewSection.apply(constraintSet)
+        splitShadeGuidelines.apply(constraintSet)
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
new file mode 100644
index 0000000..3f319ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultStatusViewSection.kt
@@ -0,0 +1,46 @@
+/*
+ * 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.ui.view.layout.sections
+
+import android.content.Context
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.R
+import com.android.systemui.keyguard.data.repository.KeyguardSection
+import javax.inject.Inject
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.END
+
+class DefaultStatusViewSection @Inject constructor(private val context: Context) :
+    KeyguardSection {
+    private val statusViewId = R.id.keyguard_status_view
+
+    override fun apply(constraintSet: ConstraintSet) {
+        constraintSet.apply {
+            constrainWidth(statusViewId, MATCH_CONSTRAINT)
+            constrainHeight(statusViewId, WRAP_CONTENT)
+            connect(statusViewId, TOP, PARENT_ID, TOP)
+            connect(statusViewId, START, PARENT_ID, START)
+            connect(statusViewId, END, PARENT_ID, END)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt
new file mode 100644
index 0000000..668b17f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeGuidelines.kt
@@ -0,0 +1,45 @@
+/*
+ * 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.ui.view.layout.sections
+
+import android.content.Context
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.R
+import com.android.systemui.keyguard.data.repository.KeyguardSection
+import javax.inject.Inject
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import androidx.constraintlayout.widget.ConstraintSet.MATCH_CONSTRAINT
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.END
+import androidx.constraintlayout.widget.ConstraintSet.VERTICAL
+
+class SplitShadeGuidelines @Inject constructor(private val context: Context) :
+    KeyguardSection {
+
+    override fun apply(constraintSet: ConstraintSet) {
+        constraintSet.apply {
+            // For use on large screens, it will provide a guideline vertically in the center to
+            // enable items to be aligned on the left or right sides
+            create(R.id.split_shade_guideline, VERTICAL)
+            setGuidelinePercent(R.id.split_shade_guideline, 0.5f)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
new file mode 100644
index 0000000..4f52962
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSurfaceBehindViewModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class KeyguardSurfaceBehindViewModel
+@Inject
+constructor(interactor: KeyguardSurfaceBehindInteractor) {
+    val surfaceBehindViewParams = interactor.viewParams
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index abd178c..6d3b7f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -17,14 +17,16 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import com.android.systemui.R
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
@@ -36,63 +38,58 @@
 @Inject
 constructor(
     @Application applicationScope: CoroutineScope,
-    private val interactor: LockscreenSceneInteractor,
+    authenticationInteractor: AuthenticationInteractor,
+    private val bouncerInteractor: BouncerInteractor,
 ) {
     /** The icon for the "lock" button on the lockscreen. */
     val lockButtonIcon: StateFlow<Icon> =
-        interactor.isDeviceLocked
-            .map { isLocked -> lockIcon(isLocked = isLocked) }
+        authenticationInteractor.isUnlocked
+            .map { isUnlocked -> lockIcon(isUnlocked = isUnlocked) }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = lockIcon(isLocked = interactor.isDeviceLocked.value),
+                initialValue = lockIcon(isUnlocked = authenticationInteractor.isUnlocked.value),
             )
 
     /** The key of the scene we should switch to when swiping up. */
-    val upDestinationSceneKey: StateFlow<SceneKey> =
-        interactor.isSwipeToDismissEnabled
-            .map { isSwipeToUnlockEnabled -> upDestinationSceneKey(isSwipeToUnlockEnabled) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = upDestinationSceneKey(interactor.isSwipeToDismissEnabled.value),
-            )
+    val upDestinationSceneKey: Flow<SceneKey> =
+        authenticationInteractor.isUnlocked.map { isUnlocked ->
+            if (isUnlocked) {
+                SceneKey.Gone
+            } else {
+                SceneKey.Bouncer
+            }
+        }
 
     /** Notifies that the lock button on the lock screen was clicked. */
     fun onLockButtonClicked() {
-        interactor.dismissLockscreen()
+        bouncerInteractor.showOrUnlockDevice()
     }
 
     /** Notifies that some content on the lock screen was clicked. */
     fun onContentClicked() {
-        interactor.dismissLockscreen()
+        bouncerInteractor.showOrUnlockDevice()
     }
 
     private fun upDestinationSceneKey(
-        isSwipeToUnlockEnabled: Boolean,
+        canSwipeToDismiss: Boolean,
     ): SceneKey {
-        return if (isSwipeToUnlockEnabled) SceneKey.Gone else SceneKey.Bouncer
+        return if (canSwipeToDismiss) SceneKey.Gone else SceneKey.Bouncer
     }
 
     private fun lockIcon(
-        isLocked: Boolean,
+        isUnlocked: Boolean,
     ): Icon {
-        return Icon.Resource(
-            res =
-                if (isLocked) {
-                    R.drawable.ic_device_lock_on
-                } else {
-                    R.drawable.ic_device_lock_off
-                },
-            contentDescription =
-                ContentDescription.Resource(
-                    res =
-                        if (isLocked) {
-                            R.string.accessibility_lock_icon
-                        } else {
-                            R.string.accessibility_unlock_button
-                        }
-                )
-        )
+        return if (isUnlocked) {
+            Icon.Resource(
+                R.drawable.ic_device_lock_off,
+                ContentDescription.Resource(R.string.accessibility_unlock_button)
+            )
+        } else {
+            Icon.Resource(
+                R.drawable.ic_device_lock_on,
+                ContentDescription.Resource(R.string.accessibility_lock_icon)
+            )
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
new file mode 100644
index 0000000..f797640
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/WindowManagerLockscreenVisibilityViewModel.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor
+import javax.inject.Inject
+
+@SysUISingleton
+class WindowManagerLockscreenVisibilityViewModel
+@Inject
+constructor(interactor: WindowManagerLockscreenVisibilityInteractor) {
+    val surfaceBehindVisibility = interactor.surfaceBehindVisibility
+    val surfaceBehindAnimating = interactor.usingKeyguardGoingAwayAnimation
+    val lockscreenVisibility = interactor.lockscreenVisibility
+    val aodVisibility = interactor.aodVisibility
+}
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
index e064839..5f7991e 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt
@@ -70,7 +70,7 @@
     var lifecycleOwner: ViewLifecycleOwner? = null
     val onAttachListener =
         object : View.OnAttachStateChangeListener {
-            override fun onViewAttachedToWindow(v: View?) {
+            override fun onViewAttachedToWindow(v: View) {
                 Assert.isMainThread()
                 lifecycleOwner?.onDestroy()
                 lifecycleOwner =
@@ -81,7 +81,7 @@
                     )
             }
 
-            override fun onViewDetachedFromWindow(v: View?) {
+            override fun onViewDetachedFromWindow(v: View) {
                 lifecycleOwner?.onDestroy()
                 lifecycleOwner = null
             }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index cc1504a..b6577f7 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -120,6 +120,14 @@
         return factory.create("ShadeLog", 500, false);
     }
 
+    /** Provides a logging buffer for Shade messages. */
+    @Provides
+    @SysUISingleton
+    @ShadeTouchLog
+    public static LogBuffer provideShadeTouchLogBuffer(LogBufferFactory factory) {
+        return factory.create("ShadeTouchLog", 500, false);
+    }
+
     /** Provides a logging buffer for all logs related to managing notification sections. */
     @Provides
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java
new file mode 100644
index 0000000..b13667e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/ShadeTouchLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for tracking touches in various shade child views. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface ShadeTouchLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
index 67a985e..a7ffc5f 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBuffer.kt
@@ -302,14 +302,14 @@
 
     @Synchronized
     override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println(HEADER_PREFIX + name)
-        pw.println("version $VERSION")
+        pw.append(HEADER_PREFIX).println(name)
+        pw.append("version ").println(VERSION)
 
         lastEvictedValues.values.sortedBy { it.timestamp }.forEach { it.dump(pw) }
         for (i in 0 until buffer.size) {
             buffer[i].dump(pw)
         }
-        pw.println(FOOTER_PREFIX + name)
+        pw.append(FOOTER_PREFIX).println(name)
     }
 
     /** Dumps an individual [TableChange]. */
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
index 35f5a8c..a91917a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt
@@ -514,7 +514,7 @@
          * Returns true when the down event of the scroll hits within the target box of the thumb.
          */
         override fun onScroll(
-            eventStart: MotionEvent,
+            eventStart: MotionEvent?,
             event: MotionEvent,
             distanceX: Float,
             distanceY: Float
@@ -528,7 +528,7 @@
          * Gestures that include a fling are considered a false gesture on the seek bar.
          */
         override fun onFling(
-            eventStart: MotionEvent,
+            eventStart: MotionEvent?,
             event: MotionEvent,
             velocityX: Float,
             velocityY: Float
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
index 207df6b..a1291a4 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataFilter.kt
@@ -149,11 +149,7 @@
         // Check if smartspace has explicitly specified whether to re-activate resumable media.
         // The default behavior is to trigger if the smartspace data is active.
         val shouldTriggerResume =
-            if (data.cardAction?.extras?.containsKey(EXTRA_KEY_TRIGGER_RESUME) == true) {
-                data.cardAction.extras.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true)
-            } else {
-                true
-            }
+            data.cardAction?.extras?.getBoolean(EXTRA_KEY_TRIGGER_RESUME, true) ?: true
         val shouldReactivate =
             shouldTriggerResume && !hasActiveMedia() && hasAnyMedia() && data.isActive
 
@@ -269,9 +265,7 @@
                     "Cannot create dismiss action click action: extras missing dismiss_intent."
                 )
             } else if (
-                dismissIntent.getComponent() != null &&
-                    dismissIntent.getComponent().getClassName() ==
-                        EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
+                dismissIntent.component?.className == EXPORTED_SMARTSPACE_TRAMPOLINE_ACTIVITY_NAME
             ) {
                 // Dismiss the card Smartspace data through Smartspace trampoline activity.
                 context.startActivity(dismissIntent)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
index 55dcd88..dddbeda 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt
@@ -22,6 +22,7 @@
 import android.app.Notification.EXTRA_SUBSTITUTE_APP_NAME
 import android.app.PendingIntent
 import android.app.StatusBarManager
+import android.app.smartspace.SmartspaceAction
 import android.app.smartspace.SmartspaceConfig
 import android.app.smartspace.SmartspaceManager
 import android.app.smartspace.SmartspaceSession
@@ -1623,20 +1624,18 @@
      *   SmartspaceTarget's data is invalid.
      */
     private fun toSmartspaceMediaData(target: SmartspaceTarget): SmartspaceMediaData {
-        var dismissIntent: Intent? = null
-        if (target.baseAction != null && target.baseAction.extras != null) {
-            dismissIntent =
-                target.baseAction.extras.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY)
-                    as Intent?
-        }
+        val baseAction: SmartspaceAction? = target.baseAction
+        val dismissIntent =
+            baseAction?.extras?.getParcelable(EXTRAS_SMARTSPACE_DISMISS_INTENT_KEY) as Intent?
 
         val isActive =
             when {
                 !mediaFlags.isPersistentSsCardEnabled() -> true
-                target.baseAction == null -> true
-                else ->
-                    target.baseAction.extras.getString(EXTRA_KEY_TRIGGER_SOURCE) !=
-                        EXTRA_VALUE_TRIGGER_PERIODIC
+                baseAction == null -> true
+                else -> {
+                    val triggerSource = baseAction.extras?.getString(EXTRA_KEY_TRIGGER_SOURCE)
+                    triggerSource != EXTRA_VALUE_TRIGGER_PERIODIC
+                }
             }
 
         packageName(target)?.let {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
index d6f941d..6a8ffb7 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaSessionBasedFilter.kt
@@ -65,7 +65,7 @@
 
     private val sessionListener =
         object : MediaSessionManager.OnActiveSessionsChangedListener {
-            override fun onActiveSessionsChanged(controllers: List<MediaController>) {
+            override fun onActiveSessionsChanged(controllers: List<MediaController>?) {
                 handleControllersChanged(controllers)
             }
         }
@@ -190,16 +190,18 @@
         }
     }
 
-    private fun handleControllersChanged(controllers: List<MediaController>) {
+    private fun handleControllersChanged(controllers: List<MediaController>?) {
         packageControllers.clear()
-        controllers.forEach { controller ->
+        controllers?.forEach { controller ->
             packageControllers.get(controller.packageName)?.let { tokens -> tokens.add(controller) }
                 ?: run {
                     val tokens = mutableListOf(controller)
                     packageControllers.put(controller.packageName, tokens)
                 }
         }
-        tokensWithNotifications.retainAll(controllers.map { TokenId(it.sessionToken) })
+        controllers?.map { TokenId(it.sessionToken) }?.let {
+            tokensWithNotifications.retainAll(it)
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
index b46ebb2..b9cc772 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/IlluminationDrawable.kt
@@ -195,7 +195,7 @@
                 }
                 addListener(
                     object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
+                        override fun onAnimationEnd(animation: Animator) {
                             backgroundAnimation = null
                         }
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
index 937a618..646d1d0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/LightSourceDrawable.kt
@@ -98,11 +98,11 @@
                         addListener(
                             object : AnimatorListenerAdapter() {
                                 var cancelled = false
-                                override fun onAnimationCancel(animation: Animator?) {
+                                override fun onAnimationCancel(animation: Animator) {
                                     cancelled = true
                                 }
 
-                                override fun onAnimationEnd(animation: Animator?) {
+                                override fun onAnimationEnd(animation: Animator) {
                                     if (cancelled) {
                                         return
                                     }
@@ -226,7 +226,7 @@
                 )
                 addListener(
                     object : AnimatorListenerAdapter() {
-                        override fun onAnimationEnd(animation: Animator?) {
+                        override fun onAnimationEnd(animation: Animator) {
                             rippleData.progress = 0f
                             rippleAnimation = null
                             invalidateSelf()
@@ -270,11 +270,8 @@
         return bounds
     }
 
-    override fun onStateChange(stateSet: IntArray?): Boolean {
+    override fun onStateChange(stateSet: IntArray): Boolean {
         val changed = super.onStateChange(stateSet)
-        if (stateSet == null) {
-            return changed
-        }
 
         val wasPressed = pressed
         var enabled = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
index 1ace316..ce50a11 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaCarouselScrollHandler.kt
@@ -127,19 +127,19 @@
         object : GestureDetector.SimpleOnGestureListener() {
             override fun onFling(
                 eStart: MotionEvent?,
-                eCurrent: MotionEvent?,
+                eCurrent: MotionEvent,
                 vX: Float,
                 vY: Float
             ) = onFling(vX, vY)
 
             override fun onScroll(
                 down: MotionEvent?,
-                lastMotion: MotionEvent?,
+                lastMotion: MotionEvent,
                 distanceX: Float,
                 distanceY: Float
-            ) = onScroll(down!!, lastMotion!!, distanceX)
+            ) = onScroll(down!!, lastMotion, distanceX)
 
-            override fun onDown(e: MotionEvent?): Boolean {
+            override fun onDown(e: MotionEvent): Boolean {
                 if (falsingProtectionNeeded) {
                     falsingCollector.onNotificationStartDismissing()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index fe8ebaf..c1c757e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -180,20 +180,20 @@
                 object : AnimatorListenerAdapter() {
                     private var cancelled: Boolean = false
 
-                    override fun onAnimationCancel(animation: Animator?) {
+                    override fun onAnimationCancel(animation: Animator) {
                         cancelled = true
                         animationPending = false
                         rootView?.removeCallbacks(startAnimation)
                     }
 
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         isCrossFadeAnimatorRunning = false
                         if (!cancelled) {
                             applyTargetStateIfNotAnimating()
                         }
                     }
 
-                    override fun onAnimationStart(animation: Animator?) {
+                    override fun onAnimationStart(animation: Animator) {
                         cancelled = false
                         animationPending = false
                     }
@@ -606,7 +606,7 @@
         val viewHost = UniqueObjectHostView(context)
         viewHost.addOnAttachStateChangeListener(
             object : View.OnAttachStateChangeListener {
-                override fun onViewAttachedToWindow(p0: View?) {
+                override fun onViewAttachedToWindow(p0: View) {
                     if (rootOverlay == null) {
                         rootView = viewHost.viewRootImpl.view
                         rootOverlay = (rootView!!.overlay as ViewGroupOverlay)
@@ -614,7 +614,7 @@
                     viewHost.removeOnAttachStateChangeListener(this)
                 }
 
-                override fun onViewDetachedFromWindow(p0: View?) {}
+                override fun onViewDetachedFromWindow(p0: View) {}
             }
         )
         return viewHost
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
index be570b4..631a0b8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHost.kt
@@ -144,12 +144,12 @@
         setListeningToMediaData(true)
         hostView.addOnAttachStateChangeListener(
             object : OnAttachStateChangeListener {
-                override fun onViewAttachedToWindow(v: View?) {
+                override fun onViewAttachedToWindow(v: View) {
                     setListeningToMediaData(true)
                     updateViewVisibility()
                 }
 
-                override fun onViewDetachedFromWindow(v: View?) {
+                override fun onViewDetachedFromWindow(v: View) {
                     setListeningToMediaData(false)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
index 583c626..16dfc21 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/SquigglyProgress.kt
@@ -117,7 +117,7 @@
                     }
                     addListener(
                         object : AnimatorListenerAdapter() {
-                            override fun onAnimationEnd(animation: Animator?) {
+                            override fun onAnimationEnd(animation: Animator) {
                                 heightAnimator = null
                             }
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
index b88eba9..a3d1d8c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseAdapter.java
@@ -157,6 +157,7 @@
         private String mDeviceId;
         private ValueAnimator mCornerAnimator;
         private ValueAnimator mVolumeAnimator;
+        private int mLatestUpdateVolume = -1;
 
         MediaDeviceBaseViewHolder(View view) {
             super(view);
@@ -314,7 +315,11 @@
             mSeekBar.setMaxVolume(device.getMaxVolume());
             final int currentVolume = device.getCurrentVolume();
             if (!mIsDragging) {
-                if (mSeekBar.getVolume() != currentVolume) {
+                if (mSeekBar.getVolume() != currentVolume && (mLatestUpdateVolume == -1
+                        || currentVolume == mLatestUpdateVolume)) {
+                    // Update only if volume of device and value of volume bar doesn't match.
+                    // Check if response volume match with the latest request, to ignore obsolete
+                    // response
                     if (isCurrentSeekbarInvisible && !mIsInitVolumeFirstTime) {
                         updateTitleIcon(currentVolume == 0 ? R.drawable.media_output_icon_volume_off
                                         : R.drawable.media_output_icon_volume,
@@ -330,12 +335,16 @@
                                 updateUnmutedVolumeIcon();
                             }
                             mSeekBar.setVolume(currentVolume);
+                            mLatestUpdateVolume = -1;
                         }
                     }
                 } else if (currentVolume == 0) {
                     mSeekBar.resetVolume();
                     updateMutedVolumeIcon();
                 }
+                if (currentVolume == mLatestUpdateVolume) {
+                    mLatestUpdateVolume = -1;
+                }
             }
             if (mIsInitVolumeFirstTime) {
                 mIsInitVolumeFirstTime = false;
@@ -360,6 +369,7 @@
                         mStartFromMute = false;
                     }
                     if (progressToVolume != deviceVolume) {
+                        mLatestUpdateVolume = progressToVolume;
                         mController.adjustVolume(device, progressToVolume);
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index 7712690..3a1d8b0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -296,7 +296,10 @@
 
     @Override
     public void stop() {
-        if (isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
+        // unregister broadcast callback should only depend on profile and registered flag
+        // rather than remote device or broadcast state
+        // otherwise it might have risks of leaking registered callback handle
+        if (mMediaOutputController.isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) {
             mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback);
             mIsLeBroadcastCallbackRegistered = false;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index f3865f5..e5a6bb5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -104,11 +104,16 @@
     @Override
     public boolean isBroadcastSupported() {
         boolean isBluetoothLeDevice = false;
+        boolean isBroadcastEnabled = false;
         if (FeatureFlagUtils.isEnabled(mContext,
                 FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST)) {
             if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
                 isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
                     mMediaOutputController.getCurrentConnectedMediaDevice());
+                // if broadcast is active, broadcast should be considered as supported
+                // there could be a valid case that broadcast is ongoing
+                // without active LEA device connected
+                isBroadcastEnabled = mMediaOutputController.isBluetoothLeBroadcastEnabled();
             }
         } else {
             // To decouple LE Audio Broadcast and Unicast, it always displays the button when there
@@ -116,7 +121,8 @@
             isBluetoothLeDevice = true;
         }
 
-        return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice;
+        return mMediaOutputController.isBroadcastSupported()
+                && (isBluetoothLeDevice || isBroadcastEnabled);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index bbd3d33..da8e106 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -201,13 +201,13 @@
     }
 
     override fun updateView(newInfo: ChipReceiverInfo, currentView: ViewGroup) {
-        val packageName = newInfo.routeInfo.clientPackageName
+        val packageName: String? = newInfo.routeInfo.clientPackageName
         var iconInfo = MediaTttUtils.getIconInfoFromPackageName(
             context,
             packageName,
             isReceiver = true,
         ) {
-            logger.logPackageNotFound(packageName)
+            packageName?.let { logger.logPackageNotFound(it) }
         }
 
         if (newInfo.appNameOverride != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
index 5013802..fbf7e25 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttReceiverRippleController.kt
@@ -68,9 +68,9 @@
         )
         rippleView.addOnAttachStateChangeListener(
             object : View.OnAttachStateChangeListener {
-                override fun onViewDetachedFromWindow(view: View?) {}
+                override fun onViewDetachedFromWindow(view: View) {}
 
-                override fun onViewAttachedToWindow(view: View?) {
+                override fun onViewAttachedToWindow(view: View) {
                     if (view == null) {
                         return
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 0b0535d..35018f1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -54,7 +54,7 @@
         // Reset all listeners to animator.
         animator.removeAllListeners()
         animator.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator?) {
+            override fun onAnimationEnd(animation: Animator) {
                 onAnimationEnd?.run()
                 isStarted = false
             }
@@ -86,7 +86,7 @@
             invalidate()
         }
         animator.addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationEnd(animation: Animator?) {
+            override fun onAnimationEnd(animation: Animator) {
                 animation?.let { visibility = GONE }
                 onAnimationEnd?.run()
                 isStarted = false
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index f75f8b9..87d0098 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -162,7 +162,7 @@
         logger: MediaTttSenderLogger,
         instanceId: InstanceId,
     ): ChipbarInfo {
-        val packageName = routeInfo.clientPackageName
+        val packageName = checkNotNull(routeInfo.clientPackageName)
         val otherDeviceName =
             if (routeInfo.name.isBlank()) {
                 context.getString(R.string.media_ttt_default_device_type)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index c816446..64de9bd 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -88,7 +88,7 @@
                 .inflate(R.layout.media_projection_recent_tasks, parent, /* attachToRoot= */ false)
                 as ViewGroup
 
-        val container = recentsRoot.findViewById<View>(R.id.media_projection_recent_tasks_container)
+        val container = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_container)
         container.setTaskHeightSize()
 
         val progress = recentsRoot.requireViewById<View>(R.id.media_projection_recent_tasks_loader)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
index 38d4e69..6480a47 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepository.kt
@@ -81,8 +81,8 @@
             return MediaProjectionState.EntireScreen
         }
         val matchingTask =
-            tasksRepository.findRunningTaskFromWindowContainerToken(session.tokenToRecord)
-                ?: return MediaProjectionState.EntireScreen
+            tasksRepository.findRunningTaskFromWindowContainerToken(
+                checkNotNull(session.tokenToRecord)) ?: return MediaProjectionState.EntireScreen
         return MediaProjectionState.SingleTask(matchingTask)
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
index 4d30634..63d4634 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskRoleManagerExt.kt
@@ -77,8 +77,13 @@
             .build()
     }
 
-    private fun PackageManager.getApplicationLabel(packageName: String?): String? =
-        runCatching { getApplicationInfo(packageName, /* flags= */ 0)!! }
+    private fun PackageManager.getApplicationLabel(packageName: String?): String? {
+        if (packageName == null) {
+            return null
+        }
+
+        return runCatching { getApplicationInfo(packageName, /* flags= */ 0)!! }
             .getOrNull()
             ?.let { info -> getApplicationLabel(info).toString() }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
deleted file mode 100644
index d1d3e3d..0000000
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2020 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.people;
-
-import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
-import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.ViewGroup;
-
-import androidx.activity.ComponentActivity;
-import androidx.lifecycle.ViewModelProvider;
-
-import com.android.systemui.compose.ComposeFacade;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.people.ui.view.PeopleViewBinder;
-import com.android.systemui.people.ui.viewmodel.PeopleViewModel;
-
-import javax.inject.Inject;
-
-import kotlin.Unit;
-import kotlin.jvm.functions.Function1;
-
-/** People Tile Widget configuration activity that shows the user their conversation tiles. */
-public class PeopleSpaceActivity extends ComponentActivity {
-
-    private static final String TAG = "PeopleSpaceActivity";
-    private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
-
-    private final PeopleViewModel.Factory mViewModelFactory;
-    private final FeatureFlags mFeatureFlags;
-
-    @Inject
-    public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory,
-            FeatureFlags featureFlags) {
-        super();
-        mViewModelFactory = viewModelFactory;
-        mFeatureFlags = featureFlags;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setResult(RESULT_CANCELED);
-
-        PeopleViewModel viewModel = new ViewModelProvider(this, mViewModelFactory).get(
-                PeopleViewModel.class);
-
-        // Update the widget ID coming from the intent.
-        int widgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID);
-        viewModel.onWidgetIdChanged(widgetId);
-
-        Function1<PeopleViewModel.Result, Unit> onResult = (result) -> {
-            finishActivity(result);
-            return null;
-        };
-
-        if (mFeatureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE)
-                && ComposeFacade.INSTANCE.isComposeAvailable()) {
-            Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity");
-            ComposeFacade.INSTANCE.setPeopleSpaceActivityContent(this, viewModel, onResult);
-        } else {
-            Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity");
-            ViewGroup view = PeopleViewBinder.create(this);
-            PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this, onResult);
-            setContentView(view);
-        }
-    }
-
-    private void finishActivity(PeopleViewModel.Result result) {
-        if (result instanceof PeopleViewModel.Result.Success) {
-            if (DEBUG) Log.d(TAG, "Widget added!");
-            Intent data = ((PeopleViewModel.Result.Success) result).getData();
-            setResult(RESULT_OK, data);
-        } else {
-            if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!");
-            setResult(RESULT_CANCELED);
-        }
-        finish();
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
new file mode 100644
index 0000000..5b7eb45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
@@ -0,0 +1,99 @@
+/*
+ * 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.people
+
+import android.appwidget.AppWidgetManager
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.ComponentActivity
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.compose.ComposeFacade.isComposeAvailable
+import com.android.systemui.compose.ComposeFacade.setPeopleSpaceActivityContent
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.people.ui.view.PeopleViewBinder
+import com.android.systemui.people.ui.view.PeopleViewBinder.bind
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.launch
+
+/** People Tile Widget configuration activity that shows the user their conversation tiles. */
+class PeopleSpaceActivity
+@Inject
+constructor(
+    private val viewModelFactory: PeopleViewModel.Factory,
+    private val featureFlags: FeatureFlags,
+) : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setResult(RESULT_CANCELED)
+
+        // Update the widget ID coming from the intent.
+        val viewModel = ViewModelProvider(this, viewModelFactory)[PeopleViewModel::class.java]
+        val widgetId =
+            intent.getIntExtra(
+                AppWidgetManager.EXTRA_APPWIDGET_ID,
+                AppWidgetManager.INVALID_APPWIDGET_ID,
+            )
+        viewModel.onWidgetIdChanged(widgetId)
+
+        // Make sure to refresh the tiles/conversations when the lifecycle is resumed, so that it
+        // updates them when going back to the Activity after leaving it.
+        // Note that we do this here instead of inside an effect in the PeopleScreen() composable
+        // because otherwise onTileRefreshRequested() will be called after the first composition,
+        // which will trigger a new recomposition and redraw, affecting the GPU memory (see
+        // b/276871425).
+        lifecycleScope.launch {
+            repeatOnLifecycle(Lifecycle.State.RESUMED) { viewModel.onTileRefreshRequested() }
+        }
+
+        // Set the content of the activity, using either the View or Compose implementation.
+        if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE) && isComposeAvailable()) {
+            Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity")
+            setPeopleSpaceActivityContent(
+                activity = this,
+                viewModel,
+                onResult = { finishActivity(it) },
+            )
+        } else {
+            Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity")
+            val view = PeopleViewBinder.create(this)
+            bind(view, viewModel, lifecycleOwner = this, onResult = { finishActivity(it) })
+            setContentView(view)
+        }
+    }
+
+    private fun finishActivity(result: PeopleViewModel.Result) {
+        if (result is PeopleViewModel.Result.Success) {
+            if (DEBUG) Log.d(TAG, "Widget added!")
+            setResult(RESULT_OK, result.data)
+        } else {
+            if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!")
+            setResult(RESULT_CANCELED)
+        }
+
+        finish()
+    }
+
+    companion object {
+        private const val TAG = "PeopleSpaceActivity"
+        private const val DEBUG = PeopleSpaceUtils.DEBUG
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
index d8a429e..46c8d35 100644
--- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
@@ -109,14 +109,6 @@
                     }
             }
         }
-
-        // Make sure to refresh the tiles/conversations when the Activity is resumed, so that it
-        // updates them when going back to the Activity after leaving it.
-        lifecycleOwner.lifecycleScope.launch {
-            lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
-                viewModel.onTileRefreshRequested()
-            }
-        }
     }
 
     private fun setNoConversationsContent(view: ViewGroup, onGotItClicked: () -> Unit) {
@@ -140,13 +132,13 @@
             LayoutInflater.from(context)
                 .inflate(R.layout.people_space_activity_no_conversations, /* root= */ view)
 
-        noConversationsView.findViewById<View>(R.id.got_it_button).setOnClickListener {
+        noConversationsView.requireViewById<View>(R.id.got_it_button).setOnClickListener {
             onGotItClicked()
         }
 
         // The Tile preview has colorBackground as its background. Change it so it's different than
         // the activity's background.
-        val item = noConversationsView.findViewById<LinearLayout>(android.R.id.background)
+        val item = noConversationsView.requireViewById<LinearLayout>(android.R.id.background)
         val shape = item.background as GradientDrawable
         val ta =
             context.theme.obtainStyledAttributes(
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt b/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt
index 88b8676..fedbdec 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt
@@ -53,11 +53,14 @@
 
     @VisibleForTesting
     companion object {
-        val OPS_MIC_CAMERA = intArrayOf(AppOpsManager.OP_CAMERA,
-                AppOpsManager.OP_PHONE_CALL_CAMERA, AppOpsManager.OP_RECORD_AUDIO,
+        val OPS_MIC_CAMERA = intArrayOf(
+                AppOpsManager.OP_CAMERA,
+                AppOpsManager.OP_PHONE_CALL_CAMERA,
+                AppOpsManager.OP_RECORD_AUDIO,
                 AppOpsManager.OP_PHONE_CALL_MICROPHONE,
                 AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
-                AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO)
+                AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
+                AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO)
         val OPS_LOCATION = intArrayOf(
                 AppOpsManager.OP_COARSE_LOCATION,
                 AppOpsManager.OP_FINE_LOCATION)
@@ -212,6 +215,7 @@
             AppOpsManager.OP_PHONE_CALL_MICROPHONE,
             AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO,
             AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
+            AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
             AppOpsManager.OP_RECORD_AUDIO -> PrivacyType.TYPE_MICROPHONE
             else -> return null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
index f4aa27d..c202f14 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
@@ -192,7 +192,7 @@
             return null
         }
         val closeAppButton =
-            window.layoutInflater.inflate(
+            checkNotNull(window).layoutInflater.inflate(
                 R.layout.privacy_dialog_card_button,
                 expandedLayout,
                 false
@@ -248,7 +248,7 @@
 
     private fun configureManageButton(element: PrivacyElement, expandedLayout: ViewGroup): View {
         val manageButton =
-            window.layoutInflater.inflate(
+            checkNotNull(window).layoutInflater.inflate(
                 R.layout.privacy_dialog_card_button,
                 expandedLayout,
                 false
diff --git a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
index fa3f878f..2d460a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
+++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java
@@ -120,6 +120,7 @@
         mUserTracker = userTracker;
         mConfigEnableLockScreenButton = mContext.getResources().getBoolean(
             android.R.bool.config_enableQrCodeScannerOnLockScreen);
+        mExecutor.execute(this::updateQRCodeScannerActivityDetails);
     }
 
     /**
@@ -158,18 +159,18 @@
      * Returns true if lock screen entry point for QR Code Scanner is to be enabled.
      */
     public boolean isEnabledForLockScreenButton() {
-        return mQRCodeScannerEnabled && isAbleToOpenCameraApp() && isAvailableOnDevice();
+        return mQRCodeScannerEnabled && isAbleToLaunchScannerActivity() && isAllowedOnLockScreen();
     }
 
-    /** Returns whether the feature is available on the device. */
-    public boolean isAvailableOnDevice() {
+    /** Returns whether the QR scanner button is allowed on lockscreen. */
+    public boolean isAllowedOnLockScreen() {
         return mConfigEnableLockScreenButton;
     }
 
     /**
-     * Returns true if the feature can open a camera app on the device.
+     * Returns true if the feature can open the configured QR scanner activity.
      */
-    public boolean isAbleToOpenCameraApp() {
+    public boolean isAbleToLaunchScannerActivity() {
         return mIntent != null && isActivityCallable(mIntent);
     }
 
@@ -355,9 +356,6 @@
 
         // Reset cached values to default as we are no longer listening
         mOnDefaultQRCodeScannerChangedListener = null;
-        mQRCodeScannerActivity = null;
-        mIntent = null;
-        mComponentName = null;
     }
 
     private void notifyQRCodeScannerActivityChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 7523d6e..ddd9463 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -7,6 +7,7 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Bundle;
@@ -549,10 +550,8 @@
         return mPages.get(0).mRecords.size();
     }
 
-    public void startTileReveal(Set<String> tileSpecs, final Runnable postAnimation) {
-        if (tileSpecs.isEmpty() || mPages.size() < 2 || getScrollX() != 0 || !beginFakeDrag()) {
-            // Do not start the reveal animation unless there are tiles to animate, multiple
-            // TileLayouts available and the user has not already started dragging.
+    public void startTileReveal(Set<String> tilesToReveal, final Runnable postAnimation) {
+        if (shouldNotRunAnimation(tilesToReveal)) {
             return;
         }
 
@@ -560,13 +559,13 @@
         final TileLayout lastPage = mPages.get(lastPageNumber);
         final ArrayList<Animator> bounceAnims = new ArrayList<>();
         for (TileRecord tr : lastPage.mRecords) {
-            if (tileSpecs.contains(tr.tile.getTileSpec())) {
+            if (tilesToReveal.contains(tr.tile.getTileSpec())) {
                 bounceAnims.add(setupBounceAnimator(tr.tileView, bounceAnims.size()));
             }
         }
 
         if (bounceAnims.isEmpty()) {
-            // All tileSpecs are on the first page. Nothing to do.
+            // All tilesToReveal are on the first page. Nothing to do.
             // TODO: potentially show a bounce animation for first page QS tiles
             endFakeDrag();
             return;
@@ -588,6 +587,16 @@
         postInvalidateOnAnimation();
     }
 
+    private boolean shouldNotRunAnimation(Set<String> tilesToReveal) {
+        boolean noAnimationNeeded = tilesToReveal.isEmpty() || mPages.size() < 2;
+        boolean scrollingInProgress = getScrollX() != 0 || !beginFakeDrag();
+        // isRunningInTestHarness() to disable animation in functional testing as it caused
+        // flakiness and is not needed there. Alternative solutions were more complex and would
+        // still be either potentially flaky or modify internal data.
+        // For more info see b/253493927 and b/293234595
+        return noAnimationNeeded || scrollingInProgress || ActivityManager.isRunningInTestHarness();
+    }
+
     private int sanitizePageAction(int action) {
         int pageLeftId = AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_LEFT.getId();
         int pageRightId = AccessibilityNodeInfo.AccessibilityAction.ACTION_PAGE_RIGHT.getId();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 1afc885..d2eac45 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -23,12 +23,14 @@
 import android.graphics.Path;
 import android.graphics.PointF;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.widget.FrameLayout;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.qs.customize.QSCustomizer;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.util.LargeScreenUtils;
 
 import java.io.PrintWriter;
@@ -129,6 +131,11 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch("QS", ev, super.dispatchTouchEvent(ev));
+    }
+
+    @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
         updateExpansion();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING b/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING
index 2d45c5b2..86ef7ef 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING
+++ b/packages/SystemUI/src/com/android/systemui/qs/TEST_MAPPING
@@ -10,6 +10,17 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "QuickSettingsDeviceResetTests",
+      "options": [
+          {
+            "exclude-annotation": "org.junit.Ignore"
+          },
+          {
+            "exclude-annotation": "androidx.test.filters.FlakyTest"
+          }
+      ]
     }
   ]
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index a162d11..18f59b1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -20,6 +20,7 @@
 import android.content.res.Resources
 import android.database.ContentObserver
 import android.provider.Settings
+import android.util.SparseArray
 import com.android.systemui.R
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -107,6 +108,7 @@
 ) : TileSpecRepository {
 
     private val mutex = Mutex()
+    private val tileSpecsPerUser = SparseArray<List<TileSpec>>()
 
     private val retailModeTiles by lazy {
         resources
@@ -142,10 +144,12 @@
                 awaitClose { secureSettings.unregisterContentObserver(observer) }
             }
             .onStart { emit(Unit) }
-            .map { secureSettings.getStringForUser(SETTING, userId) ?: "" }
-            .distinctUntilChanged()
+            .map { loadTiles(userId) }
             .onEach { logger.logTilesChangedInSettings(it, userId) }
-            .map { parseTileSpecs(it, userId) }
+            .distinctUntilChanged()
+            .map { parseTileSpecs(it, userId).also { storeTiles(userId, it) } }
+            .distinctUntilChanged()
+            .onEach { mutex.withLock { tileSpecsPerUser.put(userId, it) } }
             .flowOn(backgroundDispatcher)
     }
 
@@ -154,7 +158,7 @@
             if (tile == TileSpec.Invalid) {
                 return
             }
-            val tilesList = loadTiles(userId).toMutableList()
+            val tilesList = tileSpecsPerUser.get(userId, emptyList()).toMutableList()
             if (tile !in tilesList) {
                 if (position < 0 || position >= tilesList.size) {
                     tilesList.add(tile)
@@ -162,6 +166,7 @@
                     tilesList.add(position, tile)
                 }
                 storeTiles(userId, tilesList)
+                tileSpecsPerUser.put(userId, tilesList)
             }
         }
 
@@ -170,9 +175,10 @@
             if (tiles.all { it == TileSpec.Invalid }) {
                 return
             }
-            val tilesList = loadTiles(userId).toMutableList()
+            val tilesList = tileSpecsPerUser.get(userId, emptyList()).toMutableList()
             if (tilesList.removeAll(tiles)) {
                 storeTiles(userId, tilesList.toList())
+                tileSpecsPerUser.put(userId, tilesList)
             }
         }
 
@@ -181,18 +187,10 @@
             val filtered = tiles.filter { it != TileSpec.Invalid }
             if (filtered.isNotEmpty()) {
                 storeTiles(userId, filtered)
+                tileSpecsPerUser.put(userId, tiles)
             }
         }
 
-    private suspend fun loadTiles(@UserIdInt forUser: Int): List<TileSpec> {
-        return withContext(backgroundDispatcher) {
-            (secureSettings.getStringForUser(SETTING, forUser) ?: "")
-                .split(DELIMITER)
-                .map(TileSpec::create)
-                .filter { it !is TileSpec.Invalid }
-        }
-    }
-
     private suspend fun storeTiles(@UserIdInt forUser: Int, tiles: List<TileSpec>) {
         if (retailModeRepository.inRetailMode) {
             // No storing tiles when in retail mode
@@ -214,6 +212,12 @@
         }
     }
 
+    private suspend fun loadTiles(userId: Int): String {
+        return withContext(backgroundDispatcher) {
+            secureSettings.getStringForUser(SETTING, userId) ?: ""
+        }
+    }
+
     private fun parseTileSpecs(tilesFromSettings: String, user: Int): List<TileSpec> {
         val fromSettings =
             tilesFromSettings.split(DELIMITER).map(TileSpec::create).filter {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index ff881f7..966f370 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -56,6 +56,8 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
@@ -273,7 +275,9 @@
     }
 
     override fun addTile(spec: TileSpec, position: Int) {
-        scope.launch {
+        scope.launch(backgroundDispatcher) {
+            // Block until the list is not empty
+            currentTiles.filter { it.isNotEmpty() }.first()
             tileSpecRepository.addTile(userRepository.getSelectedUserInfo().id, spec, position)
         }
     }
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 9e365d3..1ba377b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java
@@ -120,7 +120,7 @@
         state.label = mContext.getString(R.string.qr_code_scanner_title);
         state.contentDescription = state.label;
         state.icon = ResourceIcon.get(R.drawable.ic_qr_code_scanner);
-        state.state = mQRCodeScannerController.isAbleToOpenCameraApp() ? Tile.STATE_INACTIVE
+        state.state = mQRCodeScannerController.isAbleToLaunchScannerActivity() ? Tile.STATE_INACTIVE
                 : Tile.STATE_UNAVAILABLE;
         // The assumption is that if the OEM has the QR code scanner module enabled then the scanner
         // would go to "Unavailable" state only when GMS core is updating.
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 8f9cf4b..21da596 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java
@@ -142,6 +142,9 @@
         state.contentDescription = state.label;
         state.expandedAccessibilityClassName = Switch.class.getName();
         state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+        state.secondaryLabel = state.value
+                ? ""
+                : mContext.getString(R.string.quick_settings_work_mode_paused_state);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
index 5e6a44b..4c6281e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModel.kt
@@ -16,19 +16,17 @@
 
 package com.android.systemui.qs.ui.viewmodel
 
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import javax.inject.Inject
 
 /** Models UI state and handles user input for the quick settings scene. */
 @SysUISingleton
 class QuickSettingsSceneViewModel
 @Inject
-constructor(
-    private val lockscreenSceneInteractor: LockscreenSceneInteractor,
-) {
+constructor(private val bouncerInteractor: BouncerInteractor) {
     /** Notifies that some content in quick settings was clicked. */
     fun onContentClicked() {
-        lockscreenSceneInteractor.dismissLockscreen()
+        bouncerInteractor.showOrUnlockDevice()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
index fee3960..350fa38 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/data/repository/SceneContainerRepository.kt
@@ -18,50 +18,49 @@
 
 package com.android.systemui.scene.data.repository
 
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneContainerConfig
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.scene.shared.model.SceneTransitionModel
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.stateIn
 
 /** Source of truth for scene framework application state. */
 class SceneContainerRepository
 @Inject
 constructor(
+    @Application applicationScope: CoroutineScope,
     private val config: SceneContainerConfig,
 ) {
+    private val _desiredScene = MutableStateFlow(SceneModel(config.initialSceneKey))
+    val desiredScene: StateFlow<SceneModel> = _desiredScene.asStateFlow()
 
     private val _isVisible = MutableStateFlow(true)
     val isVisible: StateFlow<Boolean> = _isVisible.asStateFlow()
 
-    private val _currentScene = MutableStateFlow(SceneModel(config.initialSceneKey))
-    val currentScene: StateFlow<SceneModel> = _currentScene.asStateFlow()
-
-    private val transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
-    val transitionProgress: Flow<Float> =
-        transitionState.flatMapLatest { observableTransitionStateFlow ->
-            observableTransitionStateFlow?.flatMapLatest { observableTransitionState ->
-                when (observableTransitionState) {
-                    is ObservableTransitionState.Idle -> flowOf(1f)
-                    is ObservableTransitionState.Transition -> observableTransitionState.progress
-                }
-            }
-                ?: flowOf(1f)
-        }
-
-    private val _transitions = MutableStateFlow<SceneTransitionModel?>(null)
-    val transitions: StateFlow<SceneTransitionModel?> = _transitions.asStateFlow()
+    private val defaultTransitionState = ObservableTransitionState.Idle(config.initialSceneKey)
+    private val _transitionState = MutableStateFlow<Flow<ObservableTransitionState>?>(null)
+    val transitionState: StateFlow<ObservableTransitionState> =
+        _transitionState
+            .flatMapLatest { innerFlowOrNull -> innerFlowOrNull ?: flowOf(defaultTransitionState) }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = defaultTransitionState,
+            )
 
     /**
-     * Returns the keys to all scenes in the container with the given name.
+     * Returns the keys to all scenes in the container.
      *
      * The scenes will be sorted in z-order such that the last one is the one that should be
      * rendered on top of all previous ones.
@@ -70,40 +69,19 @@
         return config.sceneKeys
     }
 
-    /** Sets the current scene in the container with the given name. */
-    fun setCurrentScene(scene: SceneModel) {
+    fun setDesiredScene(scene: SceneModel) {
         check(allSceneKeys().contains(scene.key)) {
             """
-                Cannot set current scene key to "${scene.key}". The configuration does not contain a
-                scene with that key.
+                Cannot set the desired scene key to "${scene.key}". The configuration does not
+                contain a scene with that key.
             """
                 .trimIndent()
         }
 
-        _currentScene.value = scene
+        _desiredScene.value = scene
     }
 
-    /** Sets the scene transition in the container with the given name. */
-    fun setSceneTransition(from: SceneKey, to: SceneKey) {
-        check(allSceneKeys().contains(from)) {
-            """
-                Cannot set current scene key to "$from". The configuration does not contain a scene
-                with that key.
-            """
-                .trimIndent()
-        }
-        check(allSceneKeys().contains(to)) {
-            """
-                Cannot set current scene key to "$to". The configuration does not contain a scene
-                with that key.
-            """
-                .trimIndent()
-        }
-
-        _transitions.value = SceneTransitionModel(from = from, to = to)
-    }
-
-    /** Sets whether the container with the given name is visible. */
+    /** Sets whether the container is visible. */
     fun setVisible(isVisible: Boolean) {
         _isVisible.value = isVisible
     }
@@ -114,6 +92,6 @@
      * Note that you must call is with `null` when the UI is done or risk a memory leak.
      */
     fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
-        this.transitionState.value = transitionState
+        _transitionState.value = transitionState
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
index 64715bc..76d9b03 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt
@@ -17,18 +17,22 @@
 package com.android.systemui.scene.domain.interactor
 
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.scene.data.repository.SceneContainerRepository
 import com.android.systemui.scene.shared.logger.SceneLogger
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.RemoteUserInput
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.scene.shared.model.SceneTransitionModel
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 /**
  * Generic business logic and app state accessors for the scene framework.
@@ -41,12 +45,76 @@
 class SceneInteractor
 @Inject
 constructor(
+    @Application applicationScope: CoroutineScope,
     private val repository: SceneContainerRepository,
     private val logger: SceneLogger,
 ) {
 
     /**
-     * Returns the keys of all scenes in the container with the given name.
+     * The currently *desired* scene.
+     *
+     * **Important:** this value will _commonly be different_ from what is being rendered in the UI,
+     * by design.
+     *
+     * There are two intended sources for this value:
+     * 1. Programmatic requests to transition to another scene (calls to [changeScene]).
+     * 2. Reports from the UI about completing a transition to another scene (calls to
+     *    [onSceneChanged]).
+     *
+     * Both the sources above cause the value of this flow to change; however, they cause mismatches
+     * in different ways.
+     *
+     * **Updates from programmatic transitions**
+     *
+     * When an external bit of code asks the framework to switch to another scene, the value here
+     * will update immediately. Downstream, the UI will detect this change and initiate the
+     * transition animation. As the transition animation progresses, a threshold will be reached, at
+     * which point the UI and the state here will match each other.
+     *
+     * **Updates from the UI**
+     *
+     * When the user interacts with the UI, the UI runs a transition animation that tracks the user
+     * pointer (for example, the user's finger). During this time, the state value here and what the
+     * UI shows will likely not match. Once/if a threshold is met, the UI reports it and commits the
+     * change, making the value here match the UI again.
+     */
+    val desiredScene: StateFlow<SceneModel> = repository.desiredScene
+
+    /**
+     * The current state of the transition.
+     *
+     * Consumers should use this state to know:
+     * 1. Whether there is an ongoing transition or if the system is at rest.
+     * 2. When transitioning, which scenes are being transitioned between.
+     * 3. When transitioning, what the progress of the transition is.
+     */
+    val transitionState: StateFlow<ObservableTransitionState> = repository.transitionState
+
+    /**
+     * The key of the scene that the UI is currently transitioning to or `null` if there is no
+     * active transition at the moment.
+     *
+     * This is a convenience wrapper around [transitionState], meant for flow-challenged consumers
+     * like Java code.
+     */
+    val transitioningTo: StateFlow<SceneKey?> =
+        transitionState
+            .map { state -> (state as? ObservableTransitionState.Transition)?.toScene }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = null,
+            )
+
+    /** Whether the scene container is visible. */
+    val isVisible: StateFlow<Boolean> = repository.isVisible
+
+    private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null)
+    /** A flow of motion events originating from outside of the scene framework. */
+    val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow()
+
+    /**
+     * Returns the keys of all scenes in the container.
      *
      * The scenes will be sorted in z-order such that the last one is the one that should be
      * rendered on top of all previous ones.
@@ -55,26 +123,20 @@
         return repository.allSceneKeys()
     }
 
-    /** Sets the scene in the container with the given name. */
-    fun setCurrentScene(scene: SceneModel, loggingReason: String) {
-        val currentSceneKey = repository.currentScene.value.key
-        if (currentSceneKey == scene.key) {
-            return
-        }
-
-        logger.logSceneChange(
-            from = currentSceneKey,
-            to = scene.key,
-            reason = loggingReason,
-        )
-        repository.setCurrentScene(scene)
-        repository.setSceneTransition(from = currentSceneKey, to = scene.key)
+    /**
+     * Requests a scene change to the given scene.
+     *
+     * The change is animated. Therefore, while the value in [desiredScene] will update immediately,
+     * it will be some time before the UI will switch to the desired scene. The scene change
+     * requested is remembered here but served by the UI layer, which will start a transition
+     * animation. Once enough of the transition has occurred, the system will come into agreement
+     * between the [desiredScene] and the UI.
+     */
+    fun changeScene(scene: SceneModel, loggingReason: String) {
+        updateDesiredScene(scene, loggingReason, logger::logSceneChangeRequested)
     }
 
-    /** The current scene in the container with the given name. */
-    val currentScene: StateFlow<SceneModel> = repository.currentScene
-
-    /** Sets the visibility of the container with the given name. */
+    /** Sets the visibility of the container. */
     fun setVisible(isVisible: Boolean, loggingReason: String) {
         val wasVisible = repository.isVisible.value
         if (wasVisible == isVisible) {
@@ -89,9 +151,6 @@
         return repository.setVisible(isVisible)
     }
 
-    /** Whether the container with the given name is visible. */
-    val isVisible: StateFlow<Boolean> = repository.isVisible
-
     /**
      * Binds the given flow so the system remembers it.
      *
@@ -101,23 +160,38 @@
         repository.setTransitionState(transitionState)
     }
 
-    /** Progress of the transition into the current scene in the container with the given name. */
-    val transitionProgress: Flow<Float> = repository.transitionProgress
-
-    /**
-     * Scene transitions as pairs of keys. A new value is emitted exactly once, each time a scene
-     * transition occurs. The flow begins with a `null` value at first, because the initial scene is
-     * not something that we transition to from another scene.
-     */
-    val transitions: StateFlow<SceneTransitionModel?> = repository.transitions
-
-    private val _remoteUserInput: MutableStateFlow<RemoteUserInput?> = MutableStateFlow(null)
-
-    /** A flow of motion events originating from outside of the scene framework. */
-    val remoteUserInput: StateFlow<RemoteUserInput?> = _remoteUserInput.asStateFlow()
-
     /** Handles a remote user input. */
     fun onRemoteUserInput(input: RemoteUserInput) {
         _remoteUserInput.value = input
     }
+
+    /**
+     * Notifies that the UI has transitioned sufficiently to the given scene.
+     *
+     * *Not intended for external use!*
+     *
+     * Once a transition between one scene and another passes a threshold, the UI invokes this
+     * method to report it, updating the value in [desiredScene] to match what the UI shows.
+     */
+    internal fun onSceneChanged(scene: SceneModel, loggingReason: String) {
+        updateDesiredScene(scene, loggingReason, logger::logSceneChangeCommitted)
+    }
+
+    private fun updateDesiredScene(
+        scene: SceneModel,
+        loggingReason: String,
+        log: (from: SceneKey, to: SceneKey, loggingReason: String) -> Unit,
+    ) {
+        val currentSceneKey = desiredScene.value.key
+        if (currentSceneKey == scene.key) {
+            return
+        }
+
+        log(
+            /* from= */ currentSceneKey,
+            /* to= */ scene.key,
+            /* loggingReason= */ loggingReason,
+        )
+        repository.setDesiredScene(scene)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
index 20ee393..1747099 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.CoreStartable
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.DisplayId
@@ -29,6 +30,7 @@
 import com.android.systemui.model.updateFlags
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.logger.SceneLogger
+import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING
@@ -39,8 +41,8 @@
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
 import kotlinx.coroutines.launch
 
 /**
@@ -72,14 +74,31 @@
         }
     }
 
-    /** Updates the visibility of the scene container based on the current scene. */
+    /** Updates the visibility of the scene container. */
     private fun hydrateVisibility() {
         applicationScope.launch {
-            sceneInteractor.currentScene
-                .map { it.key }
+            sceneInteractor.transitionState
+                .mapNotNull { state ->
+                    when (state) {
+                        is ObservableTransitionState.Idle -> {
+                            if (state.scene != SceneKey.Gone) {
+                                true to "scene is not Gone"
+                            } else {
+                                false to "scene is Gone"
+                            }
+                        }
+                        is ObservableTransitionState.Transition -> {
+                            if (state.fromScene == SceneKey.Gone) {
+                                true to "scene transitioning away from Gone"
+                            } else {
+                                null
+                            }
+                        }
+                    }
+                }
                 .distinctUntilChanged()
-                .collect { sceneKey ->
-                    sceneInteractor.setVisible(sceneKey != SceneKey.Gone, "scene is $sceneKey")
+                .collect { (isVisible, loggingReason) ->
+                    sceneInteractor.setVisible(isVisible, loggingReason)
                 }
         }
     }
@@ -88,43 +107,55 @@
     private fun automaticallySwitchScenes() {
         applicationScope.launch {
             authenticationInteractor.isUnlocked
-                .map { isUnlocked ->
-                    val currentSceneKey = sceneInteractor.currentScene.value.key
+                .mapNotNull { isUnlocked ->
+                    val renderedScenes =
+                        when (val transitionState = sceneInteractor.transitionState.value) {
+                            is ObservableTransitionState.Idle -> setOf(transitionState.scene)
+                            is ObservableTransitionState.Transition ->
+                                setOf(
+                                    transitionState.progress,
+                                    transitionState.toScene,
+                                )
+                        }
                     val isBypassEnabled = authenticationInteractor.isBypassEnabled()
                     when {
                         isUnlocked ->
-                            when (currentSceneKey) {
+                            when {
                                 // When the device becomes unlocked in Bouncer, go to Gone.
-                                is SceneKey.Bouncer ->
+                                renderedScenes.contains(SceneKey.Bouncer) ->
                                     SceneKey.Gone to "device unlocked in Bouncer scene"
+
                                 // When the device becomes unlocked in Lockscreen, go to Gone if
                                 // bypass is enabled.
-                                is SceneKey.Lockscreen ->
+                                renderedScenes.contains(SceneKey.Lockscreen) ->
                                     if (isBypassEnabled) {
                                         SceneKey.Gone to
                                             "device unlocked in Lockscreen scene with bypass"
                                     } else {
                                         null
                                     }
+
                                 // We got unlocked while on a scene that's not Lockscreen or
                                 // Bouncer, no need to change scenes.
                                 else -> null
                             }
+
                         // When the device becomes locked, to Lockscreen.
                         !isUnlocked ->
-                            when (currentSceneKey) {
+                            when {
                                 // Already on lockscreen or bouncer, no need to change scenes.
-                                is SceneKey.Lockscreen,
-                                is SceneKey.Bouncer -> null
+                                renderedScenes.contains(SceneKey.Lockscreen) ||
+                                    renderedScenes.contains(SceneKey.Bouncer) -> null
+
                                 // We got locked while on a scene that's not Lockscreen or Bouncer,
                                 // go to Lockscreen.
                                 else ->
-                                    SceneKey.Lockscreen to "device locked in $currentSceneKey scene"
+                                    SceneKey.Lockscreen to
+                                        "device locked in non-Lockscreen and non-Bouncer scene"
                             }
                         else -> null
                     }
                 }
-                .filterNotNull()
                 .collect { (targetSceneKey, loggingReason) ->
                     switchToScene(
                         targetSceneKey = targetSceneKey,
@@ -135,22 +166,39 @@
 
         applicationScope.launch {
             keyguardInteractor.wakefulnessModel
-                .map { it.state == WakefulnessState.ASLEEP }
+                .map { wakefulnessModel -> wakefulnessModel.state }
                 .distinctUntilChanged()
-                .collect { isAsleep ->
-                    if (isAsleep) {
-                        // When the device goes to sleep, reset the current scene.
-                        val isUnlocked = authenticationInteractor.isUnlocked.value
-                        val (targetSceneKey, loggingReason) =
-                            if (isUnlocked) {
-                                SceneKey.Gone to "device is asleep while unlocked"
-                            } else {
-                                SceneKey.Lockscreen to "device is asleep while locked"
+                .collect { wakefulnessState ->
+                    when (wakefulnessState) {
+                        WakefulnessState.STARTING_TO_SLEEP -> {
+                            switchToScene(
+                                targetSceneKey = SceneKey.Lockscreen,
+                                loggingReason = "device is starting to sleep",
+                            )
+                        }
+                        WakefulnessState.STARTING_TO_WAKE -> {
+                            val authMethod = authenticationInteractor.getAuthenticationMethod()
+                            val isUnlocked = authenticationInteractor.isUnlocked.value
+                            when {
+                                authMethod == AuthenticationMethodModel.None -> {
+                                    switchToScene(
+                                        targetSceneKey = SceneKey.Gone,
+                                        loggingReason =
+                                            "device is starting to wake up while auth method is" +
+                                                " none",
+                                    )
+                                }
+                                authMethod.isSecure && isUnlocked -> {
+                                    switchToScene(
+                                        targetSceneKey = SceneKey.Gone,
+                                        loggingReason =
+                                            "device is starting to wake up while unlocked with a" +
+                                                " secure auth method",
+                                    )
+                                }
                             }
-                        switchToScene(
-                            targetSceneKey = targetSceneKey,
-                            loggingReason = loggingReason,
-                        )
+                        }
+                        else -> Unit
                     }
                 }
         }
@@ -159,8 +207,9 @@
     /** Keeps [SysUiState] up-to-date */
     private fun hydrateSystemUiState() {
         applicationScope.launch {
-            sceneInteractor.currentScene
-                .map { it.key }
+            sceneInteractor.transitionState
+                .mapNotNull { it as? ObservableTransitionState.Idle }
+                .map { it.scene }
                 .distinctUntilChanged()
                 .collect { sceneKey ->
                     sysUiState.updateFlags(
@@ -177,7 +226,7 @@
     }
 
     private fun switchToScene(targetSceneKey: SceneKey, loggingReason: String) {
-        sceneInteractor.setCurrentScene(
+        sceneInteractor.changeScene(
             scene = SceneModel(targetSceneKey),
             loggingReason = loggingReason,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
index 0adbd5a..62136dc 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/logger/SceneLogger.kt
@@ -37,7 +37,7 @@
         )
     }
 
-    fun logSceneChange(
+    fun logSceneChangeRequested(
         from: SceneKey,
         to: SceneKey,
         reason: String,
@@ -50,7 +50,24 @@
                 str2 = to.toString()
                 str3 = reason
             },
-            messagePrinter = { "$str1 → $str2, reason: $str3" },
+            messagePrinter = { "Scene change requested: $str1 → $str2, reason: $str3" },
+        )
+    }
+
+    fun logSceneChangeCommitted(
+        from: SceneKey,
+        to: SceneKey,
+        reason: String,
+    ) {
+        logBuffer.log(
+            tag = TAG,
+            level = LogLevel.INFO,
+            messageInitializer = {
+                str1 = from.toString()
+                str2 = to.toString()
+                str3 = reason
+            },
+            messagePrinter = { "Scene change committed: $str1 → $str2, reason: $str3" },
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt
deleted file mode 100644
index c8f46a7..0000000
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneTransitionModel.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.scene.shared.model
-
-/** Models a transition between two scenes. */
-data class SceneTransitionModel(
-    /** The scene we transitioned away from. */
-    val from: SceneKey,
-    /** The scene we transitioned into. */
-    val to: SceneKey,
-)
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index b4ebaec..3e9bbe4 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -45,15 +45,15 @@
      */
     val allSceneKeys: List<SceneKey> = interactor.allSceneKeys()
 
-    /** The current scene. */
-    val currentScene: StateFlow<SceneModel> = interactor.currentScene
+    /** The scene that should be rendered. */
+    val currentScene: StateFlow<SceneModel> = interactor.desiredScene
 
     /** Whether the container is visible. */
     val isVisible: StateFlow<Boolean> = interactor.isVisible
 
-    /** Requests a transition to the scene with the given key. */
-    fun setCurrentScene(scene: SceneModel) {
-        interactor.setCurrentScene(
+    /** Notifies that the UI has transitioned sufficiently to the given scene. */
+    fun onSceneChanged(scene: SceneModel) {
+        interactor.onSceneChanged(
             scene = scene,
             loggingReason = SCENE_TRANSITION_LOGGING_REASON,
         )
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
index b340043..23894a3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/BaseScreenSharePermissionDialog.kt
@@ -50,22 +50,20 @@
 
     public override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        window.apply {
-            addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
-            setGravity(Gravity.CENTER)
-        }
+        window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+        window?.setGravity(Gravity.CENTER)
         setContentView(R.layout.screen_share_dialog)
-        dialogTitle = findViewById(R.id.screen_share_dialog_title)
-        warning = findViewById(R.id.text_warning)
-        startButton = findViewById(android.R.id.button1)
-        cancelButton = findViewById(android.R.id.button2)
+        dialogTitle = requireViewById(R.id.screen_share_dialog_title)
+        warning = requireViewById(R.id.text_warning)
+        startButton = requireViewById(android.R.id.button1)
+        cancelButton = requireViewById(android.R.id.button2)
         updateIcon()
         initScreenShareOptions()
         createOptionsView(getOptionsViewLayoutId())
     }
 
     private fun updateIcon() {
-        val icon = findViewById<ImageView>(R.id.screen_share_dialog_icon)
+        val icon = requireViewById<ImageView>(R.id.screen_share_dialog_icon)
         if (dialogIconTint != null) {
             icon.setColorFilter(context.getColor(dialogIconTint))
         }
@@ -92,7 +90,7 @@
                 options
             )
         adapter.setDropDownViewResource(R.layout.screen_share_dialog_spinner_item_text)
-        screenShareModeSpinner = findViewById(R.id.screen_share_mode_spinner)
+        screenShareModeSpinner = requireViewById(R.id.screen_share_mode_spinner)
         screenShareModeSpinner.adapter = adapter
         screenShareModeSpinner.onItemSelectedListener = this
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
index 604d449..e8683fb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialog.kt
@@ -100,11 +100,11 @@
     @LayoutRes override fun getOptionsViewLayoutId(): Int = R.layout.screen_record_options
 
     private fun initRecordOptionsView() {
-        audioSwitch = findViewById(R.id.screenrecord_audio_switch)
-        tapsSwitch = findViewById(R.id.screenrecord_taps_switch)
-        tapsView = findViewById(R.id.show_taps)
+        audioSwitch = requireViewById(R.id.screenrecord_audio_switch)
+        tapsSwitch = requireViewById(R.id.screenrecord_taps_switch)
+        tapsView = requireViewById(R.id.show_taps)
         updateTapsViewVisibility()
-        options = findViewById(R.id.screen_recording_options)
+        options = requireViewById(R.id.screen_recording_options)
         val a: ArrayAdapter<*> =
             ScreenRecordingAdapter(context, android.R.layout.simple_spinner_dropdown_item, MODES)
         a.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
index 76d0f6e..05a0416 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentCreator.kt
@@ -19,6 +19,7 @@
 import android.content.ClipData
 import android.content.ClipDescription
 import android.content.ComponentName
+import android.content.ContentProvider
 import android.content.Context
 import android.content.Intent
 import android.net.Uri
@@ -36,7 +37,9 @@
     fun createShareWithText(uri: Uri, extraText: String): Intent =
         createShare(uri, text = extraText)
 
-    private fun createShare(uri: Uri, subject: String? = null, text: String? = null): Intent {
+    private fun createShare(rawUri: Uri, subject: String? = null, text: String? = null): Intent {
+        val uri = uriWithoutUserId(rawUri)
+
         // Create a share intent, this will always go through the chooser activity first
         // which should not trigger auto-enter PiP
         val sharingIntent =
@@ -68,7 +71,8 @@
      * @return an ACTION_EDIT intent for the given URI, directed to config_screenshotEditor if
      *   available.
      */
-    fun createEditIntent(uri: Uri, context: Context): Intent {
+    fun createEdit(rawUri: Uri, context: Context): Intent {
+        val uri = uriWithoutUserId(rawUri)
         val editIntent = Intent(Intent.ACTION_EDIT)
 
         val editor = context.getString(R.string.config_screenshotEditor)
@@ -84,3 +88,12 @@
             .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
     }
 }
+
+/**
+ * URIs here are passed only via Intent which are sent to the target user via Intent. Because of
+ * this, the userId component can be removed to prevent compatibility issues when an app attempts
+ * valid a URI containing a userId within the authority.
+ */
+private fun uriWithoutUserId(uri: Uri): Uri {
+    return ContentProvider.getUriWithoutUserId(uri)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
index ecd4568..aa6bfc3 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt
@@ -83,7 +83,7 @@
         if (overrideTransition) {
             val runner = RemoteAnimationAdapter(SCREENSHOT_REMOTE_RUNNER, 0, 0)
             try {
-                WindowManagerGlobal.getWindowManagerService()
+                checkNotNull(WindowManagerGlobal.getWindowManagerService())
                     .overridePendingAppTransitionRemote(runner, displayTracker.defaultDisplayId)
             } catch (e: Exception) {
                 Log.e(TAG, "Error overriding screenshot app transition", e)
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
index 010658b..53dbe76 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java
@@ -334,7 +334,7 @@
         if (mScreenshotUserHandle != Process.myUserHandle()) {
             // TODO: Fix transition for work profile. Omitting it in the meantime.
             mActionExecutor.launchIntentAsync(
-                    ActionIntentCreator.INSTANCE.createEditIntent(uri, this),
+                    ActionIntentCreator.INSTANCE.createEdit(uri, this),
                     null,
                     mScreenshotUserHandle, false);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
index 204b5e6..3903bb2 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotView.java
@@ -815,7 +815,7 @@
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED, 0, mPackageName);
             prepareSharedTransition();
             mActionExecutor.launchIntentAsync(
-                    ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
+                    ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
                     imageData.editTransition.get().bundle,
                     imageData.owner, true);
         });
@@ -823,7 +823,7 @@
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED, 0, mPackageName);
             prepareSharedTransition();
             mActionExecutor.launchIntentAsync(
-                    ActionIntentCreator.INSTANCE.createEditIntent(imageData.uri, mContext),
+                    ActionIntentCreator.INSTANCE.createEdit(imageData.uri, mContext),
                     imageData.editTransition.get().bundle,
                     imageData.owner, true);
         });
diff --git a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
index fc89a9e..f4d19dc 100644
--- a/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
+++ b/packages/SystemUI/src/com/android/systemui/scrim/ScrimView.java
@@ -30,6 +30,7 @@
 import android.graphics.drawable.Drawable;
 import android.os.Looper;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 
 import androidx.annotation.Nullable;
@@ -38,6 +39,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.util.LargeScreenUtils;
 
 import java.util.concurrent.Executor;
@@ -59,6 +61,7 @@
     private float mViewAlpha = 1.0f;
     private Drawable mDrawable;
     private PorterDuffColorFilter mColorFilter;
+    private String mScrimName;
     private int mTintColor;
     private boolean mBlendWithMainColor = true;
     private Runnable mChangeRunnable;
@@ -336,6 +339,15 @@
         }
     }
 
+    public void setScrimName(String scrimName) {
+        mScrimName = scrimName;
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(mScrimName, ev, super.dispatchTouchEvent(ev));
+    }
+
     /**
      * The position of the bottom of the scrim, used for clipping.
      * @see #enableBottomEdgeConcave(boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
index d33d113..2f0fc51 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt
@@ -228,6 +228,8 @@
     }
 
     override fun onDismiss(dialog: DialogInterface?) {
-        finish()
+        if (!isChangingConfigurations) {
+            finish()
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
index 468a75d..e7ee961 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTracker.kt
@@ -48,6 +48,9 @@
     /** Remove a [Callback] previously added. */
     fun removeCallback(callback: Callback)
 
+    /** Gets the Display with the given displayId */
+    fun getDisplay(displayId: Int): Display
+
     /** Ćallback for notifying of changes. */
     interface Callback {
 
diff --git a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
index 5169f88..68cc483 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/DisplayTrackerImpl.kt
@@ -115,6 +115,10 @@
         }
     }
 
+    override fun getDisplay(displayId: Int): Display {
+        return displayManager.getDisplay(displayId)
+    }
+
     @WorkerThread
     private fun onDisplayAdded(displayId: Int, list: List<DisplayTrackerDataItem>) {
         Assert.isNotMainThread()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
index 6143308..4644d41 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
@@ -108,7 +108,7 @@
          * @see NPVCDownEventState.asStringList
          */
         fun toList(): List<Row> {
-            return buffer.asSequence().map { it.asStringList }.toList()
+            return buffer.map { it.asStringList }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
index af3cc86..c501d88 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
@@ -106,6 +106,11 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch("NPV", ev, super.dispatchTouchEvent(ev));
+    }
+
+    @Override
     public void dispatchConfigurationChanged(Configuration newConfig) {
         super.dispatchConfigurationChanged(newConfig);
         mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 416f147..132cd61 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -90,6 +90,8 @@
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
+import androidx.constraintlayout.widget.ConstraintLayout;
+
 import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
@@ -1053,10 +1055,7 @@
         mKeyguardStatusBarViewController.init();
 
         mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
-        updateViewControllers(
-                mView.findViewById(R.id.keyguard_status_view),
-                userAvatarContainer,
-                keyguardUserSwitcherView);
+        updateViewControllers(userAvatarContainer, keyguardUserSwitcherView);
 
         mNotificationStackScrollLayoutController.setOnHeightChangedListener(
                 new NsslHeightChangedListener());
@@ -1118,7 +1117,8 @@
         collectFlow(mView, mKeyguardTransitionInteractor.getDreamingToLockscreenTransition(),
                 mDreamingToLockscreenTransition, mMainDispatcher);
         collectFlow(mView, mDreamingToLockscreenTransitionViewModel.getLockscreenAlpha(),
-                setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+                setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+                mMainDispatcher);
         collectFlow(mView, mDreamingToLockscreenTransitionViewModel.lockscreenTranslationY(
                 mDreamingToLockscreenTransitionTranslationY),
                 setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
@@ -1154,7 +1154,8 @@
         collectFlow(mView, mKeyguardTransitionInteractor.getLockscreenToDreamingTransition(),
                 mLockscreenToDreamingTransition, mMainDispatcher);
         collectFlow(mView, mLockscreenToDreamingTransitionViewModel.getLockscreenAlpha(),
-                setTransitionAlpha(mNotificationStackScrollLayoutController), mMainDispatcher);
+                setDreamLockscreenTransitionAlpha(mNotificationStackScrollLayoutController),
+                mMainDispatcher);
         collectFlow(mView, mLockscreenToDreamingTransitionViewModel.lockscreenTranslationY(
                 mLockscreenToDreamingTransitionTranslationY),
                 setTransitionY(mNotificationStackScrollLayoutController), mMainDispatcher);
@@ -1218,18 +1219,31 @@
         mQsController.loadDimens();
     }
 
-    private void updateViewControllers(KeyguardStatusView keyguardStatusView,
+    private void updateViewControllers(
             FrameLayout userAvatarView,
             KeyguardUserSwitcherView keyguardUserSwitcherView) {
+        // Re-associate the KeyguardStatusViewController
         if (mKeyguardStatusViewController != null) {
             mKeyguardStatusViewController.onDestroy();
         }
-        // Re-associate the KeyguardStatusViewController
-        KeyguardStatusViewComponent statusViewComponent =
+
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            // Need a shared controller until mKeyguardStatusViewController can be removed from
+            // here, due to important state being set in that controller. Rebind in order to pick
+            // up config changes
+            mKeyguardViewConfigurator.bindKeyguardStatusView(mView);
+            mKeyguardStatusViewController =
+                    mKeyguardViewConfigurator.getKeyguardStatusViewController();
+        } else {
+            KeyguardStatusView keyguardStatusView = mView.getRootView().findViewById(
+                    R.id.keyguard_status_view);
+            KeyguardStatusViewComponent statusViewComponent =
                 mKeyguardStatusViewComponentFactory.build(keyguardStatusView);
-        mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
-        mKeyguardStatusViewController.init();
+            mKeyguardStatusViewController = statusViewComponent.getKeyguardStatusViewController();
+            mKeyguardStatusViewController.init();
+        }
         mKeyguardStatusViewController.setSplitShadeEnabled(mSplitShadeEnabled);
+
         updateClockAppearance();
 
         if (mKeyguardUserSwitcherController != null) {
@@ -1335,15 +1349,22 @@
     void reInflateViews() {
         debugLog("reInflateViews");
         // Re-inflate the status view group.
-        KeyguardStatusView keyguardStatusView =
-                mNotificationContainerParent.findViewById(R.id.keyguard_status_view);
-        int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView);
-        mNotificationContainerParent.removeView(keyguardStatusView);
-        keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate(
-                R.layout.keyguard_status_view, mNotificationContainerParent, false);
-        mNotificationContainerParent.addView(keyguardStatusView, statusIndex);
-        attachSplitShadeMediaPlayerContainer(
-                keyguardStatusView.findViewById(R.id.status_view_media_container));
+        if (!mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            KeyguardStatusView keyguardStatusView =
+                    mNotificationContainerParent.findViewById(R.id.keyguard_status_view);
+            int statusIndex = mNotificationContainerParent.indexOfChild(keyguardStatusView);
+            mNotificationContainerParent.removeView(keyguardStatusView);
+            keyguardStatusView = (KeyguardStatusView) mLayoutInflater.inflate(
+                    R.layout.keyguard_status_view, mNotificationContainerParent, false);
+            mNotificationContainerParent.addView(keyguardStatusView, statusIndex);
+
+            attachSplitShadeMediaPlayerContainer(
+                    keyguardStatusView.findViewById(R.id.status_view_media_container));
+        } else {
+            attachSplitShadeMediaPlayerContainer(
+                    mKeyguardViewConfigurator.getKeyguardRootView()
+                        .findViewById(R.id.status_view_media_container));
+        }
 
         // we need to update KeyguardStatusView constraints after reinflating it
         updateResources();
@@ -1369,8 +1390,7 @@
                         R.layout.keyguard_user_switcher /* layoutId */,
                         showKeyguardUserSwitcher /* enabled */);
 
-        updateViewControllers(mView.findViewById(R.id.keyguard_status_view), userAvatarView,
-                keyguardUserSwitcherView);
+        updateViewControllers(userAvatarView, keyguardUserSwitcherView);
 
         if (!mFeatureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             // Update keyguard bottom area
@@ -1666,8 +1686,14 @@
 
     private void updateKeyguardStatusViewAlignment(boolean animate) {
         boolean shouldBeCentered = shouldKeyguardStatusViewBeCentered();
+        ConstraintLayout layout;
+        if (mFeatureFlags.isEnabled(Flags.MIGRATE_KEYGUARD_STATUS_VIEW)) {
+            layout = mKeyguardViewConfigurator.getKeyguardRootView();
+        } else {
+            layout = mNotificationContainerParent;
+        }
         mKeyguardStatusViewController.updateAlignment(
-                mNotificationContainerParent, mSplitShadeEnabled, shouldBeCentered, animate);
+                layout, mSplitShadeEnabled, shouldBeCentered, animate);
         mKeyguardUnfoldTransition.ifPresent(t -> t.setStatusViewCentered(shouldBeCentered));
     }
 
@@ -2105,6 +2131,7 @@
         }
         updateExpansionAndVisibility();
         mNotificationStackScrollLayoutController.setPanelFlinging(false);
+        mShadeLog.d("onFlingEnd called"); // TODO(b/277909752): remove log when bug is fixed
         // expandImmediate should be always reset at the end of animation
         mQsController.setExpandImmediate(false);
     }
@@ -2640,6 +2667,11 @@
             setListening(true);
         }
         if (mBarState != SHADE) {
+            // TODO(b/277909752): remove below logs when bug is fixed
+            mShadeLog.d("onExpandingFinished called");
+            if (mSplitShadeEnabled && !mQsController.getExpanded()) {
+                mShadeLog.d("onExpandingFinished called before QS got expanded");
+            }
             // updating qsExpandImmediate is done in onPanelStateChanged for unlocked shade but
             // on keyguard panel state is always OPEN so we need to have that extra update
             mQsController.setExpandImmediate(false);
@@ -3390,7 +3422,6 @@
         ipw.print("mPanelFlingOvershootAmount="); ipw.println(mPanelFlingOvershootAmount);
         ipw.print("mLastGesturedOverExpansion="); ipw.println(mLastGesturedOverExpansion);
         ipw.print("mIsSpringBackAnimation="); ipw.println(mIsSpringBackAnimation);
-        ipw.print("mSplitShadeEnabled="); ipw.println(mSplitShadeEnabled);
         ipw.print("mHintDistance="); ipw.println(mHintDistance);
         ipw.print("mInitialOffsetOnTouch="); ipw.println(mInitialOffsetOnTouch);
         ipw.print("mCollapsedAndHeadsUpOnDown="); ipw.println(mCollapsedAndHeadsUpOnDown);
@@ -3423,13 +3454,14 @@
         ipw.print("mGestureWaitForTouchSlop="); ipw.println(mGestureWaitForTouchSlop);
         ipw.print("mIgnoreXTouchSlop="); ipw.println(mIgnoreXTouchSlop);
         ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking);
-        ipw.print("mExpandLatencyTracking="); ipw.println(mExpandLatencyTracking);
         ipw.println("gestureExclusionRect:" + calculateGestureExclusionRect());
+        Trace.beginSection("Table<DownEvents>");
         new DumpsysTableLogger(
                 TAG,
                 NPVCDownEventState.TABLE_HEADERS,
                 mLastDownEvents.toList()
         ).printTableData(ipw);
+        Trace.endSection();
     }
 
     @Override
@@ -4692,6 +4724,7 @@
     }
 
     private void onPanelStateChanged(@PanelState int state) {
+        mShadeLog.logPanelStateChanged(state);
         mQsController.updateExpansionEnabledAmbient();
 
         if (state == STATE_OPEN && mCurrentPanelState != state) {
@@ -4718,6 +4751,16 @@
         mCurrentPanelState = state;
     }
 
+    private Consumer<Float> setDreamLockscreenTransitionAlpha(
+            NotificationStackScrollLayoutController stackScroller) {
+        return (Float alpha) -> {
+            // Also animate the status bar's alpha during transitions between the lockscreen and
+            // dreams.
+            mKeyguardStatusBarViewController.setAlpha(alpha);
+            setTransitionAlpha(stackScroller).accept(alpha);
+        };
+    }
+
     private Consumer<Float> setTransitionAlpha(
             NotificationStackScrollLayoutController stackScroller) {
         return (Float alpha) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index b328c55..6f726a2 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -840,13 +840,17 @@
         pw.println("  mDeferWindowLayoutParams=" + mDeferWindowLayoutParams);
         pw.println(mCurrentState);
         if (mWindowRootView != null && mWindowRootView.getViewRootImpl() != null) {
+            Trace.beginSection("mWindowRootView.dump()");
             mWindowRootView.getViewRootImpl().dump("  ", pw);
+            Trace.endSection();
         }
+        Trace.beginSection("Table<State>");
         new DumpsysTableLogger(
                 TAG,
                 NotificationShadeWindowState.TABLE_HEADERS,
                 mStateBuffer.toList()
         ).printTableData(pw);
+        Trace.endSection();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
index d252943..e3010ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowState.kt
@@ -170,7 +170,7 @@
          * @see [NotificationShadeWindowState.asStringList]
          */
         fun toList(): List<Row> {
-            return buffer.asSequence().map { it.asStringList }.toList()
+            return buffer.map { it.asStringList }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index a9c4aeb..f9b4e67 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -107,6 +107,8 @@
 
         result = result != null ? result : super.dispatchTouchEvent(ev);
 
+        TouchLogger.logDispatchTouch(TAG, ev, result);
+
         mInteractionEventHandler.dispatchTouchEventComplete();
 
         return result;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 18e9644..832a25b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -91,6 +91,7 @@
     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final LockIconViewController mLockIconViewController;
+    private final ShadeLogger mShadeLogger;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final StatusBarWindowStateController mStatusBarWindowStateController;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
@@ -151,6 +152,7 @@
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
             NotificationInsetsController notificationInsetsController,
             AmbientState ambientState,
+            ShadeLogger shadeLogger,
             PulsingGestureListener pulsingGestureListener,
             LockscreenHostedDreamGestureListener lockscreenHostedDreamGestureListener,
             KeyguardBouncerViewModel keyguardBouncerViewModel,
@@ -176,6 +178,7 @@
         mStatusBarWindowStateController = statusBarWindowStateController;
         mLockIconViewController = lockIconViewController;
         mBackActionInteractor = backActionInteractor;
+        mShadeLogger = shadeLogger;
         mLockIconViewController.init();
         mService = centralSurfaces;
         mPowerInteractor = powerInteractor;
@@ -223,6 +226,13 @@
         return mView.findViewById(R.id.keyguard_message_area);
     }
 
+    private Boolean logDownDispatch(MotionEvent ev, String msg, Boolean result) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            mShadeLogger.logShadeWindowDispatch(ev, msg, result);
+        }
+        return result;
+    }
+
     /** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
     public void setupExpandedStatusBar() {
         mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
@@ -237,8 +247,8 @@
             @Override
             public Boolean handleDispatchTouchEvent(MotionEvent ev) {
                 if (mStatusBarViewController == null) { // Fix for b/192490822
-                    Log.w(TAG, "Ignoring touch while statusBarView not yet set.");
-                    return false;
+                    return logDownDispatch(ev,
+                            "Ignoring touch while statusBarView not yet set", false);
                 }
                 boolean isDown = ev.getActionMasked() == MotionEvent.ACTION_DOWN;
                 boolean isUp = ev.getActionMasked() == MotionEvent.ACTION_UP;
@@ -250,10 +260,9 @@
                 }
 
                 // Reset manual touch dispatch state here but make sure the UP/CANCEL event still
-                // gets
-                // delivered.
+                // gets delivered.
                 if (!isCancel && mService.shouldIgnoreTouch()) {
-                    return false;
+                    return logDownDispatch(ev, "touch ignored by CS", false);
                 }
 
                 if (isDown) {
@@ -265,8 +274,11 @@
                     mTouchActive = false;
                     mDownEvent = null;
                 }
-                if (mTouchCancelled || mExpandAnimationRunning) {
-                    return false;
+                if (mTouchCancelled) {
+                    return logDownDispatch(ev, "touch cancelled", false);
+                }
+                if (mExpandAnimationRunning) {
+                    return logDownDispatch(ev, "expand animation running", false);
                 }
 
                 if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) {
@@ -280,17 +292,17 @@
                 }
 
                 if (mIsOcclusionTransitionRunning) {
-                    return false;
+                    return logDownDispatch(ev, "occlusion transition running", false);
                 }
 
                 mFalsingCollector.onTouchEvent(ev);
                 mPulsingWakeupGestureHandler.onTouchEvent(ev);
                 if (mDreamingWakeupGestureHandler != null
                         && mDreamingWakeupGestureHandler.onTouchEvent(ev)) {
-                    return true;
+                    return logDownDispatch(ev, "dream wakeup gesture handled", true);
                 }
                 if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) {
-                    return true;
+                    return logDownDispatch(ev, "dispatched to Keyguard", true);
                 }
                 if (mBrightnessMirror != null
                         && mBrightnessMirror.getVisibility() == View.VISIBLE) {
@@ -298,7 +310,7 @@
                     // you can't touch anything other than the brightness slider while the mirror is
                     // showing and the rest of the panel is transparent.
                     if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
-                        return false;
+                        return logDownDispatch(ev, "disallowed new pointer", false);
                     }
                 }
                 if (isDown) {
@@ -329,7 +341,9 @@
                     expandingBelowNotch = true;
                 }
                 if (expandingBelowNotch) {
-                    return mStatusBarViewController.sendTouchToView(ev);
+                    return logDownDispatch(ev,
+                            "expand below notch. sending touch to status bar",
+                            mStatusBarViewController.sendTouchToView(ev));
                 }
 
                 if (!mIsTrackingBarGesture && isDown
@@ -339,9 +353,10 @@
                     if (mStatusBarViewController.touchIsWithinView(x, y)) {
                         if (mStatusBarWindowStateController.windowIsShowing()) {
                             mIsTrackingBarGesture = true;
-                            return mStatusBarViewController.sendTouchToView(ev);
-                        } else { // it's hidden or hiding, don't send to notification shade.
-                            return true;
+                            return logDownDispatch(ev, "sending touch to status bar",
+                                    mStatusBarViewController.sendTouchToView(ev));
+                        } else {
+                            return logDownDispatch(ev, "hidden or hiding", true);
                         }
                     }
                 } else if (mIsTrackingBarGesture) {
@@ -349,10 +364,10 @@
                     if (isUp || isCancel) {
                         mIsTrackingBarGesture = false;
                     }
-                    return sendToStatusBar;
+                    return logDownDispatch(ev, "sending bar gesture to status bar",
+                            sendToStatusBar);
                 }
-
-                return null;
+                return logDownDispatch(ev, "no custom touch dispatch of down event", null);
             }
 
             @Override
@@ -364,18 +379,26 @@
             public boolean shouldInterceptTouchEvent(MotionEvent ev) {
                 if (mStatusBarStateController.isDozing() && !mService.isPulsing()
                         && !mDockManager.isDocked()) {
-                    // Capture all touch events in always-on.
+                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                        mShadeLogger.d("NSWVC: capture all touch events in always-on");
+                    }
                     return true;
                 }
 
                 if (mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(ev)) {
                     // Don't allow touches to proceed to underlying views if alternate
                     // bouncer is showing
+                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                        mShadeLogger.d("NSWVC: alt bouncer showing");
+                    }
                     return true;
                 }
 
                 if (mLockIconViewController.onInterceptTouchEvent(ev)) {
                     // immediately return true; don't send the touch to the drag down helper
+                    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                        mShadeLogger.d("NSWVC: don't send touch to drag down helper");
+                    }
                     return true;
                 }
 
@@ -383,7 +406,13 @@
                         && mDragDownHelper.isDragDownEnabled()
                         && !mService.isBouncerShowing()
                         && !mStatusBarStateController.isDozing()) {
-                    return mDragDownHelper.onInterceptTouchEvent(ev);
+                    boolean result = mDragDownHelper.onInterceptTouchEvent(ev);
+                    if (result) {
+                        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+                            mShadeLogger.d("NSWVC: drag down helper intercepted");
+                        }
+                    }
+                    return result;
                 } else {
                     return false;
                 }
@@ -495,6 +524,7 @@
     }
 
     public void cancelCurrentTouch() {
+        mShadeLogger.d("NSWVC: cancelling current touch");
         if (mTouchActive) {
             final long now = mClock.uptimeMillis();
             final MotionEvent event;
@@ -508,6 +538,7 @@
                         MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                 event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
             }
+            Log.w(TAG, "Canceling current touch event (should be very rare)");
             mView.dispatchTouchEvent(event);
             event.recycle();
             mTouchCancelled = true;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 3b3df50..a4e439b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -22,6 +22,7 @@
 import android.graphics.Canvas;
 import android.graphics.Rect;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup.MarginLayoutParams;
 import android.view.WindowInsets;
@@ -183,6 +184,12 @@
     }
 
     @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch("NotificationsQuickSettingsContainer", ev,
+                super.dispatchTouchEvent(ev));
+    }
+
+    @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
         if (mIsMigratingNSSL) {
             return super.drawChild(canvas, child, drawingTime);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index baac57c..c9c911b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -787,6 +787,12 @@
 
     /** update Qs height state */
     public void setExpansionHeight(float height) {
+        // TODO(b/277909752): remove below log when bug is fixed
+        if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0) {
+            Log.wtf(TAG,
+                    "setting QS height to 0 in split shade while shade is open(ing). "
+                            + "Value of mExpandImmediate = " + mExpandImmediate);
+        }
         int maxHeight = getMaxExpansionHeight();
         height = Math.min(Math.max(
                 height, getMinExpansionHeight()), maxHeight);
@@ -933,7 +939,6 @@
         return mShadeExpandedHeight;
     }
 
-    @VisibleForTesting
     void setExpandImmediate(boolean expandImmediate) {
         if (expandImmediate != mExpandImmediate) {
             mShadeLog.logQsExpandImmediateChanged(expandImmediate);
@@ -1011,6 +1016,7 @@
                 && mPanelViewControllerLazy.get().mAnimateBack) {
             mPanelViewControllerLazy.get().adjustBackAnimationScale(adjustedExpansionFraction);
         }
+        mShadeExpansionStateManager.onQsExpansionFractionChanged(qsExpansionFraction);
         mMediaHierarchyManager.setQsExpansion(qsExpansionFraction);
         int qsPanelBottomY = calculateBottomPosition(qsExpansionFraction);
         mScrimController.setQsPosition(qsExpansionFraction, qsPanelBottomY);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 22c63817..d7a3392 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -27,6 +27,8 @@
 import com.android.systemui.assist.AssistManager;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.dagger.ShadeTouchLog;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationPresenter;
@@ -56,6 +58,7 @@
 
     private final CommandQueue mCommandQueue;
     private final Executor mMainExecutor;
+    private final LogBuffer mTouchLog;
     private final KeyguardStateController mKeyguardStateController;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarStateController mStatusBarStateController;
@@ -79,6 +82,7 @@
     public ShadeControllerImpl(
             CommandQueue commandQueue,
             @Main Executor mainExecutor,
+            @ShadeTouchLog LogBuffer touchLog,
             KeyguardStateController keyguardStateController,
             StatusBarStateController statusBarStateController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@@ -92,6 +96,7 @@
     ) {
         mCommandQueue = commandQueue;
         mMainExecutor = mainExecutor;
+        mTouchLog = touchLog;
         mShadeViewControllerLazy = shadeViewControllerLazy;
         mStatusBarStateController = statusBarStateController;
         mStatusBarWindowController = statusBarWindowController;
@@ -413,6 +418,7 @@
 
     @Override
     public void start() {
+        TouchLogger.logTouchesTo(mTouchLog);
         getShadeViewController().setTrackingStartedListener(this::runPostCollapseRunnables);
         getShadeViewController().setOpenCloseListener(
                 new OpenCloseListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 2db47ae..0554c58 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -38,6 +38,8 @@
     private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
     private val fullExpansionListeners = CopyOnWriteArrayList<ShadeFullExpansionListener>()
     private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
+    private val qsExpansionFractionListeners =
+        CopyOnWriteArrayList<ShadeQsExpansionFractionListener>()
     private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
     private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
 
@@ -45,6 +47,7 @@
     @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
     private var expanded: Boolean = false
     private var qsExpanded: Boolean = false
+    private var qsExpansionFraction = 0f
     private var tracking: Boolean = false
     private var dragDownPxAmount: Float = 0f
 
@@ -82,6 +85,15 @@
         qsExpansionListeners.remove(listener)
     }
 
+    fun addQsExpansionFractionListener(listener: ShadeQsExpansionFractionListener) {
+        qsExpansionFractionListeners.add(listener)
+        listener.onQsExpansionFractionChanged(qsExpansionFraction)
+    }
+
+    fun removeQsExpansionFractionListener(listener: ShadeQsExpansionFractionListener) {
+        qsExpansionFractionListeners.remove(listener)
+    }
+
     /** Adds a listener that will be notified when the panel state has changed. */
     fun addStateListener(listener: ShadeStateListener) {
         stateListeners.add(listener)
@@ -175,6 +187,15 @@
         qsExpansionListeners.forEach { it.onQsExpansionChanged(qsExpanded) }
     }
 
+    fun onQsExpansionFractionChanged(qsExpansionFraction: Float) {
+        this.qsExpansionFraction = qsExpansionFraction
+
+        debugLog("qsExpansionFraction=$qsExpansionFraction")
+        qsExpansionFractionListeners.forEach {
+            it.onQsExpansionFractionChanged(qsExpansionFraction)
+        }
+    }
+
     fun onShadeExpansionFullyChanged(isExpanded: Boolean) {
         this.expanded = isExpanded
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
index c6cb9c4..bea12de 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt
@@ -130,12 +130,12 @@
     private lateinit var carrierIconSlots: List<String>
     private lateinit var mShadeCarrierGroupController: ShadeCarrierGroupController
 
-    private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
-    private val clock: Clock = header.findViewById(R.id.clock)
-    private val date: TextView = header.findViewById(R.id.date)
-    private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
-    private val mShadeCarrierGroup: ShadeCarrierGroup = header.findViewById(R.id.carrier_group)
-    private val systemIcons: View = header.findViewById(R.id.shade_header_system_icons)
+    private val batteryIcon: BatteryMeterView = header.requireViewById(R.id.batteryRemainingIcon)
+    private val clock: Clock = header.requireViewById(R.id.clock)
+    private val date: TextView = header.requireViewById(R.id.date)
+    private val iconContainer: StatusIconContainer = header.requireViewById(R.id.statusIcons)
+    private val mShadeCarrierGroup: ShadeCarrierGroup = header.requireViewById(R.id.carrier_group)
+    private val systemIcons: View = header.requireViewById(R.id.shade_header_system_icons)
 
     private var roundedCorners = 0
     private var cutout: DisplayCutout? = null
@@ -582,7 +582,7 @@
     inner class CustomizerAnimationListener(
         private val enteringCustomizing: Boolean,
     ) : AnimatorListenerAdapter() {
-        override fun onAnimationEnd(animation: Animator?) {
+        override fun onAnimationEnd(animation: Animator) {
             super.onAnimationEnd(animation)
             header.animate().setListener(null)
             if (enteringCustomizing) {
@@ -590,7 +590,7 @@
             }
         }
 
-        override fun onAnimationStart(animation: Animator?) {
+        override fun onAnimationStart(animation: Animator) {
             super.onAnimationStart(animation)
             if (!enteringCustomizing) {
                 customizing = false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 1c30bdd..8d23f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -79,19 +79,39 @@
 
     fun logMotionEvent(event: MotionEvent, message: String) {
         buffer.log(
-            TAG,
-            LogLevel.VERBOSE,
-            {
-                str1 = message
-                long1 = event.eventTime
-                long2 = event.downTime
-                int1 = event.action
-                int2 = event.classification
-                double1 = event.y.toDouble()
-            },
-            {
-                "$str1: eventTime=$long1,downTime=$long2,y=$double1,action=$int1,class=$int2"
-            }
+                TAG,
+                LogLevel.VERBOSE,
+                {
+                    str1 = message
+                    long1 = event.eventTime
+                    long2 = event.downTime
+                    int1 = event.action
+                    int2 = event.classification
+                },
+                {
+                    "$str1: eventTime=$long1,downTime=$long2,action=$int1,class=$int2"
+                }
+        )
+    }
+
+    /** Logs motion event dispatch results from NotificationShadeWindowViewController. */
+    fun logShadeWindowDispatch(event: MotionEvent, message: String, result: Boolean?) {
+        buffer.log(
+                TAG,
+                LogLevel.VERBOSE,
+                {
+                    str1 = message
+                    long1 = event.eventTime
+                    long2 = event.downTime
+                },
+                {
+                    val prefix = when (result) {
+                        true -> "SHADE TOUCH REROUTED"
+                        false -> "SHADE TOUCH BLOCKED"
+                        null -> "SHADE TOUCH DISPATCHED"
+                    }
+                    "$prefix: eventTime=$long1,downTime=$long2, reason=$str1"
+                }
         )
     }
 
@@ -316,6 +336,17 @@
         )
     }
 
+    fun logPanelStateChanged(@PanelState panelState: Int) {
+        buffer.log(
+            TAG,
+            LogLevel.VERBOSE,
+            {
+                str1 = panelState.panelStateToString()
+            },
+            { "New panel State: $str1" }
+        )
+    }
+
     fun flingQs(flingType: Int, isClick: Boolean) {
         buffer.log(
             TAG,
diff --git a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl b/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionFractionListener.kt
similarity index 60%
rename from core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionFractionListener.kt
index f6c47dd..c787f49 100644
--- a/core/java/android/service/selectiontoolbar/ISelectionToolbarRenderServiceCallback.aidl
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeQsExpansionFractionListener.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -14,15 +14,10 @@
  * limitations under the License.
  */
 
-package android.service.selectiontoolbar;
+package com.android.systemui.shade
 
-import android.os.IBinder;
-
-/**
- * The interface from the SelectionToolbarRenderService to the system.
- *
- * @hide
- */
-oneway interface ISelectionToolbarRenderServiceCallback {
-    void transferTouch(in IBinder source, in IBinder target);
+/** A listener interface to be notified of expansion events for the quick settings panel. */
+fun interface ShadeQsExpansionFractionListener {
+    /** Invoked whenever the quick settings expansion fraction changes */
+    fun onQsExpansionFractionChanged(qsExpansionFraction: Float)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 2955118..05b1ac6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -106,7 +106,7 @@
             featureFlags: FeatureFlags,
         ): NotificationShadeWindowView {
             if (featureFlags.isEnabled(Flags.SCENE_CONTAINER)) {
-                return root.findViewById(R.id.legacy_window_root)
+                return root.requireViewById(R.id.legacy_window_root)
             }
             return root as NotificationShadeWindowView?
                 ?: throw IllegalStateException("root view not a NotificationShadeWindowView")
@@ -118,7 +118,7 @@
         fun providesNotificationStackScrollLayout(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): NotificationStackScrollLayout {
-            return notificationShadeWindowView.findViewById(R.id.notification_stack_scroller)
+            return notificationShadeWindowView.requireViewById(R.id.notification_stack_scroller)
         }
 
         @Provides
@@ -153,7 +153,7 @@
         fun providesNotificationPanelView(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): NotificationPanelView {
-            return notificationShadeWindowView.findViewById(R.id.notification_panel)
+            return notificationShadeWindowView.requireViewById(R.id.notification_panel)
         }
 
         /**
@@ -175,7 +175,7 @@
         fun providesLightRevealScrim(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): LightRevealScrim {
-            return notificationShadeWindowView.findViewById(R.id.light_reveal_scrim)
+            return notificationShadeWindowView.requireViewById(R.id.light_reveal_scrim)
         }
 
         @Provides
@@ -183,7 +183,7 @@
         fun providesKeyguardRootView(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): KeyguardRootView {
-            return notificationShadeWindowView.findViewById(R.id.keyguard_root_view)
+            return notificationShadeWindowView.requireViewById(R.id.keyguard_root_view)
         }
 
         @Provides
@@ -191,7 +191,7 @@
         fun providesSharedNotificationContainer(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): SharedNotificationContainer {
-            return notificationShadeWindowView.findViewById(R.id.shared_notification_container)
+            return notificationShadeWindowView.requireViewById(R.id.shared_notification_container)
         }
 
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -200,7 +200,7 @@
         fun providesAuthRippleView(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): AuthRippleView? {
-            return notificationShadeWindowView.findViewById(R.id.auth_ripple)
+            return notificationShadeWindowView.requireViewById(R.id.auth_ripple)
         }
 
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -212,9 +212,9 @@
             featureFlags: FeatureFlags
         ): LockIconView {
             if (featureFlags.isEnabled(Flags.MIGRATE_LOCK_ICON)) {
-                return keyguardRootView.findViewById(R.id.lock_icon_view)
+                return keyguardRootView.requireViewById(R.id.lock_icon_view)
             } else {
-                return notificationPanelView.findViewById(R.id.lock_icon_view)
+                return notificationPanelView.requireViewById(R.id.lock_icon_view)
             }
         }
 
@@ -224,7 +224,7 @@
         fun providesTapAgainView(
             notificationPanelView: NotificationPanelView,
         ): TapAgainView {
-            return notificationPanelView.findViewById(R.id.shade_falsing_tap_again)
+            return notificationPanelView.requireViewById(R.id.shade_falsing_tap_again)
         }
 
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -233,7 +233,7 @@
         fun providesNotificationsQuickSettingsContainer(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): NotificationsQuickSettingsContainer {
-            return notificationShadeWindowView.findViewById(R.id.notification_container_parent)
+            return notificationShadeWindowView.requireViewById(R.id.notification_container_parent)
         }
 
         // TODO(b/277762009): Only allow this view's controller to inject the view. See above.
@@ -243,7 +243,7 @@
         fun providesShadeHeaderView(
             notificationShadeWindowView: NotificationShadeWindowView,
         ): MotionLayout {
-            val stub = notificationShadeWindowView.findViewById<ViewStub>(R.id.qs_header_stub)
+            val stub = notificationShadeWindowView.requireViewById<ViewStub>(R.id.qs_header_stub)
             val layoutId = R.layout.combined_qs_header
             stub.layoutResource = layoutId
             return stub.inflate() as MotionLayout
@@ -260,7 +260,7 @@
         @SysUISingleton
         @Named(SHADE_HEADER)
         fun providesBatteryMeterView(@Named(SHADE_HEADER) view: MotionLayout): BatteryMeterView {
-            return view.findViewById(R.id.batteryRemainingIcon)
+            return view.requireViewById(R.id.batteryRemainingIcon)
         }
 
         @Provides
@@ -273,6 +273,7 @@
             tunerService: TunerService,
             @Main mainHandler: Handler,
             contentResolver: ContentResolver,
+            featureFlags: FeatureFlags,
             batteryController: BatteryController,
         ): BatteryMeterViewController {
             return BatteryMeterViewController(
@@ -283,6 +284,7 @@
                 tunerService,
                 mainHandler,
                 contentResolver,
+                featureFlags,
                 batteryController,
             )
         }
@@ -293,7 +295,7 @@
         fun providesOngoingPrivacyChip(
             @Named(SHADE_HEADER) header: MotionLayout,
         ): OngoingPrivacyChip {
-            return header.findViewById(R.id.privacy_chip)
+            return header.requireViewById(R.id.privacy_chip)
         }
 
         @Provides
@@ -302,7 +304,7 @@
         fun providesStatusIconContainer(
             @Named(SHADE_HEADER) header: MotionLayout,
         ): StatusIconContainer {
-            return header.findViewById(R.id.statusIcons)
+            return header.requireViewById(R.id.statusIcons)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.kt
new file mode 100644
index 0000000..58704bf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/TouchLogger.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.shade
+
+import android.view.MotionEvent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+
+private const val TAG = "systemui.shade.touch"
+
+/**
+ * A logger for tracking touch dispatching in the shade view hierarchy. The purpose of this logger
+ * is to passively observe dispatchTouchEvent calls in order to see which subtrees of the shade are
+ * handling touches. Additionally, some touches may be passively observed for views near the top of
+ * the shade hierarchy that cannot intercept touches, i.e. scrims. The usage of static methods for
+ * logging is sub-optimal in many ways, but it was selected in this case to make usage of this
+ * non-function diagnostic code as low friction as possible.
+ */
+class TouchLogger {
+    companion object {
+        private var touchLogger: DispatchTouchLogger? = null
+
+        @JvmStatic
+        fun logTouchesTo(buffer: LogBuffer) {
+            touchLogger = DispatchTouchLogger(buffer)
+        }
+
+        @JvmStatic
+        fun logDispatchTouch(viewTag: String, ev: MotionEvent, result: Boolean): Boolean {
+            touchLogger?.logDispatchTouch(viewTag, ev, result)
+            return result
+        }
+    }
+}
+
+/** Logs touches. */
+private class DispatchTouchLogger(private val buffer: LogBuffer) {
+    fun logDispatchTouch(viewTag: String, ev: MotionEvent, result: Boolean) {
+        // NOTE: never log position of touches for security purposes
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = viewTag
+                int1 = ev.action
+                long1 = ev.downTime
+                bool1 = result
+            },
+            { "Touch: view=$str1, type=${typeToString(int1)}, downtime=$long1, result=$bool1" }
+        )
+    }
+
+    private fun typeToString(type: Int): String {
+        return when (type) {
+            MotionEvent.ACTION_DOWN -> "DOWN"
+            MotionEvent.ACTION_UP -> "UP"
+            MotionEvent.ACTION_MOVE -> "MOVE"
+            MotionEvent.ACTION_CANCEL -> "CANCEL"
+            MotionEvent.ACTION_POINTER_DOWN -> "POINTER_DOWN"
+            MotionEvent.ACTION_POINTER_UP -> "POINTER_UP"
+            else -> "OTHER"
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index 0b3ed56..8edc26d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -16,15 +16,16 @@
 
 package com.android.systemui.shade.ui.viewmodel
 
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.scene.shared.model.SceneKey
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
 
 /** Models UI state and handles user input for the shade scene. */
@@ -33,29 +34,43 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    private val lockscreenSceneInteractor: LockscreenSceneInteractor,
+    authenticationInteractor: AuthenticationInteractor,
+    private val bouncerInteractor: BouncerInteractor,
 ) {
     /** The key of the scene we should switch to when swiping up. */
     val upDestinationSceneKey: StateFlow<SceneKey> =
-        lockscreenSceneInteractor.isDeviceLocked
-            .map { isLocked -> upDestinationSceneKey(isLocked = isLocked) }
+        combine(
+                authenticationInteractor.isUnlocked,
+                authenticationInteractor.canSwipeToDismiss,
+            ) { isUnlocked, canSwipeToDismiss ->
+                upDestinationSceneKey(
+                    isUnlocked = isUnlocked,
+                    canSwipeToDismiss = canSwipeToDismiss,
+                )
+            }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
                 initialValue =
                     upDestinationSceneKey(
-                        isLocked = lockscreenSceneInteractor.isDeviceLocked.value,
+                        isUnlocked = authenticationInteractor.isUnlocked.value,
+                        canSwipeToDismiss = authenticationInteractor.canSwipeToDismiss.value,
                     ),
             )
 
     /** Notifies that some content in the shade was clicked. */
     fun onContentClicked() {
-        lockscreenSceneInteractor.dismissLockscreen()
+        bouncerInteractor.showOrUnlockDevice()
     }
 
     private fun upDestinationSceneKey(
-        isLocked: Boolean,
+        isUnlocked: Boolean,
+        canSwipeToDismiss: Boolean,
     ): SceneKey {
-        return if (isLocked) SceneKey.Lockscreen else SceneKey.Gone
+        return when {
+            canSwipeToDismiss -> SceneKey.Lockscreen
+            isUnlocked -> SceneKey.Gone
+            else -> SceneKey.Lockscreen
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
index 37140ec..5209767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BatteryStatusChip.kt
@@ -37,8 +37,8 @@
 
     init {
         inflate(context, R.layout.battery_status_chip, this)
-        roundedContainer = findViewById(R.id.rounded_container)
-        batteryMeterView = findViewById(R.id.battery_meter_view)
+        roundedContainer = requireViewById(R.id.rounded_container)
+        batteryMeterView = requireViewById(R.id.battery_meter_view)
         updateResources()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
index 823bb35..3120128 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LightRevealScrim.kt
@@ -14,9 +14,11 @@
 import android.os.Trace
 import android.util.AttributeSet
 import android.util.MathUtils.lerp
+import android.view.MotionEvent
 import android.view.View
 import android.view.animation.PathInterpolator
 import com.android.app.animation.Interpolators
+import com.android.systemui.shade.TouchLogger
 import com.android.systemui.statusbar.LightRevealEffect.Companion.getPercentPastThreshold
 import com.android.systemui.util.getColorWithAlpha
 import com.android.systemui.util.leak.RotationUtils
@@ -234,6 +236,8 @@
     }
 }
 
+private const val TAG = "LightRevealScrim"
+
 /**
  * Scrim view that partially reveals the content underneath it using a [RadialGradient] with a
  * transparent center. The center position, size, and stops of the gradient can be manipulated to
@@ -419,15 +423,14 @@
         revealGradientCenter.y = top + (revealGradientHeight / 2f)
     }
 
-    override fun onDraw(canvas: Canvas?) {
+    override fun onDraw(canvas: Canvas) {
         if (
-            canvas == null ||
-                revealGradientWidth <= 0 ||
-                revealGradientHeight <= 0 ||
-                revealAmount == 0f
+            revealGradientWidth <= 0 ||
+            revealGradientHeight <= 0 ||
+            revealAmount == 0f
         ) {
             if (revealAmount < 1f) {
-                canvas?.drawColor(revealGradientEndColor)
+                canvas.drawColor(revealGradientEndColor)
             }
             return
         }
@@ -447,6 +450,10 @@
         canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), gradientPaint)
     }
 
+    override fun dispatchTouchEvent(event: MotionEvent): Boolean {
+        return TouchLogger.logDispatchTouch(TAG, event, super.dispatchTouchEvent(event))
+    }
+
     private fun setPaintColorFilter() {
         gradientPaint.colorFilter =
             PorterDuffColorFilter(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 4710574..672796a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -474,7 +474,7 @@
         }
         if (endlistener != null) {
             dragDownAnimator.addListener(object : AnimatorListenerAdapter() {
-                override fun onAnimationEnd(animation: Animator?) {
+                override fun onAnimationEnd(animation: Animator) {
                     endlistener.invoke()
                 }
             })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
index 750272d..17b4e3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
@@ -66,7 +66,7 @@
                 inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */)
                 oldIn.recycle()
             }
-            val outBitmap = Bitmap.createBitmap(inBitmap.width, inBitmap.height,
+            val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0,
                     Bitmap.Config.ARGB_8888)
 
             input = Allocation.createFromBitmap(renderScript, inBitmap,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index b624115..59c63aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -272,7 +272,7 @@
                             blurUtils.blurRadiusOfRatio(animation.animatedValue as Float)
                 }
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         keyguardAnimator = null
                         wakeAndUnlockBlurRadius = 0f
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
index eddb683..d1e0a71 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt
@@ -234,7 +234,7 @@
             }
 
             // Set the dot's view gravity to hug the status bar
-            (corner.findViewById<View>(R.id.privacy_dot)
+            (corner.requireViewById<View>(R.id.privacy_dot)
                     .layoutParams as FrameLayout.LayoutParams)
                         .gravity = rotatedCorner.innerGravity()
         }
@@ -255,7 +255,7 @@
         // in every rotation. The only thing we need to check is rtl
         val rtl = state.layoutRtl
         val size = Point()
-        tl.context.display.getRealSize(size)
+        tl.context.display?.getRealSize(size)
         val currentRotation = RotationUtils.getExactRotation(tl.context)
 
         val displayWidth: Int
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
index 6e8b8bd..1ad4620 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt
@@ -168,10 +168,8 @@
         }
 
         val keyFrame1Height = dotSize * 2
-        val v = currentAnimatedView!!.view
-        val chipVerticalCenter = v.top + v.measuredHeight / 2
-        val height1 = ValueAnimator.ofInt(
-                currentAnimatedView!!.view.measuredHeight, keyFrame1Height).apply {
+        val chipVerticalCenter = chipBounds.top + chipBounds.height() / 2
+        val height1 = ValueAnimator.ofInt(chipBounds.height(), keyFrame1Height).apply {
             startDelay = 8.frames
             duration = 6.frames
             interpolator = STATUS_CHIP_HEIGHT_TO_DOT_KEYFRAME_1
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
index 23edf17..2403920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt
@@ -164,7 +164,9 @@
         }
 
         private fun isChipAnimationEnabled(): Boolean {
-            return DeviceConfig.getBoolean(NAMESPACE_PRIVACY, CHIP_ANIMATION_ENABLED, true)
+            val defaultValue =
+                context.resources.getBoolean(R.bool.config_enablePrivacyChipAnimation)
+            return DeviceConfig.getBoolean(NAMESPACE_PRIVACY, CHIP_ANIMATION_ENABLED, defaultValue)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index 6fc715a..f40f570 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -170,19 +170,19 @@
         // Set hasPersistentDot to false. If the animationState is anything before ANIMATING_OUT,
         // the disappear animation will not animate into a dot but remove the chip entirely
         hasPersistentDot = false
-        // if we are currently showing a persistent dot, hide it
-        if (animationState.value == SHOWING_PERSISTENT_DOT) notifyHidePersistentDot()
-        // if we are currently animating into a dot, wait for the animation to finish and then hide
-        // the dot
-        if (animationState.value == ANIMATING_OUT) {
-            coroutineScope.launch {
-                withTimeout(DISAPPEAR_ANIMATION_DURATION) {
-                    animationState.first {
-                        it == SHOWING_PERSISTENT_DOT || it == IDLE || it == ANIMATION_QUEUED
-                    }
-                    notifyHidePersistentDot()
-                }
+
+        if (animationState.value == SHOWING_PERSISTENT_DOT) {
+            // if we are currently showing a persistent dot, hide it and update the animationState
+            notifyHidePersistentDot()
+            if (scheduledEvent.value != null) {
+                animationState.value = ANIMATION_QUEUED
+            } else {
+                animationState.value = IDLE
             }
+        } else if (animationState.value == ANIMATING_OUT) {
+            // if we are currently animating out, hide the dot. The animationState will be updated
+            // once the animation has ended in the onAnimationEnd callback
+            notifyHidePersistentDot()
         }
     }
 
@@ -243,7 +243,7 @@
         if (!event.showAnimation && event.forceVisible) {
             // If animations are turned off, we'll transition directly to the dot
             animationState.value = SHOWING_PERSISTENT_DOT
-            notifyTransitionToPersistentDot()
+            notifyTransitionToPersistentDot(event)
             return
         }
 
@@ -335,7 +335,7 @@
         }
         animators.add(chipAnimationController.onSystemEventAnimationFinish(hasPersistentDot))
         if (hasPersistentDot) {
-            val dotAnim = notifyTransitionToPersistentDot()
+            val dotAnim = notifyTransitionToPersistentDot(currentlyDisplayedEvent)
             if (dotAnim != null) {
                 animators.add(dotAnim)
             }
@@ -344,12 +344,12 @@
         return AnimatorSet().also { it.playTogether(animators) }
     }
 
-    private fun notifyTransitionToPersistentDot(): Animator? {
+    private fun notifyTransitionToPersistentDot(event: StatusEvent?): Animator? {
         logger?.logTransitionToPersistentDotCallbackInvoked()
         val anims: List<Animator> =
             listeners.mapNotNull {
                 it.onSystemStatusAnimationTransitionToPersistentDot(
-                    currentlyDisplayedEvent?.contentDescription
+                    event?.contentDescription
                 )
             }
         if (anims.isNotEmpty()) {
@@ -366,14 +366,6 @@
         logger?.logHidePersistentDotCallbackInvoked()
         val anims: List<Animator> = listeners.mapNotNull { it.onHidePersistentDot() }
 
-        if (animationState.value == SHOWING_PERSISTENT_DOT) {
-            if (scheduledEvent.value != null) {
-                animationState.value = ANIMATION_QUEUED
-            } else {
-                animationState.value = IDLE
-            }
-        }
-
         if (anims.isNotEmpty()) {
             val aSet = AnimatorSet()
             aSet.playTogether(anims)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 94251ff..6346111 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -179,15 +179,20 @@
         }
         if (weatherTarget != null) {
             val clickIntent = weatherTarget.headerAction?.intent
-            val weatherData = WeatherData.fromBundle(weatherTarget.baseAction.extras, { v ->
-                if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
-                    activityStarter.startActivity(
-                        clickIntent,
-                        true, /* dismissShade */
-                        null,
-                        false)
+            val weatherData = weatherTarget.baseAction?.extras?.let { extras ->
+                WeatherData.fromBundle(
+                    extras,
+                ) { _ ->
+                    if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
+                        activityStarter.startActivity(
+                            clickIntent,
+                            true, /* dismissShade */
+                            null,
+                            false)
+                    }
                 }
-            })
+            }
+
             if (weatherData != null) {
                 keyguardUpdateMonitor.sendWeatherData(weatherData)
             }
@@ -446,6 +451,12 @@
         session?.requestSmartspaceUpdate()
     }
 
+    fun removeViewsFromParent(viewGroup: ViewGroup) {
+        smartspaceViews.toList().forEach {
+            viewGroup.removeView(it as View)
+        }
+    }
+
     /**
      * Disconnects the smartspace view from the smartspace service and cleans up any resources.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
index 16f1a45..1b43922 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt
@@ -74,7 +74,7 @@
                     root.setTag(R.id.view_group_fade_helper_previous_value_tag, newAlpha)
                 }
                 addListener(object : AnimatorListenerAdapter() {
-                    override fun onAnimationEnd(animation: Animator?) {
+                    override fun onAnimationEnd(animation: Animator) {
                         endRunnable?.run()
                     }
                 })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 5c72731..e763797 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -99,6 +99,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.RankingUpdatedEvent;
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.NamedListenerSet;
 import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
@@ -161,7 +162,8 @@
     private final HashMap<String, FutureDismissal> mFutureDismissals = new HashMap<>();
 
     @Nullable private CollectionReadyForBuildListener mBuildListener;
-    private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
+    private final NamedListenerSet<NotifCollectionListener>
+            mNotifCollectionListeners = new NamedListenerSet<>();
     private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
     private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
 
@@ -236,7 +238,7 @@
     /** @see NotifPipeline#addCollectionListener(NotifCollectionListener) */
     void addCollectionListener(NotifCollectionListener listener) {
         Assert.isMainThread();
-        mNotifCollectionListeners.add(listener);
+        mNotifCollectionListeners.addIfAbsent(listener);
     }
 
     /** @see NotifPipeline#removeCollectionListener(NotifCollectionListener) */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
index d95d593..5acc50a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt
@@ -21,7 +21,6 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.util.Assert
 import com.android.systemui.util.ListenerSet
-import com.android.systemui.util.isNotEmpty
 import com.android.systemui.util.traceSection
 import java.util.Collections.unmodifiableList
 import java.util.concurrent.Executor
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
index 4ebf337..ad22c61 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt
@@ -51,7 +51,7 @@
 object NotifPipelineChoreographerModule
 
 @Module
-private interface PrivateModule {
+interface PrivateModule {
     @Binds
     fun bindChoreographer(impl: NotifPipelineChoreographerImpl): NotifPipelineChoreographer
 }
@@ -59,7 +59,7 @@
 private const val TIMEOUT_MS: Long = 100
 
 @SysUISingleton
-private class NotifPipelineChoreographerImpl @Inject constructor(
+class NotifPipelineChoreographerImpl @Inject constructor(
     private val viewChoreographer: Choreographer,
     @Main private val executor: DelayableExecutor
 ) : NotifPipelineChoreographer {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 0205523..240ae0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -69,6 +69,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
 import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.NamedListenerSet;
 import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
@@ -121,14 +122,14 @@
     private final List<NotifSection> mNotifSections = new ArrayList<>();
     private NotifStabilityManager mNotifStabilityManager;
 
-    private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
-            new ArrayList<>();
-    private final List<OnBeforeSortListener> mOnBeforeSortListeners =
-            new ArrayList<>();
-    private final List<OnBeforeFinalizeFilterListener> mOnBeforeFinalizeFilterListeners =
-            new ArrayList<>();
-    private final List<OnBeforeRenderListListener> mOnBeforeRenderListListeners =
-            new ArrayList<>();
+    private final NamedListenerSet<OnBeforeTransformGroupsListener>
+            mOnBeforeTransformGroupsListeners = new NamedListenerSet<>();
+    private final NamedListenerSet<OnBeforeSortListener>
+            mOnBeforeSortListeners = new NamedListenerSet<>();
+    private final NamedListenerSet<OnBeforeFinalizeFilterListener>
+            mOnBeforeFinalizeFilterListeners = new NamedListenerSet<>();
+    private final NamedListenerSet<OnBeforeRenderListListener>
+            mOnBeforeRenderListListeners = new NamedListenerSet<>();
     @Nullable private OnRenderListListener mOnRenderListListener;
 
     private List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
@@ -184,28 +185,28 @@
         Assert.isMainThread();
 
         mPipelineState.requireState(STATE_IDLE);
-        mOnBeforeTransformGroupsListeners.add(listener);
+        mOnBeforeTransformGroupsListeners.addIfAbsent(listener);
     }
 
     void addOnBeforeSortListener(OnBeforeSortListener listener) {
         Assert.isMainThread();
 
         mPipelineState.requireState(STATE_IDLE);
-        mOnBeforeSortListeners.add(listener);
+        mOnBeforeSortListeners.addIfAbsent(listener);
     }
 
     void addOnBeforeFinalizeFilterListener(OnBeforeFinalizeFilterListener listener) {
         Assert.isMainThread();
 
         mPipelineState.requireState(STATE_IDLE);
-        mOnBeforeFinalizeFilterListeners.add(listener);
+        mOnBeforeFinalizeFilterListeners.addIfAbsent(listener);
     }
 
     void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
         Assert.isMainThread();
 
         mPipelineState.requireState(STATE_IDLE);
-        mOnBeforeRenderListListeners.add(listener);
+        mOnBeforeRenderListListeners.addIfAbsent(listener);
     }
 
     void addPreRenderInvalidator(Invalidator invalidator) {
@@ -496,7 +497,9 @@
                     mTempSectionMembers.add(entry);
                 }
             }
+            Trace.beginSection(section.getLabel());
             section.getSectioner().onEntriesUpdated(mTempSectionMembers);
+            Trace.endSection();
             mTempSectionMembers.clear();
         }
         Trace.endSection();
@@ -1430,33 +1433,33 @@
 
     private void dispatchOnBeforeTransformGroups(List<ListEntry> entries) {
         Trace.beginSection("ShadeListBuilder.dispatchOnBeforeTransformGroups");
-        for (int i = 0; i < mOnBeforeTransformGroupsListeners.size(); i++) {
-            mOnBeforeTransformGroupsListeners.get(i).onBeforeTransformGroups(entries);
-        }
+        mOnBeforeTransformGroupsListeners.forEachTraced(listener -> {
+            listener.onBeforeTransformGroups(entries);
+        });
         Trace.endSection();
     }
 
     private void dispatchOnBeforeSort(List<ListEntry> entries) {
         Trace.beginSection("ShadeListBuilder.dispatchOnBeforeSort");
-        for (int i = 0; i < mOnBeforeSortListeners.size(); i++) {
-            mOnBeforeSortListeners.get(i).onBeforeSort(entries);
-        }
+        mOnBeforeSortListeners.forEachTraced(listener -> {
+            listener.onBeforeSort(entries);
+        });
         Trace.endSection();
     }
 
     private void dispatchOnBeforeFinalizeFilter(List<ListEntry> entries) {
         Trace.beginSection("ShadeListBuilder.dispatchOnBeforeFinalizeFilter");
-        for (int i = 0; i < mOnBeforeFinalizeFilterListeners.size(); i++) {
-            mOnBeforeFinalizeFilterListeners.get(i).onBeforeFinalizeFilter(entries);
-        }
+        mOnBeforeFinalizeFilterListeners.forEachTraced(listener -> {
+            listener.onBeforeFinalizeFilter(entries);
+        });
         Trace.endSection();
     }
 
     private void dispatchOnBeforeRenderList(List<ListEntry> entries) {
         Trace.beginSection("ShadeListBuilder.dispatchOnBeforeRenderList");
-        for (int i = 0; i < mOnBeforeRenderListListeners.size(); i++) {
-            mOnBeforeRenderListListeners.get(i).onBeforeRenderList(entries);
-        }
+        mOnBeforeRenderListListeners.forEachTraced(listener -> {
+            listener.onBeforeRenderList(entries);
+        });
         Trace.endSection();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
deleted file mode 100644
index f04b24e..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinator.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import android.app.Notification;
-import android.service.notification.StatusBarNotification;
-
-import com.android.systemui.ForegroundServiceController;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import javax.inject.Inject;
-
-/**
- * Handles ForegroundService and AppOp interactions with notifications.
- *  Tags notifications with appOps
- *  Lifetime extends notifications associated with an ongoing ForegroundService.
- *  Filters out notifications that represent foreground services that are no longer running
- *  Puts foreground service notifications into the FGS section. See {@link NotifCoordinators} for
- *      section ordering priority.
- *
- * Previously this logic lived in
- *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceController
- *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener
- *  frameworks/base/packages/SystemUI/src/com/android/systemui/ForegroundServiceLifetimeExtender
- */
-@CoordinatorScope
-public class AppOpsCoordinator implements Coordinator {
-    private static final String TAG = "AppOpsCoordinator";
-
-    private final ForegroundServiceController mForegroundServiceController;
-    private final AppOpsController mAppOpsController;
-    private final DelayableExecutor mMainExecutor;
-
-    private NotifPipeline mNotifPipeline;
-
-    @Inject
-    public AppOpsCoordinator(
-            ForegroundServiceController foregroundServiceController,
-            AppOpsController appOpsController,
-            @Main DelayableExecutor mainExecutor) {
-        mForegroundServiceController = foregroundServiceController;
-        mAppOpsController = appOpsController;
-        mMainExecutor = mainExecutor;
-    }
-
-    @Override
-    public void attach(NotifPipeline pipeline) {
-        mNotifPipeline = pipeline;
-
-        // filter out foreground service notifications that aren't necessary anymore
-        mNotifPipeline.addPreGroupFilter(mNotifFilter);
-
-    }
-
-    public NotifSectioner getSectioner() {
-        return mNotifSectioner;
-    }
-
-    /**
-     * Filters out notifications that represent foreground services that are no longer running or
-     * that already have an app notification with the appOps tagged to
-     */
-    private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
-        @Override
-        public boolean shouldFilterOut(NotificationEntry entry, long now) {
-            StatusBarNotification sbn = entry.getSbn();
-
-            // Filters out system-posted disclosure notifications when unneeded
-            if (mForegroundServiceController.isDisclosureNotification(sbn)
-                    && !mForegroundServiceController.isDisclosureNeededForUser(
-                            sbn.getUser().getIdentifier())) {
-                return true;
-            }
-            return false;
-        }
-    };
-
-    /**
-     * Puts colorized foreground service and call notifications into its own section.
-     */
-    private final NotifSectioner mNotifSectioner = new NotifSectioner("ForegroundService",
-            NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
-        @Override
-        public boolean isInSection(ListEntry entry) {
-            NotificationEntry notificationEntry = entry.getRepresentativeEntry();
-            if (notificationEntry != null) {
-                return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
-            }
-            return false;
-        }
-
-        private boolean isColorizedForegroundService(NotificationEntry entry) {
-            Notification notification = entry.getSbn().getNotification();
-            return notification.isForegroundService()
-                    && notification.isColorized()
-                    && entry.getImportance() > IMPORTANCE_MIN;
-        }
-
-        private boolean isCall(NotificationEntry entry) {
-            Notification notification = entry.getSbn().getNotification();
-            return entry.getImportance() > IMPORTANCE_MIN
-                    && notification.isStyle(Notification.CallStyle.class);
-        }
-    };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
new file mode 100644
index 0000000..63997f8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinator.java
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.statusbar.notification.collection.coordinator;
+
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import android.app.Notification;
+
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
+import com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt;
+
+import javax.inject.Inject;
+
+/**
+ * Handles sectioning for foreground service notifications.
+ *  Puts non-min colorized foreground service notifications into the FGS section. See
+ *  {@link NotifCoordinators} for section ordering priority.
+ */
+@CoordinatorScope
+public class ColorizedFgsCoordinator implements Coordinator {
+    private static final String TAG = "ColorizedCoordinator";
+
+    @Inject
+    public ColorizedFgsCoordinator() {
+    }
+
+    @Override
+    public void attach(NotifPipeline pipeline) {
+    }
+
+    public NotifSectioner getSectioner() {
+        return mNotifSectioner;
+    }
+
+
+    /**
+     * Puts colorized foreground service and call notifications into its own section.
+     */
+    private final NotifSectioner mNotifSectioner = new NotifSectioner("ColorizedSectioner",
+            NotificationPriorityBucketKt.BUCKET_FOREGROUND_SERVICE) {
+        @Override
+        public boolean isInSection(ListEntry entry) {
+            NotificationEntry notificationEntry = entry.getRepresentativeEntry();
+            if (notificationEntry != null) {
+                return isColorizedForegroundService(notificationEntry) || isCall(notificationEntry);
+            }
+            return false;
+        }
+
+        private boolean isColorizedForegroundService(NotificationEntry entry) {
+            Notification notification = entry.getSbn().getNotification();
+            return notification.isForegroundService()
+                    && notification.isColorized()
+                    && entry.getImportance() > IMPORTANCE_MIN;
+        }
+
+        private boolean isCall(NotificationEntry entry) {
+            Notification notification = entry.getSbn().getNotification();
+            return entry.getImportance() > IMPORTANCE_MIN
+                    && notification.isStyle(Notification.CallStyle.class);
+        }
+    };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 0ccab9e..226a957 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -33,34 +33,34 @@
 
 @CoordinatorScope
 class NotifCoordinatorsImpl @Inject constructor(
-    sectionStyleProvider: SectionStyleProvider,
-    featureFlags: FeatureFlags,
-    dataStoreCoordinator: DataStoreCoordinator,
-    hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
-    hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
-    keyguardCoordinator: KeyguardCoordinator,
-    rankingCoordinator: RankingCoordinator,
-    appOpsCoordinator: AppOpsCoordinator,
-    deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
-    bubbleCoordinator: BubbleCoordinator,
-    headsUpCoordinator: HeadsUpCoordinator,
-    gutsCoordinator: GutsCoordinator,
-    conversationCoordinator: ConversationCoordinator,
-    debugModeCoordinator: DebugModeCoordinator,
-    groupCountCoordinator: GroupCountCoordinator,
-    groupWhenCoordinator: GroupWhenCoordinator,
-    mediaCoordinator: MediaCoordinator,
-    preparationCoordinator: PreparationCoordinator,
-    remoteInputCoordinator: RemoteInputCoordinator,
-    rowAppearanceCoordinator: RowAppearanceCoordinator,
-    stackCoordinator: StackCoordinator,
-    shadeEventCoordinator: ShadeEventCoordinator,
-    smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
-    viewConfigCoordinator: ViewConfigCoordinator,
-    visualStabilityCoordinator: VisualStabilityCoordinator,
-    sensitiveContentCoordinator: SensitiveContentCoordinator,
-    dismissibilityCoordinator: DismissibilityCoordinator,
-    dreamCoordinator: DreamCoordinator,
+        sectionStyleProvider: SectionStyleProvider,
+        featureFlags: FeatureFlags,
+        dataStoreCoordinator: DataStoreCoordinator,
+        hideLocallyDismissedNotifsCoordinator: HideLocallyDismissedNotifsCoordinator,
+        hideNotifsForOtherUsersCoordinator: HideNotifsForOtherUsersCoordinator,
+        keyguardCoordinator: KeyguardCoordinator,
+        rankingCoordinator: RankingCoordinator,
+        colorizedFgsCoordinator: ColorizedFgsCoordinator,
+        deviceProvisionedCoordinator: DeviceProvisionedCoordinator,
+        bubbleCoordinator: BubbleCoordinator,
+        headsUpCoordinator: HeadsUpCoordinator,
+        gutsCoordinator: GutsCoordinator,
+        conversationCoordinator: ConversationCoordinator,
+        debugModeCoordinator: DebugModeCoordinator,
+        groupCountCoordinator: GroupCountCoordinator,
+        groupWhenCoordinator: GroupWhenCoordinator,
+        mediaCoordinator: MediaCoordinator,
+        preparationCoordinator: PreparationCoordinator,
+        remoteInputCoordinator: RemoteInputCoordinator,
+        rowAppearanceCoordinator: RowAppearanceCoordinator,
+        stackCoordinator: StackCoordinator,
+        shadeEventCoordinator: ShadeEventCoordinator,
+        smartspaceDedupingCoordinator: SmartspaceDedupingCoordinator,
+        viewConfigCoordinator: ViewConfigCoordinator,
+        visualStabilityCoordinator: VisualStabilityCoordinator,
+        sensitiveContentCoordinator: SensitiveContentCoordinator,
+        dismissibilityCoordinator: DismissibilityCoordinator,
+        dreamCoordinator: DreamCoordinator,
 ) : NotifCoordinators {
 
     private val mCoreCoordinators: MutableList<CoreCoordinator> = ArrayList()
@@ -79,7 +79,7 @@
         mCoordinators.add(hideNotifsForOtherUsersCoordinator)
         mCoordinators.add(keyguardCoordinator)
         mCoordinators.add(rankingCoordinator)
-        mCoordinators.add(appOpsCoordinator)
+        mCoordinators.add(colorizedFgsCoordinator)
         mCoordinators.add(deviceProvisionedCoordinator)
         mCoordinators.add(bubbleCoordinator)
         mCoordinators.add(debugModeCoordinator)
@@ -106,7 +106,7 @@
 
         // Manually add Ordered Sections
         mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
-        mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
+        mOrderedSections.add(colorizedFgsCoordinator.sectioner) // ForegroundService
         mOrderedSections.add(conversationCoordinator.peopleAlertingSectioner) // People Alerting
         mOrderedSections.add(conversationCoordinator.peopleSilentSectioner) // People Silent
         mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 6500ff7..73decfc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -23,6 +23,7 @@
 
 import android.annotation.IntDef;
 import android.os.RemoteException;
+import android.os.Trace;
 import android.service.notification.StatusBarNotification;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -342,11 +343,13 @@
     private void inflateEntry(NotificationEntry entry,
             NotifUiAdjustment newAdjustment,
             String reason) {
+        Trace.beginSection("PrepCoord.inflateEntry");
         abortInflation(entry, reason);
         mInflationAdjustments.put(entry, newAdjustment);
         mInflatingNotifs.add(entry);
         NotifInflater.Params params = getInflaterParams(newAdjustment, reason);
         mNotifInflater.inflateViews(entry, params, this::onInflationFinished);
+        Trace.endSection();
     }
 
     private void rebind(NotificationEntry entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index aeeeb4f..9ba1f7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -38,7 +38,7 @@
 interface SensitiveContentCoordinatorModule
 
 @Module
-private interface PrivateSensitiveContentCoordinatorModule {
+interface PrivateSensitiveContentCoordinatorModule {
     @Binds
     fun bindCoordinator(impl: SensitiveContentCoordinatorImpl): SensitiveContentCoordinator
 }
@@ -47,7 +47,7 @@
 interface SensitiveContentCoordinator : Coordinator
 
 @CoordinatorScope
-private class SensitiveContentCoordinatorImpl @Inject constructor(
+class SensitiveContentCoordinatorImpl @Inject constructor(
     private val dynamicPrivacyController: DynamicPrivacyController,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
index 357c5b2..c00bb93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt
@@ -50,7 +50,7 @@
 @Module(includes = [
     SensitiveContentCoordinatorModule::class,
 ])
-private abstract class InternalCoordinatorsModule {
+abstract class InternalCoordinatorsModule {
     @Binds
     @Internal
     abstract fun bindNotifCoordinators(impl: NotifCoordinatorsImpl): NotifCoordinators
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
index e20f0e5..e06e2d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt
@@ -22,6 +22,8 @@
 import android.service.notification.StatusBarNotification
 import com.android.systemui.statusbar.notification.collection.NotifCollection
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.NamedListenerSet
+import com.android.systemui.util.traceSection
 
 /**
  * Set of classes that represent the various events that [NotifCollection] can dispatch to
@@ -30,10 +32,10 @@
  * These events build up in a queue and are periodically emitted in chunks by the collection.
  */
 
-sealed class NotifEvent {
-    fun dispatchTo(listeners: List<NotifCollectionListener>) {
-        for (i in listeners.indices) {
-            dispatchToListener(listeners[i])
+sealed class NotifEvent(private val traceName: String) {
+    fun dispatchTo(listeners: NamedListenerSet<NotifCollectionListener>) {
+        traceSection(traceName) {
+            listeners.forEachTraced(::dispatchToListener)
         }
     }
 
@@ -43,7 +45,7 @@
 data class BindEntryEvent(
     val entry: NotificationEntry,
     val sbn: StatusBarNotification
-) : NotifEvent() {
+) : NotifEvent("onEntryBind") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onEntryBind(entry, sbn)
     }
@@ -51,7 +53,7 @@
 
 data class InitEntryEvent(
     val entry: NotificationEntry
-) : NotifEvent() {
+) : NotifEvent("onEntryInit") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onEntryInit(entry)
     }
@@ -59,7 +61,7 @@
 
 data class EntryAddedEvent(
     val entry: NotificationEntry
-) : NotifEvent() {
+) : NotifEvent("onEntryAdded") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onEntryAdded(entry)
     }
@@ -68,7 +70,7 @@
 data class EntryUpdatedEvent(
     val entry: NotificationEntry,
     val fromSystem: Boolean
-) : NotifEvent() {
+) : NotifEvent(if (fromSystem) "onEntryUpdated" else "onEntryUpdated fromSystem=true") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onEntryUpdated(entry, fromSystem)
     }
@@ -77,7 +79,7 @@
 data class EntryRemovedEvent(
     val entry: NotificationEntry,
     val reason: Int
-) : NotifEvent() {
+) : NotifEvent("onEntryRemoved ${cancellationReasonDebugString(reason)}") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onEntryRemoved(entry, reason)
     }
@@ -85,7 +87,7 @@
 
 data class CleanUpEntryEvent(
     val entry: NotificationEntry
-) : NotifEvent() {
+) : NotifEvent("onEntryCleanUp") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onEntryCleanUp(entry)
     }
@@ -93,13 +95,13 @@
 
 data class RankingUpdatedEvent(
     val rankingMap: RankingMap
-) : NotifEvent() {
+) : NotifEvent("onRankingUpdate") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onRankingUpdate(rankingMap)
     }
 }
 
-class RankingAppliedEvent() : NotifEvent() {
+class RankingAppliedEvent : NotifEvent("onRankingApplied") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onRankingApplied()
     }
@@ -110,7 +112,7 @@
     val user: UserHandle,
     val channel: NotificationChannel,
     val modificationType: Int
-) : NotifEvent() {
+) : NotifEvent("onNotificationChannelModified") {
     override fun dispatchToListener(listener: NotifCollectionListener) {
         listener.onNotificationChannelModified(pkgName, user, channel, modificationType)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
index fd5bae1..c873e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.util.Assert
 import com.android.systemui.util.ListenerSet
-import com.android.systemui.util.isNotEmpty
 import java.io.PrintWriter
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
index 7b59266..49990d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt
@@ -38,7 +38,7 @@
 }
 
 @SectionHeaderScope
-internal class SectionHeaderNodeControllerImpl @Inject constructor(
+class SectionHeaderNodeControllerImpl @Inject constructor(
     @NodeLabel override val nodeLabel: String,
     private val layoutInflater: LayoutInflater,
     @HeaderText @StringRes private val headerTextResId: Int,
@@ -103,4 +103,4 @@
     override fun offerToKeepInParentForAnimation(): Boolean = false
     override fun removeFromParentIfKeptForAnimation(): Boolean = false
     override fun resetKeepInParentForAnimation() {}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
index 2a9cfd0..75801a8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt
@@ -145,7 +145,7 @@
 }
 
 @Module
-private abstract class SectionHeaderBindingModule {
+abstract class SectionHeaderBindingModule {
     @Binds abstract fun bindsNodeController(impl: SectionHeaderNodeControllerImpl): NodeController
     @Binds abstract fun bindsSectionHeaderController(
         impl: SectionHeaderNodeControllerImpl
@@ -182,4 +182,4 @@
 
 @Scope
 @Retention(AnnotationRetention.BINARY)
-annotation class SectionHeaderScope
\ No newline at end of file
+annotation class SectionHeaderScope
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index d896541..9d95342 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.util.traceSection
 import javax.inject.Inject
 
 /**
@@ -95,7 +96,7 @@
      * @throws InflationException Exception if required icons are not valid or specified
      */
     @Throws(InflationException::class)
-    fun createIcons(entry: NotificationEntry) {
+    fun createIcons(entry: NotificationEntry) = traceSection("IconManager.createIcons") {
         // Construct the status bar icon view.
         val sbIcon = iconBuilder.createIconView(entry)
         sbIcon.scaleType = ImageView.ScaleType.CENTER_INSIDE
@@ -143,9 +144,9 @@
      * @throws InflationException Exception if required icons are not valid or specified
      */
     @Throws(InflationException::class)
-    fun updateIcons(entry: NotificationEntry) {
+    fun updateIcons(entry: NotificationEntry) = traceSection("IconManager.updateIcons") {
         if (!entry.icons.areIconsAvailable) {
-            return
+            return@traceSection
         }
         entry.icons.smallIconDescriptor = null
         entry.icons.peopleAvatarDescriptor = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 106d11f..7d1cca8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.init
 
 import android.service.notification.StatusBarNotification
-import com.android.systemui.ForegroundServiceNotificationListener
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager
@@ -70,7 +69,6 @@
     private val animatedImageNotificationManager: AnimatedImageNotificationManager,
     private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
     private val bubblesOptional: Optional<Bubbles>,
-    private val fgsNotifListener: ForegroundServiceNotificationListener,
     private val featureFlags: FeatureFlags
 ) : NotificationsController {
 
@@ -105,7 +103,6 @@
         notificationsMediaManager.setUpWithPresenter(presenter)
         notificationLogger.setUpWithContainer(listContainer)
         peopleSpaceWidgetManager.attach(notificationListener)
-        fgsNotifListener.init()
     }
 
     // TODO: Convert all functions below this line into listeners instead of public methods
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
index 78225df..b2c32cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt
@@ -57,7 +57,7 @@
 object KeyguardNotificationVisibilityProviderModule
 
 @Module
-private interface KeyguardNotificationVisibilityProviderImplModule {
+interface KeyguardNotificationVisibilityProviderImplModule {
     @Binds
     fun bindImpl(impl: KeyguardNotificationVisibilityProviderImpl):
             KeyguardNotificationVisibilityProvider
@@ -69,7 +69,7 @@
 }
 
 @SysUISingleton
-private class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
+class KeyguardNotificationVisibilityProviderImpl @Inject constructor(
     @Main private val handler: Handler,
     private val keyguardStateController: KeyguardStateController,
     private val lockscreenUserManager: NotificationLockscreenUserManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
index 5e7e4be..1b790fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java
@@ -321,7 +321,8 @@
     protected void setBackgroundTintColor(int color) {
         if (color != mCurrentBackgroundTint) {
             mCurrentBackgroundTint = color;
-            if (color == mNormalColor) {
+            // TODO(282173943): re-enable this tinting optimization when Resources are thread-safe
+            if (false && color == mNormalColor) {
                 // We don't need to tint a normal notification
                 color = 0;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
index 38a1579..9c4aa07 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt
@@ -60,7 +60,7 @@
     override fun onFinishInflate() {
         super.onFinishInflate()
 
-        appControlRow = findViewById(R.id.app_control)
+        appControlRow = requireViewById(R.id.app_control)
     }
 
     /**
@@ -143,9 +143,9 @@
     lateinit var switch: Switch
 
     override fun onFinishInflate() {
-        iconView = findViewById(R.id.icon)
-        channelName = findViewById(R.id.app_name)
-        switch = findViewById(R.id.toggle)
+        iconView = requireViewById(R.id.icon)
+        channelName = requireViewById(R.id.app_name)
+        switch = requireViewById(R.id.toggle)
 
         setOnClickListener { switch.toggle() }
     }
@@ -174,9 +174,9 @@
 
     override fun onFinishInflate() {
         super.onFinishInflate()
-        channelName = findViewById(R.id.channel_name)
-        channelDescription = findViewById(R.id.channel_description)
-        switch = findViewById(R.id.toggle)
+        channelName = requireViewById(R.id.channel_name)
+        channelDescription = requireViewById(R.id.channel_description)
+        switch = requireViewById(R.id.toggle)
         switch.setOnCheckedChangeListener { _, b ->
             channel?.let {
                 controller.proposeEditForChannel(it, if (b) it.importance else IMPORTANCE_NONE)
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 ed489a6c..d92d11b 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
@@ -1894,6 +1894,9 @@
             return traceTag;
         }
 
+        if (isSummaryWithChildren()) {
+            return traceTag + "(summary)";
+        }
         Class<? extends Notification.Style> style =
                 getEntry().getSbn().getNotification().getNotificationStyle();
         if (style == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index a4e8c2e..80f5d19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -21,12 +21,16 @@
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
 
+import android.net.Uri;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.statusbar.IStatusBarService;
@@ -71,6 +75,10 @@
 @NotificationRowScope
 public class ExpandableNotificationRowController implements NotifViewController {
     private static final String TAG = "NotifRowController";
+
+    static final Uri BUBBLES_SETTING_URI =
+            Settings.Secure.getUriFor(Settings.Secure.NOTIFICATION_BUBBLES);
+    private static final String BUBBLES_SETTING_ENABLED_VALUE = "1";
     private final ExpandableNotificationRow mView;
     private final NotificationListContainer mListContainer;
     private final RemoteInputViewSubcomponent.Factory mRemoteInputViewSubcomponentFactory;
@@ -104,6 +112,23 @@
     private final ExpandableNotificationRowDragController mDragController;
     private final NotificationDismissibilityProvider mDismissibilityProvider;
     private final IStatusBarService mStatusBarService;
+
+    private final NotificationSettingsController mSettingsController;
+
+    @VisibleForTesting
+    final NotificationSettingsController.Listener mSettingsListener =
+            new NotificationSettingsController.Listener() {
+                @Override
+                public void onSettingChanged(Uri setting, int userId, String value) {
+                    if (BUBBLES_SETTING_URI.equals(setting)) {
+                        final int viewUserId = mView.getEntry().getSbn().getUserId();
+                        if (viewUserId == UserHandle.USER_ALL || viewUserId == userId) {
+                            mView.getPrivateLayout().setBubblesEnabledForUser(
+                                    BUBBLES_SETTING_ENABLED_VALUE.equals(value));
+                        }
+                    }
+                }
+            };
     private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback =
             new ExpandableNotificationRow.ExpandableNotificationRowLogger() {
                 @Override
@@ -201,6 +226,7 @@
             FeatureFlags featureFlags,
             PeopleNotificationIdentifier peopleNotificationIdentifier,
             Optional<BubblesManager> bubblesManagerOptional,
+            NotificationSettingsController settingsController,
             ExpandableNotificationRowDragController dragController,
             NotificationDismissibilityProvider dismissibilityProvider,
             IStatusBarService statusBarService) {
@@ -229,6 +255,7 @@
         mFeatureFlags = featureFlags;
         mPeopleNotificationIdentifier = peopleNotificationIdentifier;
         mBubblesManagerOptional = bubblesManagerOptional;
+        mSettingsController = settingsController;
         mDragController = dragController;
         mMetricsLogger = metricsLogger;
         mChildrenContainerLogger = childrenContainerLogger;
@@ -298,12 +325,14 @@
                         NotificationMenuRowPlugin.class, false /* Allow multiple */);
                 mView.setOnKeyguard(mStatusBarStateController.getState() == KEYGUARD);
                 mStatusBarStateController.addCallback(mStatusBarStateListener);
+                mSettingsController.addCallback(BUBBLES_SETTING_URI, mSettingsListener);
             }
 
             @Override
             public void onViewDetachedFromWindow(View v) {
                 mPluginManager.removePluginListener(mView);
                 mStatusBarStateController.removeCallback(mStatusBarStateListener);
+                mSettingsController.removeCallback(BUBBLES_SETTING_URI, mSettingsListener);
             }
         });
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
index 6bbeebf..0989df6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/FooterView.java
@@ -16,11 +16,15 @@
 
 package com.android.systemui.statusbar.notification.row;
 
+import static android.graphics.PorterDuff.Mode.SRC_ATOP;
+
 import android.annotation.ColorInt;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.util.IndentingPrintWriter;
@@ -157,10 +161,20 @@
      */
     public void updateColors() {
         Resources.Theme theme = mContext.getTheme();
-        int textColor = getResources().getColor(R.color.notif_pill_text, theme);
-        mClearAllButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
+        final @ColorInt int textColor = getResources().getColor(R.color.notif_pill_text, theme);
+        final Drawable clearAllBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+        final Drawable manageBg = theme.getDrawable(R.drawable.notif_footer_btn_background);
+        // TODO(b/282173943): Remove redundant tinting once Resources are thread-safe
+        final @ColorInt int buttonBgColor =
+                Utils.getColorAttrDefaultColor(mContext, com.android.internal.R.attr.colorSurface);
+        final ColorFilter bgColorFilter = new PorterDuffColorFilter(buttonBgColor, SRC_ATOP);
+        if (buttonBgColor != 0) {
+            clearAllBg.setColorFilter(bgColorFilter);
+            manageBg.setColorFilter(bgColorFilter);
+        }
+        mClearAllButton.setBackground(clearAllBg);
         mClearAllButton.setTextColor(textColor);
-        mManageButton.setBackground(theme.getDrawable(R.drawable.notif_footer_btn_background));
+        mManageButton.setBackground(manageBg);
         mManageButton.setTextColor(textColor);
         final @ColorInt int labelTextColor =
                 Utils.getColorAttrDefaultColor(mContext, android.R.attr.textColorPrimary);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 0322573..065828b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -41,6 +41,8 @@
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 
+import androidx.annotation.MainThread;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.R;
@@ -65,7 +67,6 @@
 import com.android.systemui.statusbar.policy.SmartReplyView;
 import com.android.systemui.statusbar.policy.dagger.RemoteInputViewSubcomponent;
 import com.android.systemui.util.Compile;
-import com.android.systemui.wmshell.BubblesManager;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -134,6 +135,7 @@
     private PeopleNotificationIdentifier mPeopleIdentifier;
     private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory;
     private IStatusBarService mStatusBarService;
+    private boolean mBubblesEnabledForUser;
 
     /**
      * List of listeners for when content views become inactive (i.e. not the showing view).
@@ -1440,12 +1442,20 @@
         }
     }
 
+    @MainThread
+    public void setBubblesEnabledForUser(boolean enabled) {
+        mBubblesEnabledForUser = enabled;
+
+        applyBubbleAction(mExpandedChild, mNotificationEntry);
+        applyBubbleAction(mHeadsUpChild, mNotificationEntry);
+    }
+
     @VisibleForTesting
     boolean shouldShowBubbleButton(NotificationEntry entry) {
         boolean isPersonWithShortcut =
                 mPeopleIdentifier.getPeopleNotificationType(entry)
                         >= PeopleNotificationIdentifier.TYPE_FULL_PERSON;
-        return BubblesManager.areBubblesEnabled(mContext, entry.getSbn().getUser())
+        return mBubblesEnabledForUser
                 && isPersonWithShortcut
                 && entry.getBubbleMetadata() != null;
     }
@@ -2079,6 +2089,7 @@
             pw.print("null");
         }
         pw.println();
+        pw.println("mBubblesEnabledForUser: " + mBubblesEnabledForUser);
 
         pw.print("RemoteInputViews { ");
         pw.print(" visibleType: " + mVisibleType);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 9bc0333..7134f15 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -48,6 +48,7 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.transition.ChangeBounds;
@@ -118,6 +119,8 @@
     private NotificationGuts mGutsContainer;
     private OnConversationSettingsClickListener mOnConversationSettingsClickListener;
 
+    private UserManager mUm;
+
     @VisibleForTesting
     boolean mSkipPost = false;
     private int mActualHeight;
@@ -155,7 +158,9 @@
         // People Tile add request.
         if (mSelectedAction == ACTION_FAVORITE && getPriority() != mSelectedAction) {
             mShadeController.animateCollapseShade();
-            mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
+            if (mUm.isSameProfileGroup(UserHandle.USER_SYSTEM, mSbn.getNormalizedUserId())) {
+                mPeopleSpaceWidgetManager.requestPinAppWidget(mShortcutInfo, new Bundle());
+            }
         }
         mGutsContainer.closeControls(v, /* save= */ true);
     };
@@ -188,6 +193,7 @@
     public void bindNotification(
             ShortcutManager shortcutManager,
             PackageManager pm,
+            UserManager um,
             PeopleSpaceWidgetManager peopleSpaceWidgetManager,
             INotificationManager iNotificationManager,
             OnUserInteractionCallback onUserInteractionCallback,
@@ -211,6 +217,7 @@
         mEntry = entry;
         mSbn = entry.getSbn();
         mPm = pm;
+        mUm = um;
         mAppName = mPackageName;
         mOnSettingsClickListener = onSettingsClick;
         mNotificationChannel = notificationChannel;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 7dbca42..6f79ea8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -30,6 +30,7 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.util.ArraySet;
@@ -112,6 +113,9 @@
     private Runnable mOpenRunnable;
     private final INotificationManager mNotificationManager;
     private final PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
+
+    private final UserManager mUserManager;
+
     private final LauncherApps mLauncherApps;
     private final ShortcutManager mShortcutManager;
     private final UserContextProvider mContextTracker;
@@ -128,6 +132,7 @@
             AccessibilityManager accessibilityManager,
             HighPriorityProvider highPriorityProvider,
             INotificationManager notificationManager,
+            UserManager userManager,
             PeopleSpaceWidgetManager peopleSpaceWidgetManager,
             LauncherApps launcherApps,
             ShortcutManager shortcutManager,
@@ -150,6 +155,7 @@
         mAccessibilityManager = accessibilityManager;
         mHighPriorityProvider = highPriorityProvider;
         mNotificationManager = notificationManager;
+        mUserManager = userManager;
         mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
         mLauncherApps = launcherApps;
         mShortcutManager = shortcutManager;
@@ -471,6 +477,7 @@
         notificationInfoView.bindNotification(
                 mShortcutManager,
                 pmUser,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mNotificationManager,
                 mOnUserInteractionCallback,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
new file mode 100644
index 0000000..51e4537
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java
@@ -0,0 +1,180 @@
+/*
+ * 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.row;
+
+import android.content.Context;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import javax.inject.Inject;
+
+/**
+ * Centralized controller for listening to Secure Settings changes and informing in-process
+ * listeners, on a background thread.
+ */
+@SysUISingleton
+public class NotificationSettingsController implements Dumpable {
+
+    private final static String TAG = "NotificationSettingsController";
+    private final UserTracker mUserTracker;
+    private final UserTracker.Callback mCurrentUserTrackerCallback;
+    private final Handler mMainHandler;
+    private final Handler mBackgroundHandler;
+    private final ContentObserver mContentObserver;
+    private final SecureSettings mSecureSettings;
+    private final HashMap<Uri, ArrayList<Listener>> mListeners = new HashMap<>();
+
+    @Inject
+    public NotificationSettingsController(UserTracker userTracker,
+            @Main Handler mainHandler,
+            @Background Handler backgroundHandler,
+            SecureSettings secureSettings,
+            DumpManager dumpManager) {
+        mUserTracker = userTracker;
+        mMainHandler = mainHandler;
+        mBackgroundHandler = backgroundHandler;
+        mSecureSettings = secureSettings;
+        mContentObserver = new ContentObserver(mBackgroundHandler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                super.onChange(selfChange, uri);
+                synchronized (mListeners) {
+                    if (mListeners.containsKey(uri)) {
+                        int userId = mUserTracker.getUserId();
+                        String value = getCurrentSettingValue(uri, userId);
+                        for (Listener listener : mListeners.get(uri)) {
+                            mMainHandler.post(() -> listener.onSettingChanged(uri, userId, value));
+                        }
+                    }
+                }
+            }
+        };
+
+        mCurrentUserTrackerCallback = new UserTracker.Callback() {
+            @Override
+            public void onUserChanged(int newUser, Context userContext) {
+                synchronized (mListeners) {
+                    if (mListeners.size() > 0) {
+                        mSecureSettings.unregisterContentObserver(mContentObserver);
+                        for (Uri uri : mListeners.keySet()) {
+                            mSecureSettings.registerContentObserverForUser(
+                                    uri, false, mContentObserver, newUser);
+                        }
+                    }
+                }
+            }
+        };
+        mUserTracker.addCallback(
+                mCurrentUserTrackerCallback, new HandlerExecutor(mBackgroundHandler));
+
+        dumpManager.registerNormalDumpable(TAG, this);
+    }
+
+    /**
+     * Register a callback whenever the given secure settings changes.
+     *
+     * On registration, will trigger the listener on the main thread with the current value of
+     * the setting.
+     */
+    @Main
+    public void addCallback(@NonNull Uri uri, @NonNull Listener listener) {
+        if (uri == null || listener == null) {
+            return;
+        }
+        synchronized (mListeners) {
+            ArrayList<Listener> currentListeners = mListeners.get(uri);
+            if (currentListeners == null) {
+                currentListeners = new ArrayList<>();
+            }
+            if (!currentListeners.contains(listener)) {
+                currentListeners.add(listener);
+            }
+            mListeners.put(uri, currentListeners);
+            if (currentListeners.size() == 1) {
+                mSecureSettings.registerContentObserverForUser(
+                        uri, false, mContentObserver, mUserTracker.getUserId());
+            }
+        }
+        mBackgroundHandler.post(() -> {
+            int userId = mUserTracker.getUserId();
+            String value = getCurrentSettingValue(uri, userId);
+            mMainHandler.post(() -> listener.onSettingChanged(uri, userId, value));
+        });
+
+    }
+
+    public void removeCallback(Uri uri, Listener listener) {
+        synchronized (mListeners) {
+            ArrayList<Listener> currentListeners = mListeners.get(uri);
+
+            if (currentListeners != null) {
+                currentListeners.remove(listener);
+            }
+            if (currentListeners == null || currentListeners.size() == 0) {
+                mListeners.remove(uri);
+            }
+
+            if (mListeners.size() == 0) {
+                mSecureSettings.unregisterContentObserver(mContentObserver);
+            }
+        }
+    }
+
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        synchronized (mListeners) {
+            pw.println("Settings Uri Listener List:");
+            for (Uri uri : mListeners.keySet()) {
+                pw.println("   Uri=" + uri);
+                for (Listener listener : mListeners.get(uri)) {
+                    pw.println("      Listener=" + listener.getClass().getName());
+                }
+            }
+        }
+    }
+
+    private String getCurrentSettingValue(Uri uri, int userId) {
+        final String setting = uri == null ? null : uri.getLastPathSegment();
+        return mSecureSettings.getStringForUser(setting, userId);
+    }
+
+    /**
+     * Listener invoked whenever settings are changed.
+     */
+    public interface Listener {
+        @MainThread
+        void onSettingChanged(@NonNull Uri setting, int userId, @Nullable String value);
+    }
+}
\ No newline at end of file
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 d71bc2f..5e3a67e 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
@@ -93,6 +93,7 @@
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.TouchLogger;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
@@ -3480,6 +3481,11 @@
         return super.onTouchEvent(ev);
     }
 
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        return TouchLogger.logDispatchTouch(TAG, ev, super.dispatchTouchEvent(ev));
+    }
+
     void dispatchDownEventToScroller(MotionEvent ev) {
         MotionEvent downEvent = MotionEvent.obtain(ev);
         downEvent.setAction(MotionEvent.ACTION_DOWN);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index dcd18dd..baeae79 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -401,7 +401,7 @@
         isActivityIntent: Boolean,
         showOverLockscreen: Boolean,
     ): Boolean {
-        // TODO(b/184121838): Support launch animations when occluded.
+        // TODO(b/294418322): Support launch animations when occluded.
         if (keyguardStateController.isOccluded) {
             return false
         }
@@ -914,8 +914,8 @@
             val packages: Array<String> =
                 context.resources.getStringArray(R.array.system_ui_packages)
             for (pkg in packages) {
-                if (intent.component == null) break
-                if (pkg == intent.component.packageName) {
+                val componentName = intent.component ?: break
+                if (pkg == componentName.packageName) {
                     return UserHandle(UserHandle.myUserId())
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 632f241..127569d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -634,7 +634,7 @@
 
     private final ActivityIntentHelper mActivityIntentHelper;
 
-    private final NotificationStackScrollLayoutController mStackScrollerController;
+    public final NotificationStackScrollLayoutController mStackScrollerController;
 
     private final ColorExtractor.OnColorsChangedListener mOnColorsChangedListener =
             (extractor, which) -> updateTheme();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
index 7dcdc0b..97cb45a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt
@@ -22,8 +22,6 @@
 import android.view.View.LAYOUT_DIRECTION_RTL
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.policy.ConfigurationController
-
-import java.util.ArrayList
 import javax.inject.Inject
 
 @SysUISingleton
@@ -40,6 +38,7 @@
     private var localeList: LocaleList? = null
     private val context: Context
     private var layoutDirection: Int
+    private var orientation = Configuration.ORIENTATION_UNDEFINED
 
     init {
         val currentConfig = context.resources.configuration
@@ -134,8 +133,18 @@
                 it.onThemeChanged()
             }
         }
+
+        val newOrientation = newConfig.orientation
+        if (orientation != newOrientation) {
+            orientation = newOrientation
+            listeners.filterForEach({ this.listeners.contains(it) }) {
+                it.onOrientationChanged(orientation)
+            }
+        }
     }
 
+
+
     override fun addCallback(listener: ConfigurationController.ConfigurationListener) {
         listeners.add(listener)
         listener.onDensityOrFontScaleChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index f15dcc3..a1f12b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -81,6 +81,7 @@
     private final BiConsumer<Float, Float> mSetExpandedHeight = this::setAppearFraction;
     private final KeyguardBypassController mBypassController;
     private final StatusBarStateController mStatusBarStateController;
+    private final PhoneStatusBarTransitions mPhoneStatusBarTransitions;
     private final CommandQueue mCommandQueue;
     private final NotificationWakeUpCoordinator mWakeUpCoordinator;
 
@@ -109,6 +110,7 @@
             NotificationIconAreaController notificationIconAreaController,
             HeadsUpManagerPhone headsUpManager,
             StatusBarStateController stateController,
+            PhoneStatusBarTransitions phoneStatusBarTransitions,
             KeyguardBypassController bypassController,
             NotificationWakeUpCoordinator wakeUpCoordinator,
             DarkIconDispatcher darkIconDispatcher,
@@ -156,6 +158,7 @@
         });
         mBypassController = bypassController;
         mStatusBarStateController = stateController;
+        mPhoneStatusBarTransitions = phoneStatusBarTransitions;
         mWakeUpCoordinator = wakeUpCoordinator;
         mCommandQueue = commandQueue;
         mKeyguardStateController = keyguardStateController;
@@ -203,6 +206,7 @@
     @Override
     public void onHeadsUpStateChanged(@NonNull NotificationEntry entry, boolean isHeadsUp) {
         updateHeadsUpAndPulsingRoundness(entry);
+        mPhoneStatusBarTransitions.onHeadsUpStateChanged(isHeadsUp);
     }
 
     private void updateTopEntry() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
index 34bbd13..cdd410e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.util.animation.requiresRemeasuring
 
 /**
  * Renders the bottom area of the lock-screen. Concerned primarily with the quick affordance UI
@@ -98,7 +99,7 @@
         ambientIndicationArea?.let { nonNullAmbientIndicationArea ->
             // remove old ambient indication from its parent
             val originalAmbientIndicationView =
-                oldBottomArea.findViewById<View>(R.id.ambient_indication_container)
+                oldBottomArea.requireViewById<View>(R.id.ambient_indication_container)
             (originalAmbientIndicationView.parent as ViewGroup).removeView(
                 originalAmbientIndicationView
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index 117a1a4..914e0c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -87,6 +87,7 @@
     private int mStatusBarPaddingEnd;
     private int mMinDotWidth;
     private View mSystemIconsContainer;
+    private View mSystemIcons;
     private final MutableStateFlow<DarkChange> mDarkChange = StateFlowKt.MutableStateFlow(
             DarkChange.EMPTY);
 
@@ -119,6 +120,7 @@
     protected void onFinishInflate() {
         super.onFinishInflate();
         mSystemIconsContainer = findViewById(R.id.system_icons_container);
+        mSystemIcons = findViewById(R.id.system_icons);
         mMultiUserAvatar = findViewById(R.id.multi_user_avatar);
         mCarrierLabel = findViewById(R.id.keyguard_carrier_text);
         mBatteryView = mSystemIconsContainer.findViewById(R.id.battery);
@@ -167,6 +169,13 @@
                 mStatusIconContainer.getPaddingBottom()
         );
 
+        mSystemIcons.setPaddingRelative(
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_start),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_top),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_end),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_bottom)
+        );
+
         // Respect font size setting.
         mCarrierLabel.setTextSize(TypedValue.COMPLEX_UNIT_PX,
                 getResources().getDimensionPixelSize(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index 15c6dcf..cc38405 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -31,6 +31,8 @@
 
     private final float mIconAlphaWhenOpaque;
 
+    private boolean mIsHeadsUp;
+
     private View mStartSide, mStatusIcons, mBattery;
     private Animator mCurrentAnimation;
 
@@ -52,15 +54,32 @@
         return ObjectAnimator.ofFloat(v, "alpha", v.getAlpha(), toAlpha);
     }
 
-    private float getNonBatteryClockAlphaFor(int mode) {
-        return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
-                : !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
-                : mIconAlphaWhenOpaque;
+    private float getStatusIconsAlphaFor(int mode) {
+        return getDefaultAlphaFor(mode);
+    }
+
+    private float getStartSideAlphaFor(int mode) {
+        // When there's a heads up notification, we need the start side icons to show regardless of
+        // lights out mode.
+        if (mIsHeadsUp) {
+            return getIconAlphaBasedOnOpacity(mode);
+        }
+        return getDefaultAlphaFor(mode);
     }
 
     private float getBatteryClockAlpha(int mode) {
         return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_BATTERY_CLOCK
-                : getNonBatteryClockAlphaFor(mode);
+                : getIconAlphaBasedOnOpacity(mode);
+    }
+
+    private float getDefaultAlphaFor(int mode) {
+        return isLightsOut(mode) ? ICON_ALPHA_WHEN_LIGHTS_OUT_NON_BATTERY_CLOCK
+                : getIconAlphaBasedOnOpacity(mode);
+    }
+
+    private float getIconAlphaBasedOnOpacity(int mode) {
+        return !isOpaque(mode) ? ICON_ALPHA_WHEN_NOT_OPAQUE
+                : mIconAlphaWhenOpaque;
     }
 
     private boolean isOpaque(int mode) {
@@ -74,19 +93,28 @@
         applyMode(newMode, animate);
     }
 
+    /** Informs this controller that the heads up notification state has changed. */
+    public void onHeadsUpStateChanged(boolean isHeadsUp) {
+        mIsHeadsUp = isHeadsUp;
+        // We want the icon to be fully visible when the HUN appears, so just immediately change the
+        // icon visibility and don't animate.
+        applyMode(getMode(), /* animate= */ false);
+    }
+
     private void applyMode(int mode, boolean animate) {
         if (mStartSide == null) return; // pre-init
-        float newAlpha = getNonBatteryClockAlphaFor(mode);
-        float newAlphaBC = getBatteryClockAlpha(mode);
+        float newStartSideAlpha = getStartSideAlphaFor(mode);
+        float newStatusIconsAlpha = getStatusIconsAlphaFor(mode);
+        float newBatteryAlpha = getBatteryClockAlpha(mode);
         if (mCurrentAnimation != null) {
             mCurrentAnimation.cancel();
         }
         if (animate) {
             AnimatorSet anims = new AnimatorSet();
             anims.playTogether(
-                    animateTransitionTo(mStartSide, newAlpha),
-                    animateTransitionTo(mStatusIcons, newAlpha),
-                    animateTransitionTo(mBattery, newAlphaBC)
+                    animateTransitionTo(mStartSide, newStartSideAlpha),
+                    animateTransitionTo(mStatusIcons, newStatusIconsAlpha),
+                    animateTransitionTo(mBattery, newBatteryAlpha)
                     );
             if (isLightsOut(mode)) {
                 anims.setDuration(LIGHTS_OUT_DURATION);
@@ -94,9 +122,9 @@
             anims.start();
             mCurrentAnimation = anims;
         } else {
-            mStartSide.setAlpha(newAlpha);
-            mStatusIcons.setAlpha(newAlpha);
-            mBattery.setAlpha(newAlphaBC);
+            mStartSide.setAlpha(newStartSideAlpha);
+            mStatusIcons.setAlpha(newStatusIconsAlpha);
+            mBattery.setAlpha(newBatteryAlpha);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index d546a84..83a040c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -208,25 +208,29 @@
         ViewGroup.LayoutParams layoutParams = getLayoutParams();
         mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext);
         layoutParams.height = mStatusBarHeight - waterfallTopInset;
+        updatePaddings();
+        setLayoutParams(layoutParams);
+    }
 
-        int statusBarPaddingTop = getResources().getDimensionPixelSize(
-                R.dimen.status_bar_padding_top);
+    private void updatePaddings() {
         int statusBarPaddingStart = getResources().getDimensionPixelSize(
                 R.dimen.status_bar_padding_start);
-        int statusBarPaddingEnd = getResources().getDimensionPixelSize(
-                R.dimen.status_bar_padding_end);
 
-        View sbContents = findViewById(R.id.status_bar_contents);
-        sbContents.setPaddingRelative(
+        findViewById(R.id.status_bar_contents).setPaddingRelative(
                 statusBarPaddingStart,
-                statusBarPaddingTop,
-                statusBarPaddingEnd,
+                getResources().getDimensionPixelSize(R.dimen.status_bar_padding_top),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_padding_end),
                 0);
 
         findViewById(R.id.notification_lights_out)
                 .setPaddingRelative(0, statusBarPaddingStart, 0, 0);
 
-        setLayoutParams(layoutParams);
+        findViewById(R.id.system_icons).setPaddingRelative(
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_start),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_top),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_end),
+                getResources().getDimensionPixelSize(R.dimen.status_bar_icons_padding_bottom)
+        );
     }
 
     private void updateLayoutForCutout() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 2affb817..931aedd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -75,13 +75,13 @@
     }
 
     override fun onViewAttached() {
-        statusContainer = mView.findViewById(R.id.system_icons)
+        statusContainer = mView.requireViewById(R.id.system_icons)
         statusContainer.setOnHoverListener(
             statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer))
         if (moveFromCenterAnimationController == null) return
 
-        val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_start_side_except_heads_up)
-        val systemIconArea: ViewGroup = mView.findViewById(R.id.status_bar_end_side_content)
+        val statusBarLeftSide: View = mView.requireViewById(R.id.status_bar_start_side_except_heads_up)
+        val systemIconArea: ViewGroup = mView.requireViewById(R.id.status_bar_end_side_content)
 
         val viewsToAnimate = arrayOf(
             statusBarLeftSide,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index e82ac59..fc66138 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -370,6 +370,9 @@
         mScrimBehind = behindScrim;
         mScrimInFront = scrimInFront;
         updateThemeColors();
+        mNotificationsScrim.setScrimName(getScrimName(mNotificationsScrim));
+        mScrimBehind.setScrimName(getScrimName(mScrimBehind));
+        mScrimInFront.setScrimName(getScrimName(mScrimInFront));
 
         behindScrim.enableBottomEdgeConcave(mClipsQsScrim);
         mNotificationsScrim.enableRoundedCorners(true);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index c850d4f..ad18170 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -117,11 +117,11 @@
      * status bar area is contiguous.
      */
     fun currentRotationHasCornerCutout(): Boolean {
-        val cutout = context.display.cutout ?: return false
+        val cutout = checkNotNull(context.display).cutout ?: return false
         val topBounds = cutout.boundingRectTop
 
         val point = Point()
-        context.display.getRealSize(point)
+        checkNotNull(context.display).getRealSize(point)
 
         return topBounds.left <= 0 || topBounds.right >= point.x
     }
@@ -161,7 +161,7 @@
      */
     fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Pair<Int, Int> =
         traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") {
-            val displayCutout = context.display.cutout
+            val displayCutout = checkNotNull(context.display).cutout
             val key = getCacheKey(rotation, displayCutout)
 
             val screenBounds = context.resources.configuration.windowConfiguration.maxBounds
@@ -198,7 +198,7 @@
     fun getStatusBarContentAreaForRotation(
         @Rotation rotation: Int
     ): Rect {
-        val displayCutout = context.display.cutout
+        val displayCutout = checkNotNull(context.display).cutout
         val key = getCacheKey(rotation, displayCutout)
         return insetsCache[key] ?: getAndSetCalculatedAreaForRotation(
                 rotation, displayCutout, getResourcesForRotation(rotation, context), key)
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 5c1dfbe..ea57eb4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -22,6 +22,8 @@
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
+import static com.android.systemui.util.kotlin.JavaAdapterKt.combineFlows;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -60,10 +62,14 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.bouncer.ui.BouncerView;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -86,8 +92,6 @@
 import com.android.systemui.unfold.FoldAodAnimationController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 
-import dagger.Lazy;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -97,6 +101,9 @@
 
 import javax.inject.Inject;
 
+import dagger.Lazy;
+import kotlinx.coroutines.CoroutineDispatcher;
+
 /**
  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
  * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
@@ -281,6 +288,9 @@
     private int mLastBiometricMode;
     private boolean mLastScreenOffAnimationPlaying;
     private float mQsExpansion;
+
+    private FeatureFlags mFlags;
+
     final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsBackAnimationEnabled;
     private final boolean mUdfpsNewTouchDetectionEnabled;
@@ -326,6 +336,7 @@
             }
         }
     };
+    private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
 
     @Inject
     public StatusBarKeyguardViewManager(
@@ -352,7 +363,10 @@
             BouncerView primaryBouncerView,
             AlternateBouncerInteractor alternateBouncerInteractor,
             UdfpsOverlayInteractor udfpsOverlayInteractor,
-            ActivityStarter activityStarter
+            ActivityStarter activityStarter,
+            KeyguardTransitionInteractor keyguardTransitionInteractor,
+            @Main CoroutineDispatcher mainDispatcher,
+            Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor
     ) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -370,6 +384,7 @@
         mShadeController = shadeController;
         mLatencyTracker = latencyTracker;
         mKeyguardSecurityModel = keyguardSecurityModel;
+        mFlags = featureFlags;
         mPrimaryBouncerCallbackInteractor = primaryBouncerCallbackInteractor;
         mPrimaryBouncerInteractor = primaryBouncerInteractor;
         mPrimaryBouncerView = primaryBouncerView;
@@ -381,8 +396,14 @@
         mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION);
         mUdfpsOverlayInteractor = udfpsOverlayInteractor;
         mActivityStarter = activityStarter;
+        mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mMainDispatcher = mainDispatcher;
+        mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
     }
 
+    KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    CoroutineDispatcher mMainDispatcher;
+
     @Override
     public void registerCentralSurfaces(CentralSurfaces centralSurfaces,
             ShadeViewController shadeViewController,
@@ -429,6 +450,14 @@
         }
     }
 
+    private KeyguardStateController.Callback mKeyguardStateControllerCallback =
+            new KeyguardStateController.Callback() {
+                @Override
+                public void onUnlockedChanged() {
+                    updateAlternateBouncerShowing(mAlternateBouncerInteractor.maybeHide());
+                }
+            };
+
     private void registerListeners() {
         mKeyguardUpdateManager.registerCallback(mUpdateMonitorCallback);
         mStatusBarStateController.addCallback(this);
@@ -442,6 +471,32 @@
             mDockManager.addListener(mDockEventListener);
             mIsDocked = mDockManager.isDocked();
         }
+        mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
+
+        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            mShadeViewController.postToView(() ->
+                    collectFlow(
+                        getViewRootImpl().getView(),
+                        combineFlows(
+                                mKeyguardTransitionInteractor.getFinishedKeyguardState(),
+                                mWmLockscreenVisibilityInteractor.get()
+                                        .getUsingKeyguardGoingAwayAnimation(),
+                                (finishedState, animating) ->
+                                        KeyguardInteractor.Companion.isKeyguardVisibleInState(
+                                                finishedState)
+                                                || animating),
+                        this::consumeShowStatusBarKeyguardView));
+        }
+    }
+
+    private void consumeShowStatusBarKeyguardView(boolean show) {
+        if (show != mLastShowing) {
+            if (show) {
+                show(null);
+            } else {
+                hide(0, 0);
+            }
+        }
     }
 
     /** Register a callback, to be invoked by the Predictive Back system. */
@@ -1313,6 +1368,10 @@
             hideAlternateBouncer(false);
             executeAfterKeyguardGoneAction();
         }
+
+        if (mFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
+            mKeyguardTransitionInteractor.startDismissKeyguardTransition();
+        }
     }
 
     /** Display security message to relevant KeyguardMessageArea. */
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 e8da951..1bceb29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt
@@ -105,18 +105,18 @@
             }
         }
         addListener(object : AnimatorListenerAdapter() {
-            override fun onAnimationCancel(animation: Animator?) {
+            override fun onAnimationCancel(animation: Animator) {
                 if (lightRevealScrim.revealEffect !is CircleReveal) {
                     lightRevealScrim.revealAmount = 1f
                 }
             }
 
-            override fun onAnimationEnd(animation: Animator?) {
+            override fun onAnimationEnd(animation: Animator) {
                 lightRevealAnimationPlaying = false
                 interactionJankMonitor.end(CUJ_SCREEN_OFF)
             }
 
-            override fun onAnimationStart(animation: Animator?) {
+            override fun onAnimationStart(animation: Animator) {
                 interactionJankMonitor.begin(
                         notifShadeWindowControllerLazy.get().windowRootView, CUJ_SCREEN_OFF)
             }
@@ -345,7 +345,7 @@
         // portrait. If we're in another orientation, disable the screen off animation so we don't
         // animate in the keyguard AOD UI sideways or upside down.
         if (!keyguardStateController.isKeyguardScreenRotationAllowed &&
-            context.display.rotation != Surface.ROTATION_0) {
+            context.display?.rotation != Surface.ROTATION_0) {
             return false
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
index 270c592..1259477 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt
@@ -34,7 +34,7 @@
 
     override fun onFinishInflate() {
         super.onFinishInflate()
-        text = findViewById(R.id.current_user_name)
-        avatar = findViewById(R.id.current_user_avatar)
+        text = requireViewById(R.id.current_user_name)
+        avatar = requireViewById(R.id.current_user_avatar)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 3a11635..c1af6df 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -118,6 +118,11 @@
     /** The service provider name for this network connection, or the default name */
     val networkName: StateFlow<NetworkNameModel>
 
+    /**
+     * True if this type of connection is allowed while airplane mode is on, and false otherwise.
+     */
+    val isAllowedDuringAirplaneMode: StateFlow<Boolean>
+
     companion object {
         /** The default number of levels to use for [numberOfLevels]. */
         const val DEFAULT_NUM_LEVELS = 4
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
index 6b86432..17d20c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -186,6 +186,8 @@
 
     override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network"))
 
+    override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
+
     /**
      * Process a new demo mobile event. Note that [resolvedNetworkType] must be passed in separately
      * from the event, due to the requirement to reverse the mobile mappings lookup in the top-level
@@ -217,6 +219,8 @@
             (event.activity ?: TelephonyManager.DATA_ACTIVITY_NONE).toMobileDataActivityModel()
         _carrierNetworkChangeActive.value = event.carrierNetworkChange
         _resolvedNetworkType.value = resolvedNetworkType
+
+        isAllowedDuringAirplaneMode.value = false
     }
 
     fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) {
@@ -240,6 +244,7 @@
         _isInService.value = true
         _isGsm.value = false
         _carrierNetworkChangeActive.value = false
+        isAllowedDuringAirplaneMode.value = true
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
index a609917..65f4866 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepository.kt
@@ -165,6 +165,13 @@
     override val isGsm = MutableStateFlow(false).asStateFlow()
     override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow()
 
+    /**
+     * Carrier merged connections happen over wifi but are displayed as a mobile triangle. Because
+     * they occur over wifi, it's possible to have a valid carrier merged connection even during
+     * airplane mode. See b/291993542.
+     */
+    override val isAllowedDuringAirplaneMode = MutableStateFlow(true).asStateFlow()
+
     override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
index 8869dfe..8ba7d21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepository.kt
@@ -287,6 +287,15 @@
             )
             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.networkName.value)
 
+    override val isAllowedDuringAirplaneMode =
+        activeRepo
+            .flatMapLatest { it.isAllowedDuringAirplaneMode }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.isAllowedDuringAirplaneMode.value,
+            )
+
     class Factory
     @Inject
     constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
index b475183..aadc975 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryImpl.kt
@@ -59,8 +59,10 @@
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
@@ -331,6 +333,9 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
     }
 
+    /** Typical mobile connections aren't available during airplane mode. */
+    override val isAllowedDuringAirplaneMode = MutableStateFlow(false).asStateFlow()
+
     class Factory
     @Inject
     constructor(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index d42e30c..1a13827 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -111,6 +111,9 @@
     /** See [MobileIconsInteractor.isForceHidden]. */
     val isForceHidden: Flow<Boolean>
 
+    /** See [MobileConnectionRepository.isAllowedDuringAirplaneMode]. */
+    val isAllowedDuringAirplaneMode: StateFlow<Boolean>
+
     /** True when in carrier network change mode */
     val carrierNetworkChangeActive: StateFlow<Boolean>
 }
@@ -267,4 +270,6 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val isInService = connectionRepository.isInService
+
+    override val isAllowedDuringAirplaneMode = connectionRepository.isAllowedDuringAirplaneMode
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index 35f4f9a..fe24815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -102,9 +102,16 @@
             } else {
                 combine(
                     airplaneModeInteractor.isAirplaneMode,
+                    iconInteractor.isAllowedDuringAirplaneMode,
                     iconInteractor.isForceHidden,
-                ) { isAirplaneMode, isForceHidden ->
-                    !isAirplaneMode && !isForceHidden
+                ) { isAirplaneMode, isAllowedDuringAirplaneMode, isForceHidden ->
+                    if (isForceHidden) {
+                        false
+                    } else if (isAirplaneMode) {
+                        isAllowedDuringAirplaneMode
+                    } else {
+                        true
+                    }
                 }
             }
             .distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
index b11b472..b5b99a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepository.kt
@@ -29,9 +29,18 @@
     /** Observable for the current wifi default status. */
     val isWifiDefault: StateFlow<Boolean>
 
-    /** Observable for the current wifi network. */
+    /** Observable for the current primary wifi network. */
     val wifiNetwork: StateFlow<WifiNetworkModel>
 
+    /**
+     * Observable for secondary wifi networks (if any). Should specifically exclude the primary
+     * network emitted by [wifiNetwork].
+     *
+     * This isn't used by phones/tablets, which only display the primary network, but may be used by
+     * other variants like Car.
+     */
+    val secondaryNetworks: StateFlow<List<WifiNetworkModel>>
+
     /** Observable for the current wifi network activity. */
     val wifiActivity: StateFlow<DataActivityModel>
 
@@ -49,6 +58,9 @@
         const val COL_NAME_IS_ENABLED = "isEnabled"
         /** Column name to use for [isWifiDefault] for table logging. */
         const val COL_NAME_IS_DEFAULT = "isDefault"
+
+        const val CARRIER_MERGED_INVALID_SUB_ID_REASON =
+            "Wifi network was carrier merged but had invalid sub ID"
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
index e96288a..80091ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcher.kt
@@ -115,6 +115,11 @@
             .flatMapLatest { it.wifiNetwork }
             .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.wifiNetwork.value)
 
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        activeRepo
+            .flatMapLatest { it.secondaryNetworks }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), realImpl.secondaryNetworks.value)
+
     override val wifiActivity: StateFlow<DataActivityModel> =
         activeRepo
             .flatMapLatest { it.wifiActivity }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
index 7d2501ca..ab9b516 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoModeWifiDataSource.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.demomode.DemoModeController
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -56,12 +57,14 @@
         val activity = getString("activity").toActivity()
         val ssid = getString("ssid")
         val validated = getString("fully").toBoolean()
+        val hotspotDeviceType = getString("hotspot").toHotspotDeviceType()
 
         return FakeWifiEventModel.Wifi(
             level = level,
             activity = activity,
             ssid = ssid,
             validated = validated,
+            hotspotDeviceType,
         )
     }
 
@@ -82,6 +85,20 @@
             else -> WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE
         }
 
+    private fun String?.toHotspotDeviceType(): WifiNetworkModel.HotspotDeviceType {
+        return when (this) {
+            null,
+            "none" -> WifiNetworkModel.HotspotDeviceType.NONE
+            "unknown" -> WifiNetworkModel.HotspotDeviceType.UNKNOWN
+            "phone" -> WifiNetworkModel.HotspotDeviceType.PHONE
+            "tablet" -> WifiNetworkModel.HotspotDeviceType.TABLET
+            "laptop" -> WifiNetworkModel.HotspotDeviceType.LAPTOP
+            "watch" -> WifiNetworkModel.HotspotDeviceType.WATCH
+            "auto" -> WifiNetworkModel.HotspotDeviceType.AUTO
+            else -> WifiNetworkModel.HotspotDeviceType.INVALID
+        }
+    }
+
     companion object {
         const val DEFAULT_CARRIER_MERGED_SUB_ID = 10
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
index a57be66..4b19c3a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/DemoWifiRepository.kt
@@ -48,6 +48,9 @@
     private val _wifiNetwork = MutableStateFlow<WifiNetworkModel>(WifiNetworkModel.Inactive)
     override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
 
+    private val _secondaryNetworks = MutableStateFlow<List<WifiNetworkModel>>(emptyList())
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> = _secondaryNetworks
+
     private val _wifiActivity =
         MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
     override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
@@ -97,6 +100,7 @@
             isValidated = validated ?: true,
             level = level ?: 0,
             ssid = ssid ?: DEMO_NET_SSID,
+            hotspotDeviceType = hotspotDeviceType,
 
             // These fields below aren't supported in demo mode, since they aren't needed to satisfy
             // the interface.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
index f5035cbc..b2e843e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/demo/model/FakeWifiEventModel.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model
 
 import android.telephony.Annotation
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 
 /**
  * Model for demo wifi commands, ported from [NetworkControllerImpl]
@@ -29,6 +30,8 @@
         @Annotation.DataActivityType val activity: Int,
         val ssid: String?,
         val validated: Boolean?,
+        val hotspotDeviceType: WifiNetworkModel.HotspotDeviceType =
+            WifiNetworkModel.HotspotDeviceType.NONE,
     ) : FakeWifiEventModel
 
     data class CarrierMerged(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
index 9ed7c6a..36c46a9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/DisabledWifiRepository.kt
@@ -43,6 +43,9 @@
 
     override val wifiNetwork: StateFlow<WifiNetworkModel> = MutableStateFlow(NETWORK).asStateFlow()
 
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        MutableStateFlow(emptyList<WifiNetworkModel>()).asStateFlow()
+
     override val wifiActivity: StateFlow<DataActivityModel> =
         MutableStateFlow(ACTIVITY).asStateFlow()
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
new file mode 100644
index 0000000..f1b98b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryHelper.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.pipeline.wifi.data.repository.prod
+
+import android.net.wifi.WifiManager
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
+import java.util.concurrent.Executor
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Object to provide shared helper functions between [WifiRepositoryImpl] and
+ * [WifiRepositoryViaTrackerLib].
+ */
+object WifiRepositoryHelper {
+    /** Creates a flow that fetches the [DataActivityModel] from [WifiManager]. */
+    fun createActivityFlow(
+        wifiManager: WifiManager,
+        @Main mainExecutor: Executor,
+        scope: CoroutineScope,
+        tableLogBuffer: TableLogBuffer,
+        inputLogger: (String) -> Unit,
+    ): StateFlow<DataActivityModel> {
+        return conflatedCallbackFlow {
+                val callback =
+                    WifiManager.TrafficStateCallback { state ->
+                        inputLogger.invoke(prettyPrintActivity(state))
+                        trySend(state.toWifiDataActivityModel())
+                    }
+                wifiManager.registerTrafficStateCallback(mainExecutor, callback)
+                awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
+            }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = ACTIVITY_PREFIX,
+                initialValue = ACTIVITY_DEFAULT,
+            )
+            .stateIn(
+                scope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = ACTIVITY_DEFAULT,
+            )
+    }
+
+    // TODO(b/292534484): This print should only be done in [MessagePrinter] part of the log buffer.
+    private fun prettyPrintActivity(activity: Int): String {
+        return when (activity) {
+            WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
+            WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
+            WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
+            WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
+            else -> "INVALID"
+        }
+    }
+
+    private const val ACTIVITY_PREFIX = "wifiActivity"
+    val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 995de6d..7c7b58d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -28,7 +28,6 @@
 import android.net.NetworkRequest
 import android.net.wifi.WifiInfo
 import android.net.wifi.WifiManager
-import android.net.wifi.WifiManager.TrafficStateCallback
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -40,10 +39,10 @@
 import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.shared.data.model.toWifiDataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepository
 import com.android.systemui.statusbar.pipeline.shared.data.repository.ConnectivityRepositoryImpl.Companion.getMainOrUnderlyingWifiInfo
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryDagger
@@ -57,8 +56,10 @@
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
@@ -217,30 +218,20 @@
                 initialValue = WIFI_NETWORK_DEFAULT,
             )
 
+    // Secondary networks can only be supported by [WifiRepositoryViaTrackerLib].
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        MutableStateFlow(emptyList<WifiNetworkModel>()).asStateFlow()
+
     override val wifiActivity: StateFlow<DataActivityModel> =
-        conflatedCallbackFlow {
-                val callback = TrafficStateCallback { state ->
-                    logger.logActivity(prettyPrintActivity(state))
-                    trySend(state.toWifiDataActivityModel())
-                }
-                wifiManager.registerTrafficStateCallback(mainExecutor, callback)
-                awaitClose { wifiManager.unregisterTrafficStateCallback(callback) }
-            }
-            .logDiffsForTable(
-                wifiTableLogBuffer,
-                columnPrefix = ACTIVITY_PREFIX,
-                initialValue = ACTIVITY_DEFAULT,
-            )
-            .stateIn(
-                scope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = ACTIVITY_DEFAULT,
-            )
+        WifiRepositoryHelper.createActivityFlow(
+            wifiManager,
+            mainExecutor,
+            scope,
+            wifiTableLogBuffer,
+            logger::logActivity,
+        )
 
     companion object {
-        private const val ACTIVITY_PREFIX = "wifiActivity"
-
-        val ACTIVITY_DEFAULT = DataActivityModel(hasActivityIn = false, hasActivityOut = false)
         // Start out with no known wifi network.
         // Note: [WifiStatusTracker] (the old implementation of connectivity logic) does do an
         // initial fetch to get a starting wifi network. But, it uses a deprecated API
@@ -277,6 +268,8 @@
                     isValidated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED),
                     level = wifiManager.calculateSignalLevel(wifiInfo.rssi),
                     wifiInfo.ssid,
+                    // This repository doesn't support any hotspot information.
+                    WifiNetworkModel.HotspotDeviceType.NONE,
                     wifiInfo.isPasspointAp,
                     wifiInfo.isOsuAp,
                     wifiInfo.passpointProviderFriendlyName
@@ -284,16 +277,6 @@
             }
         }
 
-        private fun prettyPrintActivity(activity: Int): String {
-            return when (activity) {
-                TrafficStateCallback.DATA_ACTIVITY_NONE -> "NONE"
-                TrafficStateCallback.DATA_ACTIVITY_IN -> "IN"
-                TrafficStateCallback.DATA_ACTIVITY_OUT -> "OUT"
-                TrafficStateCallback.DATA_ACTIVITY_INOUT -> "INOUT"
-                else -> "INVALID"
-            }
-        }
-
         private val WIFI_NETWORK_CALLBACK_REQUEST: NetworkRequest =
             NetworkRequest.Builder()
                 .clearCapabilities()
@@ -301,9 +284,6 @@
                 .addTransportType(TRANSPORT_WIFI)
                 .addTransportType(TRANSPORT_CELLULAR)
                 .build()
-
-        private const val CARRIER_MERGED_INVALID_SUB_ID_REASON =
-            "Wifi network was carrier merged but had invalid sub ID"
     }
 
     @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
index 1271367..d4f40dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
@@ -17,12 +17,15 @@
 package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
 
 import android.net.wifi.WifiManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
 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.log.LogBuffer
 import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.table.TableLogBuffer
@@ -31,20 +34,25 @@
 import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibInputLog
 import com.android.systemui.statusbar.pipeline.dagger.WifiTrackerLibTableLog
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.CARRIER_MERGED_INVALID_SUB_ID_REASON
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_DEFAULT
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository.Companion.COL_NAME_IS_ENABLED
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepositoryViaTrackerLibDagger
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_STATE_DEFAULT
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
+import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Inactive.toHotspotDeviceType
+import com.android.wifitrackerlib.HotspotNetworkEntry
 import com.android.wifitrackerlib.MergedCarrierEntry
 import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
 import com.android.wifitrackerlib.WifiPickerTracker
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.callbackFlow
@@ -62,6 +70,7 @@
 class WifiRepositoryViaTrackerLib
 @Inject
 constructor(
+    featureFlags: FeatureFlags,
     @Application private val scope: CoroutineScope,
     @Main private val mainExecutor: Executor,
     private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
@@ -75,6 +84,8 @@
             mainExecutor.execute { it.currentState = Lifecycle.State.CREATED }
         }
 
+    private val isInstantTetherEnabled = featureFlags.isEnabled(Flags.INSTANT_TETHER)
+
     private var wifiPickerTracker: WifiPickerTracker? = null
 
     private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
@@ -82,7 +93,8 @@
             WifiPickerTrackerInfo(
                 state = WIFI_STATE_DEFAULT,
                 isDefault = false,
-                network = WIFI_NETWORK_DEFAULT,
+                primaryNetwork = WIFI_NETWORK_DEFAULT,
+                secondaryNetworks = emptyList(),
             )
         callbackFlow {
                 val callback =
@@ -91,6 +103,17 @@
                             val connectedEntry = wifiPickerTracker?.connectedWifiEntry
                             logOnWifiEntriesChanged(connectedEntry)
 
+                            val secondaryNetworks =
+                                if (featureFlags.isEnabled(Flags.WIFI_SECONDARY_NETWORKS)) {
+                                    val activeNetworks =
+                                        wifiPickerTracker?.activeWifiEntries ?: emptyList()
+                                    activeNetworks
+                                        .filter { it != connectedEntry && !it.isPrimaryNetwork }
+                                        .map { it.toWifiNetworkModel() }
+                                } else {
+                                    emptyList()
+                                }
+
                             // [WifiPickerTracker.connectedWifiEntry] will return the same instance
                             // but with updated internals. For example, when its validation status
                             // changes from false to true, the same instance is re-used but with the
@@ -101,8 +124,9 @@
                             // into our internal model immediately. [toWifiNetworkModel] always
                             // returns a new instance, so the flow is guaranteed to emit.
                             send(
-                                newNetwork = connectedEntry?.toWifiNetworkModel()
+                                newPrimaryNetwork = connectedEntry?.toPrimaryWifiNetworkModel()
                                         ?: WIFI_NETWORK_DEFAULT,
+                                newSecondaryNetworks = secondaryNetworks,
                                 newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
                             )
                         }
@@ -120,27 +144,37 @@
                         private fun send(
                             newState: Int = current.state,
                             newIsDefault: Boolean = current.isDefault,
-                            newNetwork: WifiNetworkModel = current.network,
+                            newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
+                            newSecondaryNetworks: List<WifiNetworkModel> =
+                                current.secondaryNetworks,
                         ) {
-                            val new = WifiPickerTrackerInfo(newState, newIsDefault, newNetwork)
+                            val new =
+                                WifiPickerTrackerInfo(
+                                    newState,
+                                    newIsDefault,
+                                    newPrimaryNetwork,
+                                    newSecondaryNetworks,
+                                )
                             current = new
                             trySend(new)
                         }
                     }
 
-                // TODO(b/292591403): [WifiPickerTrackerFactory] currently scans to see all
-                // available wifi networks every 10s. Because SysUI only needs to display the
-                // **connected** network, we don't need scans to be running. We should disable these
-                // scans (ideal) or at least run them very infrequently.
-                wifiPickerTracker = wifiPickerTrackerFactory.create(lifecycle, callback)
+                wifiPickerTracker =
+                    wifiPickerTrackerFactory.create(lifecycle, callback).apply {
+                        // By default, [WifiPickerTracker] will scan to see all available wifi
+                        // networks in the area. Because SysUI only needs to display the
+                        // **connected** network, we don't need scans to be running (and in fact,
+                        // running scans is costly and should be avoided whenever possible).
+                        this?.disableScanning()
+                    }
                 // The lifecycle must be STARTED in order for the callback to receive events.
                 mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
                 awaitClose {
                     mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
                 }
             }
-            // TODO(b/292534484): Update to Eagerly once scans are disabled. (Here and other flows)
-            .stateIn(scope, SharingStarted.WhileSubscribed(), current)
+            .stateIn(scope, SharingStarted.Eagerly, current)
     }
 
     override val isWifiEnabled: StateFlow<Boolean> =
@@ -153,49 +187,98 @@
                 columnName = COL_NAME_IS_ENABLED,
                 initialValue = false,
             )
-            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+            .stateIn(scope, SharingStarted.Eagerly, false)
 
     override val wifiNetwork: StateFlow<WifiNetworkModel> =
         wifiPickerTrackerInfo
-            .map { it.network }
+            .map { it.primaryNetwork }
             .distinctUntilChanged()
             .logDiffsForTable(
                 wifiTrackerLibTableLogBuffer,
                 columnPrefix = "",
                 initialValue = WIFI_NETWORK_DEFAULT,
             )
-            .stateIn(scope, SharingStarted.WhileSubscribed(), WIFI_NETWORK_DEFAULT)
+            .stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
+
+    override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
+        wifiPickerTrackerInfo
+            .map { it.secondaryNetworks }
+            .distinctUntilChanged()
+            .logDiffsForTable(
+                wifiTrackerLibTableLogBuffer,
+                columnPrefix = "",
+                columnName = "secondaryNetworks",
+                initialValue = emptyList(),
+            )
+            .stateIn(scope, SharingStarted.Eagerly, emptyList())
+
+    /**
+     * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
+     * primary network. Returns an inactive network if it's not primary.
+     */
+    private fun WifiEntry.toPrimaryWifiNetworkModel(): WifiNetworkModel {
+        return if (!this.isPrimaryNetwork) {
+            WIFI_NETWORK_DEFAULT
+        } else {
+            this.toWifiNetworkModel()
+        }
+    }
 
     /** Converts WifiTrackerLib's [WifiEntry] into our internal model. */
     private fun WifiEntry.toWifiNetworkModel(): WifiNetworkModel {
-        if (!this.isPrimaryNetwork) {
-            return WIFI_NETWORK_DEFAULT
-        }
         return if (this is MergedCarrierEntry) {
+            this.convertCarrierMergedToModel()
+        } else {
+            this.convertNormalToModel()
+        }
+    }
+
+    private fun MergedCarrierEntry.convertCarrierMergedToModel(): WifiNetworkModel {
+        return if (this.subscriptionId == INVALID_SUBSCRIPTION_ID) {
+            WifiNetworkModel.Invalid(CARRIER_MERGED_INVALID_SUB_ID_REASON)
+        } else {
             WifiNetworkModel.CarrierMerged(
                 networkId = NETWORK_ID,
-                // TODO(b/292534484): Fetch the real subscription ID from [MergedCarrierEntry].
-                subscriptionId = TEMP_SUB_ID,
+                subscriptionId = this.subscriptionId,
                 level = this.level,
                 // WifiManager APIs to calculate the signal level start from 0, so
                 // maxSignalLevel + 1 represents the total level buckets count.
                 numberOfLevels = wifiManager.maxSignalLevel + 1,
             )
-        } else {
-            WifiNetworkModel.Active(
-                networkId = NETWORK_ID,
-                isValidated = this.hasInternetAccess(),
-                level = this.level,
-                ssid = this.ssid,
-                // TODO(b/292534484): Fetch the real values from [WifiEntry] (#getTitle might be
-                // appropriate).
-                isPasspointAccessPoint = false,
-                isOnlineSignUpForPasspointAccessPoint = false,
-                passpointProviderFriendlyName = null,
-            )
         }
     }
 
+    private fun WifiEntry.convertNormalToModel(): WifiNetworkModel {
+        if (this.level == WIFI_LEVEL_UNREACHABLE || this.level !in WIFI_LEVEL_MIN..WIFI_LEVEL_MAX) {
+            // If our level means the network is unreachable or the level is otherwise invalid, we
+            // don't have an active network.
+            return WifiNetworkModel.Inactive
+        }
+
+        val hotspotDeviceType =
+            if (isInstantTetherEnabled && this is HotspotNetworkEntry) {
+                this.deviceType.toHotspotDeviceType()
+            } else {
+                WifiNetworkModel.HotspotDeviceType.NONE
+            }
+
+        return WifiNetworkModel.Active(
+            networkId = NETWORK_ID,
+            isValidated = this.hasInternetAccess(),
+            level = this.level,
+            ssid = this.title,
+            hotspotDeviceType = hotspotDeviceType,
+            // With WifiTrackerLib, [WifiEntry.title] will appropriately fetch the  SSID for
+            // typical wifi networks *and* passpoint/OSU APs. So, the AP-specific values can
+            // always be false/null in this repository.
+            // TODO(b/292534484): Remove these fields from the wifi network model once this
+            //  repository is fully enabled.
+            isPasspointAccessPoint = false,
+            isOnlineSignUpForPasspointAccessPoint = false,
+            passpointProviderFriendlyName = null,
+        )
+    }
+
     override val isWifiDefault: StateFlow<Boolean> =
         wifiPickerTrackerInfo
             .map { it.isDefault }
@@ -206,12 +289,16 @@
                 columnName = COL_NAME_IS_DEFAULT,
                 initialValue = false,
             )
-            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+            .stateIn(scope, SharingStarted.Eagerly, false)
 
-    // TODO(b/292534484): Re-use WifiRepositoryImpl code to implement wifi activity since
-    // WifiTrackerLib doesn't expose activity details.
     override val wifiActivity: StateFlow<DataActivityModel> =
-        MutableStateFlow(DataActivityModel(false, false))
+        WifiRepositoryHelper.createActivityFlow(
+            wifiManager,
+            mainExecutor,
+            scope,
+            wifiTrackerLibTableLogBuffer,
+            this::logActivity,
+        )
 
     private fun logOnWifiEntriesChanged(connectedEntry: WifiEntry?) {
         inputLogger.log(
@@ -231,6 +318,10 @@
         )
     }
 
+    private fun logActivity(activity: String) {
+        inputLogger.log(TAG, LogLevel.DEBUG, { str1 = activity }, { "onActivityChanged: $str1" })
+    }
+
     /**
      * Data class storing all the information fetched from [WifiPickerTracker].
      *
@@ -242,13 +333,16 @@
         /** True if wifi is currently the default connection and false otherwise. */
         val isDefault: Boolean,
         /** The currently primary wifi network. */
-        val network: WifiNetworkModel,
+        val primaryNetwork: WifiNetworkModel,
+        /** The current secondary network(s), if any. Specifically excludes the primary network. */
+        val secondaryNetworks: List<WifiNetworkModel>
     )
 
     @SysUISingleton
     class Factory
     @Inject
     constructor(
+        private val featureFlags: FeatureFlags,
         @Application private val scope: CoroutineScope,
         @Main private val mainExecutor: Executor,
         private val wifiPickerTrackerFactory: WifiPickerTrackerFactory,
@@ -257,6 +351,7 @@
     ) {
         fun create(wifiManager: WifiManager): WifiRepositoryViaTrackerLib {
             return WifiRepositoryViaTrackerLib(
+                featureFlags,
                 scope,
                 mainExecutor,
                 wifiPickerTrackerFactory,
@@ -283,13 +378,5 @@
          * to [WifiRepositoryViaTrackerLib].
          */
         private const val NETWORK_ID = -1
-
-        /**
-         * A temporary subscription ID until WifiTrackerLib exposes a method to fetch the
-         * subscription ID.
-         *
-         * Use -2 because [SubscriptionManager.INVALID_SUBSCRIPTION_ID] is -1.
-         */
-        private const val TEMP_SUB_ID = -2
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
index 4b33c88..7078a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModel.kt
@@ -17,11 +17,13 @@
 package com.android.systemui.statusbar.pipeline.wifi.shared.model
 
 import android.net.wifi.WifiManager.UNKNOWN_SSID
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
 import android.telephony.SubscriptionManager
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.log.table.Diffable
 import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
 
 /** Provides information about the current wifi network. */
 sealed class WifiNetworkModel : Diffable<WifiNetworkModel> {
@@ -52,6 +54,7 @@
             row.logChange(COL_LEVEL, LEVEL_DEFAULT)
             row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
             row.logChange(COL_SSID, null)
+            row.logChange(COL_HOTSPOT, null)
             row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
             row.logChange(COL_ONLINE_SIGN_UP, false)
             row.logChange(COL_PASSPOINT_NAME, null)
@@ -83,6 +86,7 @@
             row.logChange(COL_LEVEL, LEVEL_DEFAULT)
             row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
             row.logChange(COL_SSID, null)
+            row.logChange(COL_HOTSPOT, null)
             row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
             row.logChange(COL_ONLINE_SIGN_UP, false)
             row.logChange(COL_PASSPOINT_NAME, null)
@@ -110,6 +114,7 @@
             row.logChange(COL_LEVEL, LEVEL_DEFAULT)
             row.logChange(COL_NUM_LEVELS, NUM_LEVELS_DEFAULT)
             row.logChange(COL_SSID, null)
+            row.logChange(COL_HOTSPOT, null)
             row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
             row.logChange(COL_ONLINE_SIGN_UP, false)
             row.logChange(COL_PASSPOINT_NAME, null)
@@ -184,6 +189,7 @@
             row.logChange(COL_LEVEL, level)
             row.logChange(COL_NUM_LEVELS, numberOfLevels)
             row.logChange(COL_SSID, null)
+            row.logChange(COL_HOTSPOT, null)
             row.logChange(COL_PASSPOINT_ACCESS_POINT, false)
             row.logChange(COL_ONLINE_SIGN_UP, false)
             row.logChange(COL_PASSPOINT_NAME, null)
@@ -209,6 +215,12 @@
         /** See [android.net.wifi.WifiInfo.ssid]. */
         val ssid: String? = null,
 
+        /**
+         * The type of device providing a hotspot connection, or [HotspotDeviceType.NONE] if this
+         * isn't a hotspot connection.
+         */
+        val hotspotDeviceType: HotspotDeviceType = WifiNetworkModel.HotspotDeviceType.NONE,
+
         /** See [android.net.wifi.WifiInfo.isPasspointAp]. */
         val isPasspointAccessPoint: Boolean = false,
 
@@ -247,6 +259,9 @@
             if (prevVal.ssid != ssid) {
                 row.logChange(COL_SSID, ssid)
             }
+            if (prevVal.hotspotDeviceType != hotspotDeviceType) {
+                row.logChange(COL_HOTSPOT, hotspotDeviceType.name)
+            }
 
             // TODO(b/238425913): The passpoint-related values are frequently never used, so it
             //   would be great to not log them when they're not used.
@@ -272,6 +287,7 @@
             row.logChange(COL_LEVEL, level)
             row.logChange(COL_NUM_LEVELS, null)
             row.logChange(COL_SSID, ssid)
+            row.logChange(COL_HOTSPOT, hotspotDeviceType.name)
             row.logChange(COL_PASSPOINT_ACCESS_POINT, isPasspointAccessPoint)
             row.logChange(COL_ONLINE_SIGN_UP, isOnlineSignUpForPasspointAccessPoint)
             row.logChange(COL_PASSPOINT_NAME, passpointProviderFriendlyName)
@@ -298,13 +314,51 @@
         }
 
         companion object {
+            // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX] instead
+            // once the migration to WifiTrackerLib is complete.
             @VisibleForTesting internal const val MAX_VALID_LEVEL = 4
         }
     }
 
     companion object {
+        // TODO(b/292534484): Use [com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN] instead
+        // once the migration to WifiTrackerLib is complete.
         @VisibleForTesting internal const val MIN_VALID_LEVEL = 0
     }
+
+    /**
+     * Enum for the type of device providing the hotspot connection, or [NONE] if this connection
+     * isn't a hotspot.
+     */
+    enum class HotspotDeviceType {
+        /* This wifi connection isn't a hotspot. */
+        NONE,
+        /** The device type for this hotspot is unknown. */
+        UNKNOWN,
+        PHONE,
+        TABLET,
+        LAPTOP,
+        WATCH,
+        AUTO,
+        /** The device type sent for this hotspot is invalid to SysUI. */
+        INVALID,
+    }
+
+    /**
+     * Converts a device type from [com.android.wifitrackerlib.HotspotNetworkEntry.deviceType] to
+     * our internal representation.
+     */
+    fun @receiver:DeviceType Int.toHotspotDeviceType(): HotspotDeviceType {
+        return when (this) {
+            NetworkProviderInfo.DEVICE_TYPE_UNKNOWN -> HotspotDeviceType.UNKNOWN
+            NetworkProviderInfo.DEVICE_TYPE_PHONE -> HotspotDeviceType.PHONE
+            NetworkProviderInfo.DEVICE_TYPE_TABLET -> HotspotDeviceType.TABLET
+            NetworkProviderInfo.DEVICE_TYPE_LAPTOP -> HotspotDeviceType.LAPTOP
+            NetworkProviderInfo.DEVICE_TYPE_WATCH -> HotspotDeviceType.WATCH
+            NetworkProviderInfo.DEVICE_TYPE_AUTO -> HotspotDeviceType.AUTO
+            else -> HotspotDeviceType.INVALID
+        }
+    }
 }
 
 const val TYPE_CARRIER_MERGED = "CarrierMerged"
@@ -319,6 +373,7 @@
 const val COL_LEVEL = "level"
 const val COL_NUM_LEVELS = "maxLevel"
 const val COL_SSID = "ssid"
+const val COL_HOTSPOT = "hotspot"
 const val COL_PASSPOINT_ACCESS_POINT = "isPasspointAccessPoint"
 const val COL_ONLINE_SIGN_UP = "isOnlineSignUpForPasspointAccessPoint"
 const val COL_PASSPOINT_NAME = "passpointProviderFriendlyName"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index 7df083afc..37eda64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -162,6 +162,9 @@
         default void onIsBatteryDefenderChanged(boolean isBatteryDefender) {
         }
 
+        default void onIsIncompatibleChargingChanged(boolean isIncompatibleCharging) {
+        }
+
         @Override
         default void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
             pw.println(this);
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 d5d8f4d..4b51511 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -29,6 +29,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.hardware.usb.UsbManager;
 import android.os.BatteryManager;
 import android.os.Bundle;
 import android.os.Handler;
@@ -42,6 +43,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.Utils;
 import com.android.settingslib.fuelgauge.BatterySaverUtils;
 import com.android.settingslib.fuelgauge.Estimate;
 import com.android.settingslib.utils.PowerUtil;
@@ -97,6 +99,7 @@
     private boolean mAodPowerSave;
     private boolean mWirelessCharging;
     private boolean mIsBatteryDefender = false;
+    private boolean mIsIncompatibleCharging = false;
     private boolean mTestMode = false;
     @VisibleForTesting
     boolean mHasReceivedBattery = false;
@@ -136,6 +139,7 @@
         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
         filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
         filter.addAction(ACTION_LEVEL_TEST);
+        filter.addAction(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
         mBroadcastDispatcher.registerReceiver(this, filter);
     }
 
@@ -169,6 +173,7 @@
         ipw.print("mCharging="); ipw.println(mCharging);
         ipw.print("mCharged="); ipw.println(mCharged);
         ipw.print("mIsBatteryDefender="); ipw.println(mIsBatteryDefender);
+        ipw.print("mIsIncompatibleCharging="); ipw.println(mIsIncompatibleCharging);
         ipw.print("mPowerSave="); ipw.println(mPowerSave);
         ipw.print("mStateUnknown="); ipw.println(mStateUnknown);
         ipw.println("Callbacks:------------------");
@@ -214,6 +219,7 @@
         cb.onBatteryUnknownStateChanged(mStateUnknown);
         cb.onWirelessChargingChanged(mWirelessCharging);
         cb.onIsBatteryDefenderChanged(mIsBatteryDefender);
+        cb.onIsIncompatibleChargingChanged(mIsIncompatibleCharging);
     }
 
     @Override
@@ -229,7 +235,7 @@
         if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
             if (mTestMode && !intent.getBooleanExtra("testmode", false)) return;
             mHasReceivedBattery = true;
-            mLevel = (int)(100f
+            mLevel = (int) (100f
                     * intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
                     / intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
             mPluggedChargingSource = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
@@ -262,6 +268,12 @@
             fireBatteryLevelChanged();
         } else if (action.equals(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED)) {
             updatePowerSave();
+        } else if (action.equals(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED)) {
+            boolean isIncompatibleCharging = Utils.containsIncompatibleChargers(mContext, TAG);
+            if (isIncompatibleCharging != mIsIncompatibleCharging) {
+                mIsIncompatibleCharging = isIncompatibleCharging;
+                fireIsIncompatibleChargingChanged();
+            }
         } else if (action.equals(ACTION_LEVEL_TEST)) {
             mTestMode = true;
             mMainHandler.post(new Runnable() {
@@ -270,6 +282,7 @@
                 int mSavedLevel = mLevel;
                 boolean mSavedPluggedIn = mPluggedIn;
                 Intent mTestIntent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+
                 @Override
                 public void run() {
                     if (mCurrentLevel < 0) {
@@ -333,6 +346,13 @@
         return mIsBatteryDefender;
     }
 
+    /**
+     * Returns whether the charging adapter is incompatible.
+     */
+    public boolean isIncompatibleCharging() {
+        return mIsIncompatibleCharging;
+    }
+
     @Override
     public void getEstimatedTimeRemainingString(EstimateFetchCompletion completion) {
         // Need to fetch or refresh the estimate, but it may involve binder calls so offload the
@@ -453,6 +473,15 @@
         }
     }
 
+    private void fireIsIncompatibleChargingChanged() {
+        synchronized (mChangeCallbacks) {
+            final int n = mChangeCallbacks.size();
+            for (int i = 0; i < n; i++) {
+                mChangeCallbacks.get(i).onIsIncompatibleChargingChanged(mIsIncompatibleCharging);
+            }
+        }
+    }
+
     @Override
     public void dispatchDemoCommand(String command, Bundle args) {
         if (!mDemoModeController.isInDemoMode()) {
@@ -464,6 +493,7 @@
         String powerSave = args.getString("powersave");
         String present = args.getString("present");
         String defender = args.getString("defender");
+        String incompatible = args.getString("incompatible");
         if (level != null) {
             mLevel = Math.min(Math.max(Integer.parseInt(level), 0), 100);
         }
@@ -482,6 +512,10 @@
             mIsBatteryDefender = defender.equals("true");
             fireIsBatteryDefenderChanged();
         }
+        if (incompatible != null) {
+            mIsIncompatibleCharging = incompatible.equals("true");
+            fireIsIncompatibleChargingChanged();
+        }
         fireBatteryLevelChanged();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
index 6b80a9d..b2ef818 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ConfigurationController.java
@@ -42,5 +42,6 @@
         default void onThemeChanged() {}
         default void onLocaleListChanged() {}
         default void onLayoutDirectionChanged(boolean isLayoutRtl) {}
+        default void onOrientationChanged(int orientation) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index 4950482..ffb743f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -128,7 +128,8 @@
 
         val prefs = userContextProvider.userContext.getSharedPreferences(
             PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
-        val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
+        val seededPackages =
+            prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
 
         val controlsController = controlsComponent.getControlsController().get()
         val componentsToSeed = mutableListOf<ComponentName>()
@@ -174,7 +175,8 @@
     }
 
     private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) {
-        val seededPackages = prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet())
+        val seededPackages =
+            prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
         val updatedPkgs = seededPackages.toMutableSet()
         updatedPkgs.add(pkg)
         prefs.edit().putStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, updatedPkgs).apply()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 710588c..63dcad9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -18,6 +18,8 @@
 
 import static android.hardware.biometrics.BiometricSourceType.FACE;
 
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+
 import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -38,6 +40,7 @@
 import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 
 import dagger.Lazy;
@@ -49,6 +52,7 @@
 import javax.inject.Inject;
 
 /**
+ *
  */
 @SysUISingleton
 public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable {
@@ -103,7 +107,10 @@
      */
     private boolean mSnappingKeyguardBackAfterSwipe = false;
 
+    private FeatureFlags mFeatureFlags;
+
     /**
+     *
      */
     @Inject
     public KeyguardStateControllerImpl(
@@ -112,13 +119,15 @@
             LockPatternUtils lockPatternUtils,
             Lazy<KeyguardUnlockAnimationController> keyguardUnlockAnimationController,
             KeyguardUpdateMonitorLogger logger,
-            DumpManager dumpManager) {
+            DumpManager dumpManager,
+            FeatureFlags featureFlags) {
         mContext = context;
         mLogger = logger;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mLockPatternUtils = lockPatternUtils;
         mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
         mUnlockAnimationControllerLazy = keyguardUnlockAnimationController;
+        mFeatureFlags = featureFlags;
 
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
 
@@ -272,7 +281,8 @@
     @Override
     public boolean isKeyguardScreenRotationAllowed() {
         return SystemProperties.getBoolean("lockscreen.rot_override", false)
-                || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation);
+                || mContext.getResources().getBoolean(R.bool.config_enableLockScreenRotation)
+                || mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index f8c36dc..518a9b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -55,7 +55,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.util.DeviceConfigProxy;
-import com.android.systemui.util.Utils;
 import com.android.systemui.util.settings.SecureSettings;
 
 import java.util.ArrayList;
@@ -362,7 +361,8 @@
         private static final int MSG_ADD_CALLBACK = 3;
         private static final int MSG_REMOVE_CALLBACK = 4;
 
-        private ArrayList<LocationChangeCallback> mSettingsChangeCallbacks = new ArrayList<>();
+        private final ArrayList<LocationChangeCallback> mSettingsChangeCallbacks =
+                new ArrayList<>();
 
         H(Looper looper) {
             super(looper);
@@ -388,14 +388,23 @@
         }
 
         private void locationActiveChanged() {
-            Utils.safeForeach(mSettingsChangeCallbacks,
-                    cb -> cb.onLocationActiveChanged(mAreActiveLocationRequests));
+            synchronized (mSettingsChangeCallbacks) {
+                final int n = mSettingsChangeCallbacks.size();
+                for (int i = 0; i < n; i++) {
+                    mSettingsChangeCallbacks.get(i)
+                            .onLocationActiveChanged(mAreActiveLocationRequests);
+                }
+            }
         }
 
         private void locationSettingsChanged() {
             boolean isEnabled = isLocationEnabled();
-            Utils.safeForeach(mSettingsChangeCallbacks,
-                    cb -> cb.onLocationSettingsChanged(isEnabled));
+            synchronized (mSettingsChangeCallbacks) {
+                final int n = mSettingsChangeCallbacks.size();
+                for (int i = 0; i < n; i++) {
+                    mSettingsChangeCallbacks.get(i).onLocationSettingsChanged(isEnabled);
+                }
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
index ae9d9ee..cd1dcd5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt
@@ -64,7 +64,7 @@
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
         val availableWidth = MeasureSpec.getSize(widthMeasureSpec) - paddingStart - paddingEnd
         if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED && !freezeSwitching) {
-            onMeasureListener?.onMeasureAction(availableWidth)
+            onMeasureListener?.onMeasureAction(availableWidth, widthMeasureSpec)
         }
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
     }
@@ -74,6 +74,6 @@
     }
 
     interface OnMeasureListener {
-        fun onMeasureAction(availableWidth: Int)
+        fun onMeasureAction(availableWidth: Int, widthMeasureSpec: Int)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
index f040d0a..4a31b86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt
@@ -28,9 +28,11 @@
 import android.os.UserHandle
 import android.text.TextUtils
 import android.util.Log
+import android.view.View.MeasureSpec
 import androidx.annotation.VisibleForTesting
 import com.android.systemui.Dependency
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.time.SystemClock
 import java.text.FieldPosition
@@ -80,6 +82,7 @@
 class VariableDateViewController(
     private val systemClock: SystemClock,
     private val broadcastDispatcher: BroadcastDispatcher,
+    private val shadeExpansionStateManager: ShadeExpansionStateManager,
     private val timeTickHandler: Handler,
     view: VariableDateView
 ) : ViewController<VariableDateView>(view) {
@@ -94,6 +97,7 @@
                 post(::updateClock)
             }
         }
+    private var isQsExpanded = false
     private var lastWidth = Integer.MAX_VALUE
     private var lastText = ""
     private var currentTime = Date()
@@ -131,7 +135,11 @@
     }
 
     private val onMeasureListener = object : VariableDateView.OnMeasureListener {
-        override fun onMeasureAction(availableWidth: Int) {
+        override fun onMeasureAction(availableWidth: Int, widthMeasureSpec: Int) {
+            if (!isQsExpanded && MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {
+                // ignore measured width from AT_MOST passes when in QQS (b/289489856)
+                return
+            }
             if (availableWidth != lastWidth) {
                 // maybeChangeFormat will post if the pattern needs to change.
                 maybeChangeFormat(availableWidth)
@@ -140,6 +148,15 @@
         }
     }
 
+    private fun onQsExpansionFractionChanged(qsExpansionFraction: Float) {
+        val newIsQsExpanded = qsExpansionFraction > 0.5
+        if (newIsQsExpanded != isQsExpanded) {
+            isQsExpanded = newIsQsExpanded
+            // manually trigger a measure pass midway through the transition from QS to QQS
+            post { mView.measure(0, 0) }
+        }
+    }
+
     override fun onViewAttached() {
         val filter = IntentFilter().apply {
             addAction(Intent.ACTION_TIME_TICK)
@@ -151,6 +168,7 @@
         broadcastDispatcher.registerReceiver(intentReceiver, filter,
                 HandlerExecutor(timeTickHandler), UserHandle.SYSTEM)
 
+        shadeExpansionStateManager.addQsExpansionFractionListener(::onQsExpansionFractionChanged)
         post(::updateClock)
         mView.onAttach(onMeasureListener)
     }
@@ -158,6 +176,7 @@
     override fun onViewDetached() {
         dateFormat = null
         mView.onAttach(null)
+        shadeExpansionStateManager.removeQsExpansionFractionListener(::onQsExpansionFractionChanged)
         broadcastDispatcher.unregisterReceiver(intentReceiver)
     }
 
@@ -211,12 +230,14 @@
     class Factory @Inject constructor(
         private val systemClock: SystemClock,
         private val broadcastDispatcher: BroadcastDispatcher,
+        private val shadeExpansionStateManager: ShadeExpansionStateManager,
         @Named(Dependency.TIME_TICK_HANDLER_NAME) private val handler: Handler
     ) {
         fun create(view: VariableDateView): VariableDateViewController {
             return VariableDateViewController(
                     systemClock,
                     broadcastDispatcher,
+                    shadeExpansionStateManager,
                     handler,
                     view
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
index d4abc40..d31c001 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt
@@ -41,7 +41,7 @@
 }
 
 @Module
-private interface InternalRemoteInputViewModule {
+interface InternalRemoteInputViewModule {
     @Binds
     fun bindController(impl: RemoteInputViewControllerImpl): RemoteInputViewController
 }
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 27aaa68..eb7d339 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -353,8 +353,8 @@
                     // before CoreStartables run, and will not be removed.
                     // In many cases, it reports the battery level of the stylus.
                     registerBatteryListener(deviceId)
-                } else if (device.bluetoothAddress != null) {
-                    onStylusBluetoothConnected(deviceId, device.bluetoothAddress)
+                } else {
+                    device.bluetoothAddress?.let { onStylusBluetoothConnected(deviceId, it) }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
index 72786ef..5ad9630 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherFullscreenDialog.kt
@@ -60,7 +60,7 @@
 
     override fun getWidth(): Int {
         val displayMetrics = context.resources.displayMetrics.apply {
-            context.display.getRealMetrics(this)
+            checkNotNull(context.display).getRealMetrics(this)
         }
         return displayMetrics.widthPixels
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
index 088cd93..ee84580 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -52,22 +52,22 @@
     override fun show() {
         // need to call show() first in order to construct the listView
         super.show()
-        val listView = getListView()
+        listView?.apply {
+            isVerticalScrollBarEnabled = false
+            isHorizontalScrollBarEnabled = false
 
-        listView.setVerticalScrollBarEnabled(false)
-        listView.setHorizontalScrollBarEnabled(false)
+            // Creates a transparent spacer between items
+            val shape = ShapeDrawable()
+            shape.alpha = 0
+            divider = shape
+            dividerHeight = res.getDimensionPixelSize(
+                R.dimen.bouncer_user_switcher_popup_divider_height)
 
-        // Creates a transparent spacer between items
-        val shape = ShapeDrawable()
-        shape.setAlpha(0)
-        listView.setDivider(shape)
-        listView.setDividerHeight(res.getDimensionPixelSize(
-            R.dimen.bouncer_user_switcher_popup_divider_height))
-
-        val height = res.getDimensionPixelSize(R.dimen.bouncer_user_switcher_popup_header_height)
-        listView.addHeaderView(createSpacer(height), null, false)
-        listView.addFooterView(createSpacer(height), null, false)
-        setWidth(findMaxWidth(listView))
+            val height = res.getDimensionPixelSize(R.dimen.bouncer_user_switcher_popup_header_height)
+            addHeaderView(createSpacer(height), null, false)
+            addFooterView(createSpacer(height), null, false)
+            setWidth(findMaxWidth(this))
+        }
 
         super.show()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index f026f0f..bfed0c4 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -227,7 +227,8 @@
                     )
                 }
                 try {
-                    WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null)
+                    checkNotNull(WindowManagerGlobal.getWindowManagerService())
+                        .lockNow(/* options= */ null)
                 } catch (e: RemoteException) {
                     Log.e(
                         TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
index 00ca92d..1ac86ce 100644
--- a/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/legacyhelper/ui/LegacyUserUiHelper.kt
@@ -67,7 +67,7 @@
         val resourceId: Int? = getGuestUserRecordNameResourceId(record)
         return when {
             resourceId != null -> context.getString(resourceId)
-            record.info != null -> record.info.name
+            record.info != null -> checkNotNull(record.info.name)
             else ->
                 context.getString(
                     getUserSwitcherActionTextResourceId(
diff --git a/packages/SystemUI/src/com/android/systemui/util/IListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/IListenerSet.kt
new file mode 100644
index 0000000..b0230b8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/IListenerSet.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+/**
+ * A collection of listeners, observers, callbacks, etc.
+ *
+ * This container is optimized for infrequent mutation and frequent iteration, with thread safety
+ * and reentrant-safety guarantees as well. Specifically, to ensure that
+ * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes made to
+ * the set after the iterator is constructed.
+ */
+interface IListenerSet<E : Any> : Set<E> {
+    /**
+     * A thread-safe, reentrant-safe method to add a listener. Does nothing if the listener is
+     * already in the set.
+     */
+    fun addIfAbsent(element: E): Boolean
+
+    /** A thread-safe, reentrant-safe method to remove a listener. */
+    fun remove(element: E): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
index a47e614..f8e0b3d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt
@@ -29,20 +29,12 @@
 class ListenerSet<E : Any>
 /** Private constructor takes the internal list so that we can use auto-delegation */
 private constructor(private val listeners: CopyOnWriteArrayList<E>) :
-    Collection<E> by listeners, Set<E> {
+    Collection<E> by listeners, IListenerSet<E> {
 
     /** Create a new instance */
     constructor() : this(CopyOnWriteArrayList())
 
-    /**
-     * A thread-safe, reentrant-safe method to add a listener. Does nothing if the listener is
-     * already in the set.
-     */
-    fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element)
+    override fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(element)
 
-    /** A thread-safe, reentrant-safe method to remove a listener. */
-    fun remove(element: E): Boolean = listeners.remove(element)
+    override fun remove(element: E): Boolean = listeners.remove(element)
 }
-
-/** Extension to match Collection which is implemented to only be (easily) accessible in kotlin */
-fun <T : Any> ListenerSet<T>.isNotEmpty(): Boolean = !isEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt b/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt
new file mode 100644
index 0000000..c90b57e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/NamedListenerSet.kt
@@ -0,0 +1,96 @@
+/*
+ * 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
+
+import java.util.concurrent.CopyOnWriteArrayList
+import java.util.function.Consumer
+
+/**
+ * A collection of listeners, observers, callbacks, etc.
+ *
+ * This container is optimized for infrequent mutation and frequent iteration, with thread safety
+ * and reentrant-safety guarantees as well. Specifically, to ensure that
+ * [ConcurrentModificationException] is never thrown, this iterator will not reflect changes made to
+ * the set after the iterator is constructed.
+ *
+ * This class provides all the abilities of [ListenerSet], except that each listener has a name
+ * calculated at runtime which can be used for time-efficient tracing of listener invocations.
+ */
+class NamedListenerSet<E : Any>(
+    private val getName: (E) -> String = { it.javaClass.name },
+) : IListenerSet<E> {
+    private val listeners = CopyOnWriteArrayList<NamedListener>()
+
+    override val size: Int
+        get() = listeners.size
+
+    override fun isEmpty() = listeners.isEmpty()
+
+    override fun iterator(): Iterator<E> = iterator {
+        listeners.iterator().forEach { yield(it.listener) }
+    }
+
+    override fun containsAll(elements: Collection<E>) =
+        listeners.count { it.listener in elements } == elements.size
+
+    override fun contains(element: E) = listeners.firstOrNull { it.listener == element } != null
+
+    override fun addIfAbsent(element: E): Boolean = listeners.addIfAbsent(NamedListener(element))
+
+    override fun remove(element: E): Boolean = listeners.removeIf { it.listener == element }
+
+    /** A wrapper for the listener with an associated name. */
+    inner class NamedListener(val listener: E) {
+        val name: String = getName(listener)
+
+        override fun hashCode(): Int {
+            return listener.hashCode()
+        }
+
+        override fun equals(other: Any?): Boolean =
+            when {
+                other === null -> false
+                other === this -> true
+                other !is NamedListenerSet<*>.NamedListener -> false
+                listener == other.listener -> true
+                else -> false
+            }
+    }
+
+    /** Iterate the listeners in the set, providing the name for each one as well. */
+    inline fun forEachNamed(block: (String, E) -> Unit) =
+        namedIterator().forEach { element -> block(element.name, element.listener) }
+
+    /**
+     * Iterate the listeners in the set, wrapping each call to the block with [traceSection] using
+     * the listener name.
+     */
+    inline fun forEachTraced(block: (E) -> Unit) = forEachNamed { name, listener ->
+        traceSection(name) { block(listener) }
+    }
+
+    /**
+     * Iterate the listeners in the set, wrapping each call to the block with [traceSection] using
+     * the listener name.
+     */
+    fun forEachTraced(consumer: Consumer<E>) = forEachNamed { name, listener ->
+        traceSection(name) { consumer.accept(listener) }
+    }
+
+    /** Iterate over the [NamedListener]s currently in the set. */
+    fun namedIterator(): Iterator<NamedListener> = listeners.iterator()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index c2727fc..e0daa070 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -37,6 +37,10 @@
     /**
      * Allows lambda iteration over a list. It is done in reverse order so it is safe
      * to add or remove items during the iteration.  Skips over null items.
+     *
+     * @deprecated According to b/286841705, this is *not* safe: If an item is removed from the
+     *   list, then list.get(i) could throw an IndexOutOfBoundsException. This method should not be
+     *   used; try using `synchronized` or making a copy of the list instead.
      */
     public static <T> void safeForeach(List<T> list, Consumer<T> c) {
         for (int i = list.size() - 1; i >= 0; i--) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
index 56c5d3b..7866d76 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt
@@ -223,11 +223,11 @@
         }
     }
 
-    override fun dispatchDraw(canvas: Canvas?) {
-        canvas?.save()
-        canvas?.clipRect(boundsRect)
+    override fun dispatchDraw(canvas: Canvas) {
+        canvas.save()
+        canvas.clipRect(boundsRect)
         super.dispatchDraw(canvas)
-        canvas?.restore()
+        canvas.restore()
     }
 
     private fun updateBounds() {
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
index 12d7b4d..0d0a646 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/JavaAdapter.kt
@@ -29,7 +29,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
 /** A class allowing Java classes to collect on Kotlin flows. */
@@ -75,3 +75,7 @@
         repeatOnLifecycle(state) { flow.collect { consumer.accept(it) } }
     }
 }
+
+fun <A, B, R> combineFlows(flow1: Flow<A>, flow2: Flow<B>, bifunction: (A, B) -> R): Flow<R> {
+    return combine(flow1, flow2, bifunction)
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index e7d420b..9016220 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -419,7 +419,15 @@
 
         assertFalse(mWifiRepository.isWifiConnectedWithValidSsid());
         mWifiRepository.setWifiNetwork(
-                new WifiNetworkModel.Active(0, false, 0, "", false, false, null));
+                new WifiNetworkModel.Active(
+                        /* networkId= */ 0,
+                        /* isValidated= */ false,
+                        /* level= */ 0,
+                        /* ssid= */ "",
+                        /* hotspotDeviceType= */ WifiNetworkModel.HotspotDeviceType.NONE,
+                        /* isPasspointAccessPoint= */ false,
+                        /* isOnlineSignUpForPasspointAccessPoint= */ false,
+                        /* passpointProviderFriendlyName= */ null));
         assertTrue(mWifiRepository.isWifiConnectedWithValidSsid());
 
         mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
index ac04bc4..98d4d22 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java
@@ -23,6 +23,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -157,6 +158,12 @@
         when(mSmartspaceController.buildAndConnectDateView(any())).thenReturn(mFakeDateView);
         when(mSmartspaceController.buildAndConnectWeatherView(any())).thenReturn(mFakeWeatherView);
         when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mFakeSmartspaceView);
+        doAnswer(invocation -> {
+            removeView(mFakeDateView);
+            removeView(mFakeWeatherView);
+            removeView(mFakeSmartspaceView);
+            return null;
+        }).when(mSmartspaceController).removeViewsFromParent(any());
         mExecutor = new FakeExecutor(new FakeSystemClock());
         mFakeFeatureFlags = new FakeFeatureFlags();
         mFakeFeatureFlags.set(FACE_AUTH_REFACTOR, false);
@@ -201,6 +208,13 @@
         when(mView.findViewById(R.id.keyguard_status_area)).thenReturn(mStatusArea);
     }
 
+    private void removeView(View v) {
+        ViewGroup group = ((ViewGroup) v.getParent());
+        if (group != null) {
+            group.removeView(v);
+        }
+    }
+
     protected void init() {
         mController.init();
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index cb18229..9f7ab7b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -150,7 +150,7 @@
     private fun getPatternTopGuideline(): Float {
         val cs = ConstraintSet()
         val container =
-            mKeyguardPatternView.findViewById(R.id.pattern_container) as ConstraintLayout
+            mKeyguardPatternView.requireViewById(R.id.pattern_container) as ConstraintLayout
         cs.clone(container)
         return cs.getConstraint(R.id.pattern_top_guideline).layout.guidePercent
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 4dc7652..309d9e0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -114,7 +114,7 @@
 
         objectKeyguardPINView =
             View.inflate(mContext, R.layout.keyguard_pin_view, null)
-                .findViewById(R.id.keyguard_pin_view) as KeyguardPINView
+                .requireViewById(R.id.keyguard_pin_view) as KeyguardPINView
     }
 
     private fun constructPinViewController(
@@ -175,7 +175,7 @@
 
     private fun getPinTopGuideline(): Float {
         val cs = ConstraintSet()
-        val container = objectKeyguardPINView.findViewById(R.id.pin_container) as ConstraintLayout
+        val container = objectKeyguardPINView.requireViewById(R.id.pin_container) as ConstraintLayout
         cs.clone(container)
         return cs.getConstraint(R.id.pin_pad_top_guideline).layout.guidePercent
     }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index efb981e..0192e78 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -27,6 +27,7 @@
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.MotionEvent
+import android.view.View
 import android.view.WindowInsetsController
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
@@ -37,6 +38,7 @@
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
 import com.android.systemui.biometrics.SideFpsController
 import com.android.systemui.biometrics.SideFpsUiRequestSource
@@ -45,11 +47,14 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -66,6 +71,8 @@
 import java.util.Optional
 import junit.framework.Assert
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -80,6 +87,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
@@ -139,6 +147,9 @@
     private lateinit var testableResources: TestableResources
     private lateinit var sceneTestUtils: SceneTestUtils
     private lateinit var sceneInteractor: SceneInteractor
+    private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    private lateinit var authenticationInteractor: AuthenticationInteractor
+    private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
 
     private lateinit var underTest: KeyguardSecurityContainerController
 
@@ -176,6 +187,7 @@
         featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
         featureFlags.set(Flags.SCENE_CONTAINER, false)
         featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
+        featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
 
         keyguardPasswordViewController =
             KeyguardPasswordViewController(
@@ -198,6 +210,17 @@
         whenever(userInteractor.getSelectedUserId()).thenReturn(TARGET_USER_ID)
         sceneTestUtils = SceneTestUtils(this)
         sceneInteractor = sceneTestUtils.sceneInteractor()
+        keyguardTransitionInteractor =
+            KeyguardTransitionInteractorFactory.create(sceneTestUtils.testScope.backgroundScope)
+                .keyguardTransitionInteractor
+        sceneTransitionStateFlow =
+            MutableStateFlow(ObservableTransitionState.Idle(SceneKey.Lockscreen))
+        sceneInteractor.setTransitionState(sceneTransitionStateFlow)
+        authenticationInteractor =
+            sceneTestUtils.authenticationInteractor(
+                repository = sceneTestUtils.authenticationRepository(),
+                sceneInteractor = sceneInteractor
+            )
 
         underTest =
             KeyguardSecurityContainerController(
@@ -227,8 +250,9 @@
                 { JavaAdapter(sceneTestUtils.testScope.backgroundScope) },
                 userInteractor,
                 faceAuthAccessibilityDelegate,
+                keyguardTransitionInteractor
             ) {
-                sceneInteractor
+                authenticationInteractor
             }
     }
 
@@ -720,7 +744,7 @@
     }
 
     @Test
-    fun dismissesKeyguard_whenSceneChangesFromBouncerToGone() =
+    fun dismissesKeyguard_whenSceneChangesToGone() =
         sceneTestUtils.testScope.runTest {
             featureFlags.set(Flags.SCENE_CONTAINER, true)
 
@@ -733,20 +757,39 @@
             // is
             // not enough to trigger a dismissal of the keyguard.
             underTest.onViewAttached()
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    SceneKey.Lockscreen,
+                    SceneKey.Bouncer,
+                    flowOf(.5f)
+                )
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
 
             // While listening, going from the bouncer scene to the gone scene, does dismiss the
             // keyguard.
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
             runCurrent()
             verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
 
             // While listening, moving back to the bouncer scene does not dismiss the keyguard
             // again.
             clearInvocations(viewMediatorCallback)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(SceneKey.Gone, SceneKey.Bouncer, flowOf(.5f))
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Bouncer)
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
 
@@ -754,24 +797,56 @@
             // scene
             // does not dismiss the keyguard while we're not listening.
             underTest.onViewDetached()
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(SceneKey.Bouncer, SceneKey.Gone, flowOf(.5f))
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
 
-            // While not listening, moving back to the bouncer does not dismiss the keyguard.
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer, null), "reason")
+            // While not listening, moving to the lockscreen does not dismiss the keyguard.
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    SceneKey.Gone,
+                    SceneKey.Lockscreen,
+                    flowOf(.5f)
+                )
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Lockscreen)
             runCurrent()
             verify(viewMediatorCallback, never()).keyguardDone(anyBoolean(), anyInt())
 
             // Reattaching the view starts listening again so moving from the bouncer scene to the
-            // gone
-            // scene now does dismiss the keyguard again.
+            // gone scene now does dismiss the keyguard again, this time from lockscreen.
             underTest.onViewAttached()
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone, null), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone, null), "reason")
+            sceneTransitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    SceneKey.Lockscreen,
+                    SceneKey.Gone,
+                    flowOf(.5f)
+                )
+            runCurrent()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone, null), "reason")
+            sceneTransitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
             runCurrent()
             verify(viewMediatorCallback).keyguardDone(anyBoolean(), anyInt())
         }
 
+    @Test
+    fun testResetUserSwitcher() {
+        val userSwitcher = mock(View::class.java)
+        whenever(view.findViewById<View>(R.id.keyguard_bouncer_user_switcher))
+            .thenReturn(userSwitcher)
+
+        underTest.prepareToShow()
+        verify(userSwitcher).setAlpha(0f)
+    }
+
     private val registeredSwipeListener: KeyguardSecurityContainer.SwipeListener
         get() {
             underTest.onViewAttached()
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 20d9ef1..7d23c80 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -119,8 +120,8 @@
     @Test
     public void correctlyDump() {
         mController.onInit();
-        verify(mDumpManager).registerDumpable(mController);
+        verify(mDumpManager).registerDumpable(eq(mController.getInstanceName()), eq(mController));
         mController.onDestroy();
-        verify(mDumpManager, times(1)).unregisterDumpable(KeyguardStatusViewController.TAG);
+        verify(mDumpManager, times(1)).unregisterDumpable(eq(mController.getInstanceName()));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
index f8262d4..210f3cb 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt
@@ -21,9 +21,9 @@
 
     private lateinit var keyguardStatusView: KeyguardStatusView
     private val mediaView: View
-        get() = keyguardStatusView.findViewById(R.id.status_view_media_container)
+        get() = keyguardStatusView.requireViewById(R.id.status_view_media_container)
     private val statusViewContainer: ViewGroup
-        get() = keyguardStatusView.findViewById(R.id.status_view_container)
+        get() = keyguardStatusView.requireViewById(R.id.status_view_container)
     private val childrenExcludingMedia
         get() = statusViewContainer.children.filter { it != mediaView }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 5abab62..6f3322a 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -40,6 +40,7 @@
 import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
 import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
+import static com.android.systemui.flags.Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
@@ -89,6 +90,7 @@
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.face.FaceAuthenticateOptions;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorProperties;
@@ -121,6 +123,9 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.text.TextUtils;
+import android.view.Display;
+import android.view.DisplayAdjustments;
+import android.view.DisplayInfo;
 
 import androidx.annotation.NonNull;
 
@@ -143,6 +148,7 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -304,10 +310,12 @@
             mFingerprintAuthenticatorsRegisteredCallback;
     private IFaceAuthenticatorsRegisteredCallback mFaceAuthenticatorsRegisteredCallback;
     private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999);
+    private FakeDisplayTracker mDisplayTracker;
 
     @Before
     public void setup() throws RemoteException {
         MockitoAnnotations.initMocks(this);
+        mDisplayTracker = new FakeDisplayTracker(mContext);
         when(mSessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(mKeyguardInstanceId);
 
         when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
@@ -348,6 +356,7 @@
         allowTestableLooperAsMainThread();
         mFeatureFlags = new FakeFeatureFlags();
         mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
+        mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, false);
 
         when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
 
@@ -358,6 +367,11 @@
                         anyInt());
 
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
+        setupBiometrics(mKeyguardUpdateMonitor);
+    }
+
+    private void setupBiometrics(KeyguardUpdateMonitor keyguardUpdateMonitor)
+            throws RemoteException {
         captureAuthenticatorsRegisteredCallbacks();
         setupFaceAuth(/* isClass3 */ false);
         setupFingerprintAuth(/* isClass3 */ true);
@@ -367,9 +381,9 @@
         mBiometricEnabledOnKeyguardCallback = mBiometricEnabledCallbackArgCaptor.getValue();
         biometricsEnabledForCurrentUser();
 
-        mHandler = spy(mKeyguardUpdateMonitor.getHandler());
+        mHandler = spy(keyguardUpdateMonitor.getHandler());
         try {
-            FieldSetter.setField(mKeyguardUpdateMonitor,
+            FieldSetter.setField(keyguardUpdateMonitor,
                     KeyguardUpdateMonitor.class.getDeclaredField("mHandler"), mHandler);
         } catch (NoSuchFieldException e) {
 
@@ -3029,6 +3043,79 @@
         verify(callback).onBiometricEnrollmentStateChanged(BiometricSourceType.FACE);
     }
 
+    @Test
+    public void stopFaceAuthOnDisplayOffFlagNotEnabled_doNotRegisterForDisplayCallback() {
+        assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(0);
+    }
+
+    @Test
+    public void onDisplayOn_nothingHappens() throws RemoteException {
+        // GIVEN
+        keyguardIsVisible();
+        enableStopFaceAuthOnDisplayOff();
+
+        // WHEN the default display state changes to ON
+        triggerDefaultDisplayStateChangeToOn();
+
+        // THEN face auth is NOT started since we rely on STARTED_WAKING_UP to start face auth,
+        // NOT the display on event
+        verifyFaceAuthenticateNeverCalled();
+        verifyFaceDetectNeverCalled();
+    }
+
+    @Test
+    public void onDisplayOff_stopFaceAuth() throws RemoteException {
+        enableStopFaceAuthOnDisplayOff();
+
+        // GIVEN device is listening for face
+        mKeyguardUpdateMonitor.setKeyguardShowing(true, false);
+        mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
+        mTestableLooper.processAllMessages();
+        verifyFaceAuthenticateCall();
+
+        final CancellationSignal faceCancel = spy(mKeyguardUpdateMonitor.mFaceCancelSignal);
+        mKeyguardUpdateMonitor.mFaceCancelSignal = faceCancel;
+        KeyguardUpdateMonitorCallback callback = mock(KeyguardUpdateMonitorCallback.class);
+        mKeyguardUpdateMonitor.registerCallback(callback);
+
+        // WHEN the default display state changes to OFF
+        triggerDefaultDisplayStateChangeToOff();
+
+        // THEN face listening is stopped.
+        verify(faceCancel).cancel();
+        verify(callback).onBiometricRunningStateChanged(
+                eq(false), eq(BiometricSourceType.FACE)); // beverlyt
+
+    }
+
+    private void triggerDefaultDisplayStateChangeToOn() {
+        triggerDefaultDisplayStateChangeTo(true);
+    }
+
+    private void triggerDefaultDisplayStateChangeToOff() {
+        triggerDefaultDisplayStateChangeTo(false);
+    }
+
+    /**
+     * @param on true for Display.STATE_ON, else Display.STATE_OFF
+     */
+    private void triggerDefaultDisplayStateChangeTo(boolean on) {
+        DisplayManagerGlobal displayManagerGlobal = mock(DisplayManagerGlobal.class);
+        DisplayInfo displayInfoWithDisplayState = new DisplayInfo();
+        displayInfoWithDisplayState.state = on ? Display.STATE_ON : Display.STATE_OFF;
+        when(displayManagerGlobal.getDisplayInfo(mDisplayTracker.getDefaultDisplayId()))
+                .thenReturn(displayInfoWithDisplayState);
+        mDisplayTracker.setAllDisplays(new Display[]{
+                new Display(
+                        displayManagerGlobal,
+                        mDisplayTracker.getDefaultDisplayId(),
+                        displayInfoWithDisplayState,
+                        DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS
+                )
+        });
+        mDisplayTracker.triggerOnDisplayChanged(mDisplayTracker.getDefaultDisplayId());
+    }
+
     private void verifyFingerprintAuthenticateNeverCalled() {
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), any());
         verify(mFingerprintManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
@@ -3297,6 +3384,18 @@
         mTestableLooper.processAllMessages();
     }
 
+    private void enableStopFaceAuthOnDisplayOff() throws RemoteException {
+        cleanupKeyguardUpdateMonitor();
+        clearInvocations(mFaceManager);
+        clearInvocations(mFingerprintManager);
+        clearInvocations(mBiometricManager);
+        clearInvocations(mStatusBarStateController);
+        mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, true);
+        mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
+        setupBiometrics(mKeyguardUpdateMonitor);
+        assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(1);
+    }
+
     private Intent putPhoneInfo(Intent intent, Bundle data, Boolean simInited) {
         int subscription = simInited
                 ? 1/* mock subid=1 */ : SubscriptionManager.PLACEHOLDER_SUBSCRIPTION_ID_BASE;
@@ -3374,7 +3473,7 @@
                     mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
                     mFaceWakeUpTriggersConfig, mDevicePostureController,
                     Optional.of(mInteractiveToAuthProvider), mFeatureFlags,
-                    mTaskStackChangeListeners, mActivityTaskManager);
+                    mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
index 956e0b81..b18137c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerBaseTest.java
@@ -49,7 +49,7 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.StatusBarState;
@@ -164,8 +164,9 @@
                 mVibrator,
                 mAuthRippleController,
                 mResources,
-                new KeyguardTransitionInteractor(mTransitionRepository,
-                        TestScopeProvider.getTestScope().getBackgroundScope()),
+                KeyguardTransitionInteractorFactory.create(
+                        TestScopeProvider.getTestScope().getBackgroundScope(),
+                                mTransitionRepository).getKeyguardTransitionInteractor(),
                 KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(),
                 mFeatureFlags,
                 mPrimaryBouncerInteractor
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index ed6a891..45021ba 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -20,7 +20,9 @@
 
 import static com.android.keyguard.LockIconView.ICON_LOCK;
 import static com.android.keyguard.LockIconView.ICON_UNLOCK;
+import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.eq;
@@ -33,11 +35,13 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.Pair;
+import android.view.HapticFeedbackConstants;
 import android.view.View;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.settingslib.udfps.UdfpsOverlayParams;
+import com.android.systemui.biometrics.UdfpsController;
 import com.android.systemui.doze.util.BurnInHelperKt;
 
 import org.junit.Test;
@@ -339,4 +343,59 @@
         // THEN the lock icon is shown
         verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
     }
+
+    @Test
+    public void playHaptic_onTouchExploration_NoOneWayHaptics_usesVibrate() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+
+        // WHEN request to vibrate on touch exploration
+        mUnderTest.vibrateOnTouchExploration();
+
+        // THEN vibrates
+        verify(mVibrator).vibrate(
+                anyInt(),
+                any(),
+                eq(UdfpsController.EFFECT_CLICK),
+                eq("lock-icon-down"),
+                any());
+    }
+
+    @Test
+    public void playHaptic_onTouchExploration_withOneWayHaptics_performHapticFeedback() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+
+        // WHEN request to vibrate on touch exploration
+        mUnderTest.vibrateOnTouchExploration();
+
+        // THEN performHapticFeedback is used
+        verify(mVibrator).performHapticFeedback(any(), eq(HapticFeedbackConstants.CONTEXT_CLICK));
+    }
+
+    @Test
+    public void playHaptic_onLongPress_NoOneWayHaptics_usesVibrate() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
+
+        // WHEN request to vibrate on long press
+        mUnderTest.vibrateOnLongPress();
+
+        // THEN uses vibrate
+        verify(mVibrator).vibrate(
+                anyInt(),
+                any(),
+                eq(UdfpsController.EFFECT_CLICK),
+                eq("lock-screen-lock-icon-longpress"),
+                any());
+    }
+
+    @Test
+    public void playHaptic_onLongPress_withOneWayHaptics_performHapticFeedback() {
+        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
+
+        // WHEN request to vibrate on long press
+        mUnderTest.vibrateOnLongPress();
+
+        // THEN uses perform haptic feedback
+        verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS));
+
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
index 9fcb9c8..7c2550f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/NumPadAnimatorTest.kt
@@ -47,10 +47,10 @@
 
     @Test
     fun testOnLayout() {
-        underTest.onLayout(100)
+        underTest.onLayout(100, 100)
         verify(background).cornerRadius = 50f
         reset(background)
-        underTest.onLayout(100)
+        underTest.onLayout(100, 100)
         verify(background, never()).cornerRadius = anyFloat()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
index 6fe8892..9f9b9a4 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/SplitShadeTransitionAdapterTest.kt
@@ -19,9 +19,11 @@
 import android.testing.AndroidTestingRunner
 import android.transition.TransitionValues
 import android.view.View
+import android.view.ViewGroup
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardStatusViewController.SplitShadeTransitionAdapter
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -84,5 +86,5 @@
     startValues: TransitionValues?,
     endValues: TransitionValues?
 ): Animator? {
-    return createAnimator(/* sceneRoot= */ null, startValues, endValues)
+    return createAnimator(/* sceneRoot= */ mock(), startValues, endValues)
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
index 01d3a39..ea7cc3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/FaceScanningProviderFactoryTest.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.biometrics.AuthController
 import com.android.systemui.decor.FaceScanningProviderFactory
 import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.log.ScreenDecorationsLogger
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.util.mockito.whenever
@@ -80,6 +82,8 @@
             R.bool.config_fillMainBuiltInDisplayCutout,
             true
         )
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION, true)
         underTest =
             FaceScanningProviderFactory(
                 authController,
@@ -87,7 +91,8 @@
                 statusBarStateController,
                 keyguardUpdateMonitor,
                 mock(Executor::class.java),
-                ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest"))
+                ScreenDecorationsLogger(logcatLogBuffer("FaceScanningProviderFactoryTest")),
+                featureFlags,
             )
 
         whenever(authController.faceSensorLocation).thenReturn(Point(10, 10))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
deleted file mode 100644
index b47b08c..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * Copyright (C) 2017 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 static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.TestCase.fail;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.UserIdInt;
-import android.app.AppOpsManager;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.widget.RemoteViews;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.messages.nano.SystemMessageProto;
-import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class ForegroundServiceControllerTest extends SysuiTestCase {
-    private ForegroundServiceController mFsc;
-    private ForegroundServiceNotificationListener mListener;
-    private NotifCollectionListener mCollectionListener;
-    @Mock private AppOpsController mAppOpsController;
-    @Mock private Handler mMainHandler;
-    @Mock private NotifPipeline mNotifPipeline;
-
-    @Before
-    public void setUp() throws Exception {
-        // allow the TestLooper to be asserted as the main thread these tests
-        allowTestableLooperAsMainThread();
-
-        MockitoAnnotations.initMocks(this);
-        mFsc = new ForegroundServiceController(mAppOpsController, mMainHandler);
-        mListener = new ForegroundServiceNotificationListener(
-                mContext, mFsc, mNotifPipeline);
-        mListener.init();
-        ArgumentCaptor<NotifCollectionListener> entryListenerCaptor =
-                ArgumentCaptor.forClass(NotifCollectionListener.class);
-        verify(mNotifPipeline).addCollectionListener(
-                entryListenerCaptor.capture());
-        mCollectionListener = entryListenerCaptor.getValue();
-    }
-
-    @Test
-    public void testAppOpsChangedCalledFromBgThread() {
-        try {
-            // WHEN onAppOpChanged is called from a different thread than the MainLooper
-            disallowTestableLooperAsMainThread();
-            NotificationEntry entry = createFgEntry();
-            mFsc.onAppOpChanged(
-                    AppOpsManager.OP_CAMERA,
-                    entry.getSbn().getUid(),
-                    entry.getSbn().getPackageName(),
-                    true);
-
-            // This test is run on the TestableLooper, which is not the MainLooper, so
-            // we expect an exception to be thrown
-            fail("onAppOpChanged shouldn't be allowed to be called from a bg thread.");
-        } catch (IllegalStateException e) {
-            // THEN expect an exception
-        }
-    }
-
-    @Test
-    public void testAppOpsCRUD() {
-        // no crash on remove that doesn't exist
-        mFsc.onAppOpChanged(9, 1000, "pkg1", false);
-        assertNull(mFsc.getAppOps(0, "pkg1"));
-
-        // multiuser & multipackage
-        mFsc.onAppOpChanged(8, 50, "pkg1", true);
-        mFsc.onAppOpChanged(1, 60, "pkg3", true);
-        mFsc.onAppOpChanged(7, 500000, "pkg2", true);
-
-        assertEquals(1, mFsc.getAppOps(0, "pkg1").size());
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
-
-        assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
-        assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
-
-        assertEquals(1, mFsc.getAppOps(0, "pkg3").size());
-        assertTrue(mFsc.getAppOps(0, "pkg3").contains(1));
-
-        // multiple ops for the same package
-        mFsc.onAppOpChanged(9, 50, "pkg1", true);
-        mFsc.onAppOpChanged(5, 50, "pkg1", true);
-
-        assertEquals(3, mFsc.getAppOps(0, "pkg1").size());
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(9));
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
-
-        assertEquals(1, mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").size());
-        assertTrue(mFsc.getAppOps(UserHandle.getUserId(500000), "pkg2").contains(7));
-
-        // remove one of the multiples
-        mFsc.onAppOpChanged(9, 50, "pkg1", false);
-        assertEquals(2, mFsc.getAppOps(0, "pkg1").size());
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(8));
-        assertTrue(mFsc.getAppOps(0, "pkg1").contains(5));
-
-        // remove last op
-        mFsc.onAppOpChanged(1, 60, "pkg3", false);
-        assertNull(mFsc.getAppOps(0, "pkg3"));
-    }
-
-    @Test
-    public void testDisclosurePredicate() {
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, "com.example.app1",
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user1_disclosure = makeMockSBN(USERID_ONE, "android",
-                SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
-                null, Notification.FLAG_NO_CLEAR);
-
-        assertTrue(mFsc.isDisclosureNotification(sbn_user1_disclosure));
-        assertFalse(mFsc.isDisclosureNotification(sbn_user1_app1));
-    }
-
-    @Test
-    public void testNeedsDisclosureAfterRemovingUnrelatedNotification() {
-        final String PKG1 = "com.example.app100";
-
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
-
-        // first add a normal notification
-        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-        // nothing required yet
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        // now the app starts a fg service
-        entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        // add the fg notification
-        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
-        // remove the boring notification
-        entryRemoved(sbn_user1_app1);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has STILL got it covered
-        entryRemoved(sbn_user1_app1_fg);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-    }
-
-    @Test
-    public void testSimpleAddRemove() {
-        final String PKG1 = "com.example.app1";
-        final String PKG2 = "com.example.app2";
-
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-
-        // no services are "running"
-        entryAdded(makeMockDisclosure(USERID_ONE, null),
-                NotificationManager.IMPORTANCE_DEFAULT);
-
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // switch to different package
-        entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryUpdated(makeMockDisclosure(USERID_TWO, new String[]{PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO)); // finally user2 needs one too
-
-        entryUpdated(makeMockDisclosure(USERID_ONE, new String[]{PKG2, PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryRemoved(makeMockDisclosure(USERID_ONE, null /*unused*/));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryRemoved(makeMockDisclosure(USERID_TWO, null /*unused*/));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-    }
-
-    @Test
-    public void testDisclosureBasic() {
-        final String PKG1 = "com.example.app0";
-
-        StatusBarNotification sbn_user1_app1 = makeMockSBN(USERID_ONE, PKG1,
-                5000, "monkeys", Notification.FLAG_AUTO_CANCEL);
-        StatusBarNotification sbn_user1_app1_fg = makeMockFgSBN(USERID_ONE, PKG1);
-
-        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT); // not fg
-        entryAdded(makeMockDisclosure(USERID_ONE, new String[]{PKG1}),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE)); // app1 has got it covered
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // let's take out the other notification and see what happens.
-
-        entryRemoved(sbn_user1_app1);
-        assertFalse(
-                mFsc.isDisclosureNeededForUser(USERID_ONE)); // still covered by sbn_user1_app1_fg
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // let's attempt to downgrade the notification from FLAG_FOREGROUND and see what we get
-        StatusBarNotification sbn_user1_app1_fg_sneaky = makeMockFgSBN(USERID_ONE, PKG1);
-        sbn_user1_app1_fg_sneaky.getNotification().flags = 0;
-        entryUpdated(sbn_user1_app1_fg_sneaky,
-                NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // ok, ok, we'll put it back
-        sbn_user1_app1_fg_sneaky.getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
-        entryUpdated(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_DEFAULT);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        entryRemoved(sbn_user1_app1_fg_sneaky);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE)); // should be required!
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-
-        // now let's test an upgrade
-        entryAdded(sbn_user1_app1, NotificationManager.IMPORTANCE_DEFAULT);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-        sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        entryUpdated(sbn_user1_app1,
-                NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
-
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-
-        // remove it, make sure we're out of compliance again
-        entryRemoved(sbn_user1_app1); // was fg, should return true
-        entryRemoved(sbn_user1_app1);
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-
-        // importance upgrade
-        entryAdded(sbn_user1_app1_fg, NotificationManager.IMPORTANCE_MIN);
-        assertTrue(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-        sbn_user1_app1.getNotification().flags |= Notification.FLAG_FOREGROUND_SERVICE;
-        entryUpdated(sbn_user1_app1_fg,
-                NotificationManager.IMPORTANCE_DEFAULT); // this is now a fg notification
-
-        // finally, let's turn off the service
-        entryAdded(makeMockDisclosure(USERID_ONE, null),
-                NotificationManager.IMPORTANCE_DEFAULT);
-
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_ONE));
-        assertFalse(mFsc.isDisclosureNeededForUser(USERID_TWO));
-    }
-
-    @Test
-    public void testNoNotifsNorAppOps_noSystemAlertWarningRequired() {
-        // no notifications nor app op signals that this package/userId requires system alert
-        // warning
-        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, "any"));
-    }
-
-    @Test
-    public void testCustomLayouts_systemAlertWarningRequired() {
-        // GIVEN a notification with a custom layout
-        final String pkg = "com.example.app0";
-        StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
-                false);
-
-        // WHEN the custom layout entry is added
-        entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
-        // THEN a system alert warning is required since there aren't any notifications that can
-        // display the app ops
-        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
-    }
-
-    @Test
-    public void testStandardLayoutExists_noSystemAlertWarningRequired() {
-        // GIVEN two notifications (one with a custom layout, the other with a standard layout)
-        final String pkg = "com.example.app0";
-        StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
-                false);
-        StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-
-        // WHEN the entries are added
-        entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-        entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
-        // THEN no system alert warning is required, since there is at least one notification
-        // with a standard layout that can display the app ops on the notification
-        assertFalse(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
-    }
-
-    @Test
-    public void testStandardLayoutRemoved_systemAlertWarningRequired() {
-        // GIVEN two notifications (one with a custom layout, the other with a standard layout)
-        final String pkg = "com.example.app0";
-        StatusBarNotification customLayoutNotif = makeMockSBN(USERID_ONE, pkg, 0,
-                false);
-        StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-
-        // WHEN the entries are added and then the standard layout notification is removed
-        entryAdded(customLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-        entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-        entryRemoved(standardLayoutNotif);
-
-        // THEN a system alert warning is required since there aren't any notifications that can
-        // display the app ops
-        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
-    }
-
-    @Test
-    public void testStandardLayoutUpdatedToCustomLayout_systemAlertWarningRequired() {
-        // GIVEN a standard layout notification and then an updated version with a customLayout
-        final String pkg = "com.example.app0";
-        StatusBarNotification standardLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, true);
-        StatusBarNotification updatedToCustomLayoutNotif = makeMockSBN(USERID_ONE, pkg, 1, false);
-
-        // WHEN the entries is added and then updated to a custom layout
-        entryAdded(standardLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-        entryUpdated(updatedToCustomLayoutNotif, NotificationManager.IMPORTANCE_MIN);
-
-        // THEN a system alert warning is required since there aren't any notifications that can
-        // display the app ops
-        assertTrue(mFsc.isSystemAlertWarningNeeded(USERID_ONE, pkg));
-    }
-
-    private StatusBarNotification makeMockSBN(int userId, String pkg, int id, String tag,
-            int flags) {
-        final Notification n = mock(Notification.class);
-        n.extras = new Bundle();
-        n.flags = flags;
-        return makeMockSBN(userId, pkg, id, tag, n);
-    }
-
-    private StatusBarNotification makeMockSBN(int userid, String pkg, int id, String tag,
-            Notification n) {
-        final StatusBarNotification sbn = mock(StatusBarNotification.class);
-        when(sbn.getNotification()).thenReturn(n);
-        when(sbn.getId()).thenReturn(id);
-        when(sbn.getPackageName()).thenReturn(pkg);
-        when(sbn.getTag()).thenReturn(tag);
-        when(sbn.getUserId()).thenReturn(userid);
-        when(sbn.getUser()).thenReturn(new UserHandle(userid));
-        when(sbn.getKey()).thenReturn("MOCK:"+userid+"|"+pkg+"|"+id+"|"+tag);
-        return sbn;
-    }
-
-    private StatusBarNotification makeMockSBN(int uid, String pkg, int id,
-            boolean usesStdLayout) {
-        StatusBarNotification sbn = makeMockSBN(uid, pkg, id, "foo", 0);
-        if (usesStdLayout) {
-            sbn.getNotification().contentView = null;
-            sbn.getNotification().headsUpContentView = null;
-            sbn.getNotification().bigContentView = null;
-        } else {
-            sbn.getNotification().contentView = mock(RemoteViews.class);
-        }
-        return sbn;
-    }
-
-    private StatusBarNotification makeMockFgSBN(int uid, String pkg, int id,
-            boolean usesStdLayout) {
-        StatusBarNotification sbn =
-                makeMockSBN(uid, pkg, id, "foo", Notification.FLAG_FOREGROUND_SERVICE);
-        if (usesStdLayout) {
-            sbn.getNotification().contentView = null;
-            sbn.getNotification().headsUpContentView = null;
-            sbn.getNotification().bigContentView = null;
-        } else {
-            sbn.getNotification().contentView = mock(RemoteViews.class);
-        }
-        return sbn;
-    }
-
-    private StatusBarNotification makeMockFgSBN(int uid, String pkg) {
-        return makeMockSBN(uid, pkg, 1000, "foo", Notification.FLAG_FOREGROUND_SERVICE);
-    }
-
-    private StatusBarNotification makeMockDisclosure(int userid, String[] pkgs) {
-        final Notification n = mock(Notification.class);
-        n.flags = Notification.FLAG_ONGOING_EVENT;
-        final Bundle extras = new Bundle();
-        if (pkgs != null) extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS, pkgs);
-        n.extras = extras;
-        n.when = System.currentTimeMillis() - 10000; // ten seconds ago
-        final StatusBarNotification sbn = makeMockSBN(userid, "android",
-                SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES,
-                null, n);
-        sbn.getNotification().extras = extras;
-        return sbn;
-    }
-
-    private NotificationEntry addFgEntry() {
-        NotificationEntry entry = createFgEntry();
-        mCollectionListener.onEntryAdded(entry);
-        return entry;
-    }
-
-    private NotificationEntry createFgEntry() {
-        return new NotificationEntryBuilder()
-                .setSbn(makeMockFgSBN(0, TEST_PACKAGE_NAME, 1000, true))
-                .setImportance(NotificationManager.IMPORTANCE_DEFAULT)
-                .build();
-    }
-
-    private void entryRemoved(StatusBarNotification notification) {
-        mCollectionListener.onEntryRemoved(
-                new NotificationEntryBuilder()
-                        .setSbn(notification)
-                        .build(),
-                REASON_APP_CANCEL);
-    }
-
-    private void entryAdded(StatusBarNotification notification, int importance) {
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setSbn(notification)
-                .setImportance(importance)
-                .build();
-        mCollectionListener.onEntryAdded(entry);
-    }
-
-    private void entryUpdated(StatusBarNotification notification, int importance) {
-        NotificationEntry entry = new NotificationEntryBuilder()
-                .setSbn(notification)
-                .setImportance(importance)
-                .build();
-        mCollectionListener.onEntryUpdated(entry);
-    }
-
-    @UserIdInt private static final int USERID_ONE = 10; // UserManagerService.MIN_USER_ID;
-    @UserIdInt private static final int USERID_TWO = USERID_ONE + 1;
-    private static final String TEST_PACKAGE_NAME = "test";
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 796e665..f81ef10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -92,6 +92,8 @@
 import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl;
 import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerResDelegate;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.log.ScreenDecorationsLogger;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.FakeDisplayTracker;
@@ -226,13 +228,16 @@
         doAnswer(it -> !(mMockCutoutList.isEmpty())).when(mCutoutFactory).getHasProviders();
         doReturn(mMockCutoutList).when(mCutoutFactory).getProviders();
 
+        FakeFeatureFlags featureFlags = new FakeFeatureFlags();
+        featureFlags.set(Flags.STOP_PULSING_FACE_SCANNING_ANIMATION, true);
         mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl(
                 BOUNDS_POSITION_TOP,
                 mAuthController,
                 mStatusBarStateController,
                 mKeyguardUpdateMonitor,
                 mExecutor,
-                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer"))));
+                new ScreenDecorationsLogger(logcatLogBuffer("TestLogBuffer")),
+                featureFlags));
 
         mScreenDecorations = spy(new ScreenDecorations(mContext, mSecureSettings,
                 mCommandRegistry, mUserTracker, mDisplayTracker, mDotViewController,
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 a48fa5d..0fb0b03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -721,6 +721,36 @@
     }
 
     @Test
+    public void windowMagnifierEditMode_performA11yClickAction_exitEditMode() {
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        View closeButton = getInternalView(R.id.close_button);
+        View bottomRightCorner = getInternalView(R.id.bottom_right_corner);
+        View bottomLeftCorner = getInternalView(R.id.bottom_left_corner);
+        View topRightCorner = getInternalView(R.id.top_right_corner);
+        View topLeftCorner = getInternalView(R.id.top_left_corner);
+
+        assertEquals(View.VISIBLE, closeButton.getVisibility());
+        assertEquals(View.VISIBLE, bottomRightCorner.getVisibility());
+        assertEquals(View.VISIBLE, bottomLeftCorner.getVisibility());
+        assertEquals(View.VISIBLE, topRightCorner.getVisibility());
+        assertEquals(View.VISIBLE, topLeftCorner.getVisibility());
+
+        final View mirrorView = mWindowManager.getAttachedView();
+        mirrorView.performAccessibilityAction(AccessibilityAction.ACTION_CLICK.getId(), null);
+
+        assertEquals(View.GONE, closeButton.getVisibility());
+        assertEquals(View.GONE, bottomRightCorner.getVisibility());
+        assertEquals(View.GONE, bottomLeftCorner.getVisibility());
+        assertEquals(View.GONE, topRightCorner.getVisibility());
+        assertEquals(View.GONE, topLeftCorner.getVisibility());
+    }
+
+    @Test
     public void enableWindowMagnification_hasA11yWindowTitle() {
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index 316de59..2233e322 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -70,7 +70,7 @@
         assertTrue(dialog.isShowing)
 
         // The dialog is now fullscreen.
-        val window = dialog.window
+        val window = checkNotNull(dialog.window)
         val decorView = window.decorView as DecorView
         assertEquals(MATCH_PARENT, window.attributes.width)
         assertEquals(MATCH_PARENT, window.attributes.height)
@@ -172,14 +172,15 @@
         // Important: the power menu animation relies on this behavior to know when to animate (see
         // http://ag/16774605).
         val dialog = runOnMainThreadAndWaitForIdleSync { TestDialog(context) }
-        dialog.window.setWindowAnimations(0)
-        assertEquals(0, dialog.window.attributes.windowAnimations)
+        val window = checkNotNull(dialog.window)
+        window.setWindowAnimations(0)
+        assertEquals(0, window.attributes.windowAnimations)
 
         val touchSurface = createTouchSurface()
         runOnMainThreadAndWaitForIdleSync {
             dialogLaunchAnimator.showFromView(dialog, touchSurface)
         }
-        assertNotEquals(0, dialog.window.attributes.windowAnimations)
+        assertNotEquals(0, window.attributes.windowAnimations)
     }
 
     @Test
@@ -351,13 +352,14 @@
 
         init {
             // We need to set the window type for dialogs shown by SysUI, otherwise WM will throw.
-            window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+            checkNotNull(window).setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
         }
 
         override fun onCreate(savedInstanceState: Bundle?) {
             super.onCreate(savedInstanceState)
             setContentView(contentView)
 
+            val window = checkNotNull(window)
             window.setLayout(DIALOG_WIDTH, DIALOG_HEIGHT)
             window.setBackgroundDrawable(windowBackground)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 61a6512..b23f7f2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -19,6 +19,8 @@
 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
 
+import static com.android.systemui.appops.AppOpsControllerImpl.OPS_MIC;
+
 import static junit.framework.TestCase.assertFalse;
 
 import static org.junit.Assert.assertEquals;
@@ -171,6 +173,28 @@
                 TEST_UID, TEST_PACKAGE_NAME, true);
     }
 
+
+    // Only the app ops in the {@link com.android.systemui.appops.AppOpsControllerImpl.OPS} will be
+    // supported by the {@link AppOpsControllerImpl} to add callbacks. The state changes of active
+    // app ops will be notified by the callback.
+    @Test
+    public void addCallback_partialIncludedCode() {
+        mController.addCallback(new int[]{AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+                AppOpsManager.OP_FINE_LOCATION}, mCallback);
+        mController.onOpActiveChanged(
+                AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+        mController.onOpActiveChanged(
+                AppOpsManager.OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO, TEST_UID, TEST_PACKAGE_NAME,
+                true);
+        assertEquals(2, mController.getActiveAppOps().size());
+
+        mTestableLooper.processAllMessages();
+        verify(mCallback).onActiveStateChanged(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO,
+                TEST_UID, TEST_PACKAGE_NAME, true);
+        verify(mCallback, never()).onActiveStateChanged(AppOpsManager.OP_RECORD_AUDIO,
+                TEST_UID, TEST_PACKAGE_NAME, true);
+    }
+
     @Test
     public void addCallback_notIncludedCode() {
         mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
@@ -504,41 +528,17 @@
     }
 
     @Test
-    public void testUnpausedRecordingSentActive() {
-        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
-        mTestableLooper.processAllMessages();
-        mController.onOpActiveChanged(
-                AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
-
-        mTestableLooper.processAllMessages();
-        mRecordingCallback.onRecordingConfigChanged(Collections.emptyList());
-
-        mTestableLooper.processAllMessages();
-
-        verify(mCallback).onActiveStateChanged(
-                AppOpsManager.OP_RECORD_AUDIO, TEST_UID, TEST_PACKAGE_NAME, true);
+    public void testUnPausedRecordingSentActive() {
+        for (int i = 0; i < OPS_MIC.length; i++) {
+            verifyUnPausedSentActive(OPS_MIC[i]);
+        }
     }
 
     @Test
     public void testAudioPausedSentInactive() {
-        mController.addCallback(new int[]{AppOpsManager.OP_RECORD_AUDIO}, mCallback);
-        mTestableLooper.processAllMessages();
-        mController.onOpActiveChanged(
-                AppOpsManager.OPSTR_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
-        mTestableLooper.processAllMessages();
-
-        AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class);
-        when(mockARC.getClientUid()).thenReturn(TEST_UID_OTHER);
-        when(mockARC.isClientSilenced()).thenReturn(true);
-
-        mRecordingCallback.onRecordingConfigChanged(List.of(mockARC));
-        mTestableLooper.processAllMessages();
-
-        InOrder inOrder = inOrder(mCallback);
-        inOrder.verify(mCallback).onActiveStateChanged(
-                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
-        inOrder.verify(mCallback).onActiveStateChanged(
-                AppOpsManager.OP_RECORD_AUDIO, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
+        for (int i = 0; i < OPS_MIC.length; i++) {
+            verifyAudioPausedSentInactive(OPS_MIC[i]);
+        }
     }
 
     @Test
@@ -673,6 +673,41 @@
         assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode());
     }
 
+    private void verifyUnPausedSentActive(int micOpCode) {
+        mController.addCallback(new int[]{micOpCode}, mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(AppOpsManager.opToPublicName(micOpCode), TEST_UID,
+                TEST_PACKAGE_NAME, true);
+
+        mTestableLooper.processAllMessages();
+        mRecordingCallback.onRecordingConfigChanged(Collections.emptyList());
+
+        mTestableLooper.processAllMessages();
+
+        verify(mCallback).onActiveStateChanged(micOpCode, TEST_UID, TEST_PACKAGE_NAME, true);
+    }
+
+    private void verifyAudioPausedSentInactive(int micOpCode) {
+        mController.addCallback(new int[]{micOpCode}, mCallback);
+        mTestableLooper.processAllMessages();
+        mController.onOpActiveChanged(AppOpsManager.opToPublicName(micOpCode), TEST_UID_OTHER,
+                TEST_PACKAGE_NAME, true);
+        mTestableLooper.processAllMessages();
+
+        AudioRecordingConfiguration mockARC = mock(AudioRecordingConfiguration.class);
+        when(mockARC.getClientUid()).thenReturn(TEST_UID_OTHER);
+        when(mockARC.isClientSilenced()).thenReturn(true);
+
+        mRecordingCallback.onRecordingConfigChanged(List.of(mockARC));
+        mTestableLooper.processAllMessages();
+
+        InOrder inOrder = inOrder(mCallback);
+        inOrder.verify(mCallback).onActiveStateChanged(
+                micOpCode, TEST_UID_OTHER, TEST_PACKAGE_NAME, true);
+        inOrder.verify(mCallback).onActiveStateChanged(
+                micOpCode, TEST_UID_OTHER, TEST_PACKAGE_NAME, false);
+    }
+
     private class TestHandler extends AppOpsControllerImpl.H {
         TestHandler(Looper looper) {
             mController.super(looper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index 0056970..d3a2a73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -18,23 +18,30 @@
 
 package com.android.systemui.authentication.data.repository
 
+import android.app.admin.DevicePolicyManager
+import android.content.Intent
 import android.content.pm.UserInfo
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import java.util.function.Function
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.runBlocking
+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.junit.runners.JUnit4
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
@@ -43,6 +50,7 @@
 class AuthenticationRepositoryTest : SysuiTestCase() {
 
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
+    @Mock private lateinit var getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>
 
     private val testUtils = SceneTestUtils(this)
     private val testScope = testUtils.testScope
@@ -50,24 +58,49 @@
 
     private lateinit var underTest: AuthenticationRepository
 
+    private var currentSecurityMode: KeyguardSecurityModel.SecurityMode =
+        KeyguardSecurityModel.SecurityMode.PIN
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         userRepository.setUserInfos(USER_INFOS)
         runBlocking { userRepository.setSelectedUserInfo(USER_INFOS[0]) }
+        whenever(getSecurityMode.apply(anyInt())).thenAnswer { currentSecurityMode }
 
         underTest =
             AuthenticationRepositoryImpl(
                 applicationScope = testScope.backgroundScope,
-                getSecurityMode = { KeyguardSecurityModel.SecurityMode.PIN },
+                getSecurityMode = getSecurityMode,
                 backgroundDispatcher = testUtils.testDispatcher,
                 userRepository = userRepository,
                 keyguardRepository = testUtils.keyguardRepository,
                 lockPatternUtils = lockPatternUtils,
+                broadcastDispatcher = fakeBroadcastDispatcher,
             )
     }
 
     @Test
+    fun authenticationMethod() =
+        testScope.runTest {
+            val authMethod by collectLastValue(underTest.authenticationMethod)
+            runCurrent()
+            dispatchBroadcast()
+            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin)
+            assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin)
+
+            setSecurityModeAndDispatchBroadcast(KeyguardSecurityModel.SecurityMode.Pattern)
+            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pattern)
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(AuthenticationMethodModel.Pattern)
+
+            setSecurityModeAndDispatchBroadcast(KeyguardSecurityModel.SecurityMode.None)
+            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None)
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(AuthenticationMethodModel.None)
+        }
+
+    @Test
     fun isAutoConfirmEnabled() =
         testScope.runTest {
             whenever(lockPatternUtils.isAutoPinConfirmEnabled(USER_INFOS[0].id)).thenReturn(true)
@@ -95,6 +128,20 @@
             assertThat(values.last()).isTrue()
         }
 
+    private fun setSecurityModeAndDispatchBroadcast(
+        securityMode: KeyguardSecurityModel.SecurityMode,
+    ) {
+        currentSecurityMode = securityMode
+        dispatchBroadcast()
+    }
+
+    private fun dispatchBroadcast() {
+        fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
+            context,
+            Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED)
+        )
+    }
+
     companion object {
         private val USER_INFOS =
             listOf(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index a86937f..fc7d20a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -19,12 +19,16 @@
 import android.app.admin.DevicePolicyManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
 import kotlin.time.Duration.Companion.milliseconds
 import kotlin.time.Duration.Companion.seconds
@@ -44,88 +48,147 @@
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
     private val repository: AuthenticationRepository = utils.authenticationRepository()
+    private val sceneInteractor = utils.sceneInteractor()
     private val underTest =
         utils.authenticationInteractor(
             repository = repository,
+            sceneInteractor = sceneInteractor,
         )
 
     @Test
-    fun getAuthenticationMethod() =
+    fun authenticationMethod() =
         testScope.runTest {
-            assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin)
+            val authMethod by collectLastValue(underTest.authenticationMethod)
+            runCurrent()
+            assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Pin)
+            assertThat(underTest.getAuthenticationMethod())
+                .isEqualTo(DomainLayerAuthenticationMethodModel.Pin)
 
             utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
+                DataLayerAuthenticationMethodModel.Password
             )
 
+            assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Password)
             assertThat(underTest.getAuthenticationMethod())
-                .isEqualTo(AuthenticationMethodModel.Password)
+                .isEqualTo(DomainLayerAuthenticationMethodModel.Password)
         }
 
     @Test
-    fun getAuthenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() =
+    fun authenticationMethod_noneTreatedAsSwipe_whenLockscreenEnabled() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.authenticationRepository.setLockscreenEnabled(true)
+            val authMethod by collectLastValue(underTest.authenticationMethod)
+            runCurrent()
 
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(true)
+            }
+
+            assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
             assertThat(underTest.getAuthenticationMethod())
-                .isEqualTo(AuthenticationMethodModel.Swipe)
+                .isEqualTo(DomainLayerAuthenticationMethodModel.Swipe)
         }
 
     @Test
-    fun getAuthenticationMethod_none_whenLockscreenDisabled() =
+    fun authenticationMethod_none_whenLockscreenDisabled() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.authenticationRepository.setLockscreenEnabled(false)
+            val authMethod by collectLastValue(underTest.authenticationMethod)
+            runCurrent()
 
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(false)
+            }
+
+            assertThat(authMethod).isEqualTo(DomainLayerAuthenticationMethodModel.None)
             assertThat(underTest.getAuthenticationMethod())
-                .isEqualTo(AuthenticationMethodModel.None)
+                .isEqualTo(DomainLayerAuthenticationMethodModel.None)
         }
 
     @Test
     fun isUnlocked_whenAuthMethodIsNoneAndLockscreenDisabled_isTrue() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.authenticationRepository.setLockscreenEnabled(false)
-
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            // Toggle isUnlocked, twice.
-            //
-            // This is done because the underTest.isUnlocked flow doesn't receive values from
-            // just changing the state above; the actual isUnlocked state needs to change to
-            // cause the logic under test to "pick up" the current state again.
-            //
-            // It is done twice to make sure that we don't actually change the isUnlocked
-            // state from what it originally was.
-            utils.authenticationRepository.setUnlocked(
-                !utils.authenticationRepository.isUnlocked.value
-            )
-            runCurrent()
-            utils.authenticationRepository.setUnlocked(
-                !utils.authenticationRepository.isUnlocked.value
-            )
-            runCurrent()
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(false)
+                // Toggle isUnlocked, twice.
+                //
+                // This is done because the underTest.isUnlocked flow doesn't receive values from
+                // just changing the state above; the actual isUnlocked state needs to change to
+                // cause the logic under test to "pick up" the current state again.
+                //
+                // It is done twice to make sure that we don't actually change the isUnlocked state
+                // from what it originally was.
+                setUnlocked(!utils.authenticationRepository.isUnlocked.value)
+                runCurrent()
+                setUnlocked(!utils.authenticationRepository.isUnlocked.value)
+                runCurrent()
+            }
+
             assertThat(isUnlocked).isTrue()
         }
 
     @Test
-    fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isFalse() =
+    fun isUnlocked_whenAuthMethodIsNoneAndLockscreenEnabled_isTrue() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(true)
+            }
 
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            assertThat(isUnlocked).isFalse()
+            assertThat(isUnlocked).isTrue()
+        }
+
+    @Test
+    fun canSwipeToDismiss_onLockscreenWithSwipe_isTrue() =
+        testScope.runTest {
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(true)
+            }
+            switchToScene(SceneKey.Lockscreen)
+
+            val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
+            assertThat(canSwipeToDismiss).isTrue()
+        }
+
+    @Test
+    fun canSwipeToDismiss_onLockscreenWithPin_isFalse() =
+        testScope.runTest {
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setLockscreenEnabled(true)
+            }
+            switchToScene(SceneKey.Lockscreen)
+
+            val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
+            assertThat(canSwipeToDismiss).isFalse()
+        }
+
+    @Test
+    fun canSwipeToDismiss_afterLockscreenDismissedInSwipeMode_isFalse() =
+        testScope.runTest {
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+                setLockscreenEnabled(true)
+            }
+            switchToScene(SceneKey.Lockscreen)
+            switchToScene(SceneKey.Gone)
+
+            val canSwipeToDismiss by collectLastValue(underTest.canSwipeToDismiss)
+            assertThat(canSwipeToDismiss).isFalse()
         }
 
     @Test
     fun isAuthenticationRequired_lockedAndSecured_true() =
         testScope.runTest {
-            utils.authenticationRepository.setUnlocked(false)
-            runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
+            utils.authenticationRepository.apply {
+                setUnlocked(false)
+                runCurrent()
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
+            }
 
             assertThat(underTest.isAuthenticationRequired()).isTrue()
         }
@@ -133,9 +196,11 @@
     @Test
     fun isAuthenticationRequired_lockedAndNotSecured_false() =
         testScope.runTest {
-            utils.authenticationRepository.setUnlocked(false)
-            runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.apply {
+                setUnlocked(false)
+                runCurrent()
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+            }
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -143,11 +208,11 @@
     @Test
     fun isAuthenticationRequired_unlockedAndSecured_false() =
         testScope.runTest {
-            utils.authenticationRepository.setUnlocked(true)
-            runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
+            utils.authenticationRepository.apply {
+                setUnlocked(true)
+                runCurrent()
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Password)
+            }
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -155,9 +220,11 @@
     @Test
     fun isAuthenticationRequired_unlockedAndNotSecured_false() =
         testScope.runTest {
-            utils.authenticationRepository.setUnlocked(true)
-            runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            utils.authenticationRepository.apply {
+                setUnlocked(true)
+                runCurrent()
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.None)
+            }
 
             assertThat(underTest.isAuthenticationRequired()).isFalse()
         }
@@ -166,7 +233,9 @@
     fun authenticate_withCorrectPin_returnsTrue() =
         testScope.runTest {
             val isThrottled by collectLastValue(underTest.isThrottled)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
             assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)).isTrue()
             assertThat(isThrottled).isFalse()
         }
@@ -174,23 +243,30 @@
     @Test
     fun authenticate_withIncorrectPin_returnsFalse() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
             assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4))).isFalse()
         }
 
     @Test(expected = IllegalArgumentException::class)
     fun authenticate_withEmptyPin_throwsException() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
             underTest.authenticate(listOf())
         }
 
     @Test
     fun authenticate_withCorrectMaxLengthPin_returnsTrue() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             val pin = List(16) { 9 }
-            utils.authenticationRepository.overrideCredential(pin)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                overrideCredential(pin)
+            }
+
             assertThat(underTest.authenticate(pin)).isTrue()
         }
 
@@ -203,7 +279,9 @@
             // If the policy changes, there is work to do in SysUI.
             assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
             assertThat(underTest.authenticate(List(17) { 9 })).isFalse()
         }
 
@@ -212,7 +290,7 @@
         testScope.runTest {
             val isThrottled by collectLastValue(underTest.isThrottled)
             utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
+                DataLayerAuthenticationMethodModel.Password
             )
 
             assertThat(underTest.authenticate("password".toList())).isTrue()
@@ -223,7 +301,7 @@
     fun authenticate_withIncorrectPassword_returnsFalse() =
         testScope.runTest {
             utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
+                DataLayerAuthenticationMethodModel.Password
             )
 
             assertThat(underTest.authenticate("alohomora".toList())).isFalse()
@@ -233,7 +311,7 @@
     fun authenticate_withCorrectPattern_returnsTrue() =
         testScope.runTest {
             utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern
+                DataLayerAuthenticationMethodModel.Pattern
             )
 
             assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN)).isTrue()
@@ -243,21 +321,21 @@
     fun authenticate_withIncorrectPattern_returnsFalse() =
         testScope.runTest {
             utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern
+                DataLayerAuthenticationMethodModel.Pattern
             )
 
             assertThat(
                     underTest.authenticate(
                         listOf(
-                            AuthenticationMethodModel.Pattern.PatternCoordinate(
+                            AuthenticationPatternCoordinate(
                                 x = 2,
                                 y = 0,
                             ),
-                            AuthenticationMethodModel.Pattern.PatternCoordinate(
+                            AuthenticationPatternCoordinate(
                                 x = 2,
                                 y = 1,
                             ),
-                            AuthenticationMethodModel.Pattern.PatternCoordinate(
+                            AuthenticationPatternCoordinate(
                                 x = 2,
                                 y = 2,
                             ),
@@ -271,8 +349,10 @@
     fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNullAndHasNoEffect() =
         testScope.runTest {
             val isThrottled by collectLastValue(underTest.isThrottled)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+            }
             assertThat(
                     underTest.authenticate(
                         FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply {
@@ -289,8 +369,10 @@
     fun tryAutoConfirm_withAutoConfirmWrongPinCorrectLength_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+            }
             assertThat(
                     underTest.authenticate(
                         FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 },
@@ -305,8 +387,10 @@
     fun tryAutoConfirm_withAutoConfirmLongerPin_returnsFalseAndDoesNotUnlockDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+            }
             assertThat(
                     underTest.authenticate(
                         FakeAuthenticationRepository.DEFAULT_PIN + listOf(7),
@@ -321,8 +405,10 @@
     fun tryAutoConfirm_withAutoConfirmCorrectPin_returnsTrueAndUnlocksDevice() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+            }
             assertThat(
                     underTest.authenticate(
                         FakeAuthenticationRepository.DEFAULT_PIN,
@@ -337,8 +423,10 @@
     fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNullAndHasNoEffects() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmEnabled(false)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(false)
+            }
             assertThat(
                     underTest.authenticate(
                         FakeAuthenticationRepository.DEFAULT_PIN,
@@ -354,7 +442,7 @@
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
             utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
+                DataLayerAuthenticationMethodModel.Password
             )
 
             assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true)).isNull()
@@ -367,7 +455,9 @@
             val isUnlocked by collectLastValue(underTest.isUnlocked)
             val throttling by collectLastValue(underTest.throttling)
             val isThrottled by collectLastValue(underTest.isThrottled)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
             underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
             assertThat(isUnlocked).isTrue()
             assertThat(isThrottled).isFalse()
@@ -456,8 +546,10 @@
     fun hintedPinLength_withoutAutoConfirm_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmEnabled(false)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(false)
+            }
 
             assertThat(hintedPinLength).isNull()
         }
@@ -466,13 +558,15 @@
     fun hintedPinLength_withAutoConfirmPinTooShort_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.overrideCredential(
-                buildList {
-                    repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
-                }
-            )
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                overrideCredential(
+                    buildList {
+                        repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
+                    }
+                )
+                setAutoConfirmEnabled(true)
+            }
 
             assertThat(hintedPinLength).isNull()
         }
@@ -481,11 +575,15 @@
     fun hintedPinLength_withAutoConfirmPinAtRightLength_isSameLength() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
-            utils.authenticationRepository.overrideCredential(
-                buildList { repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) } }
-            )
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                setAutoConfirmEnabled(true)
+                overrideCredential(
+                    buildList {
+                        repeat(utils.authenticationRepository.hintedPinLength) { add(it + 1) }
+                    }
+                )
+            }
 
             assertThat(hintedPinLength).isEqualTo(utils.authenticationRepository.hintedPinLength)
         }
@@ -494,14 +592,20 @@
     fun hintedPinLength_withAutoConfirmPinTooLong_isNull() =
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            utils.authenticationRepository.overrideCredential(
-                buildList {
-                    repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
-                }
-            )
-            utils.authenticationRepository.setAutoConfirmEnabled(true)
+            utils.authenticationRepository.apply {
+                setAuthenticationMethod(DataLayerAuthenticationMethodModel.Pin)
+                overrideCredential(
+                    buildList {
+                        repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
+                    }
+                )
+                setAutoConfirmEnabled(true)
+            }
 
             assertThat(hintedPinLength).isNull()
         }
+
+    private fun switchToScene(sceneKey: SceneKey) {
+        sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
index 40b5729..ec8be8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewControllerTest.java
@@ -35,6 +35,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.phone.StatusBarLocation;
 import com.android.systemui.statusbar.policy.BatteryController;
@@ -63,6 +64,7 @@
     private ContentResolver mContentResolver;
     @Mock
     private BatteryController mBatteryController;
+    private FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
 
     private BatteryMeterViewController mController;
 
@@ -160,6 +162,7 @@
                 mTunerService,
                 mHandler,
                 mContentResolver,
+                mFakeFeatureFlags,
                 mBatteryController
         );
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
index c84efac..f0f4ca7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt
@@ -131,6 +131,16 @@
     }
 
     @Test
+    fun contentDescription_isIncompatibleCharging_notCharging() {
+        mBatteryMeterView.onBatteryLevelChanged(45, true)
+        mBatteryMeterView.onIsIncompatibleChargingChanged(true)
+
+        assertThat(mBatteryMeterView.contentDescription).isEqualTo(
+                context.getString(R.string.accessibility_battery_level, 45)
+        )
+    }
+
+    @Test
     fun changesFromEstimateToPercent_textAndContentDescriptionChanges() {
         mBatteryMeterView.onBatteryLevelChanged(15, false)
         mBatteryMeterView.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
@@ -231,14 +241,33 @@
         assertThat(drawable.displayShield).isFalse()
     }
 
+    @Test
+    fun isIncompatibleChargingChanged_true_drawableGetsChargingFalse() {
+        mBatteryMeterView.onBatteryLevelChanged(45, true)
+        val drawable = getBatteryDrawable()
+
+        mBatteryMeterView.onIsIncompatibleChargingChanged(true)
+
+        assertThat(drawable.getCharging()).isFalse()
+    }
+
+    @Test
+    fun isIncompatibleChargingChanged_false_drawableGetsChargingTrue() {
+        mBatteryMeterView.onBatteryLevelChanged(45, true)
+        val drawable = getBatteryDrawable()
+
+        mBatteryMeterView.onIsIncompatibleChargingChanged(false)
+
+        assertThat(drawable.getCharging()).isTrue()
+    }
+
     private fun getBatteryDrawable(): AccessorizedBatteryDrawable {
         return (mBatteryMeterView.getChildAt(0) as ImageView)
                 .drawable as AccessorizedBatteryDrawable
     }
 
     private class Fetcher : BatteryEstimateFetcher {
-        override fun fetchBatteryTimeRemainingEstimate(
-                completion: EstimateFetchCompletion) {
+        override fun fetchBatteryTimeRemainingEstimate(completion: EstimateFetchCompletion) {
             completion.onBatteryRemainingEstimateRetrieved(ESTIMATE)
         }
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index e3e6130..4e52e64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -139,6 +139,7 @@
     @Before
     fun setup() {
         featureFlags.set(Flags.BIOMETRIC_BP_STRONG, useNewBiometricPrompt)
+        featureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
     @After
@@ -151,7 +152,10 @@
     @Test
     fun testNotifiesAnimatedIn() {
         initializeFingerprintContainer()
-        verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+        verify(callback).onDialogAnimatedIn(
+            authContainer?.requestId ?: 0L,
+            true /* startFingerprintNow */
+        )
     }
 
     @Test
@@ -196,7 +200,10 @@
         waitForIdleSync()
 
         // attaching the view resets the state and allows this to happen again
-        verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+        verify(callback).onDialogAnimatedIn(
+            authContainer?.requestId ?: 0L,
+            true /* startFingerprintNow */
+        )
     }
 
     @Test
@@ -211,7 +218,10 @@
 
         // the first time is triggered by initializeFingerprintContainer()
         // the second time was triggered by dismissWithoutCallback()
-        verify(callback, times(2)).onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */)
+        verify(callback, times(2)).onDialogAnimatedIn(
+            authContainer?.requestId ?: 0L,
+            true /* startFingerprintNow */
+        )
     }
 
     @Test
@@ -517,10 +527,11 @@
         { authBiometricFingerprintViewModel },
         { promptSelectorInteractor },
         { bpCredentialInteractor },
-        PromptViewModel(promptSelectorInteractor, vibrator),
+        PromptViewModel(promptSelectorInteractor, vibrator, featureFlags),
         { credentialViewModel },
         Handler(TestableLooper.get(this).looper),
-        fakeExecutor
+        fakeExecutor,
+        vibrator
     ) {
         override fun postOnAnimation(runnable: Runnable) {
             runnable.run()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 3d4171f..48e5131 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -197,6 +197,8 @@
     private ArgumentCaptor<String> mMessageCaptor;
     @Mock
     private Resources mResources;
+    @Mock
+    private VibratorHelper mVibratorHelper;
 
     private TestableContext mContextSpy;
     private Execution mExecution;
@@ -515,7 +517,7 @@
 
         assertThat(mModalityCaptor.getValue().intValue()).isEqualTo(modality);
         assertThat(mMessageCaptor.getValue()).isEqualTo(
-                mContext.getString(R.string.biometric_face_not_recognized));
+                mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead));
     }
 
     @Test
@@ -1097,7 +1099,7 @@
                     () -> mBiometricPromptCredentialInteractor, () -> mPromptSelectionInteractor,
                     () -> mCredentialViewModel, () -> mPromptViewModel,
                     mInteractionJankMonitor, mHandler,
-                    mBackgroundExecutor, mUdfpsUtils);
+                    mBackgroundExecutor, mUdfpsUtils, mVibratorHelper);
         }
 
         @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 2b08c66..994db46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -164,7 +164,7 @@
         context.addMockSystemService(WindowManager::class.java, windowManager)
 
         whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
-        whenEver(sideFpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
+        whenEver(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
             .thenReturn(mock(LottieAnimationView::class.java))
         with(mock(ViewPropertyAnimator::class.java)) {
             whenEver(sideFpsView.animate()).thenReturn(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 40b1f20..4d19543 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -19,6 +19,7 @@
 import android.hardware.biometrics.PromptInfo
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
+import android.view.HapticFeedbackConstants
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
@@ -33,6 +34,8 @@
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
@@ -71,13 +74,15 @@
 
     private lateinit var selector: PromptSelectorInteractor
     private lateinit var viewModel: PromptViewModel
+    private val featureFlags = FakeFeatureFlags()
 
     @Before
     fun setup() {
         selector = PromptSelectorInteractorImpl(promptRepository, lockPatternUtils)
         selector.resetPrompt()
 
-        viewModel = PromptViewModel(selector, vibrator)
+        viewModel = PromptViewModel(selector, vibrator, featureFlags)
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false)
     }
 
     @Test
@@ -149,6 +154,29 @@
             verify(vibrator, never()).vibrateAuthError(any())
         }
 
+    @Test
+    fun playSuccessHaptic_onwayHapticsEnabled_SetsConfirmConstant() = runGenericTest {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+        val expectConfirmation = testCase.expectConfirmation(atLeastOneFailure = false)
+        viewModel.showAuthenticated(testCase.authenticatedModality, 1_000L)
+
+        if (expectConfirmation) {
+            viewModel.confirmAuthenticated()
+        }
+
+        val currentConstant by collectLastValue(viewModel.hapticsToPlay)
+        assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.CONFIRM)
+    }
+
+    @Test
+    fun playErrorHaptic_onwayHapticsEnabled_SetsRejectConstant() = runGenericTest {
+        featureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true)
+        viewModel.showTemporaryError("test", "messageAfterError", false)
+
+        val currentConstant by collectLastValue(viewModel.hapticsToPlay)
+        assertThat(currentConstant).isEqualTo(HapticFeedbackConstants.REJECT)
+    }
+
     private suspend fun TestScope.showAuthenticated(
         authenticatedModality: BiometricModality,
         expectConfirmation: Boolean,
@@ -499,6 +527,7 @@
         val messageVisible by collectLastValue(viewModel.isIndicatorMessageVisible)
         val size by collectLastValue(viewModel.size)
         val legacyState by collectLastValue(viewModel.legacyState)
+        val confirmationRequired by collectLastValue(viewModel.isConfirmationRequired)
 
         if (testCase.isCoex && testCase.authenticatedByFingerprint) {
             viewModel.ensureFingerprintHasStarted(isDelayed = true)
@@ -507,7 +536,11 @@
         viewModel.showHelp(helpMessage)
 
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
-        assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+        if (confirmationRequired == true) {
+            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+        } else {
+            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+        }
         assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
         assertThat(messageVisible).isTrue()
         assertThat(authenticating).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
index 186df02..38e5728 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt
@@ -165,6 +165,28 @@
         assertFalse(bouncerRepository.alternateBouncerVisible.value)
     }
 
+    @Test
+    fun alternateBouncerUiAvailable_fromMultipleSources() {
+        assertFalse(bouncerRepository.alternateBouncerUIAvailable.value)
+
+        // GIVEN there are two different sources indicating the alternate bouncer is available
+        underTest.setAlternateBouncerUIAvailable(true, "source1")
+        underTest.setAlternateBouncerUIAvailable(true, "source2")
+        assertTrue(bouncerRepository.alternateBouncerUIAvailable.value)
+
+        // WHEN one of the sources no longer says the UI is available
+        underTest.setAlternateBouncerUIAvailable(false, "source1")
+
+        // THEN alternate bouncer UI is still available (from the other source)
+        assertTrue(bouncerRepository.alternateBouncerUIAvailable.value)
+
+        // WHEN all sources say the UI is not available
+        underTest.setAlternateBouncerUIAvailable(false, "source2")
+
+        // THEN alternate boucer UI is not available
+        assertFalse(bouncerRepository.alternateBouncerUIAvailable.value)
+    }
+
     private fun givenCanShowAlternateBouncer() {
         bouncerRepository.setAlternateBouncerUIAvailable(true)
         biometricSettingsRepository.setFingerprintEnrolled(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 14fc931..86e0c75 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -19,8 +19,9 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
@@ -69,10 +70,11 @@
     @Test
     fun pinAuthMethod() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(underTest.message)
 
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            runCurrent()
             utils.authenticationRepository.setUnlocked(false)
             underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -100,10 +102,11 @@
     @Test
     fun pinAuthMethod_tryAutoConfirm_withAutoConfirmPin() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(underTest.message)
 
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            runCurrent()
             utils.authenticationRepository.setAutoConfirmEnabled(true)
             utils.authenticationRepository.setUnlocked(false)
             underTest.showOrUnlockDevice()
@@ -136,10 +139,11 @@
     @Test
     fun pinAuthMethod_tryAutoConfirm_withoutAutoConfirmPin() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(underTest.message)
 
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            runCurrent()
             utils.authenticationRepository.setUnlocked(false)
             underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -165,11 +169,12 @@
     @Test
     fun passwordAuthMethod() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(underTest.message)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
+            runCurrent()
             utils.authenticationRepository.setUnlocked(false)
             underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -197,11 +202,12 @@
     @Test
     fun patternAuthMethod() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(underTest.message)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern
             )
+            runCurrent()
             utils.authenticationRepository.setUnlocked(false)
             underTest.showOrUnlockDevice()
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -214,11 +220,7 @@
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PATTERN)
 
             // Wrong input.
-            assertThat(
-                    underTest.authenticate(
-                        listOf(AuthenticationMethodModel.Pattern.PatternCoordinate(1, 2))
-                    )
-                )
+            assertThat(underTest.authenticate(listOf(AuthenticationPatternCoordinate(1, 2))))
                 .isFalse()
             assertThat(message).isEqualTo(MESSAGE_WRONG_PATTERN)
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
@@ -234,7 +236,7 @@
     @Test
     fun showOrUnlockDevice_notLocked_switchesToGoneScene() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(true)
             runCurrent()
@@ -247,8 +249,9 @@
     @Test
     fun showOrUnlockDevice_authMethodNotSecure_switchesToGoneScene() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.authenticationRepository.setLockscreenEnabled(true)
             utils.authenticationRepository.setUnlocked(false)
 
             underTest.showOrUnlockDevice()
@@ -259,11 +262,12 @@
     @Test
     fun showOrUnlockDevice_customMessageShown() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(underTest.message)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
+            runCurrent()
             utils.authenticationRepository.setUnlocked(false)
 
             val customMessage = "Hello there!"
@@ -279,7 +283,7 @@
             val isThrottled by collectLastValue(underTest.isThrottled)
             val throttling by collectLastValue(underTest.throttling)
             val message by collectLastValue(underTest.message)
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             runCurrent()
             underTest.showOrUnlockDevice()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index 2cc9493..7af8a04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -18,19 +18,17 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
-@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class AuthMethodBouncerViewModelTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 0df0a17..2c96bcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -18,8 +18,9 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
@@ -55,6 +56,7 @@
     private val underTest =
         utils.bouncerViewModel(
             bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
         )
 
     @Test
@@ -86,7 +88,8 @@
     @Test
     fun authMethod_reusesInstances() =
         testScope.runTest {
-            val seen = mutableMapOf<AuthenticationMethodModel, AuthMethodBouncerViewModel>()
+            val seen =
+                mutableMapOf<DomainLayerAuthenticationMethodModel, AuthMethodBouncerViewModel>()
             val authMethodViewModel: AuthMethodBouncerViewModel? by
                 collectLastValue(underTest.authMethod)
             // First pass, populate our "seen" map:
@@ -105,7 +108,7 @@
     @Test
     fun authMethodsToTest_returnsCompleteSampleOfAllAuthMethodTypes() {
         assertThat(authMethodsToTest().map { it::class }.toSet())
-            .isEqualTo(AuthenticationMethodModel::class.sealedSubclasses.toSet())
+            .isEqualTo(DomainLayerAuthenticationMethodModel::class.sealedSubclasses.toSet())
     }
 
     @Test
@@ -113,7 +116,9 @@
         testScope.runTest {
             val message by collectLastValue(underTest.message)
             val throttling by collectLastValue(bouncerInteractor.throttling)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
             assertThat(message?.isUpdateAnimated).isTrue()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
@@ -136,7 +141,9 @@
                     }
                 )
             val throttling by collectLastValue(bouncerInteractor.throttling)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
             assertThat(isInputEnabled).isTrue()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
@@ -153,7 +160,9 @@
     fun throttlingDialogMessage() =
         testScope.runTest {
             val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAuthenticationMethod(
+                DataLayerAuthenticationMethodModel.Pin
+            )
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) {
                 // Wrong PIN.
@@ -166,13 +175,32 @@
             assertThat(throttlingDialogMessage).isNull()
         }
 
-    private fun authMethodsToTest(): List<AuthenticationMethodModel> {
+    private fun authMethodsToTest(): List<DomainLayerAuthenticationMethodModel> {
         return listOf(
-            AuthenticationMethodModel.None,
-            AuthenticationMethodModel.Swipe,
-            AuthenticationMethodModel.Pin,
-            AuthenticationMethodModel.Password,
-            AuthenticationMethodModel.Pattern,
+            DomainLayerAuthenticationMethodModel.None,
+            DomainLayerAuthenticationMethodModel.Swipe,
+            DomainLayerAuthenticationMethodModel.Pin,
+            DomainLayerAuthenticationMethodModel.Password,
+            DomainLayerAuthenticationMethodModel.Pattern,
         )
     }
+
+    private fun FakeAuthenticationRepository.setAuthenticationMethod(
+        model: DomainLayerAuthenticationMethodModel,
+    ) {
+        setAuthenticationMethod(
+            when (model) {
+                is DomainLayerAuthenticationMethodModel.None,
+                is DomainLayerAuthenticationMethodModel.Swipe ->
+                    DataLayerAuthenticationMethodModel.None
+                is DomainLayerAuthenticationMethodModel.Pin ->
+                    DataLayerAuthenticationMethodModel.Pin
+                is DomainLayerAuthenticationMethodModel.Password ->
+                    DataLayerAuthenticationMethodModel.Password
+                is DomainLayerAuthenticationMethodModel.Pattern ->
+                    DataLayerAuthenticationMethodModel.Pattern
+            }
+        )
+        setLockscreenEnabled(model !is DomainLayerAuthenticationMethodModel.None)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 7f8d54c..4380af8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,7 +19,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
@@ -55,6 +55,7 @@
     private val bouncerViewModel =
         utils.bouncerViewModel(
             bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
         )
     private val underTest =
         PasswordBouncerViewModel(
@@ -72,14 +73,15 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
@@ -92,14 +94,15 @@
     @Test
     fun onPasswordInputChanged() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             runCurrent()
@@ -114,12 +117,13 @@
     @Test
     fun onAuthenticateKeyPressed_whenCorrect() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("password")
@@ -132,14 +136,15 @@
     @Test
     fun onAuthenticateKeyPressed_whenWrong() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("wrong")
@@ -154,14 +159,15 @@
     @Test
     fun onAuthenticateKeyPressed_correctAfterWrong() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Password
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPasswordInputChanged("wrong")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 57fcbe5..ea2cad2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -19,8 +19,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
@@ -57,6 +57,7 @@
     private val bouncerViewModel =
         utils.bouncerViewModel(
             bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
         )
     private val underTest =
         PatternBouncerViewModel(
@@ -75,7 +76,7 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
@@ -83,7 +84,8 @@
                 AuthenticationMethodModel.Pattern
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
@@ -97,7 +99,7 @@
     @Test
     fun onDragStart() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
@@ -105,7 +107,8 @@
                 AuthenticationMethodModel.Pattern
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             runCurrent()
@@ -121,14 +124,15 @@
     @Test
     fun onDragEnd_whenCorrect() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
             utils.authenticationRepository.setAuthenticationMethod(
                 AuthenticationMethodModel.Pattern
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -168,7 +172,7 @@
     @Test
     fun onDragEnd_whenWrong() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
@@ -176,7 +180,8 @@
                 AuthenticationMethodModel.Pattern
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
@@ -200,7 +205,7 @@
     @Test
     fun onDragEnd_correctAfterWrong() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
@@ -208,7 +213,8 @@
                 AuthenticationMethodModel.Pattern
             )
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onDragStart()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 81c68ed..531f86a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -19,8 +19,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
@@ -57,6 +57,7 @@
     private val bouncerViewModel =
         utils.bouncerViewModel(
             bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
         )
     private val underTest =
         PinBouncerViewModel(
@@ -75,11 +76,13 @@
     @Test
     fun onShown() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
 
             underTest.onShown()
@@ -92,12 +95,14 @@
     @Test
     fun onPinButtonClicked() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             runCurrent()
@@ -112,12 +117,14 @@
     @Test
     fun onBackspaceButtonClicked() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             runCurrent()
@@ -134,11 +141,13 @@
     @Test
     fun onPinEdit() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
 
@@ -156,12 +165,14 @@
     @Test
     fun onBackspaceButtonLongPressed() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             runCurrent()
@@ -180,10 +191,12 @@
     @Test
     fun onAuthenticateButtonClicked_whenCorrect() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -198,12 +211,14 @@
     @Test
     fun onAuthenticateButtonClicked_whenWrong() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -222,12 +237,14 @@
     @Test
     fun onAuthenticateButtonClicked_correctAfterWrong() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             underTest.onPinButtonClicked(1)
@@ -254,11 +271,13 @@
     @Test
     fun onAutoConfirm_whenCorrect() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             utils.authenticationRepository.setAutoConfirmEnabled(true)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
@@ -271,13 +290,15 @@
     @Test
     fun onAutoConfirm_whenWrong() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             utils.authenticationRepository.setAutoConfirmEnabled(true)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
             underTest.onShown()
             FakeAuthenticationRepository.DEFAULT_PIN.dropLast(1).forEach { digit ->
diff --git a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index d6cafcb..5a5c058 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -211,7 +211,7 @@
                 context.resources.getFloat(R.dimen.physical_charger_port_location_normalized_y)
         val expectedCenterX: Float
         val expectedCenterY: Float
-        when (context.display.rotation) {
+        when (checkNotNull(context.display).rotation) {
             Surface.ROTATION_90 -> {
                 expectedCenterX = width * normalizedPortPosY
                 expectedCenterY = height * (1 - normalizedPortPosX)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
index 42f28c8..2ae342a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt
@@ -125,7 +125,7 @@
             control
         )
         cvh.bindData(cws, false)
-        val chevronIcon = baseLayout.findViewById<View>(R.id.chevron_icon)
+        val chevronIcon = baseLayout.requireViewById<View>(R.id.chevron_icon)
 
         assertThat(chevronIcon.visibility).isEqualTo(View.VISIBLE)
     }
@@ -138,4 +138,4 @@
 private val DRAWABLE = GradientDrawable()
 private val COLOR = ColorStateList.valueOf(0xffff00)
 private val DEFAULT_CONTROL = Control.StatelessBuilder(
-        CONTROL_ID, mock(PendingIntent::class.java)).build()
\ No newline at end of file
+        CONTROL_ID, mock(PendingIntent::class.java)).build()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index fcd6568..a400ff9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -365,7 +365,8 @@
         val selectedItems =
             listOf(
                 SelectedItem.StructureItem(
-                    StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
+                    StructureInfo(checkNotNull(ComponentName.unflattenFromString("pkg/.cls1")),
+                        "a", ArrayList())
                 ),
             )
         preferredPanelRepository.setSelectedComponent(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index 49cdfa7..7311f4a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -33,15 +33,14 @@
 import android.app.AlarmManager;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.DejankUtils;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.wakelock.WakeLockFake;
 
 import org.junit.After;
@@ -53,6 +52,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
+@TestableLooper.RunWithLooper
 public class DozeUiTest extends SysuiTestCase {
 
     @Mock
@@ -62,23 +62,19 @@
     @Mock
     private DozeParameters mDozeParameters;
     @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
     private DozeHost mHost;
     @Mock
     private DozeLog mDozeLog;
-    @Mock
-    private TunerService mTunerService;
     private WakeLockFake mWakeLock;
     private Handler mHandler;
     private HandlerThread mHandlerThread;
     private DozeUi mDozeUi;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
 
     @Before
     public void setUp() throws Exception {
+        allowTestableLooperAsMainThread();
         MockitoAnnotations.initMocks(this);
+        DejankUtils.setImmediate(true);
 
         mHandlerThread = new HandlerThread("DozeUiTest");
         mHandlerThread.start();
@@ -86,12 +82,13 @@
         mHandler = mHandlerThread.getThreadHandler();
 
         mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
-                mDozeParameters, mStatusBarStateController, mDozeLog);
+                mDozeParameters, mDozeLog);
         mDozeUi.setDozeMachine(mMachine);
     }
 
     @After
     public void tearDown() throws Exception {
+        DejankUtils.setImmediate(false);
         mHandlerThread.quit();
         mHandler = null;
         mHandlerThread = null;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
index 35f0f6c..37c70d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FakeFeatureFlagsTest.kt
@@ -28,12 +28,12 @@
 @RunWith(AndroidTestingRunner::class)
 class FakeFeatureFlagsTest : SysuiTestCase() {
 
-    private val unreleasedFlag = UnreleasedFlag(-1000, "-1000", "test")
-    private val releasedFlag = ReleasedFlag(-1001, "-1001", "test")
-    private val stringFlag = StringFlag(-1002, "-1002", "test")
-    private val resourceBooleanFlag = ResourceBooleanFlag(-1003, "-1003", "test", resourceId = -1)
-    private val resourceStringFlag = ResourceStringFlag(-1004, "-1004", "test", resourceId = -1)
-    private val sysPropBooleanFlag = SysPropBooleanFlag(-1005, "test", "test")
+    private val unreleasedFlag = UnreleasedFlag("-1000", "test")
+    private val releasedFlag = ReleasedFlag("-1001", "test")
+    private val stringFlag = StringFlag("-1002", "test")
+    private val resourceBooleanFlag = ResourceBooleanFlag("-1003", "test", resourceId = -1)
+    private val resourceStringFlag = ResourceStringFlag("-1004", "test", resourceId = -1)
+    private val sysPropBooleanFlag = SysPropBooleanFlag("test", "test")
 
     /**
      * FakeFeatureFlags does not honor any default values. All flags which are accessed must be
@@ -46,43 +46,43 @@
             assertThat(flags.isEnabled(Flags.TEAMFOOD)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("id=1")
+            assertThat(ex.message).contains("UNKNOWN(teamfood)")
         }
         try {
             assertThat(flags.isEnabled(unreleasedFlag)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(id=-1000)")
+            assertThat(ex.message).contains("UNKNOWN(-1000)")
         }
         try {
             assertThat(flags.isEnabled(releasedFlag)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(id=-1001)")
+            assertThat(ex.message).contains("UNKNOWN(-1001)")
         }
         try {
             assertThat(flags.isEnabled(resourceBooleanFlag)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(id=-1003)")
+            assertThat(ex.message).contains("UNKNOWN(-1003)")
         }
         try {
             assertThat(flags.isEnabled(sysPropBooleanFlag)).isFalse()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(id=-1005)")
+            assertThat(ex.message).contains("UNKNOWN(test)")
         }
         try {
             assertThat(flags.getString(stringFlag)).isEmpty()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(id=-1002)")
+            assertThat(ex.message).contains("UNKNOWN(-1002)")
         }
         try {
             assertThat(flags.getString(resourceStringFlag)).isEmpty()
             fail("Expected an exception when accessing an unspecified flag.")
         } catch (ex: IllegalStateException) {
-            assertThat(ex.message).contains("UNKNOWN(id=-1004)")
+            assertThat(ex.message).contains("UNKNOWN(-1004)")
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 18f7db1..ff15cb3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -74,10 +74,10 @@
     private val serverFlagReader = ServerFlagReaderFake()
 
     private val teamfoodableFlagA = UnreleasedFlag(
-        500, name = "a", namespace = "test", teamfood = true
+        name = "a", namespace = "test", teamfood = true
     )
     private val teamfoodableFlagB = ReleasedFlag(
-        501, name = "b", namespace = "test", teamfood = true
+        name = "b", namespace = "test", teamfood = true
     )
 
     @Before
@@ -119,7 +119,6 @@
         assertThat(
             featureFlagsDebug.isEnabled(
                 ReleasedFlag(
-                    2,
                     name = "2",
                     namespace = "test"
                 )
@@ -128,7 +127,6 @@
         assertThat(
             featureFlagsDebug.isEnabled(
                 UnreleasedFlag(
-                    3,
                     name = "3",
                     namespace = "test"
                 )
@@ -137,7 +135,6 @@
         assertThat(
             featureFlagsDebug.isEnabled(
                 ReleasedFlag(
-                    4,
                     name = "4",
                     namespace = "test"
                 )
@@ -146,7 +143,6 @@
         assertThat(
             featureFlagsDebug.isEnabled(
                 UnreleasedFlag(
-                    5,
                     name = "5",
                     namespace = "test"
                 )
@@ -208,23 +204,22 @@
         assertThat(
             featureFlagsDebug.isEnabled(
                 ResourceBooleanFlag(
-                    1,
                     "1",
                     "test",
                     1001
                 )
             )
         ).isFalse()
-        assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag(2, "2", "test", 1002))).isTrue()
-        assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag(3, "3", "test", 1003))).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag("2", "test", 1002))).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(ResourceBooleanFlag("3", "test", 1003))).isTrue()
 
         Assert.assertThrows(NameNotFoundException::class.java) {
-            featureFlagsDebug.isEnabled(ResourceBooleanFlag(4, "4", "test", 1004))
+            featureFlagsDebug.isEnabled(ResourceBooleanFlag("4", "test", 1004))
         }
         // Test that resource is loaded (and validated) even when the setting is set.
         //  This prevents developers from not noticing when they reference an invalid resource.
         Assert.assertThrows(NameNotFoundException::class.java) {
-            featureFlagsDebug.isEnabled(ResourceBooleanFlag(5, "5", "test", 1005))
+            featureFlagsDebug.isEnabled(ResourceBooleanFlag("5", "test", 1005))
         }
     }
 
@@ -237,30 +232,29 @@
             return@thenAnswer it.getArgument(1)
         }
 
-        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(1, "a", "test"))).isFalse()
-        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(2, "b", "test"))).isTrue()
-        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(3, "c", "test", true))).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("a", "test"))).isFalse()
+        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("b", "test"))).isTrue()
+        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("c", "test", true))).isTrue()
         assertThat(
             featureFlagsDebug.isEnabled(
                 SysPropBooleanFlag(
-                    4,
                     "d",
                     "test",
                     false
                 )
             )
         ).isFalse()
-        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag(5, "e", "test"))).isFalse()
+        assertThat(featureFlagsDebug.isEnabled(SysPropBooleanFlag("e", "test"))).isFalse()
     }
 
     @Test
     fun readStringFlag() {
         whenever(flagManager.readFlagValue<String>(eq("3"), any())).thenReturn("foo")
         whenever(flagManager.readFlagValue<String>(eq("4"), any())).thenReturn("bar")
-        assertThat(featureFlagsDebug.getString(StringFlag(1, "1", "test", "biz"))).isEqualTo("biz")
-        assertThat(featureFlagsDebug.getString(StringFlag(2, "2", "test", "baz"))).isEqualTo("baz")
-        assertThat(featureFlagsDebug.getString(StringFlag(3, "3", "test", "buz"))).isEqualTo("foo")
-        assertThat(featureFlagsDebug.getString(StringFlag(4, "4", "test", "buz"))).isEqualTo("bar")
+        assertThat(featureFlagsDebug.getString(StringFlag("1", "test", "biz"))).isEqualTo("biz")
+        assertThat(featureFlagsDebug.getString(StringFlag("2", "test", "baz"))).isEqualTo("baz")
+        assertThat(featureFlagsDebug.getString(StringFlag("3", "test", "buz"))).isEqualTo("foo")
+        assertThat(featureFlagsDebug.getString(StringFlag("4", "test", "buz"))).isEqualTo("bar")
     }
 
     @Test
@@ -279,7 +273,6 @@
         assertThat(
             featureFlagsDebug.getString(
                 ResourceStringFlag(
-                    1,
                     "1",
                     "test",
                     1001
@@ -289,7 +282,6 @@
         assertThat(
             featureFlagsDebug.getString(
                 ResourceStringFlag(
-                    2,
                     "2",
                     "test",
                     1002
@@ -299,7 +291,6 @@
         assertThat(
             featureFlagsDebug.getString(
                 ResourceStringFlag(
-                    3,
                     "3",
                     "test",
                     1003
@@ -308,15 +299,15 @@
         ).isEqualTo("override3")
 
         Assert.assertThrows(NullPointerException::class.java) {
-            featureFlagsDebug.getString(ResourceStringFlag(4, "4", "test", 1004))
+            featureFlagsDebug.getString(ResourceStringFlag("4", "test", 1004))
         }
         Assert.assertThrows(NameNotFoundException::class.java) {
-            featureFlagsDebug.getString(ResourceStringFlag(5, "5", "test", 1005))
+            featureFlagsDebug.getString(ResourceStringFlag("5", "test", 1005))
         }
         // Test that resource is loaded (and validated) even when the setting is set.
         //  This prevents developers from not noticing when they reference an invalid resource.
         Assert.assertThrows(NameNotFoundException::class.java) {
-            featureFlagsDebug.getString(ResourceStringFlag(6, "6", "test", 1005))
+            featureFlagsDebug.getString(ResourceStringFlag("6", "test", 1005))
         }
     }
 
@@ -324,10 +315,10 @@
     fun readIntFlag() {
         whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(22)
         whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(48)
-        assertThat(featureFlagsDebug.getInt(IntFlag(1, "1", "test", 12))).isEqualTo(12)
-        assertThat(featureFlagsDebug.getInt(IntFlag(2, "2", "test", 93))).isEqualTo(93)
-        assertThat(featureFlagsDebug.getInt(IntFlag(3, "3", "test", 8))).isEqualTo(22)
-        assertThat(featureFlagsDebug.getInt(IntFlag(4, "4", "test", 234))).isEqualTo(48)
+        assertThat(featureFlagsDebug.getInt(IntFlag("1", "test", 12))).isEqualTo(12)
+        assertThat(featureFlagsDebug.getInt(IntFlag("2", "test", 93))).isEqualTo(93)
+        assertThat(featureFlagsDebug.getInt(IntFlag("3", "test", 8))).isEqualTo(22)
+        assertThat(featureFlagsDebug.getInt(IntFlag("4", "test", 234))).isEqualTo(48)
     }
 
     @Test
@@ -339,30 +330,30 @@
         whenever(resources.getInteger(1005)).thenThrow(NotFoundException("unknown resource"))
         whenever(resources.getInteger(1006)).thenThrow(NotFoundException("unknown resource"))
 
-        whenever(flagManager.readFlagValue<Int>(eq(3), any())).thenReturn(20)
-        whenever(flagManager.readFlagValue<Int>(eq(4), any())).thenReturn(500)
-        whenever(flagManager.readFlagValue<Int>(eq(5), any())).thenReturn(9519)
+        whenever(flagManager.readFlagValue<Int>(eq("3"), any())).thenReturn(20)
+        whenever(flagManager.readFlagValue<Int>(eq("4"), any())).thenReturn(500)
+        whenever(flagManager.readFlagValue<Int>(eq("5"), any())).thenReturn(9519)
 
-        assertThat(featureFlagsDebug.getInt(ResourceIntFlag(1, "1", "test", 1001))).isEqualTo(88)
-        assertThat(featureFlagsDebug.getInt(ResourceIntFlag(2, "2", "test", 1002))).isEqualTo(61)
-        assertThat(featureFlagsDebug.getInt(ResourceIntFlag(3, "3", "test", 1003))).isEqualTo(20)
+        assertThat(featureFlagsDebug.getInt(ResourceIntFlag("1", "test", 1001))).isEqualTo(88)
+        assertThat(featureFlagsDebug.getInt(ResourceIntFlag("2", "test", 1002))).isEqualTo(61)
+        assertThat(featureFlagsDebug.getInt(ResourceIntFlag("3", "test", 1003))).isEqualTo(20)
 
         Assert.assertThrows(NotFoundException::class.java) {
-            featureFlagsDebug.getInt(ResourceIntFlag(4, "4", "test", 1004))
+            featureFlagsDebug.getInt(ResourceIntFlag("4", "test", 1004))
         }
         // Test that resource is loaded (and validated) even when the setting is set.
         //  This prevents developers from not noticing when they reference an invalid resource.
         Assert.assertThrows(NotFoundException::class.java) {
-            featureFlagsDebug.getInt(ResourceIntFlag(5, "5", "test", 1005))
+            featureFlagsDebug.getInt(ResourceIntFlag("5", "test", 1005))
         }
     }
 
     @Test
     fun broadcastReceiver_IgnoresInvalidData() {
-        addFlag(UnreleasedFlag(1, "1", "test"))
-        addFlag(ResourceBooleanFlag(2, "2", "test", 1002))
-        addFlag(StringFlag(3, "3", "test", "flag3"))
-        addFlag(ResourceStringFlag(4, "4", "test", 1004))
+        addFlag(UnreleasedFlag("1", "test"))
+        addFlag(ResourceBooleanFlag("2", "test", 1002))
+        addFlag(StringFlag("3", "test", "flag3"))
+        addFlag(ResourceStringFlag("4", "test", 1004))
 
         broadcastReceiver.onReceive(mockContext, null)
         broadcastReceiver.onReceive(mockContext, Intent())
@@ -378,7 +369,7 @@
 
     @Test
     fun intentWithId_NoValueKeyClears() {
-        addFlag(UnreleasedFlag(1, name = "1", namespace = "test"))
+        addFlag(UnreleasedFlag(name = "1", namespace = "test"))
 
         // trying to erase an id not in the map does nothing
         broadcastReceiver.onReceive(
@@ -397,10 +388,10 @@
 
     @Test
     fun setBooleanFlag() {
-        addFlag(UnreleasedFlag(1, "1", "test"))
-        addFlag(UnreleasedFlag(2, "2", "test"))
-        addFlag(ResourceBooleanFlag(3, "3", "test", 1003))
-        addFlag(ResourceBooleanFlag(4, "4", "test", 1004))
+        addFlag(UnreleasedFlag("1", "test"))
+        addFlag(UnreleasedFlag("2", "test"))
+        addFlag(ResourceBooleanFlag("3", "test", 1003))
+        addFlag(ResourceBooleanFlag("4", "test", 1004))
 
         setByBroadcast("1", false)
         verifyPutData("1", "{\"type\":\"boolean\",\"value\":false}")
@@ -417,8 +408,8 @@
 
     @Test
     fun setStringFlag() {
-        addFlag(StringFlag(1, "1", "1", "test"))
-        addFlag(ResourceStringFlag(2, "2", "test", 1002))
+        addFlag(StringFlag("1", "1", "test"))
+        addFlag(ResourceStringFlag("2", "test", 1002))
 
         setByBroadcast("1", "override1")
         verifyPutData("1", "{\"type\":\"string\",\"value\":\"override1\"}")
@@ -429,7 +420,7 @@
 
     @Test
     fun setFlag_ClearsCache() {
-        val flag1 = addFlag(StringFlag(1, "1", "test", "flag1"))
+        val flag1 = addFlag(StringFlag("1", "test", "flag1"))
         whenever(flagManager.readFlagValue<String>(eq("1"), any())).thenReturn("original")
 
         // gets the flag & cache it
@@ -451,7 +442,7 @@
 
     @Test
     fun serverSide_Overrides_MakesFalse() {
-        val flag = ReleasedFlag(100, "100", "test")
+        val flag = ReleasedFlag("100", "test")
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
 
@@ -460,7 +451,7 @@
 
     @Test
     fun serverSide_Overrides_MakesTrue() {
-        val flag = UnreleasedFlag(100, name = "100", namespace = "test")
+        val flag = UnreleasedFlag(name = "100", namespace = "test")
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
         assertThat(featureFlagsDebug.isEnabled(flag)).isTrue()
@@ -494,18 +485,18 @@
 
     @Test
     fun dumpFormat() {
-        val flag1 = ReleasedFlag(1, "1", "test")
-        val flag2 = ResourceBooleanFlag(2, "2", "test", 1002)
-        val flag3 = UnreleasedFlag(3, "3", "test")
-        val flag4 = StringFlag(4, "4", "test", "")
-        val flag5 = StringFlag(5, "5", "test", "flag5default")
-        val flag6 = ResourceStringFlag(6, "6", "test", 1006)
-        val flag7 = ResourceStringFlag(7, "7", "test", 1007)
+        val flag1 = ReleasedFlag("1", "test")
+        val flag2 = ResourceBooleanFlag("2", "test", 1002)
+        val flag3 = UnreleasedFlag("3", "test")
+        val flag4 = StringFlag("4", "test", "")
+        val flag5 = StringFlag("5", "test", "flag5default")
+        val flag6 = ResourceStringFlag("6", "test", 1006)
+        val flag7 = ResourceStringFlag("7", "test", 1007)
 
         whenever(resources.getBoolean(1002)).thenReturn(true)
         whenever(resources.getString(1006)).thenReturn("resource1006")
         whenever(resources.getString(1007)).thenReturn("resource1007")
-        whenever(flagManager.readFlagValue(eq(7), eq(StringFlagSerializer)))
+        whenever(flagManager.readFlagValue(eq("7"), eq(StringFlagSerializer)))
             .thenReturn("override7")
 
         // WHEN the flags have been accessed
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index 917147b..16b4595 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -44,7 +44,7 @@
     private val serverFlagReader = ServerFlagReaderFake()
 
 
-    private val flagA = ReleasedFlag(501, name = "a", namespace = "test")
+    private val flagA = ReleasedFlag(name = "a", namespace = "test")
 
     @Before
     fun setup() {
@@ -62,11 +62,10 @@
 
     @Test
     fun testBooleanResourceFlag() {
-        val flagId = 213
         val flagResourceId = 3
         val flagName = "213"
         val flagNamespace = "test"
-        val flag = ResourceBooleanFlag(flagId, flagName, flagNamespace, flagResourceId)
+        val flag = ResourceBooleanFlag(flagName, flagNamespace, flagResourceId)
         whenever(mResources.getBoolean(flagResourceId)).thenReturn(true)
         assertThat(featureFlagsRelease.isEnabled(flag)).isTrue()
     }
@@ -79,33 +78,32 @@
         whenever(mResources.getString(1004)).thenAnswer { throw NameNotFoundException() }
 
         assertThat(featureFlagsRelease.getString(
-            ResourceStringFlag(1, "1", "test", 1001))).isEqualTo("")
+            ResourceStringFlag("1", "test", 1001))).isEqualTo("")
         assertThat(featureFlagsRelease.getString(
-            ResourceStringFlag(2, "2", "test", 1002))).isEqualTo("res2")
+            ResourceStringFlag("2", "test", 1002))).isEqualTo("res2")
 
         assertThrows(NullPointerException::class.java) {
-            featureFlagsRelease.getString(ResourceStringFlag(3, "3", "test", 1003))
+            featureFlagsRelease.getString(ResourceStringFlag("3", "test", 1003))
         }
         assertThrows(NameNotFoundException::class.java) {
-            featureFlagsRelease.getString(ResourceStringFlag(4, "4", "test", 1004))
+            featureFlagsRelease.getString(ResourceStringFlag("4", "test", 1004))
         }
     }
 
     @Test
     fun testSysPropBooleanFlag() {
-        val flagId = 213
         val flagName = "sys_prop_flag"
         val flagNamespace = "test"
         val flagDefault = true
 
-        val flag = SysPropBooleanFlag(flagId, flagName, flagNamespace, flagDefault)
+        val flag = SysPropBooleanFlag(flagName, flagNamespace, flagDefault)
         whenever(mSystemProperties.getBoolean(flagName, flagDefault)).thenReturn(flagDefault)
         assertThat(featureFlagsRelease.isEnabled(flag)).isEqualTo(flagDefault)
     }
 
     @Test
     fun serverSide_OverridesReleased_MakesFalse() {
-        val flag = ReleasedFlag(100, "100", "test")
+        val flag = ReleasedFlag("100", "test")
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, false)
 
@@ -114,7 +112,7 @@
 
     @Test
     fun serverSide_OverridesUnreleased_Ignored() {
-        val flag = UnreleasedFlag(100, "100", "test")
+        val flag = UnreleasedFlag("100", "test")
 
         serverFlagReader.setFlagValue(flag.namespace, flag.name, true)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
index 28131b5..b02baa7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagCommandTest.kt
@@ -33,10 +33,10 @@
     @Mock private lateinit var featureFlags: FeatureFlagsDebug
     @Mock private lateinit var pw: PrintWriter
     private val flagMap = mutableMapOf<String, Flag<*>>()
-    private val flagA = UnreleasedFlag(500, "500", "test")
-    private val flagB = ReleasedFlag(501, "501", "test")
-    private val stringFlag = StringFlag(502, "502", "test", "abracadabra")
-    private val intFlag = IntFlag(503, "503", "test", 12)
+    private val flagA = UnreleasedFlag("500", "test")
+    private val flagB = ReleasedFlag("501", "test")
+    private val stringFlag = StringFlag("502", "test", "abracadabra")
+    private val intFlag = IntFlag("503", "test", 12)
 
     private lateinit var cmd: FlagCommand
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
index e679d47..303aaa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt
@@ -64,14 +64,14 @@
         verifyNoMoreInteractions(mFlagSettingsHelper)
 
         // adding the first listener registers the observer
-        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
+        mFlagManager.addListener(ReleasedFlag("1", "test"), listener1)
         val observer = withArgCaptor<ContentObserver> {
             verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
         }
         verifyNoMoreInteractions(mFlagSettingsHelper)
 
         // adding another listener does nothing
-        mFlagManager.addListener(ReleasedFlag(2, "2", "test"), listener2)
+        mFlagManager.addListener(ReleasedFlag("2", "test"), listener2)
         verifyNoMoreInteractions(mFlagSettingsHelper)
 
         // removing the original listener does nothing with second one still present
@@ -89,7 +89,7 @@
         val listener = mock<FlagListenable.Listener>()
         val clearCacheAction = mock<Consumer<String>>()
         mFlagManager.clearCacheAction = clearCacheAction
-        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
+        mFlagManager.addListener(ReleasedFlag("1", "test"), listener)
         val observer = withArgCaptor<ContentObserver> {
             verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
         }
@@ -101,8 +101,8 @@
     fun testObserverInvokesListeners() {
         val listener1 = mock<FlagListenable.Listener>()
         val listener10 = mock<FlagListenable.Listener>()
-        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
-        mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10)
+        mFlagManager.addListener(ReleasedFlag("1", "test"), listener1)
+        mFlagManager.addListener(ReleasedFlag("10", "test"), listener10)
         val observer = withArgCaptor<ContentObserver> {
             verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
         }
@@ -127,8 +127,8 @@
     fun testOnlySpecificFlagListenerIsInvoked() {
         val listener1 = mock<FlagListenable.Listener>()
         val listener10 = mock<FlagListenable.Listener>()
-        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener1)
-        mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener10)
+        mFlagManager.addListener(ReleasedFlag("1", "test"), listener1)
+        mFlagManager.addListener(ReleasedFlag("10", "test"), listener10)
 
         mFlagManager.dispatchListenersAndMaybeRestart("1", null)
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
@@ -148,8 +148,8 @@
     @Test
     fun testSameListenerCanBeUsedForMultipleFlags() {
         val listener = mock<FlagListenable.Listener>()
-        mFlagManager.addListener(ReleasedFlag(1, "1", "test"), listener)
-        mFlagManager.addListener(ReleasedFlag(10, "10", "test"), listener)
+        mFlagManager.addListener(ReleasedFlag("1", "test"), listener)
+        mFlagManager.addListener(ReleasedFlag("10", "test"), listener)
 
         mFlagManager.dispatchListenersAndMaybeRestart("1", null)
         val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
@@ -177,7 +177,7 @@
     @Test
     fun testListenerCanSuppressRestart() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.addListener(ReleasedFlag(1, "1", "test")) { event ->
+        mFlagManager.addListener(ReleasedFlag("1", "test")) { event ->
             event.requestNoRestart()
         }
         mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
@@ -188,7 +188,7 @@
     @Test
     fun testListenerOnlySuppressesRestartForOwnFlag() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event ->
+        mFlagManager.addListener(ReleasedFlag("10", "test")) { event ->
             event.requestNoRestart()
         }
         mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
@@ -199,10 +199,10 @@
     @Test
     fun testRestartWhenNotAllListenersRequestSuppress() {
         val restartAction = mock<Consumer<Boolean>>()
-        mFlagManager.addListener(ReleasedFlag(10, "10", "test")) { event ->
+        mFlagManager.addListener(ReleasedFlag("10", "test")) { event ->
             event.requestNoRestart()
         }
-        mFlagManager.addListener(ReleasedFlag(10, "10", "test")) {
+        mFlagManager.addListener(ReleasedFlag("10", "test")) {
             // do not request
         }
         mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
index 953b7fb..1d1949d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -53,7 +53,7 @@
 
     @Test
     fun testChange_alertsListener() {
-        val flag = ReleasedFlag(1, "flag_1", "test")
+        val flag = ReleasedFlag("flag_1", "test")
         serverFlagReader.listenForChanges(listOf(flag), changeListener)
 
         deviceConfig.setProperty(NAMESPACE, "flag_1", "1", false)
@@ -65,7 +65,7 @@
     @Test
     fun testChange_ignoresListenersDuringTest() {
         val serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor, true)
-        val flag = ReleasedFlag(1, "1", "   test")
+        val flag = ReleasedFlag("1", "   test")
         serverFlagReader.listenForChanges(listOf(flag), changeListener)
 
         deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index 28328c2..4a79a21 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -236,7 +236,7 @@
                 mScreenOffAnimationController, mAuthController, mShadeExpansionStateManager,
                 mShadeWindowLogger);
         mFeatureFlags = new FakeFeatureFlags();
-
+        mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
 
         DejankUtils.setImmediate(true);
 
@@ -634,6 +634,7 @@
         TestableLooper.get(this).processAllMessages();
 
         assertFalse(mViewMediator.isShowingAndNotOccluded());
+        verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
     }
 
     @Test
@@ -650,6 +651,7 @@
         TestableLooper.get(this).processAllMessages();
 
         assertTrue(mViewMediator.isShowingAndNotOccluded());
+        verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(true);
     }
 
     @Test
@@ -658,6 +660,9 @@
         startMockKeyguardExitAnimation();
         cancelMockKeyguardExitAnimation();
 
+        // Calling cancel above results in keyguard not visible, as there is no pending lock
+        verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
+
         mViewMediator.maybeHandlePendingLock();
         TestableLooper.get(this).processAllMessages();
 
@@ -672,10 +677,15 @@
 
     @Test
     @TestableLooper.RunWithLooper(setAsMainLooper = true)
-    public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimation() {
+    public void testStartKeyguardExitAnimation_expectSurfaceBehindRemoteAnimationAndExits() {
         startMockKeyguardExitAnimation();
         assertTrue(mViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind());
 
+        mViewMediator.mViewMediatorCallback.keyguardDonePending(true,
+                mUpdateMonitor.getCurrentUser());
+        mViewMediator.mViewMediatorCallback.readyForKeyguardDone();
+        TestableLooper.get(this).processAllMessages();
+        verify(mKeyguardUnlockAnimationController).notifyFinishedKeyguardExitAnimation(false);
     }
 
     @Test
@@ -1058,7 +1068,8 @@
                 mSystemClock,
                 mDispatcher,
                 () -> mDreamingToLockscreenTransitionViewModel,
-                mSystemPropertiesHelper);
+                mSystemPropertiesHelper,
+                () -> mock(WindowManagerLockscreenVisibilityManager.class));
         mViewMediator.start();
 
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 7510373..9d983b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -137,27 +137,18 @@
     }
 
     @Test
-    fun getPickerScreenState_enabledIfConfiguredOnDevice_canOpenCamera() = runTest {
-        whenever(controller.isAvailableOnDevice).thenReturn(true)
-        whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
+    fun getPickerScreenState_enabledIfConfiguredOnDevice_isEnabledForPickerState() = runTest {
+        whenever(controller.isAllowedOnLockScreen).thenReturn(true)
+        whenever(controller.isAbleToLaunchScannerActivity).thenReturn(true)
 
         assertThat(underTest.getPickerScreenState())
             .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default())
     }
 
     @Test
-    fun getPickerScreenState_disabledIfConfiguredOnDevice_cannotOpenCamera() = runTest {
-        whenever(controller.isAvailableOnDevice).thenReturn(true)
-        whenever(controller.isAbleToOpenCameraApp).thenReturn(false)
-
-        assertThat(underTest.getPickerScreenState())
-            .isInstanceOf(KeyguardQuickAffordanceConfig.PickerScreenState.Disabled::class.java)
-    }
-
-    @Test
-    fun getPickerScreenState_unavailableIfNotConfiguredOnDevice() = runTest {
-        whenever(controller.isAvailableOnDevice).thenReturn(false)
-        whenever(controller.isAbleToOpenCameraApp).thenReturn(true)
+    fun getPickerScreenState_disabledIfConfiguredOnDevice_isDisabledForPickerState() = runTest {
+        whenever(controller.isAllowedOnLockScreen).thenReturn(true)
+        whenever(controller.isAbleToLaunchScannerActivity).thenReturn(false)
 
         assertThat(underTest.getPickerScreenState())
             .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice)
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 85ee0e4..fe5b812 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
@@ -52,6 +52,7 @@
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.FACE_AUTH_REFACTOR
+import com.android.systemui.flags.Flags.KEYGUARD_WM_STATE_REFACTOR
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
@@ -168,7 +169,11 @@
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
         trustRepository = FakeTrustRepository()
-        featureFlags = FakeFeatureFlags().apply { set(FACE_AUTH_REFACTOR, true) }
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(FACE_AUTH_REFACTOR, true)
+                set(KEYGUARD_WM_STATE_REFACTOR, false)
+            }
         val withDeps =
             KeyguardInteractorFactory.create(
                 featureFlags = featureFlags,
@@ -332,9 +337,6 @@
                 )
                 .isFalse()
 
-            whenever(faceManager.sensorPropertiesInternal).thenReturn(null)
-            assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
-
             whenever(faceManager.sensorPropertiesInternal).thenReturn(listOf())
             assertThat(createDeviceEntryFaceAuthRepositoryImpl().isDetectionSupported).isFalse()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 5e3376a..5ead16b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -63,6 +63,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
@@ -193,7 +194,7 @@
             assertThat(underTest.isKeyguardShowing()).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isShowing).thenReturn(true)
             captor.value.onKeyguardShowingChanged()
@@ -255,7 +256,7 @@
             assertThat(latest).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isOccluded).thenReturn(true)
             captor.value.onKeyguardShowingChanged()
@@ -280,7 +281,7 @@
             assertThat(isKeyguardUnlocked).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isUnlocked).thenReturn(true)
             captor.value.onUnlockedChanged()
@@ -454,7 +455,7 @@
             assertThat(latest).isFalse()
 
             val captor = argumentCaptor<KeyguardStateController.Callback>()
-            verify(keyguardStateController).addCallback(captor.capture())
+            verify(keyguardStateController, atLeastOnce()).addCallback(captor.capture())
 
             whenever(keyguardStateController.isKeyguardGoingAway).thenReturn(true)
             captor.value.onKeyguardGoingAwayChanged()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
new file mode 100644
index 0000000..bed959f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSurfaceBehindRepositoryImplTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardSurfaceBehindRepositoryImplTest : SysuiTestCase() {
+    private val testScope = TestScope()
+
+    private lateinit var underTest: KeyguardSurfaceBehindRepositoryImpl
+
+    @Before
+    fun setUp() {
+        underTest = KeyguardSurfaceBehindRepositoryImpl()
+    }
+
+    @Test
+    fun testSetAnimatingSurface() {
+        testScope.runTest {
+            val values by collectValues(underTest.isAnimatingSurface)
+
+            runCurrent()
+            underTest.setAnimatingSurface(true)
+            runCurrent()
+            underTest.setAnimatingSurface(false)
+            runCurrent()
+
+            // Default (first) value should be false.
+            assertThat(values).isEqualTo(listOf(false, true, false))
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
new file mode 100644
index 0000000..e2bf2f8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorTest.kt
@@ -0,0 +1,190 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+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.shade.data.repository.FakeShadeRepository
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromLockscreenTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+    private lateinit var underTest: FromLockscreenTransitionInteractor
+
+    // Override the fromLockscreenTransitionInteractor provider from the superclass so our underTest
+    // interactor is provided to any classes that need it.
+    override var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+        Lazy {
+            underTest
+        }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+
+        underTest =
+            FromLockscreenTransitionInteractor(
+                transitionRepository = super.transitionRepository,
+                transitionInteractor = super.transitionInteractor,
+                scope = super.testScope.backgroundScope,
+                keyguardInteractor = super.keyguardInteractor,
+                flags = FakeFeatureFlags(),
+                shadeRepository = FakeShadeRepository(),
+            )
+    }
+
+    @Test
+    fun testSurfaceBehindVisibility_nonNullOnlyForRelevantTransitions() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindVisibility)
+            runCurrent()
+
+            // Transition-specific surface visibility should be null ("don't care") initially.
+            assertEquals(
+                listOf(
+                    null,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // LOCKSCREEN -> AOD does not have any specific surface visibility.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null,
+                    true, // Surface is made visible immediately during LOCKSCREEN -> GONE
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testSurfaceBehindModel() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindModel)
+            runCurrent()
+
+            assertEquals(
+                values,
+                listOf(
+                    null, // We should start null ("don't care").
+                )
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // LOCKSCREEN -> AOD does not have specific view params.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    value = 0.01f,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                    value = 0.99f,
+                )
+            )
+            runCurrent()
+
+            assertEquals(3, values.size)
+            val model1percent = values[1]
+            val model99percent = values[2]
+
+            try {
+                // We should initially have an alpha of 0f when unlocking, so the surface is not
+                // visible
+                // while lockscreen UI animates out.
+                assertEquals(0f, model1percent!!.alpha)
+
+                // By the end it should probably be visible.
+                assertTrue(model99percent!!.alpha > 0f)
+            } catch (e: NullPointerException) {
+                fail("surfaceBehindModel was unexpectedly null.")
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
new file mode 100644
index 0000000..85bc374
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractorTest.kt
@@ -0,0 +1,214 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+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.util.mockito.mock
+import dagger.Lazy
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import junit.framework.Assert.fail
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class FromPrimaryBouncerTransitionInteractorTest : KeyguardTransitionInteractorTestCase() {
+    private lateinit var underTest: FromPrimaryBouncerTransitionInteractor
+
+    // Override the fromPrimaryBouncerTransitionInteractor provider from the superclass so our
+    // underTest interactor is provided to any classes that need it.
+    override var fromPrimaryBouncerTransitionInteractorLazy:
+        Lazy<FromPrimaryBouncerTransitionInteractor>? =
+        Lazy {
+            underTest
+        }
+
+    @Before
+    override fun setUp() {
+        super.setUp()
+
+        underTest =
+            FromPrimaryBouncerTransitionInteractor(
+                transitionRepository = super.transitionRepository,
+                transitionInteractor = super.transitionInteractor,
+                scope = super.testScope.backgroundScope,
+                keyguardInteractor = super.keyguardInteractor,
+                flags = FakeFeatureFlags(),
+                keyguardSecurityModel = mock(),
+            )
+    }
+
+    @Test
+    fun testSurfaceBehindVisibility() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindVisibility)
+            runCurrent()
+
+            // Transition-specific surface visibility should be null ("don't care") initially.
+            assertEquals(
+                listOf(
+                    null,
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have any specific visibility.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.01f,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null,
+                    false, // Surface is only made visible once the bouncer UI animates out.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.99f,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null,
+                    false,
+                    true, // Surface should eventually be visible.
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testSurfaceBehindModel() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindModel)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    null, // PRIMARY_BOUNCER -> LOCKSCREEN does not have specific view params.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.01f,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.RUNNING,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                    value = 0.99f,
+                )
+            )
+            runCurrent()
+
+            assertEquals(3, values.size)
+            val model1percent = values[1]
+            val model99percent = values[2]
+
+            try {
+                // We should initially have an alpha of 0f when unlocking, so the surface is not
+                // visible
+                // while lockscreen UI animates out.
+                assertEquals(0f, model1percent!!.alpha)
+
+                // By the end it should probably be visible.
+                assertTrue(model99percent!!.alpha > 0f)
+            } catch (e: NullPointerException) {
+                fail("surfaceBehindModel was unexpectedly null.")
+            }
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
new file mode 100644
index 0000000..fdcc66b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardSurfaceBehindInteractorTest.kt
@@ -0,0 +1,169 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.keyguard.data.repository.FakeKeyguardSurfaceBehindRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertTrue
+import kotlinx.coroutines.flow.flowOf
+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.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardSurfaceBehindInteractor
+    private lateinit var repository: FakeKeyguardSurfaceBehindRepository
+
+    @Mock
+    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+    @Mock
+    private lateinit var fromPrimaryBouncerTransitionInteractor:
+        FromPrimaryBouncerTransitionInteractor
+
+    private val lockscreenSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.33f)
+    private val primaryBouncerSurfaceBehindModel = KeyguardSurfaceBehindModel(alpha = 0.66f)
+
+    private val testScope = TestScope()
+
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+
+        whenever(fromLockscreenTransitionInteractor.surfaceBehindModel)
+            .thenReturn(flowOf(lockscreenSurfaceBehindModel))
+        whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindModel)
+            .thenReturn(flowOf(primaryBouncerSurfaceBehindModel))
+
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                )
+                .keyguardTransitionInteractor
+
+        repository = FakeKeyguardSurfaceBehindRepository()
+        underTest =
+            KeyguardSurfaceBehindInteractor(
+                repository = repository,
+                fromLockscreenInteractor = fromLockscreenTransitionInteractor,
+                fromPrimaryBouncerInteractor = fromPrimaryBouncerTransitionInteractor,
+                transitionInteractor = transitionInteractor,
+            )
+    }
+
+    @Test
+    fun viewParamsSwitchToCorrectFlow() =
+        testScope.runTest {
+            val values by collectValues(underTest.viewParams)
+
+            // Start on the LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            // We're on LOCKSCREEN; we should be using the default params.
+            assertEquals(1, values.size)
+            assertTrue(values[0].alpha == 0f)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We're going from LOCKSCREEN -> GONE, we should be using the lockscreen interactor's
+            // surface behind model.
+            assertEquals(2, values.size)
+            assertEquals(values[1], lockscreenSurfaceBehindModel)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We're going from PRIMARY_BOUNCER -> GONE, we should be using the bouncer interactor's
+            // surface behind model.
+            assertEquals(3, values.size)
+            assertEquals(values[2], primaryBouncerSurfaceBehindModel)
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // Once PRIMARY_BOUNCER -> GONE finishes, we should be using default params, which is
+            // alpha=1f when we're GONE.
+            assertEquals(4, values.size)
+            assertEquals(1f, values[3].alpha)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
new file mode 100644
index 0000000..8db19ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTestCase.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+
+open class KeyguardTransitionInteractorTestCase : SysuiTestCase() {
+    val testDispatcher = StandardTestDispatcher()
+    val testScope = TestScope(testDispatcher)
+
+    lateinit var keyguardRepository: FakeKeyguardRepository
+    lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+    lateinit var keyguardInteractor: KeyguardInteractor
+    lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+    /**
+     * Replace these lazy providers with non-null ones if you want test dependencies to use a real
+     * instance of the interactor for the test.
+     */
+    open var fromLockscreenTransitionInteractorLazy: Lazy<FromLockscreenTransitionInteractor>? =
+        null
+    open var fromPrimaryBouncerTransitionInteractorLazy:
+        Lazy<FromPrimaryBouncerTransitionInteractor>? =
+        null
+
+    open fun setUp() {
+        keyguardRepository = FakeKeyguardRepository()
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        keyguardInteractor =
+            KeyguardInteractorFactory.create(repository = keyguardRepository).keyguardInteractor
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    repository = transitionRepository,
+                    keyguardInteractor = keyguardInteractor,
+                    scope = testScope.backgroundScope,
+                    fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractorLazy
+                            ?: Lazy { mock() },
+                    fromPrimaryBouncerTransitionInteractor =
+                        fromPrimaryBouncerTransitionInteractorLazy ?: Lazy { mock() },
+                )
+                .also {
+                    fromLockscreenTransitionInteractorLazy = it.fromLockscreenTransitionInteractor
+                    fromPrimaryBouncerTransitionInteractorLazy =
+                        it.fromPrimaryBouncerTransitionInteractor
+                }
+                .keyguardTransitionInteractor
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
index aa6bd4e..4b221a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt
@@ -104,12 +104,21 @@
 
         whenever(keyguardSecurityModel.getSecurityMode(anyInt())).thenReturn(PIN)
 
-        featureFlags = FakeFeatureFlags().apply { set(Flags.FACE_AUTH_REFACTOR, true) }
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.FACE_AUTH_REFACTOR, true)
+                set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
+            }
 
         transitionInteractor =
             KeyguardTransitionInteractorFactory.create(
                     scope = testScope,
                     repository = transitionRepository,
+                    keyguardInteractor = createKeyguardInteractor(),
+                    fromLockscreenTransitionInteractor = { fromLockscreenTransitionInteractor },
+                    fromPrimaryBouncerTransitionInteractor = {
+                        fromPrimaryBouncerTransitionInteractor
+                    },
                 )
                 .keyguardTransitionInteractor
 
@@ -119,6 +128,7 @@
                     keyguardInteractor = createKeyguardInteractor(),
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
+                    flags = featureFlags,
                     shadeRepository = shadeRepository,
                 )
                 .apply { start() }
@@ -129,6 +139,7 @@
                     keyguardInteractor = createKeyguardInteractor(),
                     transitionRepository = transitionRepository,
                     transitionInteractor = transitionInteractor,
+                    flags = featureFlags,
                     keyguardSecurityModel = keyguardSecurityModel,
                 )
                 .apply { start() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
deleted file mode 100644
index 86e56bf..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/LockscreenSceneInteractorTest.kt
+++ /dev/null
@@ -1,172 +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.systemui.keyguard.domain.interactor
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.scene.SceneTestUtils
-import com.android.systemui.scene.shared.model.SceneKey
-import com.android.systemui.scene.shared.model.SceneModel
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.runCurrent
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@SmallTest
-@RunWith(JUnit4::class)
-class LockscreenSceneInteractorTest : SysuiTestCase() {
-
-    private val utils = SceneTestUtils(this)
-    private val testScope = utils.testScope
-    private val sceneInteractor = utils.sceneInteractor()
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = utils.authenticationRepository(),
-        )
-    private val underTest =
-        utils.lockScreenSceneInteractor(
-            authenticationInteractor = authenticationInteractor,
-            bouncerInteractor =
-                utils.bouncerInteractor(
-                    authenticationInteractor = authenticationInteractor,
-                    sceneInteractor = sceneInteractor,
-                ),
-        )
-
-    @Test
-    fun isDeviceLocked() =
-        testScope.runTest {
-            val isDeviceLocked by collectLastValue(underTest.isDeviceLocked)
-
-            utils.authenticationRepository.setUnlocked(false)
-            assertThat(isDeviceLocked).isTrue()
-
-            utils.authenticationRepository.setUnlocked(true)
-            assertThat(isDeviceLocked).isFalse()
-        }
-
-    @Test
-    fun isSwipeToDismissEnabled_deviceLockedAndAuthMethodSwipe_true() =
-        testScope.runTest {
-            val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
-
-            utils.authenticationRepository.setUnlocked(false)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-
-            assertThat(isSwipeToDismissEnabled).isTrue()
-        }
-
-    @Test
-    fun isSwipeToDismissEnabled_deviceUnlockedAndAuthMethodSwipe_false() =
-        testScope.runTest {
-            val isSwipeToDismissEnabled by collectLastValue(underTest.isSwipeToDismissEnabled)
-
-            utils.authenticationRepository.setUnlocked(true)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-
-            assertThat(isSwipeToDismissEnabled).isFalse()
-        }
-
-    @Test
-    fun dismissLockScreen_deviceLockedWithSecureAuthMethod_switchesToBouncer() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            utils.authenticationRepository.setUnlocked(false)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            underTest.dismissLockscreen()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
-        }
-
-    @Test
-    fun dismissLockScreen_deviceUnlocked_switchesToGone() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            utils.authenticationRepository.setUnlocked(true)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            underTest.dismissLockscreen()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-        }
-
-    @Test
-    fun dismissLockScreen_deviceLockedWithInsecureAuthMethod_switchesToGone() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            utils.authenticationRepository.setUnlocked(false)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
-
-            underTest.dismissLockscreen()
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
-        }
-
-    @Test
-    fun switchFromLockScreenToGone_authMethodNotSwipe_doesNotUnlockDevice() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Lockscreen), "reason")
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            assertThat(isUnlocked).isFalse()
-
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason")
-
-            assertThat(isUnlocked).isFalse()
-        }
-
-    @Test
-    fun switchFromNonLockScreenToGone_authMethodSwipe_doesNotUnlockDevice() =
-        testScope.runTest {
-            val isUnlocked by collectLastValue(authenticationInteractor.isUnlocked)
-            runCurrent()
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
-            runCurrent()
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            runCurrent()
-            assertThat(isUnlocked).isFalse()
-
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason")
-
-            assertThat(isUnlocked).isFalse()
-        }
-
-    @Test
-    fun authMethodChangedToNone_notOnLockScreenScene_doesNotDismissLockScreen() =
-        testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            runCurrent()
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.QuickSettings), "reason")
-            runCurrent()
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
-
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
-
-            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.QuickSettings))
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index baa5ee8..1dcb55d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -44,6 +44,8 @@
 import com.android.systemui.keyguard.util.IndicationHelper
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.ActivityStarter.OnDismissAction
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
@@ -80,6 +82,7 @@
     private lateinit var configurationRepository: FakeConfigurationRepository
     private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var trustRepository: FakeTrustRepository
+    private lateinit var powerRepository: FakePowerRepository
 
     @Mock private lateinit var indicationHelper: IndicationHelper
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
@@ -102,6 +105,7 @@
                 set(Flags.DELAY_BOUNCER, false)
             }
         trustRepository = FakeTrustRepository()
+        powerRepository = FakePowerRepository()
         underTest =
             OccludingAppDeviceEntryInteractor(
                 BiometricMessageInteractor(
@@ -145,6 +149,14 @@
                 testScope.backgroundScope,
                 mockedContext,
                 activityStarter,
+                PowerInteractor(
+                    powerRepository,
+                    keyguardRepository,
+                    falsingCollector = mock(),
+                    screenOffAnimationController = mock(),
+                    statusBarStateController = mock(),
+                ),
+                FakeFeatureFlags().apply { set(Flags.FP_LISTEN_OCCLUDING_APPS, true) },
             )
     }
 
@@ -160,6 +172,18 @@
         }
 
     @Test
+    fun fingerprintSuccess_notInteractive_doesNotGoToHomeScreen() =
+        testScope.runTest {
+            givenOnOccludingApp(true)
+            powerRepository.setInteractive(false)
+            fingerprintAuthRepository.setAuthenticationStatus(
+                SuccessFingerprintAuthenticationStatus(0, true)
+            )
+            runCurrent()
+            verifyNeverGoToHomeScreen()
+        }
+
+    @Test
     fun fingerprintSuccess_notOnOccludingApp_doesNotGoToHomeScreen() =
         testScope.runTest {
             givenOnOccludingApp(false)
@@ -291,6 +315,7 @@
         }
 
     private fun givenOnOccludingApp(isOnOccludingApp: Boolean) {
+        powerRepository.setInteractive(true)
         keyguardRepository.setKeyguardOccluded(isOnOccludingApp)
         keyguardRepository.setKeyguardShowing(isOnOccludingApp)
         bouncerRepository.setPrimaryShow(!isOnOccludingApp)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
new file mode 100644
index 0000000..73ecae5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/WindowManagerLockscreenVisibilityInteractorTest.kt
@@ -0,0 +1,412 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectValues
+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.util.mockito.whenever
+import junit.framework.Assert.assertEquals
+import kotlinx.coroutines.flow.MutableStateFlow
+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.MockitoAnnotations.initMocks
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: WindowManagerLockscreenVisibilityInteractor
+
+    @Mock private lateinit var surfaceBehindInteractor: KeyguardSurfaceBehindInteractor
+    @Mock
+    private lateinit var fromLockscreenTransitionInteractor: FromLockscreenTransitionInteractor
+    @Mock
+    private lateinit var fromPrimaryBouncerTransitionInteractor:
+        FromPrimaryBouncerTransitionInteractor
+
+    private val lockscreenSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+    private val primaryBouncerSurfaceVisibilityFlow = MutableStateFlow<Boolean?>(false)
+    private val surfaceBehindIsAnimatingFlow = MutableStateFlow(false)
+
+    private val testScope = TestScope()
+
+    private lateinit var keyguardInteractor: KeyguardInteractor
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+    private lateinit var transitionInteractor: KeyguardTransitionInteractor
+
+    @Before
+    fun setUp() {
+        initMocks(this)
+
+        whenever(fromLockscreenTransitionInteractor.surfaceBehindVisibility)
+            .thenReturn(lockscreenSurfaceVisibilityFlow)
+        whenever(fromPrimaryBouncerTransitionInteractor.surfaceBehindVisibility)
+            .thenReturn(primaryBouncerSurfaceVisibilityFlow)
+        whenever(surfaceBehindInteractor.isAnimatingSurface)
+            .thenReturn(surfaceBehindIsAnimatingFlow)
+
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                )
+                .also { keyguardInteractor = it.keyguardInteractor }
+                .keyguardTransitionInteractor
+
+        underTest =
+            WindowManagerLockscreenVisibilityInteractor(
+                keyguardInteractor = keyguardInteractor,
+                transitionInteractor = transitionInteractor,
+                surfaceBehindInteractor = surfaceBehindInteractor,
+                fromLockscreenTransitionInteractor,
+                fromPrimaryBouncerTransitionInteractor,
+            )
+    }
+
+    @Test
+    fun surfaceBehindVisibility_switchesToCorrectFlow() =
+        testScope.runTest {
+            val values by collectValues(underTest.surfaceBehindVisibility)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // We should start with the surface invisible on LOCKSCREEN.
+                ),
+                values
+            )
+
+            val lockscreenSpecificSurfaceVisibility = true
+            lockscreenSurfaceVisibilityFlow.emit(lockscreenSpecificSurfaceVisibility)
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We started a transition from LOCKSCREEN, we should be using the value emitted by the
+            // lockscreenSurfaceVisibilityFlow.
+            assertEquals(
+                listOf(
+                    false,
+                    lockscreenSpecificSurfaceVisibility,
+                ),
+                values
+            )
+
+            // Go back to LOCKSCREEN, since we won't emit 'true' twice in a row.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    lockscreenSpecificSurfaceVisibility,
+                    false, // FINISHED (LOCKSCREEN)
+                ),
+                values
+            )
+
+            val bouncerSpecificVisibility = true
+            primaryBouncerSurfaceVisibilityFlow.emit(bouncerSpecificVisibility)
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.PRIMARY_BOUNCER,
+                    to = KeyguardState.GONE,
+                )
+            )
+
+            runCurrent()
+
+            // We started a transition from PRIMARY_BOUNCER, we should be using the value emitted by
+            // the
+            // primaryBouncerSurfaceVisibilityFlow.
+            assertEquals(
+                listOf(
+                    false,
+                    lockscreenSpecificSurfaceVisibility,
+                    false,
+                    bouncerSpecificVisibility,
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testUsingGoingAwayAnimation_duringTransitionToGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // Not using the animation when we're just sitting on LOCKSCREEN.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(true)
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true, // Still true when we're FINISHED -> GONE, since we're still animating.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(false)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // False once the animation ends.
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun testNotUsingGoingAwayAnimation_evenWhenAnimating_ifStateIsNotGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.usingKeyguardGoingAwayAnimation)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false, // Not using the animation when we're just sitting on LOCKSCREEN.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(true)
+            runCurrent()
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true, // We're happily animating while transitioning to gone.
+                ),
+                values
+            )
+
+            // Oh no, we're still surfaceBehindAnimating=true, but no longer transitioning to GONE.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // Despite the animator still running, this should be false.
+                ),
+                values
+            )
+
+            surfaceBehindIsAnimatingFlow.emit(false)
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    false,
+                    true,
+                    false, // The animator ending should have no effect.
+                ),
+                values
+            )
+        }
+
+    @Test
+    fun lockscreenVisibility_visibleWhenGone() =
+        testScope.runTest {
+            val values by collectValues(underTest.lockscreenVisibility)
+
+            // Start on LOCKSCREEN.
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true, // Unsurprisingly, we should start with the lockscreen visible on
+                    // LOCKSCREEN.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.STARTED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true, // Lockscreen remains visible while we're transitioning to GONE.
+                ),
+                values
+            )
+
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    transitionState = TransitionState.FINISHED,
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.GONE,
+                )
+            )
+            runCurrent()
+
+            assertEquals(
+                listOf(
+                    true,
+                    false, // Once we're fully GONE, the lockscreen should not be visible.
+                ),
+                values
+            )
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
new file mode 100644
index 0000000..a22f603
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplierTest.kt
@@ -0,0 +1,160 @@
+/*
+ * 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.ui.binder
+
+import android.testing.TestableLooper.RunWithLooper
+import android.view.RemoteAnimationTarget
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardViewController
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.AnimatorTestRule
+import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertNull
+import junit.framework.Assert.assertTrue
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.doAnswer
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWithLooper(setAsMainLooper = true)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class KeyguardSurfaceBehindParamsApplierTest : SysuiTestCase() {
+    @get:Rule val animatorTestRule = AnimatorTestRule()
+
+    private lateinit var underTest: KeyguardSurfaceBehindParamsApplier
+    private lateinit var executor: FakeExecutor
+
+    @Mock private lateinit var keyguardViewController: KeyguardViewController
+
+    @Mock private lateinit var interactor: KeyguardSurfaceBehindInteractor
+
+    @Mock private lateinit var remoteAnimationTarget: RemoteAnimationTarget
+
+    private var isAnimatingSurface: Boolean? = null
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        executor = FakeExecutor(FakeSystemClock())
+        underTest =
+            KeyguardSurfaceBehindParamsApplier(
+                executor = executor,
+                keyguardViewController = keyguardViewController,
+                interactor = interactor,
+            )
+
+        doAnswer {
+                (it.arguments[0] as Boolean).let { animating -> isAnimatingSurface = animating }
+            }
+            .whenever(interactor)
+            .setAnimatingSurface(anyBoolean())
+    }
+
+    @After
+    fun tearDown() {
+        animatorTestRule.advanceTimeBy(1000.toLong())
+    }
+
+    @Test
+    fun testNotAnimating_setParamsWithNoAnimation() {
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                alpha = 0.3f,
+                translationY = 300f,
+            )
+
+        // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+        // just yet.
+        assertNull(isAnimatingSurface)
+
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        // We should now explicitly not be animating the surface.
+        assertFalse(checkNotNull(isAnimatingSurface))
+    }
+
+    @Test
+    fun testAnimating_paramsThenSurfaceProvided() {
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                animateFromAlpha = 0f,
+                alpha = 0.3f,
+                animateFromTranslationY = 0f,
+                translationY = 300f,
+            )
+
+        // A surface has not yet been provided, so we shouldn't have set animating to false OR true
+        // just yet.
+        assertNull(isAnimatingSurface)
+
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        // We should now be animating the surface.
+        assertTrue(checkNotNull(isAnimatingSurface))
+    }
+
+    @Test
+    fun testAnimating_surfaceThenParamsProvided() {
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        // The default params (which do not animate) should have been applied, so we're explicitly
+        // NOT animating yet.
+        assertFalse(checkNotNull(isAnimatingSurface))
+
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                animateFromAlpha = 0f,
+                alpha = 0.3f,
+                animateFromTranslationY = 0f,
+                translationY = 300f,
+            )
+
+        // We should now be animating the surface.
+        assertTrue(checkNotNull(isAnimatingSurface))
+    }
+
+    @Test
+    fun testAnimating_thenReleased_animatingIsFalse() {
+        underTest.viewParams =
+            KeyguardSurfaceBehindModel(
+                animateFromAlpha = 0f,
+                alpha = 0.3f,
+                animateFromTranslationY = 0f,
+                translationY = 300f,
+            )
+        underTest.applyParamsToSurface(remoteAnimationTarget)
+
+        assertTrue(checkNotNull(isAnimatingSurface))
+
+        underTest.notifySurfaceReleased()
+
+        // Releasing the surface should immediately cancel animators.
+        assertFalse(checkNotNull(isAnimatingSurface))
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
new file mode 100644
index 0000000..623c877
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityManagerTest.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.ui.binder
+
+import android.app.IActivityTaskManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager
+import com.android.systemui.statusbar.policy.KeyguardStateController
+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
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class WindowManagerLockscreenVisibilityManagerTest : SysuiTestCase() {
+    private lateinit var underTest: WindowManagerLockscreenVisibilityManager
+    private lateinit var executor: FakeExecutor
+
+    @Mock private lateinit var activityTaskManagerService: IActivityTaskManager
+
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+
+    @Mock private lateinit var keyguardSurfaceBehindAnimator: KeyguardSurfaceBehindParamsApplier
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        executor = FakeExecutor(FakeSystemClock())
+
+        underTest =
+            WindowManagerLockscreenVisibilityManager(
+                executor = executor,
+                activityTaskManagerService = activityTaskManagerService,
+                keyguardStateController = keyguardStateController,
+                keyguardSurfaceBehindAnimator = keyguardSurfaceBehindAnimator,
+            )
+    }
+
+    @Test
+    fun testLockscreenVisible_andAodVisible() {
+        underTest.setLockscreenShown(true)
+        underTest.setAodVisible(true)
+
+        verify(activityTaskManagerService).setLockScreenShown(true, true)
+        verifyNoMoreInteractions(activityTaskManagerService)
+    }
+
+    @Test
+    fun testGoingAway_whenLockscreenVisible_thenSurfaceMadeVisible() {
+        underTest.setLockscreenShown(true)
+        underTest.setAodVisible(true)
+
+        verify(activityTaskManagerService).setLockScreenShown(true, true)
+        verifyNoMoreInteractions(activityTaskManagerService)
+
+        underTest.setSurfaceBehindVisibility(true)
+
+        verify(activityTaskManagerService).keyguardGoingAway(anyInt())
+        verifyNoMoreInteractions(activityTaskManagerService)
+    }
+
+    @Test
+    fun testSurfaceVisible_whenLockscreenNotShowing_doesNotTriggerGoingAway() {
+        underTest.setLockscreenShown(false)
+        underTest.setAodVisible(false)
+
+        verify(activityTaskManagerService).setLockScreenShown(false, false)
+        verifyNoMoreInteractions(activityTaskManagerService)
+
+        underTest.setSurfaceBehindVisibility(true)
+
+        verifyNoMoreInteractions(activityTaskManagerService)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 66631dc..addb181 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -17,6 +17,8 @@
 
 package com.android.systemui.keyguard.ui.view.layout.blueprints
 
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -26,15 +28,17 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultLockIconSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultSettingsPopupMenuSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultShortcutsSection
+import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@RunWith(JUnit4::class)
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class DefaultKeyguardBlueprintTest : SysuiTestCase() {
     private lateinit var underTest: DefaultKeyguardBlueprint
@@ -45,6 +49,8 @@
     @Mock
     private lateinit var defaultAmbientIndicationAreaSection: DefaultAmbientIndicationAreaSection
     @Mock private lateinit var defaultSettingsPopupMenuSection: DefaultSettingsPopupMenuSection
+    @Mock private lateinit var defaultStatusViewSection: DefaultStatusViewSection
+    @Mock private lateinit var splitShadeGuidelines: SplitShadeGuidelines
 
     @Before
     fun setup() {
@@ -57,6 +63,8 @@
                 defaultShortcutsSection,
                 defaultAmbientIndicationAreaSection,
                 defaultSettingsPopupMenuSection,
+                defaultStatusViewSection,
+                splitShadeGuidelines,
             )
     }
 
@@ -69,5 +77,7 @@
         verify(defaultShortcutsSection).apply(cs)
         verify(defaultAmbientIndicationAreaSection).apply(cs)
         verify(defaultSettingsPopupMenuSection).apply(cs)
+        verify(defaultStatusViewSection).apply(cs)
+        verify(splitShadeGuidelines).apply(cs)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
index 1444f8d..379c03c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultLockIconSectionTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index c67f535..bfc6f31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -21,9 +21,7 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
-import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 63ee240..23f243c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -19,7 +19,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
@@ -49,14 +49,11 @@
     private val underTest =
         LockscreenSceneViewModel(
             applicationScope = testScope.backgroundScope,
-            interactor =
-                utils.lockScreenSceneInteractor(
+            authenticationInteractor = authenticationInteractor,
+            bouncerInteractor =
+                utils.bouncerInteractor(
                     authenticationInteractor = authenticationInteractor,
-                    bouncerInteractor =
-                        utils.bouncerInteractor(
-                            authenticationInteractor = authenticationInteractor,
-                            sceneInteractor = sceneInteractor,
-                        ),
+                    sceneInteractor = sceneInteractor,
                 ),
         )
 
@@ -87,21 +84,24 @@
         }
 
     @Test
-    fun upTransitionSceneKey_swipeToUnlockedEnabled_gone() =
+    fun upTransitionSceneKey_canSwipeToUnlock_gone() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Swipe)
-            utils.authenticationRepository.setUnlocked(false)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.authenticationRepository.setUnlocked(true)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
         }
 
     @Test
-    fun upTransitionSceneKey_swipeToUnlockedNotEnabled_bouncer() =
+    fun upTransitionSceneKey_cannotSwipeToUnlock_bouncer() =
         testScope.runTest {
             val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
 
             assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Bouncer)
         }
@@ -109,7 +109,7 @@
     @Test
     fun onLockButtonClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             runCurrent()
@@ -122,7 +122,7 @@
     @Test
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(true)
             runCurrent()
@@ -135,7 +135,7 @@
     @Test
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             runCurrent()
@@ -148,7 +148,7 @@
     @Test
     fun onLockButtonClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(true)
             runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
index 80ab418..0ad14d0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsFingerprintViewModelTest.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -83,16 +83,21 @@
         bouncerRepository = FakeKeyguardBouncerRepository()
         transitionRepository = FakeKeyguardTransitionRepository()
         shadeRepository = FakeShadeRepository()
-        val transitionInteractor =
-            KeyguardTransitionInteractor(
-                transitionRepository,
-                testScope.backgroundScope,
-            )
         val keyguardInteractor =
             KeyguardInteractorFactory.create(
+                    repository = keyguardRepository,
                     featureFlags = featureFlags,
                 )
                 .keyguardInteractor
+
+        val transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                    keyguardInteractor = keyguardInteractor,
+                )
+                .keyguardTransitionInteractor
+
         underTest =
             FingerprintViewModel(
                 context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
index 0456824..edcaa1d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/UdfpsLockscreenViewModelTest.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.domain.interactor.UdfpsKeyguardInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -98,15 +98,20 @@
                 bouncerRepository = it.bouncerRepository
             }
 
+        val transitionInteractor =
+            KeyguardTransitionInteractorFactory.create(
+                    scope = testScope.backgroundScope,
+                    repository = transitionRepository,
+                    keyguardInteractor = keyguardInteractor,
+                )
+                .keyguardTransitionInteractor
+
         underTest =
             UdfpsLockscreenViewModel(
                 context,
                 lockscreenColorResId,
                 alternateBouncerResId,
-                KeyguardTransitionInteractor(
-                    transitionRepository,
-                    testScope.backgroundScope,
-                ),
+                transitionInteractor,
                 UdfpsKeyguardInteractor(
                     configRepository,
                     BurnInInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
index 5b8272b0..ef51e47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaCarouselControllerTest.kt
@@ -30,6 +30,7 @@
 import com.android.internal.logging.InstanceId
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.keyguard.TestScopeProvider
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.classifier.FalsingCollector
@@ -37,6 +38,7 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -66,7 +68,6 @@
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -132,7 +133,7 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        context.resources.configuration.locales = LocaleList(Locale.US, Locale.UK)
+        context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK))
         transitionRepository = FakeKeyguardTransitionRepository()
         mediaCarouselController =
             MediaCarouselController(
@@ -152,7 +153,11 @@
                 debugLogger,
                 mediaFlags,
                 keyguardUpdateMonitor,
-                KeyguardTransitionInteractor(transitionRepository, TestScope().backgroundScope),
+                KeyguardTransitionInteractorFactory.create(
+                        scope = TestScopeProvider.getTestScope().backgroundScope,
+                        repository = transitionRepository,
+                    )
+                    .keyguardTransitionInteractor,
                 globalSettings
             )
         verify(configurationController).addCallback(capture(configListener))
@@ -730,13 +735,13 @@
 
     @Test
     fun testOnLocaleListChanged_playersAreAddedBack() {
-        context.resources.configuration.locales = LocaleList(Locale.US, Locale.UK, Locale.CANADA)
+        context.resources.configuration.setLocales(LocaleList(Locale.US, Locale.UK, Locale.CANADA))
         testConfigurationChange(configListener.value::onLocaleListChanged)
 
         verify(pageIndicator, never()).tintList =
             ColorStateList.valueOf(context.getColor(R.color.media_paging_indicator))
 
-        context.resources.configuration.locales = LocaleList(Locale.UK, Locale.US, Locale.CANADA)
+        context.resources.configuration.setLocales(LocaleList(Locale.UK, Locale.US, Locale.CANADA))
         testConfigurationChange(configListener.value::onLocaleListChanged)
 
         verify(pageIndicator).tintList =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index ab24c46..db00e09 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -278,6 +278,21 @@
     }
 
     @Test
+    public void
+            whenNotBroadcasting_verifyLeBroadcastServiceCallBackIsUnregisteredIfProfileEnabled() {
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        mIsBroadcasting = true;
+
+        mMediaOutputBaseDialogImpl.start();
+        verify(mLocalBluetoothLeBroadcast).registerServiceCallBack(any(), any());
+
+        mIsBroadcasting = false;
+        mMediaOutputBaseDialogImpl.stop();
+        verify(mLocalBluetoothLeBroadcast).unregisterServiceCallBack(any());
+    }
+
+    @Test
     public void refresh_checkStopText() {
         mStopText = "test_string";
         mMediaOutputBaseDialogImpl.refresh();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index 3e69a29..bfc8c83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -256,6 +256,30 @@
     }
 
     @Test
+    public void isBroadcastSupported_noBleDeviceAndEnabledBroadcast_returnsTrue() {
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(true);
+        FeatureFlagUtils.setEnabled(mContext,
+                FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
+        when(mMediaDevice.isBLEDevice()).thenReturn(false);
+
+        assertThat(mMediaOutputDialog.isBroadcastSupported()).isTrue();
+    }
+
+    @Test
+    public void isBroadcastSupported_noBleDeviceAndDisabledBroadcast_returnsFalse() {
+        when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+                mLocalBluetoothLeBroadcast);
+        when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false);
+        FeatureFlagUtils.setEnabled(mContext,
+                FeatureFlagUtils.SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, true);
+        when(mMediaDevice.isBLEDevice()).thenReturn(false);
+
+        assertThat(mMediaOutputDialog.isBroadcastSupported()).isFalse();
+    }
+
+    @Test
     public void getBroadcastIconVisibility_isBroadcasting_returnVisible() {
         when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
                 mLocalBluetoothLeBroadcast);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
index ee3b80a..906420d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/view/TaskPreviewSizeProviderTest.kt
@@ -123,7 +123,7 @@
 
     private fun givenDisplay(width: Int, height: Int, isTablet: Boolean = false) {
         val bounds = Rect(0, 0, width, height)
-        val windowMetrics = WindowMetrics(bounds, null)
+        val windowMetrics = WindowMetrics(bounds, { null }, 1.0f)
         whenever(windowManager.maximumWindowMetrics).thenReturn(windowMetrics)
         whenever(windowManager.currentWindowMetrics).thenReturn(windowMetrics)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
index 3a74c72..7bd97ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -108,20 +108,6 @@
         }
 
     @Test
-    fun mediaProjectionState_onSessionSet_tokenNull_emitsEntireScreen() =
-        testScope.runTest {
-            val state by collectLastValue(repo.mediaProjectionState)
-            runCurrent()
-
-            fakeMediaProjectionManager.dispatchOnSessionSet(
-                session =
-                    ContentRecordingSession.createTaskSession(/* taskWindowContainerToken= */ null)
-            )
-
-            assertThat(state).isEqualTo(MediaProjectionState.EntireScreen)
-        }
-
-    @Test
     fun mediaProjectionState_sessionSet_taskWithToken_noMatchingRunningTask_emitsEntireScreen() =
         testScope.runTest {
             val state by collectLastValue(repo.mediaProjectionState)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index fab1de0..2d3dc58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -73,7 +73,7 @@
                 context,
                 windowManager,
                 ViewConfiguration.get(context),
-                Handler.createAsync(Looper.myLooper()),
+                Handler.createAsync(checkNotNull(Looper.myLooper())),
                 vibratorHelper,
                 configurationController,
                 latencyTracker,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
index d933b57..1536c17 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -69,6 +69,7 @@
 import com.android.wm.shell.bubbles.Bubbles
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
+import kotlin.test.assertNotNull
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
@@ -672,7 +673,7 @@
             extras().bool(EXTRA_USE_STYLUS_MODE).isTrue()
         }
         iconCaptor.value?.let { icon ->
-            assertThat(icon).isNotNull()
+            assertNotNull(icon)
             assertThat(icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
         }
     }
@@ -755,7 +756,7 @@
             assertThat(shortLabel).isEqualTo(NOTE_TASK_SHORT_LABEL)
             assertThat(longLabel).isEqualTo(NOTE_TASK_LONG_LABEL)
             assertThat(isLongLived).isEqualTo(true)
-            assertThat(icon.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
+            assertThat(icon?.resId).isEqualTo(R.drawable.ic_note_task_shortcut_widget)
             assertThat(extras?.getString(EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE))
                 .isEqualTo(NOTE_TASK_PACKAGE_NAME)
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt
index db96d55..14ecf93 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt
@@ -143,6 +143,16 @@
     }
 
     @Test
+    fun testVoiceActivationPrivacyItems() {
+        doReturn(listOf(AppOpItem(AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, TEST_UID,
+                TEST_PACKAGE_NAME, 0)))
+                .`when`(appOpsController).getActiveAppOps(anyBoolean())
+        val privacyItems = appOpsPrivacyItemMonitor.getActivePrivacyItems()
+        assertEquals(1, privacyItems.size)
+        assertEquals(PrivacyType.TYPE_MICROPHONE, privacyItems[0].privacyType)
+    }
+
+    @Test
     fun testSimilarItemsDifferentTimeStamp() {
         doReturn(listOf(AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 0),
                 AppOpItem(AppOpsManager.OP_CAMERA, TEST_UID, TEST_PACKAGE_NAME, 1)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
index 65210d6..e905e9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java
@@ -132,7 +132,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isFalse();
     }
 
     @Test
@@ -151,7 +151,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
     }
 
     @Test
@@ -161,7 +161,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
     }
 
     @Test
@@ -171,7 +171,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
     }
 
     @Test
@@ -181,7 +181,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/abc.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
     }
 
     @Test
@@ -191,7 +191,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isFalse();
     }
 
     @Test
@@ -201,24 +201,24 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
                 "def/.ijk", false);
         verifyActivityDetails("def/.ijk");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
                 null, false);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
-        // Once from setup + twice from this function
-        verify(mCallback, times(3)).onQRCodeScannerActivityChanged();
+        // twice from this function
+        verify(mCallback, times(2)).onQRCodeScannerActivityChanged();
     }
 
     @Test
@@ -228,7 +228,7 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isFalse();
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
@@ -236,14 +236,14 @@
 
         verifyActivityDetails("def/.ijk");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
         mProxyFake.setProperty(DeviceConfig.NAMESPACE_SYSTEMUI,
                 SystemUiDeviceConfigFlags.DEFAULT_QR_CODE_SCANNER,
                 null, false);
         verifyActivityDetails(null);
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isFalse();
         verify(mCallback, times(2)).onQRCodeScannerActivityChanged();
     }
 
@@ -295,19 +295,20 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
         mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "0",
                 UserHandle.USER_CURRENT);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAllowedOnLockScreen()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
         mSecureSettings.putStringForUser(LOCK_SCREEN_SHOW_QR_CODE_SCANNER, "1",
                 UserHandle.USER_CURRENT);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
         // Once from setup + twice from this function
         verify(mCallback, times(3)).onQRCodeScannerPreferenceChanged();
     }
@@ -319,13 +320,13 @@
                 /* enableOnLockScreen */ true);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
 
+        // even if unregistered, intent and activity details are retained
         mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
                 QR_CODE_SCANNER_PREFERENCE_CHANGE);
-        verifyActivityDetails(null);
-        assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isFalse();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
+        assertThat(mController.isAllowedOnLockScreen()).isTrue();
 
         // Unregister once again and make sure it affects the next register event
         mController.unregisterQRCodeScannerChangeObservers(DEFAULT_QR_CODE_SCANNER_CHANGE,
@@ -334,7 +335,7 @@
                 QR_CODE_SCANNER_PREFERENCE_CHANGE);
         verifyActivityDetails("abc/.def");
         assertThat(mController.isEnabledForLockScreenButton()).isTrue();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
     }
 
     @Test
@@ -344,7 +345,7 @@
                 /* enableOnLockScreen */ false);
         assertThat(mController.getIntent()).isNotNull();
         assertThat(mController.isEnabledForLockScreenButton()).isFalse();
-        assertThat(mController.isAbleToOpenCameraApp()).isTrue();
+        assertThat(mController.isAbleToLaunchScannerActivity()).isTrue();
         assertThat(getSettingsQRCodeDefaultComponent()).isNull();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index fda63ed..72c31b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -32,6 +32,7 @@
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
@@ -372,12 +373,23 @@
             assertThat(loadTilesForUser(0)).isNull()
         }
 
+    @Test
+    fun emptyTilesReplacedByDefaultInSettings() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tilesSpecs(0))
+            runCurrent()
+
+            assertThat(loadTilesForUser(0))
+                .isEqualTo(getDefaultTileSpecs().map { it.spec }.joinToString(","))
+        }
+
     private fun getDefaultTileSpecs(): List<TileSpec> {
         return QSHost.getDefaultSpecs(context.resources).map(TileSpec::create)
     }
 
-    private fun storeTilesForUser(specs: String, forUser: Int) {
+    private fun TestScope.storeTilesForUser(specs: String, forUser: Int) {
         secureSettings.putStringForUser(SETTING, specs, forUser)
+        runCurrent()
     }
 
     private fun loadTilesForUser(forUser: Int): String? {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 30cea2d..6689514 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -648,6 +648,22 @@
             assertThat(tiles!![1].spec).isEqualTo(CUSTOM_TILE_SPEC)
         }
 
+    @Test
+    fun tileAddedOnEmptyList_blocked() =
+        testScope.runTest(USER_INFO_0) {
+            val tiles by collectLastValue(underTest.currentTiles)
+            val specs = listOf(TileSpec.create("a"), TileSpec.create("b"))
+            val newTile = TileSpec.create("c")
+
+            underTest.addTile(newTile)
+
+            assertThat(tiles!!.isEmpty()).isTrue()
+
+            tileSpecRepository.setTiles(USER_INFO_0.id, specs)
+
+            assertThat(tiles!!.size).isEqualTo(3)
+        }
+
     private fun QSTile.State.fillIn(state: Int, label: CharSequence, secondaryLabel: CharSequence) {
         this.state = state
         this.label = label
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 6f2d904..71aa7a8 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
@@ -117,7 +117,7 @@
 
     @Test
     public void testQRCodeTileUnavailable() {
-        when(mController.isAbleToOpenCameraApp()).thenReturn(false);
+        when(mController.isAbleToLaunchScannerActivity()).thenReturn(false);
         QSTile.State state = new QSTile.State();
         mTile.handleUpdateState(state, null);
         assertEquals(state.state, Tile.STATE_UNAVAILABLE);
@@ -127,7 +127,7 @@
 
     @Test
     public void testQRCodeTileAvailable() {
-        when(mController.isAbleToOpenCameraApp()).thenReturn(true);
+        when(mController.isAbleToLaunchScannerActivity()).thenReturn(true);
         QSTile.State state = new QSTile.State();
         mTile.handleUpdateState(state, null);
         assertEquals(state.state, Tile.STATE_INACTIVE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index ee42a70..2cb0205 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
@@ -46,21 +46,17 @@
 
     private val underTest =
         QuickSettingsSceneViewModel(
-            lockscreenSceneInteractor =
-                utils.lockScreenSceneInteractor(
+            bouncerInteractor =
+                utils.bouncerInteractor(
                     authenticationInteractor = authenticationInteractor,
-                    bouncerInteractor =
-                        utils.bouncerInteractor(
-                            authenticationInteractor = authenticationInteractor,
-                            sceneInteractor = sceneInteractor,
-                        ),
+                    sceneInteractor = sceneInteractor,
                 ),
         )
 
     @Test
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(true)
             runCurrent()
@@ -73,7 +69,7 @@
     @Test
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
new file mode 100644
index 0000000..53c04cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -0,0 +1,538 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.scene
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
+import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.shared.model.WakefulnessState
+import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
+import com.android.systemui.model.SysUiState
+import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
+import com.android.systemui.scene.domain.startable.SceneContainerStartable
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
+import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
+import com.android.systemui.settings.FakeDisplayTracker
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+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.junit.runners.JUnit4
+
+/**
+ * Integration test cases for the Scene Framework.
+ *
+ * **Principles**
+ * * All test cases here should be done from the perspective of the view-models of the system.
+ * * Focus on happy paths, let smaller unit tests focus on failure cases.
+ * * These are _integration_ tests and, as such, are larger and harder to maintain than unit tests.
+ *   Therefore, when adding or modifying test cases, consider whether what you're testing is better
+ *   covered by a more granular unit test.
+ * * Please reuse the helper methods in this class (for example, [putDeviceToSleep] or
+ *   [emulateUserDrivenTransition]).
+ * * All tests start with the device locked and with a PIN auth method. The class offers useful
+ *   methods like [setAuthMethod], [unlockDevice], [lockDevice], etc. to help you set up a starting
+ *   state that makes more sense for your test case.
+ * * All helper methods in this class make assertions that are meant to make sure that they're only
+ *   being used when the state is as required (e.g. cannot unlock an already unlocked device, cannot
+ *   put to sleep a device that's already asleep, etc.).
+ */
+@SmallTest
+@RunWith(JUnit4::class)
+class SceneFrameworkIntegrationTest : SysuiTestCase() {
+
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+
+    private val sceneContainerConfig = utils.fakeSceneContainerConfig()
+    private val sceneRepository =
+        utils.fakeSceneContainerRepository(
+            containerConfig = sceneContainerConfig,
+        )
+    private val sceneInteractor =
+        utils.sceneInteractor(
+            repository = sceneRepository,
+        )
+
+    private val authenticationRepository = utils.authenticationRepository()
+    private val authenticationInteractor =
+        utils.authenticationInteractor(
+            repository = authenticationRepository,
+            sceneInteractor = sceneInteractor,
+        )
+
+    private val transitionState =
+        MutableStateFlow<ObservableTransitionState>(
+            ObservableTransitionState.Idle(sceneContainerConfig.initialSceneKey)
+        )
+    private val sceneContainerViewModel =
+        SceneContainerViewModel(
+                interactor = sceneInteractor,
+            )
+            .apply { setTransitionState(transitionState) }
+
+    private val bouncerInteractor =
+        utils.bouncerInteractor(
+            authenticationInteractor = authenticationInteractor,
+            sceneInteractor = sceneInteractor,
+        )
+    private val bouncerViewModel =
+        utils.bouncerViewModel(
+            bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
+        )
+
+    private val lockscreenSceneViewModel =
+        LockscreenSceneViewModel(
+            applicationScope = testScope.backgroundScope,
+            authenticationInteractor = authenticationInteractor,
+            bouncerInteractor = bouncerInteractor,
+        )
+
+    private val shadeSceneViewModel =
+        ShadeSceneViewModel(
+            applicationScope = testScope.backgroundScope,
+            authenticationInteractor = authenticationInteractor,
+            bouncerInteractor = bouncerInteractor,
+        )
+
+    private val keyguardRepository = utils.keyguardRepository()
+    private val keyguardInteractor =
+        utils.keyguardInteractor(
+            repository = keyguardRepository,
+        )
+
+    @Before
+    fun setUp() {
+        val featureFlags = FakeFeatureFlags().apply { set(Flags.SCENE_CONTAINER, true) }
+
+        authenticationRepository.setUnlocked(false)
+
+        val displayTracker = FakeDisplayTracker(context)
+        val sysUiState = SysUiState(displayTracker)
+        val startable =
+            SceneContainerStartable(
+                applicationScope = testScope.backgroundScope,
+                sceneInteractor = sceneInteractor,
+                authenticationInteractor = authenticationInteractor,
+                keyguardInteractor = keyguardInteractor,
+                featureFlags = featureFlags,
+                sysUiState = sysUiState,
+                displayId = displayTracker.defaultDisplayId,
+                sceneLogger = mock(),
+            )
+        startable.start()
+
+        assertWithMessage("Initial scene key mismatch!")
+            .that(sceneContainerViewModel.currentScene.value.key)
+            .isEqualTo(sceneContainerConfig.initialSceneKey)
+        assertWithMessage("Initial scene container visibility mismatch!")
+            .that(sceneContainerViewModel.isVisible.value)
+            .isTrue()
+    }
+
+    @Test
+    fun clickLockButtonAndEnterCorrectPin_unlocksDevice() =
+        testScope.runTest {
+            lockscreenSceneViewModel.onLockButtonClicked()
+            assertCurrentScene(SceneKey.Bouncer)
+            emulateUiSceneTransition()
+
+            enterPin()
+            assertCurrentScene(SceneKey.Gone)
+            emulateUiSceneTransition(
+                expectedVisible = false,
+            )
+        }
+
+    @Test
+    fun swipeUpOnLockscreen_enterCorrectPin_unlocksDevice() =
+        testScope.runTest {
+            val upDestinationSceneKey by
+                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Bouncer)
+            emulateUserDrivenTransition(
+                to = upDestinationSceneKey,
+            )
+
+            enterPin()
+            assertCurrentScene(SceneKey.Gone)
+            emulateUiSceneTransition(
+                expectedVisible = false,
+            )
+        }
+
+    @Test
+    fun swipeUpOnLockscreen_withAuthMethodSwipe_dismissesLockscreen() =
+        testScope.runTest {
+            setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+
+            val upDestinationSceneKey by
+                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+            emulateUserDrivenTransition(
+                to = upDestinationSceneKey,
+            )
+        }
+
+    @Test
+    fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+        testScope.runTest {
+            val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+            setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            // Emulate a user swipe to the shade scene.
+            emulateUserDrivenTransition(to = SceneKey.Shade)
+            assertCurrentScene(SceneKey.Shade)
+
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Lockscreen)
+            emulateUserDrivenTransition(
+                to = upDestinationSceneKey,
+            )
+        }
+
+    @Test
+    fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() =
+        testScope.runTest {
+            val upDestinationSceneKey by collectLastValue(shadeSceneViewModel.upDestinationSceneKey)
+            setAuthMethod(DomainLayerAuthenticationMethodModel.Swipe)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            // Emulate a user swipe to dismiss the lockscreen.
+            emulateUserDrivenTransition(to = SceneKey.Gone)
+            assertCurrentScene(SceneKey.Gone)
+
+            // Emulate a user swipe to the shade scene.
+            emulateUserDrivenTransition(to = SceneKey.Shade)
+            assertCurrentScene(SceneKey.Shade)
+
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+            emulateUserDrivenTransition(
+                to = upDestinationSceneKey,
+            )
+        }
+
+    @Test
+    fun withAuthMethodNone_deviceWakeUp_skipsLockscreen() =
+        testScope.runTest {
+            setAuthMethod(AuthenticationMethodModel.None)
+            putDeviceToSleep(instantlyLockDevice = false)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            wakeUpDevice()
+            assertCurrentScene(SceneKey.Gone)
+        }
+
+    @Test
+    fun withAuthMethodSwipe_deviceWakeUp_doesNotSkipLockscreen() =
+        testScope.runTest {
+            setAuthMethod(AuthenticationMethodModel.Swipe)
+            putDeviceToSleep(instantlyLockDevice = false)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            wakeUpDevice()
+            assertCurrentScene(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun deviceGoesToSleep_switchesToLockscreen() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+
+            putDeviceToSleep()
+            assertCurrentScene(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun deviceGoesToSleep_wakeUp_unlock() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+            putDeviceToSleep()
+            assertCurrentScene(SceneKey.Lockscreen)
+            wakeUpDevice()
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+        }
+
+    @Test
+    fun deviceWakesUpWhileUnlocked_dismissesLockscreen() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+            putDeviceToSleep(instantlyLockDevice = false)
+            assertCurrentScene(SceneKey.Lockscreen)
+            wakeUpDevice()
+            assertCurrentScene(SceneKey.Gone)
+        }
+
+    @Test
+    fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() =
+        testScope.runTest {
+            unlockDevice()
+            val upDestinationSceneKey by
+                collectLastValue(lockscreenSceneViewModel.upDestinationSceneKey)
+            assertThat(upDestinationSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun deviceGoesToSleep_withLockTimeout_staysOnLockscreen() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(SceneKey.Gone)
+            putDeviceToSleep(instantlyLockDevice = false)
+            assertCurrentScene(SceneKey.Lockscreen)
+
+            // Pretend like the timeout elapsed and now lock the device.
+            lockDevice()
+            assertCurrentScene(SceneKey.Lockscreen)
+        }
+
+    /**
+     * Asserts that the current scene in the view-model matches what's expected.
+     *
+     * Note that this doesn't assert what the current scene is in the UI.
+     */
+    private fun TestScope.assertCurrentScene(expected: SceneKey) {
+        runCurrent()
+        assertWithMessage("Current scene mismatch!")
+            .that(sceneContainerViewModel.currentScene.value.key)
+            .isEqualTo(expected)
+    }
+
+    /**
+     * Returns the [SceneKey] of the current scene as displayed in the UI.
+     *
+     * This can be different than the value in [SceneContainerViewModel.currentScene], by design, as
+     * the UI must gradually transition between scenes.
+     */
+    private fun getCurrentSceneInUi(): SceneKey {
+        return when (val state = transitionState.value) {
+            is ObservableTransitionState.Idle -> state.scene
+            is ObservableTransitionState.Transition -> state.fromScene
+        }
+    }
+
+    /** Updates the current authentication method and related states in the data layer. */
+    private fun TestScope.setAuthMethod(
+        authMethod: DomainLayerAuthenticationMethodModel,
+    ) {
+        // Set the lockscreen enabled bit _before_ set the auth method as the code picks up on the
+        // lockscreen enabled bit _after_ the auth method is changed and the lockscreen enabled bit
+        // is not an observable that can trigger a new evaluation.
+        authenticationRepository.setLockscreenEnabled(authMethod !is AuthenticationMethodModel.None)
+        authenticationRepository.setAuthenticationMethod(authMethod.toDataLayer())
+        if (!authMethod.isSecure) {
+            // When the auth method is not secure, the device is never considered locked.
+            authenticationRepository.setUnlocked(true)
+        }
+        runCurrent()
+    }
+
+    /**
+     * Emulates a complete transition in the UI from whatever the current scene is in the UI to
+     * whatever the current scene should be, based on the value in
+     * [SceneContainerViewModel.onSceneChanged].
+     *
+     * This should post a series of values into [transitionState] to emulate a gradual scene
+     * transition and culminate with a call to [SceneContainerViewModel.onSceneChanged].
+     *
+     * The method asserts that a transition is actually required. E.g. it will fail if the current
+     * scene in [transitionState] is already caught up with the scene in
+     * [SceneContainerViewModel.currentScene].
+     *
+     * @param expectedVisible Whether [SceneContainerViewModel.isVisible] should be set at the end
+     *   of the UI transition.
+     */
+    private fun TestScope.emulateUiSceneTransition(
+        expectedVisible: Boolean = true,
+    ) {
+        val to = sceneContainerViewModel.currentScene.value
+        val from = getCurrentSceneInUi()
+        assertWithMessage("Cannot transition to ${to.key} as the UI is already on that scene!")
+            .that(to.key)
+            .isNotEqualTo(from)
+
+        // Begin to transition.
+        val progressFlow = MutableStateFlow(0f)
+        transitionState.value =
+            ObservableTransitionState.Transition(
+                fromScene = getCurrentSceneInUi(),
+                toScene = to.key,
+                progress = progressFlow,
+            )
+        runCurrent()
+
+        // Report progress of transition.
+        while (progressFlow.value < 1f) {
+            progressFlow.value += 0.2f
+            runCurrent()
+        }
+
+        // End the transition and report the change.
+        transitionState.value = ObservableTransitionState.Idle(to.key)
+
+        sceneContainerViewModel.onSceneChanged(to)
+        runCurrent()
+
+        assertWithMessage("Visibility mismatch after scene transition from $from to ${to.key}!")
+            .that(sceneContainerViewModel.isVisible.value)
+            .isEqualTo(expectedVisible)
+    }
+
+    /**
+     * Emulates a fire-and-forget user action (a fling or back, not a pointer-tracking swipe) that
+     * causes a scene change to the [to] scene.
+     *
+     * This also includes the emulation of the resulting UI transition that culminates with the UI
+     * catching up with the requested scene change (see [emulateUiSceneTransition]).
+     *
+     * @param to The scene to transition to.
+     */
+    private fun TestScope.emulateUserDrivenTransition(
+        to: SceneKey?,
+    ) {
+        checkNotNull(to)
+
+        sceneInteractor.changeScene(SceneModel(to), "reason")
+        assertThat(sceneContainerViewModel.currentScene.value.key).isEqualTo(to)
+
+        emulateUiSceneTransition(
+            expectedVisible = to != SceneKey.Gone,
+        )
+    }
+
+    /**
+     * Locks the device immediately (without delay).
+     *
+     * Asserts the device to be lockable (e.g. that the current authentication is secure).
+     *
+     * Not to be confused with [putDeviceToSleep], which may also instantly lock the device.
+     */
+    private suspend fun TestScope.lockDevice() {
+        val authMethod = authenticationInteractor.getAuthenticationMethod()
+        assertWithMessage("The authentication method of $authMethod is not secure, cannot lock!")
+            .that(authMethod.isSecure)
+            .isTrue()
+
+        authenticationRepository.setUnlocked(false)
+        runCurrent()
+    }
+
+    /** Unlocks the device by entering the correct PIN. Ends up in the Gone scene. */
+    private fun TestScope.unlockDevice() {
+        assertWithMessage("Cannot unlock a device that's already unlocked!")
+            .that(authenticationInteractor.isUnlocked.value)
+            .isFalse()
+
+        lockscreenSceneViewModel.onLockButtonClicked()
+        runCurrent()
+        emulateUiSceneTransition()
+
+        enterPin()
+        emulateUiSceneTransition(
+            expectedVisible = false,
+        )
+    }
+
+    /**
+     * Enters the correct PIN in the bouncer UI.
+     *
+     * Asserts that the current scene is [SceneKey.Bouncer] and that the current bouncer UI is a PIN
+     * before proceeding.
+     *
+     * Does not assert that the device is locked or unlocked.
+     */
+    private fun TestScope.enterPin() {
+        assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
+            .that(getCurrentSceneInUi())
+            .isEqualTo(SceneKey.Bouncer)
+        val authMethodViewModel by collectLastValue(bouncerViewModel.authMethod)
+        assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
+            .that(authMethodViewModel)
+            .isInstanceOf(PinBouncerViewModel::class.java)
+
+        val pinBouncerViewModel = authMethodViewModel as PinBouncerViewModel
+        FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+            pinBouncerViewModel.onPinButtonClicked(digit)
+        }
+        pinBouncerViewModel.onAuthenticateButtonClicked()
+        runCurrent()
+    }
+
+    /** Changes device wakefulness state from asleep to awake, going through intermediary states. */
+    private fun TestScope.wakeUpDevice() {
+        val wakefulnessModel = keyguardRepository.wakefulness.value
+        assertWithMessage("Cannot wake up device as it's already awake!")
+            .that(wakefulnessModel.isStartingToWakeOrAwake())
+            .isFalse()
+
+        keyguardRepository.setWakefulnessModel(
+            wakefulnessModel.copy(state = WakefulnessState.STARTING_TO_WAKE)
+        )
+        runCurrent()
+        keyguardRepository.setWakefulnessModel(
+            wakefulnessModel.copy(state = WakefulnessState.AWAKE)
+        )
+        runCurrent()
+    }
+
+    /** Changes device wakefulness state from awake to asleep, going through intermediary states. */
+    private suspend fun TestScope.putDeviceToSleep(
+        instantlyLockDevice: Boolean = true,
+    ) {
+        val wakefulnessModel = keyguardRepository.wakefulness.value
+        assertWithMessage("Cannot put device to sleep as it's already asleep!")
+            .that(wakefulnessModel.isStartingToWakeOrAwake())
+            .isTrue()
+
+        keyguardRepository.setWakefulnessModel(
+            wakefulnessModel.copy(state = WakefulnessState.STARTING_TO_SLEEP)
+        )
+        runCurrent()
+        keyguardRepository.setWakefulnessModel(
+            wakefulnessModel.copy(state = WakefulnessState.ASLEEP)
+        )
+        runCurrent()
+
+        if (instantlyLockDevice) {
+            lockDevice()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
index 56e3e96..181f8a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/data/repository/SceneContainerRepositoryTest.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.scene.shared.model.SceneTransitionModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,6 +38,7 @@
 class SceneContainerRepositoryTest : SysuiTestCase() {
 
     private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
 
     @Test
     fun allSceneKeys() {
@@ -56,97 +56,82 @@
     }
 
     @Test
-    fun currentScene() = runTest {
-        val underTest = utils.fakeSceneContainerRepository()
-        val currentScene by collectLastValue(underTest.currentScene)
-        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+    fun desiredScene() =
+        testScope.runTest {
+            val underTest = utils.fakeSceneContainerRepository()
+            val currentScene by collectLastValue(underTest.desiredScene)
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-        underTest.setCurrentScene(SceneModel(SceneKey.Shade))
-        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
-    }
+            underTest.setDesiredScene(SceneModel(SceneKey.Shade))
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
+        }
 
     @Test(expected = IllegalStateException::class)
-    fun setCurrentScene_noSuchSceneInContainer_throws() {
+    fun setDesiredScene_noSuchSceneInContainer_throws() {
         val underTest =
             utils.fakeSceneContainerRepository(
                 utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
             )
-        underTest.setCurrentScene(SceneModel(SceneKey.Shade))
+        underTest.setDesiredScene(SceneModel(SceneKey.Shade))
     }
 
     @Test
-    fun isVisible() = runTest {
-        val underTest = utils.fakeSceneContainerRepository()
-        val isVisible by collectLastValue(underTest.isVisible)
-        assertThat(isVisible).isTrue()
+    fun isVisible() =
+        testScope.runTest {
+            val underTest = utils.fakeSceneContainerRepository()
+            val isVisible by collectLastValue(underTest.isVisible)
+            assertThat(isVisible).isTrue()
 
-        underTest.setVisible(false)
-        assertThat(isVisible).isFalse()
+            underTest.setVisible(false)
+            assertThat(isVisible).isFalse()
 
-        underTest.setVisible(true)
-        assertThat(isVisible).isTrue()
-    }
+            underTest.setVisible(true)
+            assertThat(isVisible).isTrue()
+        }
 
     @Test
-    fun transitionProgress() = runTest {
-        val underTest = utils.fakeSceneContainerRepository()
-        val sceneTransitionProgress by collectLastValue(underTest.transitionProgress)
-        assertThat(sceneTransitionProgress).isEqualTo(1f)
+    fun transitionState_defaultsToIdle() =
+        testScope.runTest {
+            val underTest = utils.fakeSceneContainerRepository()
+            val transitionState by collectLastValue(underTest.transitionState)
 
-        val transitionState =
-            MutableStateFlow<ObservableTransitionState>(
-                ObservableTransitionState.Idle(SceneKey.Lockscreen)
-            )
-        underTest.setTransitionState(transitionState)
-        assertThat(sceneTransitionProgress).isEqualTo(1f)
-
-        val progress = MutableStateFlow(1f)
-        transitionState.value =
-            ObservableTransitionState.Transition(
-                fromScene = SceneKey.Lockscreen,
-                toScene = SceneKey.Shade,
-                progress = progress,
-            )
-        assertThat(sceneTransitionProgress).isEqualTo(1f)
-
-        progress.value = 0.1f
-        assertThat(sceneTransitionProgress).isEqualTo(0.1f)
-
-        progress.value = 0.9f
-        assertThat(sceneTransitionProgress).isEqualTo(0.9f)
-
-        underTest.setTransitionState(null)
-        assertThat(sceneTransitionProgress).isEqualTo(1f)
-    }
+            assertThat(transitionState)
+                .isEqualTo(
+                    ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+                )
+        }
 
     @Test
-    fun setSceneTransition() = runTest {
-        val underTest = utils.fakeSceneContainerRepository()
-        val sceneTransition by collectLastValue(underTest.transitions)
-        assertThat(sceneTransition).isNull()
+    fun transitionState_reflectsUpdates() =
+        testScope.runTest {
+            val underTest = utils.fakeSceneContainerRepository()
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(SceneKey.Lockscreen)
+                )
+            underTest.setTransitionState(transitionState)
+            val reflectedTransitionState by collectLastValue(underTest.transitionState)
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
 
-        underTest.setSceneTransition(SceneKey.Lockscreen, SceneKey.QuickSettings)
-        assertThat(sceneTransition)
-            .isEqualTo(
-                SceneTransitionModel(from = SceneKey.Lockscreen, to = SceneKey.QuickSettings)
-            )
-    }
+            val progress = MutableStateFlow(1f)
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = SceneKey.Lockscreen,
+                    toScene = SceneKey.Shade,
+                    progress = progress,
+                )
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
 
-    @Test(expected = IllegalStateException::class)
-    fun setSceneTransition_noFromSceneInContainer_throws() {
-        val underTest =
-            utils.fakeSceneContainerRepository(
-                utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
-            )
-        underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen)
-    }
+            progress.value = 0.1f
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
 
-    @Test(expected = IllegalStateException::class)
-    fun setSceneTransition_noToSceneInContainer_throws() {
-        val underTest =
-            utils.fakeSceneContainerRepository(
-                utils.fakeSceneContainerConfig(listOf(SceneKey.QuickSettings, SceneKey.Lockscreen)),
-            )
-        underTest.setSceneTransition(SceneKey.Shade, SceneKey.Lockscreen)
-    }
+            progress.value = 0.9f
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+            underTest.setTransitionState(null)
+            assertThat(reflectedTransitionState)
+                .isEqualTo(
+                    ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
+                )
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
index 4facc7a..16cc924 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
-import com.android.systemui.scene.shared.model.SceneTransitionModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -39,6 +38,7 @@
 class SceneInteractorTest : SysuiTestCase() {
 
     private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val repository = utils.fakeSceneContainerRepository()
     private val underTest = utils.sceneInteractor(repository = repository)
 
@@ -48,77 +48,115 @@
     }
 
     @Test
-    fun currentScene() = runTest {
-        val currentScene by collectLastValue(underTest.currentScene)
-        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+    fun changeScene() =
+        testScope.runTest {
+            val desiredScene by collectLastValue(underTest.desiredScene)
+            assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-        underTest.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
-        assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
-    }
+            underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
+            assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Shade))
+        }
 
     @Test
-    fun sceneTransitionProgress() = runTest {
-        val transitionProgress by collectLastValue(underTest.transitionProgress)
-        assertThat(transitionProgress).isEqualTo(1f)
+    fun onSceneChanged() =
+        testScope.runTest {
+            val desiredScene by collectLastValue(underTest.desiredScene)
+            assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-        val progress = MutableStateFlow(0.55f)
-        repository.setTransitionState(
-            MutableStateFlow(
+            underTest.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
+            assertThat(desiredScene).isEqualTo(SceneModel(SceneKey.Shade))
+        }
+
+    @Test
+    fun transitionState() =
+        testScope.runTest {
+            val underTest = utils.fakeSceneContainerRepository()
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(SceneKey.Lockscreen)
+                )
+            underTest.setTransitionState(transitionState)
+            val reflectedTransitionState by collectLastValue(underTest.transitionState)
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+            val progress = MutableStateFlow(1f)
+            transitionState.value =
                 ObservableTransitionState.Transition(
                     fromScene = SceneKey.Lockscreen,
                     toScene = SceneKey.Shade,
                     progress = progress,
-                ),
-            )
-        )
-        assertThat(transitionProgress).isEqualTo(0.55f)
-    }
-
-    @Test
-    fun isVisible() = runTest {
-        val isVisible by collectLastValue(underTest.isVisible)
-        assertThat(isVisible).isTrue()
-
-        underTest.setVisible(false, "reason")
-        assertThat(isVisible).isFalse()
-
-        underTest.setVisible(true, "reason")
-        assertThat(isVisible).isTrue()
-    }
-
-    @Test
-    fun sceneTransitions() = runTest {
-        val transitions by collectLastValue(underTest.transitions)
-        assertThat(transitions).isNull()
-
-        val initialSceneKey = underTest.currentScene.value.key
-        underTest.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
-        assertThat(transitions)
-            .isEqualTo(
-                SceneTransitionModel(
-                    from = initialSceneKey,
-                    to = SceneKey.Shade,
                 )
-            )
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
 
-        underTest.setCurrentScene(SceneModel(SceneKey.QuickSettings), "reason")
-        assertThat(transitions)
-            .isEqualTo(
-                SceneTransitionModel(
-                    from = SceneKey.Shade,
-                    to = SceneKey.QuickSettings,
+            progress.value = 0.1f
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+            progress.value = 0.9f
+            assertThat(reflectedTransitionState).isEqualTo(transitionState.value)
+
+            underTest.setTransitionState(null)
+            assertThat(reflectedTransitionState)
+                .isEqualTo(
+                    ObservableTransitionState.Idle(utils.fakeSceneContainerConfig().initialSceneKey)
                 )
-            )
-    }
-
-    @Test
-    fun remoteUserInput() = runTest {
-        val remoteUserInput by collectLastValue(underTest.remoteUserInput)
-        assertThat(remoteUserInput).isNull()
-
-        for (input in SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE) {
-            underTest.onRemoteUserInput(input)
-            assertThat(remoteUserInput).isEqualTo(input)
         }
-    }
+
+    @Test
+    fun transitioningTo() =
+        testScope.runTest {
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(underTest.desiredScene.value.key)
+                )
+            underTest.setTransitionState(transitionState)
+
+            val transitionTo by collectLastValue(underTest.transitioningTo)
+            assertThat(transitionTo).isNull()
+
+            underTest.changeScene(SceneModel(SceneKey.Shade), "reason")
+            assertThat(transitionTo).isNull()
+
+            val progress = MutableStateFlow(0f)
+            transitionState.value =
+                ObservableTransitionState.Transition(
+                    fromScene = underTest.desiredScene.value.key,
+                    toScene = SceneKey.Shade,
+                    progress = progress,
+                )
+            assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+            progress.value = 0.5f
+            assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+            progress.value = 1f
+            assertThat(transitionTo).isEqualTo(SceneKey.Shade)
+
+            transitionState.value = ObservableTransitionState.Idle(SceneKey.Shade)
+            assertThat(transitionTo).isNull()
+        }
+
+    @Test
+    fun isVisible() =
+        testScope.runTest {
+            val isVisible by collectLastValue(underTest.isVisible)
+            assertThat(isVisible).isTrue()
+
+            underTest.setVisible(false, "reason")
+            assertThat(isVisible).isFalse()
+
+            underTest.setVisible(true, "reason")
+            assertThat(isVisible).isTrue()
+        }
+
+    @Test
+    fun remoteUserInput() =
+        testScope.runTest {
+            val remoteUserInput by collectLastValue(underTest.remoteUserInput)
+            assertThat(remoteUserInput).isNull()
+
+            for (input in SceneTestUtils.REMOTE_INPUT_DOWN_GESTURE) {
+                underTest.onRemoteUserInput(input)
+                assertThat(remoteUserInput).isEqualTo(input)
+            }
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 6be19b9..951cadd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -21,6 +21,7 @@
 import android.view.Display
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
@@ -28,15 +29,18 @@
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import com.android.systemui.model.SysUiState
 import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.SceneTestUtils.Companion.toDataLayer
+import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 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.junit.runners.JUnit4
@@ -76,61 +80,53 @@
             sceneLogger = mock(),
         )
 
-    @Before
-    fun setUp() {
-        prepareState()
-    }
-
     @Test
-    fun hydrateVisibility_featureEnabled() =
+    fun hydrateVisibility() =
         testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+            val currentDesiredSceneKey by
+                collectLastValue(sceneInteractor.desiredScene.map { it.key })
             val isVisible by collectLastValue(sceneInteractor.isVisible)
-            prepareState(
-                isFeatureEnabled = true,
-                isDeviceUnlocked = true,
-                initialSceneKey = SceneKey.Gone,
-            )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+            val transitionStateFlow =
+                prepareState(
+                    isDeviceUnlocked = true,
+                    initialSceneKey = SceneKey.Gone,
+                )
+            assertThat(currentDesiredSceneKey).isEqualTo(SceneKey.Gone)
             assertThat(isVisible).isTrue()
 
             underTest.start()
-
             assertThat(isVisible).isFalse()
 
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
+            sceneInteractor.changeScene(SceneModel(SceneKey.Shade), "reason")
+            transitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    fromScene = SceneKey.Gone,
+                    toScene = SceneKey.Shade,
+                    progress = flowOf(0.5f),
+                )
             assertThat(isVisible).isTrue()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Shade), "reason")
+            transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Shade)
+            assertThat(isVisible).isTrue()
+
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+            transitionStateFlow.value =
+                ObservableTransitionState.Transition(
+                    fromScene = SceneKey.Shade,
+                    toScene = SceneKey.Gone,
+                    progress = flowOf(0.5f),
+                )
+            assertThat(isVisible).isTrue()
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
+            transitionStateFlow.value = ObservableTransitionState.Idle(SceneKey.Gone)
+            assertThat(isVisible).isFalse()
         }
 
     @Test
-    fun hydrateVisibility_featureDisabled() =
+    fun switchToLockscreenWhenDeviceLocks() =
         testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
-            val isVisible by collectLastValue(sceneInteractor.isVisible)
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = false,
-                isDeviceUnlocked = true,
-                initialSceneKey = SceneKey.Lockscreen,
-            )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
-            assertThat(isVisible).isTrue()
-
-            underTest.start()
-            assertThat(isVisible).isTrue()
-
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Gone), "reason")
-            assertThat(isVisible).isTrue()
-
-            sceneInteractor.setCurrentScene(SceneModel(SceneKey.Shade), "reason")
-            assertThat(isVisible).isTrue()
-        }
-
-    @Test
-    fun switchToLockscreenWhenDeviceLocks_featureEnabled() =
-        testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
-            prepareState(
-                isFeatureEnabled = true,
                 isDeviceUnlocked = true,
                 initialSceneKey = SceneKey.Gone,
             )
@@ -143,28 +139,10 @@
         }
 
     @Test
-    fun switchToLockscreenWhenDeviceLocks_featureDisabled() =
+    fun switchFromBouncerToGoneWhenDeviceUnlocked() =
         testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = false,
-                isDeviceUnlocked = false,
-                initialSceneKey = SceneKey.Gone,
-            )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
-            underTest.start()
-
-            authenticationRepository.setUnlocked(false)
-
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
-        }
-
-    @Test
-    fun switchFromBouncerToGoneWhenDeviceUnlocked_featureEnabled() =
-        testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
-            prepareState(
-                isFeatureEnabled = true,
                 isDeviceUnlocked = false,
                 initialSceneKey = SceneKey.Bouncer,
             )
@@ -177,28 +155,10 @@
         }
 
     @Test
-    fun switchFromBouncerToGoneWhenDeviceUnlocked_featureDisabled() =
+    fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn() =
         testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = false,
-                isDeviceUnlocked = false,
-                initialSceneKey = SceneKey.Bouncer,
-            )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
-            underTest.start()
-
-            authenticationRepository.setUnlocked(true)
-
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Bouncer)
-        }
-
-    @Test
-    fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOn() =
-        testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
-            prepareState(
-                isFeatureEnabled = true,
                 isBypassEnabled = true,
                 initialSceneKey = SceneKey.Lockscreen,
             )
@@ -211,11 +171,10 @@
         }
 
     @Test
-    fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOn_bypassOff() =
+    fun stayOnLockscreenWhenDeviceUnlocksWithBypassOff() =
         testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = true,
                 isBypassEnabled = false,
                 initialSceneKey = SceneKey.Lockscreen,
             )
@@ -228,93 +187,25 @@
         }
 
     @Test
-    fun switchFromLockscreenToGoneWhenDeviceUnlocksWithBypassOn_featureOff_bypassOn() =
+    fun switchToLockscreenWhenDeviceSleepsLocked() =
         testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
             prepareState(
-                isFeatureEnabled = false,
-                isBypassEnabled = true,
-                initialSceneKey = SceneKey.Lockscreen,
-            )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
-            underTest.start()
-
-            authenticationRepository.setUnlocked(true)
-
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
-        }
-
-    @Test
-    fun switchToGoneWhenDeviceSleepsUnlocked_featureEnabled() =
-        testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
-            prepareState(
-                isFeatureEnabled = true,
-                isDeviceUnlocked = true,
-                initialSceneKey = SceneKey.Shade,
-            )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
-            underTest.start()
-
-            keyguardRepository.setWakefulnessModel(ASLEEP)
-
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
-        }
-
-    @Test
-    fun switchToGoneWhenDeviceSleepsUnlocked_featureDisabled() =
-        testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
-            prepareState(
-                isFeatureEnabled = false,
-                isDeviceUnlocked = true,
-                initialSceneKey = SceneKey.Shade,
-            )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
-            underTest.start()
-
-            keyguardRepository.setWakefulnessModel(ASLEEP)
-
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
-        }
-
-    @Test
-    fun switchToLockscreenWhenDeviceSleepsLocked_featureEnabled() =
-        testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
-            prepareState(
-                isFeatureEnabled = true,
                 isDeviceUnlocked = false,
                 initialSceneKey = SceneKey.Shade,
             )
             assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
             underTest.start()
 
-            keyguardRepository.setWakefulnessModel(ASLEEP)
+            keyguardRepository.setWakefulnessModel(STARTING_TO_SLEEP)
 
             assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
         }
 
     @Test
-    fun switchToLockscreenWhenDeviceSleepsLocked_featureDisabled() =
-        testScope.runTest {
-            val currentSceneKey by collectLastValue(sceneInteractor.currentScene.map { it.key })
-            prepareState(
-                isFeatureEnabled = false,
-                isDeviceUnlocked = false,
-                initialSceneKey = SceneKey.Shade,
-            )
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
-            underTest.start()
-
-            keyguardRepository.setWakefulnessModel(ASLEEP)
-
-            assertThat(currentSceneKey).isEqualTo(SceneKey.Shade)
-        }
-
-    @Test
     fun hydrateSystemUiState() =
         testScope.runTest {
+            val transitionStateFlow = prepareState()
             underTest.start()
             runCurrent()
             clearInvocations(sysUiState)
@@ -327,29 +218,125 @@
                     SceneKey.QuickSettings,
                 )
                 .forEachIndexed { index, sceneKey ->
-                    sceneInteractor.setCurrentScene(SceneModel(sceneKey), "reason")
+                    sceneInteractor.changeScene(SceneModel(sceneKey), "reason")
                     runCurrent()
+                    verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY)
 
+                    sceneInteractor.onSceneChanged(SceneModel(sceneKey), "reason")
+                    runCurrent()
+                    verify(sysUiState, times(index)).commitUpdate(Display.DEFAULT_DISPLAY)
+
+                    transitionStateFlow.value = ObservableTransitionState.Idle(sceneKey)
+                    runCurrent()
                     verify(sysUiState, times(index + 1)).commitUpdate(Display.DEFAULT_DISPLAY)
                 }
         }
 
+    @Test
+    fun switchToGoneWhenDeviceStartsToWakeUp_authMethodNone() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.None,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
+    fun stayOnLockscreenWhenDeviceStartsToWakeUp_authMethodSwipe() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Swipe,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun doesNotSwitchToGoneWhenDeviceStartsToWakeUp_authMethodSecure() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Pin,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun switchToGoneWhenDeviceStartsToWakeUp_authMethodSecure_deviceUnlocked() =
+        testScope.runTest {
+            val currentSceneKey by collectLastValue(sceneInteractor.desiredScene.map { it.key })
+            prepareState(
+                initialSceneKey = SceneKey.Lockscreen,
+                authenticationMethod = AuthenticationMethodModel.Pin,
+                isDeviceUnlocked = false,
+            )
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Lockscreen)
+            underTest.start()
+
+            authenticationRepository.setUnlocked(true)
+            runCurrent()
+            keyguardRepository.setWakefulnessModel(STARTING_TO_WAKE)
+
+            assertThat(currentSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
     private fun prepareState(
-        isFeatureEnabled: Boolean = true,
         isDeviceUnlocked: Boolean = false,
         isBypassEnabled: Boolean = false,
         initialSceneKey: SceneKey? = null,
-    ) {
-        featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled)
+        authenticationMethod: AuthenticationMethodModel? = null,
+    ): MutableStateFlow<ObservableTransitionState> {
+        featureFlags.set(Flags.SCENE_CONTAINER, true)
         authenticationRepository.setUnlocked(isDeviceUnlocked)
         keyguardRepository.setBypassEnabled(isBypassEnabled)
-        initialSceneKey?.let { sceneInteractor.setCurrentScene(SceneModel(it), "reason") }
+        val transitionStateFlow =
+            MutableStateFlow<ObservableTransitionState>(
+                ObservableTransitionState.Idle(SceneKey.Lockscreen)
+            )
+        sceneInteractor.setTransitionState(transitionStateFlow)
+        initialSceneKey?.let {
+            transitionStateFlow.value = ObservableTransitionState.Idle(it)
+            sceneInteractor.changeScene(SceneModel(it), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(it), "reason")
+        }
+        authenticationMethod?.let {
+            authenticationRepository.setAuthenticationMethod(authenticationMethod.toDataLayer())
+            authenticationRepository.setLockscreenEnabled(
+                authenticationMethod != AuthenticationMethodModel.None
+            )
+        }
+        return transitionStateFlow
     }
 
     companion object {
-        private val ASLEEP =
+        private val STARTING_TO_SLEEP =
             WakefulnessModel(
-                state = WakefulnessState.ASLEEP,
+                state = WakefulnessState.STARTING_TO_SLEEP,
+                lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                lastSleepReason = WakeSleepReason.POWER_BUTTON
+            )
+        private val STARTING_TO_WAKE =
+            WakefulnessModel(
+                state = WakefulnessState.STARTING_TO_WAKE,
                 lastWakeReason = WakeSleepReason.POWER_BUTTON,
                 lastSleepReason = WakeSleepReason.POWER_BUTTON
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index 9f3b12b..da6c4269 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -69,7 +69,8 @@
         val currentScene by collectLastValue(underTest.currentScene)
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
 
-        underTest.setCurrentScene(SceneModel(SceneKey.Shade))
+        underTest.onSceneChanged(SceneModel(SceneKey.Shade))
+
         assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Shade))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
index 07feedf..ad6909d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordPermissionDialogTest.kt
@@ -126,6 +126,7 @@
 
     private fun onSpinnerItemSelected(position: Int) {
         val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner)
-        spinner.onItemSelectedListener.onItemSelected(spinner, mock(), position, /* id= */ 0)
+        checkNotNull(spinner.onItemSelectedListener)
+            .onItemSelected(spinner, mock(), position, /* id= */ 0)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
index 77e4d89..2d3ee0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionIntentCreatorTest.kt
@@ -34,7 +34,7 @@
 class ActionIntentCreatorTest : SysuiTestCase() {
 
     @Test
-    fun testCreateShareIntent() {
+    fun testCreateShare() {
         val uri = Uri.parse("content://fake")
 
         val output = ActionIntentCreator.createShare(uri)
@@ -59,7 +59,17 @@
     }
 
     @Test
-    fun testCreateShareIntentWithSubject() {
+    fun testCreateShare_embeddedUserIdRemoved() {
+        val uri = Uri.parse("content://555@fake")
+
+        val output = ActionIntentCreator.createShare(uri)
+
+        assertThat(output.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java))
+            .hasData(Uri.parse("content://fake"))
+    }
+
+    @Test
+    fun testCreateShareWithSubject() {
         val uri = Uri.parse("content://fake")
         val subject = "Example subject"
 
@@ -83,7 +93,7 @@
     }
 
     @Test
-    fun testCreateShareIntentWithExtraText() {
+    fun testCreateShareWithText() {
         val uri = Uri.parse("content://fake")
         val extraText = "Extra text"
 
@@ -107,13 +117,13 @@
     }
 
     @Test
-    fun testCreateEditIntent() {
+    fun testCreateEdit() {
         val uri = Uri.parse("content://fake")
         val context = mock<Context>()
 
         whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
 
-        val output = ActionIntentCreator.createEditIntent(uri, context)
+        val output = ActionIntentCreator.createEdit(uri, context)
 
         assertThat(output).hasAction(Intent.ACTION_EDIT)
         assertThat(output).hasData(uri)
@@ -129,7 +139,18 @@
     }
 
     @Test
-    fun testCreateEditIntent_withEditor() {
+    fun testCreateEdit_embeddedUserIdRemoved() {
+        val uri = Uri.parse("content://555@fake")
+        val context = mock<Context>()
+        whenever(context.getString(eq(R.string.config_screenshotEditor))).thenReturn("")
+
+        val output = ActionIntentCreator.createEdit(uri, context)
+
+        assertThat(output).hasData(Uri.parse("content://fake"))
+    }
+
+    @Test
+    fun testCreateEdit_withEditor() {
         val uri = Uri.parse("content://fake")
         val context = mock<Context>()
         val component = ComponentName("com.android.foo", "com.android.foo.Something")
@@ -137,7 +158,7 @@
         whenever(context.getString(eq(R.string.config_screenshotEditor)))
             .thenReturn(component.flattenToString())
 
-        val output = ActionIntentCreator.createEditIntent(uri, context)
+        val output = ActionIntentCreator.createEdit(uri, context)
 
         assertThat(output).hasComponent(component)
     }
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 c3540cf..981e44b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -424,6 +424,10 @@
         when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
         when(mView.findViewById(R.id.keyguard_status_view))
                 .thenReturn(mock(KeyguardStatusView.class));
+        View rootView = mock(View.class);
+        when(mView.getRootView()).thenReturn(rootView);
+        when(rootView.findViewById(R.id.keyguard_status_view))
+                .thenReturn(mock(KeyguardStatusView.class));
         mNotificationContainerParent = new NotificationsQuickSettingsContainer(getContext(), null);
         mNotificationContainerParent.addView(keyguardStatusView);
         mNotificationContainerParent.onFinishInflate();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 7b3e89d..1edeeff 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -95,7 +95,7 @@
     @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
     @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
-    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var ambientState: AmbientState
     @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
     @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
@@ -150,44 +150,45 @@
         testScope = TestScope()
         underTest =
             NotificationShadeWindowViewController(
-                lockscreenShadeTransitionController,
-                FalsingCollectorFake(),
-                sysuiStatusBarStateController,
-                dockManager,
-                notificationShadeDepthController,
-                view,
-                notificationPanelViewController,
-                ShadeExpansionStateManager(),
-                stackScrollLayoutController,
-                statusBarKeyguardViewManager,
-                statusBarWindowStateController,
-                lockIconViewController,
-                centralSurfaces,
-                backActionInteractor,
-                powerInteractor,
-                notificationShadeWindowController,
-                unfoldTransitionProgressProvider,
-                keyguardUnlockAnimationController,
-                notificationInsetsController,
-                ambientState,
-                pulsingGestureListener,
-                mLockscreenHostedDreamGestureListener,
-                keyguardBouncerViewModel,
-                keyguardBouncerComponentFactory,
-                mock(KeyguardMessageAreaController.Factory::class.java),
-                keyguardTransitionInteractor,
-                primaryBouncerToGoneTransitionViewModel,
-                notificationExpansionRepository,
-                featureFlags,
-                FakeSystemClock(),
-                BouncerMessageInteractor(
-                    FakeBouncerMessageRepository(),
-                    mock(BouncerMessageFactory::class.java),
-                    FakeUserRepository(),
-                    CountDownTimerUtil(),
-                    featureFlags
-                ),
-                BouncerLogger(logcatLogBuffer("BouncerLog"))
+                    lockscreenShadeTransitionController,
+                    FalsingCollectorFake(),
+                    sysuiStatusBarStateController,
+                    dockManager,
+                    notificationShadeDepthController,
+                    view,
+                    notificationPanelViewController,
+                    ShadeExpansionStateManager(),
+                    stackScrollLayoutController,
+                    statusBarKeyguardViewManager,
+                    statusBarWindowStateController,
+                    lockIconViewController,
+                    centralSurfaces,
+                    backActionInteractor,
+                    powerInteractor,
+                    notificationShadeWindowController,
+                    unfoldTransitionProgressProvider,
+                    keyguardUnlockAnimationController,
+                    notificationInsetsController,
+                    ambientState,
+                    shadeLogger,
+                    pulsingGestureListener,
+                    mLockscreenHostedDreamGestureListener,
+                    keyguardBouncerViewModel,
+                    keyguardBouncerComponentFactory,
+                    mock(KeyguardMessageAreaController.Factory::class.java),
+                    keyguardTransitionInteractor,
+                    primaryBouncerToGoneTransitionViewModel,
+                    notificationExpansionRepository,
+                    featureFlags,
+                    FakeSystemClock(),
+                    BouncerMessageInteractor(
+                        FakeBouncerMessageRepository(),
+                        mock(BouncerMessageFactory::class.java),
+                        FakeUserRepository(),
+                        CountDownTimerUtil(),
+                        featureFlags
+                    ),
+                    BouncerLogger(logcatLogBuffer("BouncerLog"))
             )
         underTest.setupExpandedStatusBar()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
index 5c3ce71..829184c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -105,6 +105,7 @@
     @Mock private lateinit var lockIconViewController: LockIconViewController
     @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
     @Mock private lateinit var ambientState: AmbientState
+    @Mock private lateinit var shadeLogger: ShadeLogger
     @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
     @Mock
     private lateinit var mLockscreenHostedDreamGestureListener: LockscreenHostedDreamGestureListener
@@ -179,6 +180,7 @@
                 keyguardUnlockAnimationController,
                 notificationInsetsController,
                 ambientState,
+                shadeLogger,
                 pulsingGestureListener,
                 mLockscreenHostedDreamGestureListener,
                 keyguardBouncerViewModel,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
index 112a09b..577b6e0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerLegacyTest.kt
@@ -584,7 +584,7 @@
     private fun emptyInsets() = mock(WindowInsets::class.java)
 
     private fun WindowInsets.withCutout(): WindowInsets {
-        whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+        whenever(checkNotNull(displayCutout).safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
         return this
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
index 8d3c4b2..405199e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationsQSContainerControllerTest.kt
@@ -567,7 +567,7 @@
     private fun emptyInsets() = mock(WindowInsets::class.java)
 
     private fun WindowInsets.withCutout(): WindowInsets {
-        whenever(displayCutout.safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
+        whenever(checkNotNull(displayCutout).safeInsetBottom).thenReturn(CUTOUT_HEIGHT)
         return this
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
index 52e0c9c..6a14a00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -22,6 +22,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.assist.AssistManager
+import com.android.systemui.log.LogBuffer
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.NotificationShadeWindowController
@@ -59,6 +60,7 @@
     @Mock private lateinit var shadeViewController: ShadeViewController
     @Mock private lateinit var nswvc: NotificationShadeWindowViewController
     @Mock private lateinit var display: Display
+    @Mock private lateinit var touchLog: LogBuffer
 
     private lateinit var shadeController: ShadeControllerImpl
 
@@ -71,6 +73,7 @@
             ShadeControllerImpl(
                 commandQueue,
                 FakeExecutor(FakeSystemClock()),
+                touchLog,
                 keyguardStateController,
                 statusBarStateController,
                 statusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
index 2501f85..8f8b840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt
@@ -138,19 +138,19 @@
 
     @Before
     fun setup() {
-        whenever<Clock>(view.findViewById(R.id.clock)).thenReturn(clock)
+        whenever<Clock>(view.requireViewById(R.id.clock)).thenReturn(clock)
         whenever(clock.context).thenReturn(mockedContext)
 
-        whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
+        whenever<TextView>(view.requireViewById(R.id.date)).thenReturn(date)
         whenever(date.context).thenReturn(mockedContext)
 
-        whenever<ShadeCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
+        whenever<ShadeCarrierGroup>(view.requireViewById(R.id.carrier_group)).thenReturn(carrierGroup)
 
-        whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
+        whenever<BatteryMeterView>(view.requireViewById(R.id.batteryRemainingIcon))
             .thenReturn(batteryMeterView)
 
-        whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
-        whenever<View>(view.findViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons)
+        whenever<StatusIconContainer>(view.requireViewById(R.id.statusIcons)).thenReturn(statusIcons)
+        whenever<View>(view.requireViewById(R.id.shade_header_system_icons)).thenReturn(systemIcons)
 
         viewContext = Mockito.spy(context)
         whenever(view.context).thenReturn(viewContext)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index 8739b28..69b9525 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -18,7 +18,7 @@
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.android.systemui.scene.shared.model.SceneKey
@@ -42,19 +42,17 @@
     private val authenticationInteractor =
         utils.authenticationInteractor(
             repository = utils.authenticationRepository(),
+            sceneInteractor = sceneInteractor,
         )
 
     private val underTest =
         ShadeSceneViewModel(
             applicationScope = testScope.backgroundScope,
-            lockscreenSceneInteractor =
-                utils.lockScreenSceneInteractor(
+            authenticationInteractor = authenticationInteractor,
+            bouncerInteractor =
+                utils.bouncerInteractor(
                     authenticationInteractor = authenticationInteractor,
-                    bouncerInteractor =
-                        utils.bouncerInteractor(
-                            authenticationInteractor = authenticationInteractor,
-                            sceneInteractor = sceneInteractor,
-                        ),
+                    sceneInteractor = sceneInteractor,
                 ),
         )
 
@@ -79,9 +77,33 @@
         }
 
     @Test
+    fun upTransitionSceneKey_authMethodSwipe_lockscreenNotDismissed_goesToLockscreen() =
+        testScope.runTest {
+            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
+
+            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Lockscreen)
+        }
+
+    @Test
+    fun upTransitionSceneKey_authMethodSwipe_lockscreenDismissed_goesToGone() =
+        testScope.runTest {
+            val upTransitionSceneKey by collectLastValue(underTest.upDestinationSceneKey)
+            utils.authenticationRepository.setLockscreenEnabled(true)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Gone), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Gone), "reason")
+
+            assertThat(upTransitionSceneKey).isEqualTo(SceneKey.Gone)
+        }
+
+    @Test
     fun onContentClicked_deviceUnlocked_switchesToGone() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(true)
             runCurrent()
@@ -94,7 +116,7 @@
     @Test
     fun onContentClicked_deviceLockedSecurely_switchesToBouncer() =
         testScope.runTest {
-            val currentScene by collectLastValue(sceneInteractor.currentScene)
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             utils.authenticationRepository.setUnlocked(false)
             runCurrent()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index 58b44ae..19dc72d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -236,7 +236,8 @@
         `when`(precondition.conditionsMet()).thenReturn(true)
 
         // Given a session is created
-        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        val weatherView =
+            checkNotNull(controller.buildAndConnectWeatherView(fakeParent, customView))
         controller.stateChangeListener.onViewAttachedToWindow(weatherView)
         verify(smartspaceManager).createSmartspaceSession(any())
 
@@ -258,7 +259,8 @@
 
         // Given a session is created
         val customView = Mockito.mock(TestView::class.java)
-        val weatherView = controller.buildAndConnectWeatherView(fakeParent, customView)
+        val weatherView =
+            checkNotNull(controller.buildAndConnectWeatherView(fakeParent, customView))
         controller.stateChangeListener.onViewAttachedToWindow(weatherView)
         verify(smartspaceManager).createSmartspaceSession(any())
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
index b6da20f..280897d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java
@@ -17,21 +17,19 @@
 
 package com.android.systemui.statusbar;
 
-import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
-
 import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 
 import android.app.ActivityManager;
 import android.app.Notification;
-import android.app.PendingIntent;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -67,17 +65,15 @@
     private static final String TEST_PACKAGE_NAME = "test";
     private static final int TEST_UID = 0;
 
-    protected static final int TEST_MINIMUM_DISPLAY_TIME = 200;
-    protected static final int TEST_STICKY_DISPLAY_TIME = 1000;
-    protected static final int TEST_AUTO_DISMISS_TIME = 500;
+    protected static final int TEST_MINIMUM_DISPLAY_TIME = 400;
+    protected static final int TEST_AUTO_DISMISS_TIME = 600;
+    protected static final int TEST_STICKY_AUTO_DISMISS_TIME = 800;
     // Number of notifications to use in tests requiring multiple notifications
     private static final int TEST_NUM_NOTIFICATIONS = 4;
-    protected static final int TEST_TIMEOUT_TIME = 15000;
+    protected static final int TEST_TIMEOUT_TIME = 2_000;
     protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true;
 
-    protected NotificationEntry mEntry;
     protected Handler mTestHandler;
-    private StatusBarNotification mSbn;
     protected boolean mTimedOut = false;
 
     @Mock protected ExpandableNotificationRow mRow;
@@ -88,8 +84,8 @@
         private TestableAlertingNotificationManager(Handler handler) {
             super(new HeadsUpManagerLogger(logcatLogBuffer()), handler);
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+            mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
         }
 
         @Override
@@ -114,7 +110,7 @@
         return new TestableAlertingNotificationManager(mTestHandler);
     }
 
-    protected StatusBarNotification createNewSbn(int id, Notification n) {
+    protected StatusBarNotification createSbn(int id, Notification n) {
         return new StatusBarNotification(
                 TEST_PACKAGE_NAME /* pkg */,
                 TEST_PACKAGE_NAME,
@@ -128,45 +124,53 @@
                 0 /* postTime */);
     }
 
-    protected StatusBarNotification createNewSbn(int id, Notification.Builder n) {
-        return new StatusBarNotification(
-                TEST_PACKAGE_NAME /* pkg */,
-                TEST_PACKAGE_NAME,
-                id,
-                null /* tag */,
-                TEST_UID,
-                0 /* initialPid */,
-                n.build(),
-                new UserHandle(ActivityManager.getCurrentUser()),
-                null /* overrideGroupKey */,
-                0 /* postTime */);
+    protected StatusBarNotification createSbn(int id, Notification.Builder n) {
+        return createSbn(id, n.build());
     }
 
-    protected StatusBarNotification createNewNotification(int id) {
-        Notification.Builder n = new Notification.Builder(mContext, "")
+    protected StatusBarNotification createSbn(int id) {
+        final Notification.Builder b = new Notification.Builder(mContext, "")
                 .setSmallIcon(R.drawable.ic_person)
                 .setContentTitle("Title")
                 .setContentText("Text");
-        return createNewSbn(id, n);
+        return createSbn(id, b);
     }
 
-    protected StatusBarNotification createStickySbn(int id) {
-        Notification stickyHun = new Notification.Builder(mContext, "")
-                .setSmallIcon(R.drawable.ic_person)
-                .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
-                .build();
-        stickyHun.flags |= FLAG_FSI_REQUESTED_BUT_DENIED;
-        return createNewSbn(id, stickyHun);
+    protected NotificationEntry createEntry(int id, Notification n) {
+        return new NotificationEntryBuilder().setSbn(createSbn(id, n)).build();
+    }
+
+    protected NotificationEntry createEntry(int id) {
+        return new NotificationEntryBuilder().setSbn(createSbn(id)).build();
+    }
+
+    protected void verifyAlertingAtTime(AlertingNotificationManager anm, NotificationEntry entry,
+            boolean shouldBeAlerting, int whenToCheckAlertingMillis, String whenCondition) {
+        final Boolean[] wasAlerting = {null};
+        final Runnable checkAlerting =
+                () -> wasAlerting[0] = anm.isAlerting(entry.getKey());
+
+        mTestHandler.postDelayed(checkAlerting, whenToCheckAlertingMillis);
+        mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+        TestableLooper.get(this).processMessages(2);
+
+        assertFalse("Test timed out", mTimedOut);
+        if (shouldBeAlerting) {
+            assertTrue("Should still be alerting after " + whenCondition, wasAlerting[0]);
+        } else {
+            assertFalse("Should not still be alerting after " + whenCondition, wasAlerting[0]);
+        }
+        assertFalse("Should not still be alerting after processing",
+                anm.isAlerting(entry.getKey()));
     }
 
     @Before
     public void setUp() {
         mTestHandler = Handler.createAsync(Looper.myLooper());
-        mSbn = createNewNotification(0 /* id */);
-        mEntry = new NotificationEntryBuilder()
-                .setSbn(mSbn)
-                .build();
-        mEntry.setRow(mRow);
+
+        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME);
     }
 
     @After
@@ -176,59 +180,64 @@
 
     @Test
     public void testShowNotification_addsEntry() {
-        AlertingNotificationManager alm = createAlertingNotificationManager();
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        alm.showNotification(mEntry);
+        alm.showNotification(entry);
 
-        assertTrue(alm.isAlerting(mEntry.getKey()));
+        assertTrue(alm.isAlerting(entry.getKey()));
         assertTrue(alm.hasNotifications());
-        assertEquals(mEntry, alm.getEntry(mEntry.getKey()));
+        assertEquals(entry, alm.getEntry(entry.getKey()));
     }
 
     @Test
     public void testShowNotification_autoDismisses() {
-        AlertingNotificationManager alm = createAlertingNotificationManager();
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        alm.showNotification(mEntry);
-        mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+        alm.showNotification(entry);
 
-        // Wait for remove runnable and then process it immediately
-        TestableLooper.get(this).processMessages(1);
+        verifyAlertingAtTime(alm, entry, false, TEST_AUTO_DISMISS_TIME * 3 / 2,
+                "auto dismiss time");
 
-        assertFalse("Test timed out", mTimedOut);
-        assertFalse(alm.isAlerting(mEntry.getKey()));
+        assertFalse(alm.isAlerting(entry.getKey()));
     }
 
     @Test
     public void testRemoveNotification_removeDeferred() {
-        AlertingNotificationManager alm = createAlertingNotificationManager();
-        alm.showNotification(mEntry);
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
 
         // Try to remove but defer, since the notification has not been shown long enough.
-        alm.removeNotification(mEntry.getKey(), false /* releaseImmediately */);
+        final boolean removedImmediately = alm.removeNotification(entry.getKey(),
+                false /* releaseImmediately */);
 
-        assertTrue(alm.isAlerting(mEntry.getKey()));
+        assertFalse(removedImmediately);
+        assertTrue(alm.isAlerting(entry.getKey()));
     }
 
     @Test
     public void testRemoveNotification_forceRemove() {
-        AlertingNotificationManager alm = createAlertingNotificationManager();
-        alm.showNotification(mEntry);
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
 
         // Remove forcibly with releaseImmediately = true.
-        alm.removeNotification(mEntry.getKey(), true /* releaseImmediately */);
+        final boolean removedImmediately = alm.removeNotification(entry.getKey(),
+                true /* releaseImmediately */);
 
-        assertFalse(alm.isAlerting(mEntry.getKey()));
+        assertTrue(removedImmediately);
+        assertFalse(alm.isAlerting(entry.getKey()));
     }
 
     @Test
     public void testReleaseAllImmediately() {
-        AlertingNotificationManager alm = createAlertingNotificationManager();
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
         for (int i = 0; i < TEST_NUM_NOTIFICATIONS; i++) {
-            StatusBarNotification sbn = createNewNotification(i);
-            NotificationEntry entry = new NotificationEntryBuilder()
-                    .setSbn(sbn)
-                    .build();
+            final NotificationEntry entry = createEntry(i);
             entry.setRow(mRow);
             alm.showNotification(entry);
         }
@@ -240,10 +249,12 @@
 
     @Test
     public void testCanRemoveImmediately_notShownLongEnough() {
-        AlertingNotificationManager alm = createAlertingNotificationManager();
-        alm.showNotification(mEntry);
+        final AlertingNotificationManager alm = createAlertingNotificationManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        alm.showNotification(entry);
 
         // The entry has just been added so we should not remove immediately.
-        assertFalse(alm.canRemoveImmediately(mEntry.getKey()));
+        assertFalse(alm.canRemoveImmediately(entry.getKey()));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
index 724ea02..e4da53a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
@@ -47,7 +47,7 @@
         processor = MediaArtworkProcessor()
 
         val point = Point()
-        context.display.getSize(point)
+        checkNotNull(context.display).getSize(point)
         screenWidth = point.x
         screenHeight = point.y
     }
@@ -106,4 +106,4 @@
         // THEN the processed bitmap is null
         assertThat(background).isNull()
     }
-}
\ No newline at end of file
+}
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 414256f..6be2fa5 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
@@ -400,15 +400,16 @@
 
         // remove persistent dot
         systemStatusAnimationScheduler.removePersistentDot()
-        testScheduler.runCurrent()
+
+        // verify that the onHidePersistentDot callback is invoked
+        verify(listener, times(1)).onHidePersistentDot()
 
         // skip disappear animation
         animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
         testScheduler.runCurrent()
 
-        // verify that animationState changes to IDLE and onHidePersistentDot callback is invoked
+        // verify that animationState changes to IDLE
         assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
-        verify(listener, times(1)).onHidePersistentDot()
     }
 
     @Test
@@ -473,7 +474,6 @@
 
         // request removal of persistent dot
         systemStatusAnimationScheduler.removePersistentDot()
-        testScheduler.runCurrent()
 
         // schedule another high priority event while the event is animating out
         createAndScheduleFakePrivacyEvent()
@@ -489,6 +489,42 @@
         verify(listener, times(1)).onHidePersistentDot()
     }
 
+    @Test
+    fun testDotIsRemoved_evenIfAnimatorCallbackIsDelayed() = runTest {
+        // Instantiate class under test with TestScope from runTest
+        initializeSystemStatusAnimationScheduler(testScope = this)
+
+        // create and schedule high priority event
+        createAndScheduleFakePrivacyEvent()
+
+        // skip chip animation lifecycle and fast forward to ANIMATING_OUT state
+        fastForwardAnimationToState(ANIMATING_OUT)
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+        verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+
+        // request removal of persistent dot
+        systemStatusAnimationScheduler.removePersistentDot()
+
+        // verify that the state is still ANIMATING_OUT
+        assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+
+        // skip disappear animation duration
+        testScheduler.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION + 1)
+        // In an old implementation this would trigger a coroutine timeout causing the
+        // onHidePersistentDot callback to be missed.
+        testScheduler.runCurrent()
+
+        // advance animator time to invoke onAnimationEnd callback
+        animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+        testScheduler.runCurrent()
+
+        // verify that onHidePersistentDot is invoked despite the animator callback being delayed
+        // (it's invoked more than DISAPPEAR_ANIMATION_DURATION after the dot removal was requested)
+        verify(listener, times(1)).onHidePersistentDot()
+        // verify that animationState is IDLE
+        assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+    }
+
     private fun TestScope.fastForwardAnimationToState(@SystemAnimationState animationState: Int) {
         // this function should only be called directly after posting a status event
         assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 2de5705..9036f22 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -278,7 +278,7 @@
         `when`(deviceProvisionedController.isCurrentUserSetup).thenReturn(false)
 
         // WHEN a connection attempt is made and view is attached
-        val view = controller.buildAndConnectView(fakeParent)
+        val view = controller.buildAndConnectView(fakeParent)!!
         controller.stateChangeListener.onViewAttachedToWindow(view)
 
         // THEN no session is created
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
similarity index 69%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
index bc32759..f2207af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/AppOpsCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ColorizedFgsCoordinatorTest.java
@@ -24,97 +24,53 @@
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Intent;
 import android.graphics.Color;
 import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.systemui.ForegroundServiceController;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.appops.AppOpsController;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
-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;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
-public class AppOpsCoordinatorTest extends SysuiTestCase {
-    private static final String TEST_PKG = "test_pkg";
-    private static final int NOTIF_USER_ID = 0;
+public class ColorizedFgsCoordinatorTest extends SysuiTestCase {
 
-    @Mock private ForegroundServiceController mForegroundServiceController;
-    @Mock private AppOpsController mAppOpsController;
+    private static final int NOTIF_USER_ID = 0;
     @Mock private NotifPipeline mNotifPipeline;
 
     private NotificationEntryBuilder mEntryBuilder;
-    private AppOpsCoordinator mAppOpsCoordinator;
-    private NotifFilter mForegroundFilter;
+    private ColorizedFgsCoordinator mColorizedFgsCoordinator;
     private NotifSectioner mFgsSection;
 
-    private FakeSystemClock mClock = new FakeSystemClock();
-    private FakeExecutor mExecutor = new FakeExecutor(mClock);
-
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
         allowTestableLooperAsMainThread();
 
-        mAppOpsCoordinator =
-                new AppOpsCoordinator(
-                        mForegroundServiceController,
-                        mAppOpsController,
-                        mExecutor);
+        mColorizedFgsCoordinator = new ColorizedFgsCoordinator();
 
         mEntryBuilder = new NotificationEntryBuilder()
                 .setUser(new UserHandle(NOTIF_USER_ID));
 
-        mAppOpsCoordinator.attach(mNotifPipeline);
+        mColorizedFgsCoordinator.attach(mNotifPipeline);
 
-        // capture filter
-        ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
-        verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
-        mForegroundFilter = filterCaptor.getValue();
-
-        mFgsSection = mAppOpsCoordinator.getSectioner();
-    }
-
-    @Test
-    public void filterTest_disclosureUnnecessary() {
-        NotificationEntry entry = mEntryBuilder.build();
-        StatusBarNotification sbn = entry.getSbn();
-
-        // GIVEN the notification is a disclosure notification
-        when(mForegroundServiceController.isDisclosureNotification(sbn)).thenReturn(true);
-
-        // GIVEN the disclosure isn't needed for this user
-        when(mForegroundServiceController.isDisclosureNeededForUser(sbn.getUserId()))
-                .thenReturn(false);
-
-        // THEN filter out the notification
-        assertTrue(mForegroundFilter.shouldFilterOut(entry, 0));
+        mFgsSection = mColorizedFgsCoordinator.getSectioner();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
index 764005b8..0cc0b98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt
@@ -17,6 +17,10 @@
 
 package com.android.systemui.statusbar.notification.row
 
+import android.app.Notification
+import android.net.Uri
+import android.os.UserHandle
+import android.os.UserHandle.USER_ALL
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
@@ -29,13 +33,17 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.SbnBuilder
 import com.android.systemui.statusbar.SmartReplyController
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
 import com.android.systemui.statusbar.notification.collection.provider.NotificationDismissibilityProvider
 import com.android.systemui.statusbar.notification.collection.render.FakeNodeController
 import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager
 import com.android.systemui.statusbar.notification.logging.NotificationLogger
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController.BUBBLES_SETTING_URI
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
@@ -46,9 +54,9 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
 import com.android.systemui.util.time.SystemClock
 import com.android.systemui.wmshell.BubblesManager
-import java.util.Optional
 import junit.framework.Assert
 import org.junit.After
 import org.junit.Before
@@ -56,9 +64,11 @@
 import org.junit.runner.RunWith
 import org.mockito.Mockito
 import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
+import java.util.Optional
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -94,10 +104,10 @@
     private val featureFlags: FeatureFlags = mock()
     private val peopleNotificationIdentifier: PeopleNotificationIdentifier = mock()
     private val bubblesManager: BubblesManager = mock()
+    private val settingsController: NotificationSettingsController = mock()
     private val dragController: ExpandableNotificationRowDragController = mock()
     private val dismissibilityProvider: NotificationDismissibilityProvider = mock()
     private val statusBarService: IStatusBarService = mock()
-
     private lateinit var controller: ExpandableNotificationRowController
 
     @Before
@@ -134,11 +144,16 @@
                 featureFlags,
                 peopleNotificationIdentifier,
                 Optional.of(bubblesManager),
+                settingsController,
                 dragController,
                 dismissibilityProvider,
                 statusBarService
             )
         whenever(view.childrenContainer).thenReturn(childrenContainer)
+
+        val notification = Notification.Builder(mContext).build()
+        val sbn = SbnBuilder().setNotification(notification).build()
+        whenever(view.entry).thenReturn(NotificationEntryBuilder().setSbn(sbn).build())
     }
 
     @After
@@ -206,4 +221,74 @@
         verify(view).removeChildNotification(eq(childView))
         verify(listContainer).notifyGroupChildRemoved(eq(childView), eq(childrenContainer))
     }
+
+    @Test
+    fun registerSettingsListener_forBubbles() {
+        controller.init(mock(NotificationEntry::class.java))
+        val viewStateObserver = withArgCaptor {
+            verify(view).addOnAttachStateChangeListener(capture());
+        }
+        viewStateObserver.onViewAttachedToWindow(view);
+        verify(settingsController).addCallback(any(), any());
+    }
+
+    @Test
+    fun unregisterSettingsListener_forBubbles() {
+        controller.init(mock(NotificationEntry::class.java))
+        val viewStateObserver = withArgCaptor {
+            verify(view).addOnAttachStateChangeListener(capture());
+        }
+        viewStateObserver.onViewDetachedFromWindow(view);
+        verify(settingsController).removeCallback(any(), any());
+    }
+
+    @Test
+    fun settingsListener_invalidUri() {
+        controller.mSettingsListener.onSettingChanged(Uri.EMPTY, view.entry.sbn.userId, "1")
+
+        verify(view, never()).getPrivateLayout()
+    }
+
+    @Test
+    fun settingsListener_invalidUserId() {
+        controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, "1")
+        controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, -1000, null)
+
+        verify(view, never()).getPrivateLayout()
+    }
+
+    @Test
+    fun settingsListener_validUserId() {
+        val childView: NotificationContentView = mock()
+        whenever(view.privateLayout).thenReturn(childView)
+
+        controller.mSettingsListener.onSettingChanged(
+                BUBBLES_SETTING_URI, view.entry.sbn.userId, "1")
+        verify(childView).setBubblesEnabledForUser(true)
+
+        controller.mSettingsListener.onSettingChanged(
+                BUBBLES_SETTING_URI, view.entry.sbn.userId, "9")
+        verify(childView).setBubblesEnabledForUser(false)
+    }
+
+    @Test
+    fun settingsListener_userAll() {
+        val childView: NotificationContentView = mock()
+        whenever(view.privateLayout).thenReturn(childView)
+
+        val notification = Notification.Builder(mContext).build()
+        val sbn = SbnBuilder().setNotification(notification)
+                .setUser(UserHandle.of(USER_ALL))
+                .build()
+        whenever(view.entry).thenReturn(NotificationEntryBuilder()
+                .setSbn(sbn)
+                .setUser(UserHandle.of(USER_ALL))
+                .build())
+
+        controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 9, "1")
+        verify(childView).setBubblesEnabledForUser(true)
+
+        controller.mSettingsListener.onSettingChanged(BUBBLES_SETTING_URI, 1, "0")
+        verify(childView).setBubblesEnabledForUser(false)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
index 0b90ebe..c4baa69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt
@@ -250,6 +250,9 @@
             .thenReturn(actionListMarginTarget)
         view.setContainingNotification(mockContainingNotification)
 
+        // Given: controller says bubbles are enabled for the user
+        view.setBubblesEnabledForUser(true);
+
         // When: call NotificationContentView.setExpandedChild() to set the expandedChild
         view.expandedChild = mockExpandedChild
 
@@ -305,6 +308,12 @@
         // NotificationEntry, which should show bubble button
         view.onNotificationUpdated(createMockNotificationEntry(true))
 
+        // Then: no bubble yet
+        assertEquals(notificationContentMargin, getMarginBottom(actionListMarginTarget))
+
+        // Given: controller says bubbles are enabled for the user
+        view.setBubblesEnabledForUser(true);
+
         // Then: bottom margin of actionListMarginTarget should not change, still be 20
         assertEquals(0, getMarginBottom(actionListMarginTarget))
     }
@@ -405,7 +414,6 @@
             val userMock: UserHandle = mock()
             whenever(this.sbn).thenReturn(sbnMock)
             whenever(sbnMock.user).thenReturn(userMock)
-            doReturn(showButton).whenever(view).shouldShowBubbleButton(this)
         }
 
     private fun createLinearLayoutWithBottomMargin(bottomMargin: Int): LinearLayout {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 90adabf..596e9a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -62,6 +62,7 @@
 import android.graphics.drawable.Icon;
 import android.os.Handler;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -132,6 +133,8 @@
     @Mock
     private PackageManager mMockPackageManager;
     @Mock
+    private UserManager mUserManager;
+    @Mock
     private OnUserInteractionCallback mOnUserInteractionCallback;
     @Mock
     private BubblesManager mBubblesManager;
@@ -238,6 +241,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -262,6 +266,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -314,6 +319,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -339,6 +345,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -363,6 +370,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -398,6 +406,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -423,6 +432,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -452,6 +462,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -476,6 +487,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -504,6 +516,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -532,6 +545,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -563,6 +577,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -600,6 +615,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -628,6 +644,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -663,6 +680,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -691,6 +709,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -735,6 +754,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -778,6 +798,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -822,6 +843,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -860,6 +882,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -896,6 +919,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -936,6 +960,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -967,6 +992,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -996,6 +1022,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1033,6 +1060,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1069,6 +1097,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1104,6 +1133,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1143,6 +1173,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1173,6 +1204,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1198,6 +1230,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1219,11 +1252,13 @@
 
     @Test
     public void testSelectPriorityRequestsPinPeopleTile() {
+        when(mUserManager.isSameProfileGroup(anyInt(), anyInt())).thenReturn(true);
         //WHEN channel is default importance
         mNotificationChannel.setImportantConversation(false);
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1250,10 +1285,45 @@
     }
 
     @Test
+    public void testSelectPriorityRequestsPinPeopleTile_noMultiuser() {
+        when(mUserManager.isSameProfileGroup(anyInt(), anyInt())).thenReturn(false);
+        //WHEN channel is default importance
+        mNotificationChannel.setImportantConversation(false);
+        mNotificationInfo.bindNotification(
+                mShortcutManager,
+                mMockPackageManager,
+                mUserManager,
+                mPeopleSpaceWidgetManager,
+                mMockINotificationManager,
+                mOnUserInteractionCallback,
+                TEST_PACKAGE_NAME,
+                mNotificationChannel,
+                mEntry,
+                mBubbleMetadata,
+                null,
+                mIconFactory,
+                mContext,
+                true,
+                mTestHandler,
+                mTestHandler, null, Optional.of(mBubblesManager),
+                mShadeController);
+
+        // WHEN user clicks "priority"
+        mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
+
+        // and then done
+        mNotificationInfo.findViewById(R.id.done).performClick();
+
+        // No widget prompt; on a secondary user
+        verify(mPeopleSpaceWidgetManager, never()).requestPinAppWidget(any(), any());
+    }
+
+    @Test
     public void testSelectDefaultDoesNotRequestPinPeopleTile() {
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
@@ -1288,6 +1358,7 @@
         mNotificationInfo.bindNotification(
                 mShortcutManager,
                 mMockPackageManager,
+                mUserManager,
                 mPeopleSpaceWidgetManager,
                 mMockINotificationManager,
                 mOnUserInteractionCallback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 3cefc99..705d52b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -52,6 +52,7 @@
 import android.graphics.Color;
 import android.os.Binder;
 import android.os.Handler;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
@@ -137,6 +138,8 @@
     @Mock private HeadsUpManagerPhone mHeadsUpManagerPhone;
     @Mock private ActivityStarter mActivityStarter;
 
+    @Mock private UserManager mUserManager;
+
     @Before
     public void setUp() {
         mTestableLooper = TestableLooper.get(this);
@@ -147,7 +150,7 @@
 
         mGutsManager = new NotificationGutsManager(mContext, mHandler, mHandler,
                 mAccessibilityManager,
-                mHighPriorityProvider, mINotificationManager,
+                mHighPriorityProvider, mINotificationManager, mUserManager,
                 mPeopleSpaceWidgetManager, mLauncherApps, mShortcutManager,
                 mChannelEditorDialogController, mContextTracker, mAssistantFeedbackController,
                 Optional.of(mBubblesManager), new UiEventLoggerFake(), mOnUserInteractionCallback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
new file mode 100644
index 0000000..614995b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt
@@ -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.systemui.statusbar.notification.row
+
+import android.app.ActivityManager
+import android.database.ContentObserver
+import android.net.Uri
+import android.os.Handler
+import android.provider.Settings.Secure
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.notification.row.NotificationSettingsController.Listener
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.SecureSettings
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.anyString
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NotificationSettingsControllerTest : SysuiTestCase() {
+
+    val setting1: String = Secure.NOTIFICATION_BUBBLES
+    val setting2: String = Secure.ACCESSIBILITY_ENABLED
+    val settingUri1: Uri = Secure.getUriFor(setting1)
+    val settingUri2: Uri = Secure.getUriFor(setting2)
+
+    @Mock
+    private lateinit var userTracker: UserTracker
+    private lateinit var mainHandler: Handler
+    private lateinit var backgroundHandler: Handler
+    private lateinit var testableLooper: TestableLooper
+    @Mock
+    private lateinit var secureSettings: SecureSettings
+    @Mock
+    private lateinit var dumpManager: DumpManager
+
+    @Captor
+    private lateinit var userTrackerCallbackCaptor: ArgumentCaptor<UserTracker.Callback>
+    @Captor
+    private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
+
+    private lateinit var controller: NotificationSettingsController
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        testableLooper = TestableLooper.get(this)
+        mainHandler = Handler(testableLooper.looper)
+        backgroundHandler = Handler(testableLooper.looper)
+        allowTestableLooperAsMainThread()
+        controller =
+                NotificationSettingsController(
+                        userTracker,
+                        mainHandler,
+                        backgroundHandler,
+                        secureSettings,
+                        dumpManager
+                )
+    }
+
+    @After
+    fun tearDown() {
+        disallowTestableLooperAsMainThread()
+    }
+
+    @Test
+    fun creationRegistersCallbacks() {
+        verify(userTracker).addCallback(any(), any())
+        verify(dumpManager).registerNormalDumpable(anyString(), eq(controller))
+    }
+    @Test
+    fun updateContentObserverRegistration_onUserChange_noSettingsListeners() {
+        verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any())
+        val userCallback = userTrackerCallbackCaptor.value
+        val userId = 9
+
+        // When: User is changed
+        userCallback.onUserChanged(userId, context)
+
+        // Validate: Nothing to do, since we aren't monitoring settings
+        verify(secureSettings, never()).unregisterContentObserver(any())
+        verify(secureSettings, never()).registerContentObserverForUser(
+                any(Uri::class.java), anyBoolean(), any(), anyInt())
+    }
+    @Test
+    fun updateContentObserverRegistration_onUserChange_withSettingsListeners() {
+        // When: someone is listening to a setting
+        controller.addCallback(settingUri1,
+                Mockito.mock(Listener::class.java))
+
+        verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any())
+        val userCallback = userTrackerCallbackCaptor.value
+        val userId = 9
+
+        // Then: User is changed
+        userCallback.onUserChanged(userId, context)
+
+        // Validate: The tracker is unregistered and re-registered with the new user
+        verify(secureSettings).unregisterContentObserver(any())
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri1), eq(false), any(), eq(userId))
+    }
+
+    @Test
+    fun addCallback_onlyFirstForUriRegistersObserver() {
+        controller.addCallback(settingUri1,
+                Mockito.mock(Listener::class.java))
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+
+        controller.addCallback(settingUri1,
+                Mockito.mock(Listener::class.java))
+        verify(secureSettings).registerContentObserverForUser(
+                any(Uri::class.java), anyBoolean(), any(), anyInt())
+    }
+
+    @Test
+    fun addCallback_secondUriRegistersObserver() {
+        controller.addCallback(settingUri1,
+                Mockito.mock(Listener::class.java))
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+
+        controller.addCallback(settingUri2,
+                Mockito.mock(Listener::class.java))
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri2), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri1), anyBoolean(), any(), anyInt())
+    }
+
+    @Test
+    fun removeCallback_lastUnregistersObserver() {
+        val listenerSetting1 : Listener = mock()
+        val listenerSetting2 : Listener = mock()
+        controller.addCallback(settingUri1, listenerSetting1)
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri1), eq(false), any(), eq(ActivityManager.getCurrentUser()))
+
+        controller.addCallback(settingUri2, listenerSetting2)
+        verify(secureSettings).registerContentObserverForUser(
+                eq(settingUri2), anyBoolean(), any(), anyInt())
+
+        controller.removeCallback(settingUri2, listenerSetting2)
+        verify(secureSettings, never()).unregisterContentObserver(any())
+
+        controller.removeCallback(settingUri1, listenerSetting1)
+        verify(secureSettings).unregisterContentObserver(any())
+    }
+
+    @Test
+    fun addCallback_updatesCurrentValue() {
+        whenever(secureSettings.getStringForUser(
+                setting1, ActivityManager.getCurrentUser())).thenReturn("9")
+        whenever(secureSettings.getStringForUser(
+                setting2, ActivityManager.getCurrentUser())).thenReturn("5")
+
+        val listenerSetting1a : Listener = mock()
+        val listenerSetting1b : Listener = mock()
+        val listenerSetting2 : Listener = mock()
+
+        controller.addCallback(settingUri1, listenerSetting1a)
+        controller.addCallback(settingUri1, listenerSetting1b)
+        controller.addCallback(settingUri2, listenerSetting2)
+
+        testableLooper.processAllMessages()
+
+        verify(listenerSetting1a).onSettingChanged(
+                settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting1b).onSettingChanged(
+                settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting2).onSettingChanged(
+                settingUri2, ActivityManager.getCurrentUser(), "5")
+    }
+
+    @Test
+    fun removeCallback_noMoreUpdates() {
+        whenever(secureSettings.getStringForUser(
+                setting1, ActivityManager.getCurrentUser())).thenReturn("9")
+
+        val listenerSetting1a : Listener = mock()
+        val listenerSetting1b : Listener = mock()
+
+        // First, register
+        controller.addCallback(settingUri1, listenerSetting1a)
+        controller.addCallback(settingUri1, listenerSetting1b)
+        testableLooper.processAllMessages()
+
+        verify(secureSettings).registerContentObserverForUser(
+                any(Uri::class.java), anyBoolean(), capture(settingsObserverCaptor), anyInt())
+        verify(listenerSetting1a).onSettingChanged(
+                settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting1b).onSettingChanged(
+                settingUri1, ActivityManager.getCurrentUser(), "9")
+        Mockito.clearInvocations(listenerSetting1b)
+        Mockito.clearInvocations(listenerSetting1a)
+
+        // Remove one of them
+        controller.removeCallback(settingUri1, listenerSetting1a)
+
+        // On update, only remaining listener should get the callback
+        settingsObserverCaptor.value.onChange(false, settingUri1)
+        testableLooper.processAllMessages()
+
+        verify(listenerSetting1a, never()).onSettingChanged(
+                settingUri1, ActivityManager.getCurrentUser(), "9")
+        verify(listenerSetting1b).onSettingChanged(
+                settingUri1, ActivityManager.getCurrentUser(), "9")
+    }
+
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 61da901..39b2948 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -115,6 +115,7 @@
 import com.android.systemui.keyguard.ScreenLifecycle;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel;
+import com.android.systemui.log.LogBuffer;
 import com.android.systemui.navigationbar.NavigationBarController;
 import com.android.systemui.notetask.NoteTaskController;
 import com.android.systemui.plugins.ActivityStarter;
@@ -441,6 +442,7 @@
         mShadeController = spy(new ShadeControllerImpl(
                 mCommandQueue,
                 mMainExecutor,
+                mock(LogBuffer.class),
                 mKeyguardStateController,
                 mStatusBarStateController,
                 mStatusBarKeyguardViewManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
index 6155e3c..56d2397 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt
@@ -212,13 +212,13 @@
     @Test
     fun localeListChanged_listenerNotified() {
         val config = mContext.resources.configuration
-        config.locales = LocaleList(Locale.CANADA, Locale.GERMANY)
+        config.setLocales(LocaleList(Locale.CANADA, Locale.GERMANY))
         mConfigurationController.onConfigurationChanged(config)
 
         val listener = createAndAddListener()
 
         // WHEN the locales are updated
-        config.locales = LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE)
+        config.setLocales(LocaleList(Locale.FRANCE, Locale.JAPAN, Locale.CHINESE))
         mConfigurationController.onConfigurationChanged(config)
 
         // THEN the listener is notified
@@ -274,6 +274,23 @@
     }
 
     @Test
+    fun orientationUpdated_listenerNotified() {
+        val config = mContext.resources.configuration
+        config.orientation = Configuration.ORIENTATION_LANDSCAPE
+        mConfigurationController.onConfigurationChanged(config)
+
+        val listener = createAndAddListener()
+
+        // WHEN the orientation is updated
+        config.orientation = Configuration.ORIENTATION_PORTRAIT
+        mConfigurationController.onConfigurationChanged(config)
+
+        // THEN the listener is notified
+        assertThat(listener.orientationChanged).isTrue()
+    }
+
+
+    @Test
     fun multipleUpdates_listenerNotifiedOfAll() {
         val config = mContext.resources.configuration
         config.densityDpi = 14
@@ -325,6 +342,7 @@
         var themeChanged = false
         var localeListChanged = false
         var layoutDirectionChanged = false
+        var orientationChanged = false
 
         override fun onConfigChanged(newConfig: Configuration?) {
             changedConfig = newConfig
@@ -350,6 +368,9 @@
         override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
             layoutDirectionChanged = true
         }
+        override fun onOrientationChanged(orientation: Int) {
+            orientationChanged = true
+        }
 
         fun assertNoMethodsCalled() {
             assertThat(densityOrFontScaleChanged).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 2f1e372..ec6286b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -75,6 +75,7 @@
     private HeadsUpManagerPhone mHeadsUpManager;
     private View mOperatorNameView;
     private StatusBarStateController mStatusbarStateController;
+    private PhoneStatusBarTransitions mPhoneStatusBarTransitions;
     private KeyguardBypassController mBypassController;
     private NotificationWakeUpCoordinator mWakeUpCoordinator;
     private KeyguardStateController mKeyguardStateController;
@@ -95,6 +96,7 @@
         mHeadsUpManager = mock(HeadsUpManagerPhone.class);
         mOperatorNameView = new View(mContext);
         mStatusbarStateController = mock(StatusBarStateController.class);
+        mPhoneStatusBarTransitions = mock(PhoneStatusBarTransitions.class);
         mBypassController = mock(KeyguardBypassController.class);
         mWakeUpCoordinator = mock(NotificationWakeUpCoordinator.class);
         mKeyguardStateController = mock(KeyguardStateController.class);
@@ -105,6 +107,7 @@
                 mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
                 mStatusbarStateController,
+                mPhoneStatusBarTransitions,
                 mBypassController,
                 mWakeUpCoordinator,
                 mDarkIconDispatcher,
@@ -188,6 +191,7 @@
                 mock(NotificationIconAreaController.class),
                 mHeadsUpManager,
                 mStatusbarStateController,
+                mPhoneStatusBarTransitions,
                 mBypassController,
                 mWakeUpCoordinator,
                 mDarkIconDispatcher,
@@ -283,4 +287,18 @@
                 /* delta = */ 0.001
         );
     }
+
+    @Test
+    public void onHeadsUpStateChanged_true_transitionsNotified() {
+        mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, true);
+
+        verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(true);
+    }
+
+    @Test
+    public void onHeadsUpStateChanged_false_transitionsNotified() {
+        mHeadsUpAppearanceController.onHeadsUpStateChanged(mEntry, false);
+
+        verify(mPhoneStatusBarTransitions).onHeadsUpStateChanged(false);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
index 72522ca..bb20d18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java
@@ -39,7 +39,6 @@
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
 import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
@@ -62,8 +61,6 @@
 public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest {
     @Rule public MockitoRule rule = MockitoJUnit.rule();
 
-    private HeadsUpManagerPhone mHeadsUpManager;
-
     private final HeadsUpManagerLogger mHeadsUpManagerLogger = new HeadsUpManagerLogger(
             logcatLogBuffer());
     @Mock private GroupMembershipManager mGroupManager;
@@ -108,25 +105,8 @@
         }
     }
 
-    @Override
-    protected AlertingNotificationManager createAlertingNotificationManager() {
-        return mHeadsUpManager;
-    }
-
-    @Before
-    @Override
-    public void setUp() {
-        AccessibilityManagerWrapper accessibilityMgr =
-                mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
-        when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
-                .thenReturn(TEST_AUTO_DISMISS_TIME);
-        when(mVSProvider.isReorderingAllowed()).thenReturn(true);
-        mDependency.injectMockDependency(NotificationShadeWindowController.class);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.integer.ambient_notification_extension_time, 500);
-
-        super.setUp();
-        mHeadsUpManager = new TestableHeadsUpManagerPhone(
+    private HeadsUpManagerPhone createHeadsUpManagerPhone() {
+        return new TestableHeadsUpManagerPhone(
                 mContext,
                 mHeadsUpManagerLogger,
                 mGroupManager,
@@ -141,6 +121,26 @@
         );
     }
 
+    @Override
+    protected AlertingNotificationManager createAlertingNotificationManager() {
+        return createHeadsUpManagerPhone();
+    }
+
+    @Before
+    @Override
+    public void setUp() {
+        final AccessibilityManagerWrapper accessibilityMgr =
+                mDependency.injectMockDependency(AccessibilityManagerWrapper.class);
+        when(accessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt()))
+                .thenReturn(TEST_AUTO_DISMISS_TIME);
+        when(mVSProvider.isReorderingAllowed()).thenReturn(true);
+        mDependency.injectMockDependency(NotificationShadeWindowController.class);
+        mContext.getOrCreateTestableResources().addOverride(
+                R.integer.ambient_notification_extension_time, 500);
+
+        super.setUp();
+    }
+
     @After
     @Override
     public void tearDown() {
@@ -149,63 +149,67 @@
 
     @Test
     public void testSnooze() {
-        mHeadsUpManager.showNotification(mEntry);
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.snooze();
+        hmp.showNotification(entry);
+        hmp.snooze();
 
-        assertTrue(mHeadsUpManager.isSnoozed(mEntry.getSbn().getPackageName()));
+        assertTrue(hmp.isSnoozed(entry.getSbn().getPackageName()));
     }
 
     @Test
     public void testSwipedOutNotification() {
-        mHeadsUpManager.showNotification(mEntry);
-        mHeadsUpManager.addSwipedOutNotification(mEntry.getKey());
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        hmp.showNotification(entry);
+        hmp.addSwipedOutNotification(entry.getKey());
 
         // Remove should succeed because the notification is swiped out
-        mHeadsUpManager.removeNotification(mEntry.getKey(), false /* releaseImmediately */);
+        final boolean removedImmediately = hmp.removeNotification(entry.getKey(),
+                /* releaseImmediately = */ false);
 
-        assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+        assertTrue(removedImmediately);
+        assertFalse(hmp.isAlerting(entry.getKey()));
     }
 
     @Test
     public void testCanRemoveImmediately_swipedOut() {
-        mHeadsUpManager.showNotification(mEntry);
-        mHeadsUpManager.addSwipedOutNotification(mEntry.getKey());
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        hmp.showNotification(entry);
+        hmp.addSwipedOutNotification(entry.getKey());
 
         // Notification is swiped so it can be immediately removed.
-        assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
+        assertTrue(hmp.canRemoveImmediately(entry.getKey()));
     }
 
     @Ignore("b/141538055")
     @Test
     public void testCanRemoveImmediately_notTopEntry() {
-        NotificationEntry laterEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(1))
-                .build();
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry earlierEntry = createEntry(/* id = */ 0);
+        final NotificationEntry laterEntry = createEntry(/* id = */ 1);
         laterEntry.setRow(mRow);
-        mHeadsUpManager.showNotification(mEntry);
-        mHeadsUpManager.showNotification(laterEntry);
+
+        hmp.showNotification(earlierEntry);
+        hmp.showNotification(laterEntry);
 
         // Notification is "behind" a higher priority notification so we can remove it immediately.
-        assertTrue(mHeadsUpManager.canRemoveImmediately(mEntry.getKey()));
+        assertTrue(hmp.canRemoveImmediately(earlierEntry.getKey()));
     }
 
     @Test
     public void testExtendHeadsUp() {
-        mHeadsUpManager.showNotification(mEntry);
-        Runnable pastNormalTimeRunnable =
-                () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey());
-        mTestHandler.postDelayed(pastNormalTimeRunnable,
-                TEST_AUTO_DISMISS_TIME + mHeadsUpManager.mExtensionTime / 2);
-        mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_TIMEOUT_TIME);
+        final HeadsUpManagerPhone hmp = createHeadsUpManagerPhone();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.extendHeadsUp();
+        hmp.showNotification(entry);
+        hmp.extendHeadsUp();
 
-        // Wait for normal time runnable and extended remove runnable and process them on arrival.
-        TestableLooper.get(this).processMessages(2);
-
-        assertFalse("Test timed out", mTimedOut);
-        assertTrue("Pulse was not extended", mLivesPastNormalTime);
-        assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+        final int pastNormalTimeMillis = TEST_AUTO_DISMISS_TIME + hmp.mExtensionTime / 2;
+        verifyAlertingAtTime(hmp, entry, true, pastNormalTimeMillis, "normal time");
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
new file mode 100644
index 0000000..4af1b24
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.testing.TestableLooper
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT
+import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class PhoneStatusBarTransitionsTest : SysuiTestCase() {
+
+    // PhoneStatusBarView does a lot of non-standard things when inflating, so just use mocks.
+    private val batteryView = mock<View>()
+    private val statusIcons = mock<View>()
+    private val startIcons = mock<View>()
+    private val statusBarView =
+        mock<PhoneStatusBarView>().apply {
+            whenever(this.context).thenReturn(mContext)
+            whenever(this.findViewById<View>(R.id.battery)).thenReturn(batteryView)
+            whenever(this.findViewById<View>(R.id.statusIcons)).thenReturn(statusIcons)
+            whenever(this.findViewById<View>(R.id.status_bar_start_side_except_heads_up))
+                .thenReturn(startIcons)
+        }
+    private val backgroundView = mock<View>().apply { whenever(this.context).thenReturn(mContext) }
+
+    private val underTest: PhoneStatusBarTransitions by lazy {
+        PhoneStatusBarTransitions(statusBarView, backgroundView).also {
+            // The views' alphas will be set when PhoneStatusBarTransitions is created and we want
+            // to ignore those in the tests, so clear those verifications here.
+            reset(batteryView)
+            reset(statusIcons)
+            reset(startIcons)
+        }
+    }
+
+    @Before
+    fun setUp() {
+        context.orCreateTestableResources.addOverride(
+            R.dimen.status_bar_icon_drawing_alpha,
+            RESOURCE_ALPHA,
+        )
+    }
+
+    @Test
+    fun transitionTo_lightsOutMode_batteryTranslucent() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+        val alpha = batteryView.capturedAlpha()
+        assertThat(alpha).isGreaterThan(0)
+        assertThat(alpha).isLessThan(1)
+    }
+
+    @Test
+    fun transitionTo_lightsOutMode_statusIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+        assertThat(statusIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    @Test
+    fun transitionTo_lightsOutMode_startIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    @Test
+    fun transitionTo_lightsOutTransparentMode_batteryTranslucent() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+        val alpha = batteryView.capturedAlpha()
+        assertThat(alpha).isGreaterThan(0)
+        assertThat(alpha).isLessThan(1)
+    }
+
+    @Test
+    fun transitionTo_lightsOutTransparentMode_statusIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+        assertThat(statusIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    @Test
+    fun transitionTo_lightsOutTransparentMode_startIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT_TRANSPARENT, /* animate= */ false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    @Test
+    fun transitionTo_translucentMode_batteryIconShown() {
+        underTest.transitionTo(/* mode= */ MODE_TRANSLUCENT, /* animate= */ false)
+
+        assertThat(batteryView.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun transitionTo_semiTransparentMode_statusIconsShown() {
+        underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+
+        assertThat(statusIcons.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun transitionTo_transparentMode_startIconsShown() {
+        // Transparent is the default, so we need to switch to a different mode first
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.transitionTo(/* mode= */ MODE_TRANSPARENT, /* animate= */ false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun transitionTo_opaqueMode_batteryIconUsesResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+        assertThat(batteryView.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun transitionTo_opaqueMode_statusIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+        assertThat(statusIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun transitionTo_opaqueMode_startIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_true_semiTransparentMode_startIconsShown() {
+        underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(true)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_true_opaqueMode_startIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(true)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    /** Regression test for b/291173113. */
+    @Test
+    fun onHeadsUpStateChanged_true_lightsOutMode_startIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(true)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_false_semiTransparentMode_startIconsShown() {
+        underTest.transitionTo(/* mode= */ MODE_SEMI_TRANSPARENT, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(1)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_false_opaqueMode_startIconsUseResourceAlpha() {
+        underTest.transitionTo(/* mode= */ MODE_OPAQUE, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(RESOURCE_ALPHA)
+    }
+
+    @Test
+    fun onHeadsUpStateChanged_false_lightsOutMode_startIconsHidden() {
+        underTest.transitionTo(/* mode= */ MODE_LIGHTS_OUT, /* animate= */ false)
+        reset(startIcons)
+
+        underTest.onHeadsUpStateChanged(false)
+
+        assertThat(startIcons.capturedAlpha()).isEqualTo(0)
+    }
+
+    private fun View.capturedAlpha(): Float {
+        val captor = argumentCaptor<Float>()
+        verify(this).alpha = captor.capture()
+        return captor.value
+    }
+
+    private companion object {
+        const val RESOURCE_ALPHA = 0.34f
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
index 1759fb7..210c5ab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt
@@ -463,10 +463,10 @@
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
             mock(DumpManager::class.java))
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
         val firstDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 800, 600))
 
         // WHEN: get insets on the second display
         val secondDisplayInsets = provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
@@ -482,14 +482,14 @@
         val provider = StatusBarContentInsetsProvider(contextMock, configurationController,
             mock(DumpManager::class.java))
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
         val firstDisplayInsetsFirstCall = provider
             .getStatusBarContentAreaForRotation(ROTATION_NONE)
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 800, 600)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 800, 600))
         provider.getStatusBarContentAreaForRotation(ROTATION_NONE)
 
-        configuration.windowConfiguration.maxBounds = Rect(0, 0, 1080, 2160)
+        configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160))
 
         // WHEN: get insets on the first display again
         val firstDisplayInsetsSecondCall = provider
@@ -577,4 +577,4 @@
                 " expected=$expected actual=$actual",
                 expected.equals(actual))
     }
-}
\ No newline at end of file
+}
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 ed9cf3f..0da7360 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
@@ -35,6 +35,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
+
 import android.service.trust.TrustAgentService;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
@@ -73,6 +75,8 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
+import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
 import com.android.systemui.plugins.ActivityStarter;
@@ -201,7 +205,10 @@
                         mBouncerView,
                         mAlternateBouncerInteractor,
                         mUdfpsOverlayInteractor,
-                        mActivityStarter) {
+                        mActivityStarter,
+                        mock(KeyguardTransitionInteractor.class),
+                        StandardTestDispatcher(null, null),
+                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -701,7 +708,10 @@
                         mBouncerView,
                         mAlternateBouncerInteractor,
                         mUdfpsOverlayInteractor,
-                        mActivityStarter) {
+                        mActivityStarter,
+                        mock(KeyguardTransitionInteractor.class),
+                        StandardTestDispatcher(null, null),
+                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 9c52788..34c4ac1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -32,7 +32,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.logging.testing.FakeMetricsLogger;
-import com.android.systemui.ForegroundServiceNotificationListener;
 import com.android.systemui.InitController;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.ActivityStarter;
@@ -94,7 +93,6 @@
         mDependency.injectTestDependency(ShadeController.class, mShadeController);
         mDependency.injectMockDependency(NotificationRemoteInputManager.Callback.class);
         mDependency.injectMockDependency(NotificationShadeWindowController.class);
-        mDependency.injectMockDependency(ForegroundServiceNotificationListener.class);
 
         NotificationShadeWindowView notificationShadeWindowView =
                 mock(NotificationShadeWindowView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 6306a36..50ee6a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -55,6 +55,8 @@
     override val networkName =
         MutableStateFlow<NetworkNameModel>(NetworkNameModel.Default("default"))
 
+    override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
+
     fun setDataEnabled(enabled: Boolean) {
         _dataEnabled.value = enabled
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
index 441186a..a251c28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/CarrierMergedConnectionRepositoryTest.kt
@@ -20,6 +20,7 @@
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -319,6 +320,14 @@
             job.cancel()
         }
 
+    @Test
+    fun isAllowedDuringAirplaneMode_alwaysTrue() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
+
+            assertThat(latest).isTrue()
+        }
+
     private companion object {
         const val SUB_ID = 123
         const val NET_ID = 456
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
index b701fbd..3dd2eaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/FullMobileConnectionRepositoryTest.kt
@@ -22,6 +22,7 @@
 import android.telephony.TelephonyManager
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
@@ -84,7 +85,11 @@
     @Before
     fun setUp() {
         mobileRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer)
-        carrierMergedRepo = FakeMobileConnectionRepository(SUB_ID, tableLogBuffer)
+        carrierMergedRepo =
+            FakeMobileConnectionRepository(SUB_ID, tableLogBuffer).apply {
+                // Mimicks the real carrier merged repository
+                this.isAllowedDuringAirplaneMode.value = true
+            }
 
         whenever(
                 mobileFactory.build(
@@ -300,6 +305,24 @@
         }
 
     @Test
+    fun isAllowedDuringAirplaneMode_updatesWhenCarrierMergedUpdates() =
+        testScope.runTest {
+            initializeRepo(startingIsCarrierMerged = false)
+
+            val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
+
+            assertThat(latest).isFalse()
+
+            underTest.setIsCarrierMerged(true)
+
+            assertThat(latest).isTrue()
+
+            underTest.setIsCarrierMerged(false)
+
+            assertThat(latest).isFalse()
+        }
+
+    @Test
     fun factory_reusesLogBuffersForSameConnection() =
         testScope.runTest {
             val realLoggerFactory =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index cf832b4..1ff737b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -53,6 +53,7 @@
 import androidx.test.filters.SmallTest
 import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
@@ -812,6 +813,14 @@
             job.cancel()
         }
 
+    @Test
+    fun isAllowedDuringAirplaneMode_alwaysFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
+
+            assertThat(latest).isFalse()
+        }
+
     private inline fun <reified T> getTelephonyCallbackForType(): T {
         return MobileTelephonyHelpers.getTelephonyCallbackForType(telephonyManager)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index c4e4193..8d1da69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -77,6 +77,8 @@
 
     override val isForceHidden = MutableStateFlow(false)
 
+    override val isAllowedDuringAirplaneMode = MutableStateFlow(false)
+
     fun setIsEmergencyOnly(emergency: Boolean) {
         _isEmergencyOnly.value = emergency
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index c276865..58d3804 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.mobile.MobileIconCarrierIdOverridesImpl
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType
@@ -473,6 +474,18 @@
             job.cancel()
         }
 
+    @Test
+    fun isAllowedDuringAirplaneMode_matchesRepo() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.isAllowedDuringAirplaneMode)
+
+            connectionRepository.isAllowedDuringAirplaneMode.value = true
+            assertThat(latest).isTrue()
+
+            connectionRepository.isAllowedDuringAirplaneMode.value = false
+            assertThat(latest).isFalse()
+        }
+
     private fun createInteractor(
         overrides: MobileIconCarrierIdOverrides = MobileIconCarrierIdOverridesImpl()
     ) =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index b5ab29d..72feec7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -116,12 +116,13 @@
         }
 
     @Test
-    fun isVisible_airplane_false() =
+    fun isVisible_airplaneAndNotAllowed_false() =
         testScope.runTest {
             var latest: Boolean? = null
             val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
 
             airplaneModeRepository.setIsAirplaneMode(true)
+            interactor.isAllowedDuringAirplaneMode.value = false
             interactor.isForceHidden.value = false
 
             assertThat(latest).isFalse()
@@ -129,6 +130,22 @@
             job.cancel()
         }
 
+    /** Regression test for b/291993542. */
+    @Test
+    fun isVisible_airplaneButAllowed_true() =
+        testScope.runTest {
+            var latest: Boolean? = null
+            val job = underTest.isVisible.onEach { latest = it }.launchIn(this)
+
+            airplaneModeRepository.setIsAirplaneMode(true)
+            interactor.isAllowedDuringAirplaneMode.value = true
+            interactor.isForceHidden.value = false
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
     @Test
     fun isVisible_forceHidden_false() =
         testScope.runTest {
@@ -157,7 +174,7 @@
             airplaneModeRepository.setIsAirplaneMode(true)
             assertThat(latest).isFalse()
 
-            airplaneModeRepository.setIsAirplaneMode(false)
+            interactor.isAllowedDuringAirplaneMode.value = true
             assertThat(latest).isTrue()
 
             interactor.isForceHidden.value = true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
index 1bf431b..4f7bb72 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/FakeWifiRepository.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.statusbar.pipeline.wifi.data.repository
 
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.ACTIVITY_DEFAULT
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryHelper.ACTIVITY_DEFAULT
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -34,6 +34,8 @@
         MutableStateFlow(WifiNetworkModel.Inactive)
     override val wifiNetwork: StateFlow<WifiNetworkModel> = _wifiNetwork
 
+    override val secondaryNetworks = MutableStateFlow<List<WifiNetworkModel>>(emptyList())
+
     private val _wifiActivity = MutableStateFlow(ACTIVITY_DEFAULT)
     override val wifiActivity: StateFlow<DataActivityModel> = _wifiActivity
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index fef042b..bea1154 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -489,6 +489,26 @@
         }
 
     @Test
+    fun wifiNetwork_neverHasHotspot() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.ssid).thenReturn(SSID)
+                    whenever(this.isPrimary).thenReturn(true)
+                }
+            val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
+
+            getNetworkCallback()
+                .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
+
+            assertThat(latest is WifiNetworkModel.Active).isTrue()
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
+        }
+
+    @Test
     fun wifiNetwork_isCarrierMerged_flowHasCarrierMerged() =
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
@@ -984,6 +1004,27 @@
         }
 
     @Test
+    fun secondaryNetworks_alwaysEmpty() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.secondaryNetworks)
+            collectLastValue(underTest.wifiNetwork)
+
+            // Even WHEN we do have non-primary wifi info
+            val wifiInfo =
+                mock<WifiInfo>().apply {
+                    whenever(this.ssid).thenReturn(SSID)
+                    whenever(this.isPrimary).thenReturn(false)
+                }
+            val network = mock<Network>().apply { whenever(this.getNetId()).thenReturn(NETWORK_ID) }
+
+            getNetworkCallback()
+                .onCapabilitiesChanged(network, createWifiNetworkCapabilities(wifiInfo))
+
+            // THEN the secondary networks list is empty because this repo doesn't support it
+            assertThat(latest).isEmpty()
+        }
+
+    @Test
     fun isWifiConnectedWithValidSsid_inactiveNetwork_false() =
         testScope.runTest {
             collectLastValue(underTest.wifiNetwork)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
index 7002cbb..662e36a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
@@ -18,13 +18,18 @@
 
 import android.net.wifi.WifiManager
 import android.net.wifi.WifiManager.UNKNOWN_SSID
+import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import android.testing.TestableLooper
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.statusbar.connectivity.WifiPickerTrackerFactory
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl.Companion.WIFI_NETWORK_DEFAULT
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -34,8 +39,13 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.wifitrackerlib.HotspotNetworkEntry
+import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
 import com.android.wifitrackerlib.MergedCarrierEntry
 import com.android.wifitrackerlib.WifiEntry
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN
+import com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE
 import com.android.wifitrackerlib.WifiPickerTracker
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -45,6 +55,7 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
+import org.mockito.Mockito.verify
 
 /**
  * Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests.
@@ -57,10 +68,25 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class WifiRepositoryViaTrackerLibTest : SysuiTestCase() {
 
-    private lateinit var underTest: WifiRepositoryViaTrackerLib
+    // Using lazy means that the class will only be constructed once it's fetched. Because the
+    // repository internally sets some values on construction, we need to set up some test
+    // parameters (like feature flags) *before* construction. Using lazy allows us to do that setup
+    // inside each test case without needing to manually recreate the repository.
+    private val underTest: WifiRepositoryViaTrackerLib by lazy {
+        WifiRepositoryViaTrackerLib(
+            featureFlags,
+            testScope.backgroundScope,
+            executor,
+            wifiPickerTrackerFactory,
+            wifiManager,
+            logger,
+            tableLogger,
+        )
+    }
 
     private val executor = FakeExecutor(FakeSystemClock())
     private val logger = LogBuffer("name", maxSize = 100, logcatEchoTracker = mock())
+    private val featureFlags = FakeFeatureFlags()
     private val tableLogger = mock<TableLogBuffer>()
     private val wifiManager =
         mock<WifiManager>().apply { whenever(this.maxSignalLevel).thenReturn(10) }
@@ -74,12 +100,22 @@
 
     @Before
     fun setUp() {
+        featureFlags.set(Flags.INSTANT_TETHER, false)
+        featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, false)
         whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor)))
             .thenReturn(wifiPickerTracker)
-        underTest = createRepo()
     }
 
     @Test
+    fun wifiPickerTrackerCreation_scansDisabled() =
+        testScope.runTest {
+            collectLastValue(underTest.wifiNetwork)
+            testScope.runCurrent()
+
+            verify(wifiPickerTracker).disableScanning()
+        }
+
+    @Test
     fun isWifiEnabled_enabled_true() =
         testScope.runTest {
             val latest by collectLastValue(underTest.isWifiEnabled)
@@ -238,7 +274,7 @@
                 mock<WifiEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.level).thenReturn(3)
-                    whenever(this.ssid).thenReturn(SSID)
+                    whenever(this.title).thenReturn(TITLE)
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
@@ -246,7 +282,240 @@
             assertThat(latest is WifiNetworkModel.Active).isTrue()
             val latestActive = latest as WifiNetworkModel.Active
             assertThat(latestActive.level).isEqualTo(3)
-            assertThat(latestActive.ssid).isEqualTo(SSID)
+            assertThat(latestActive.ssid).isEqualTo(TITLE)
+        }
+
+    @Test
+    fun accessPointInfo_alwaysFalse() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(3)
+                    whenever(this.title).thenReturn(TITLE)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.Active).isTrue()
+            val latestActive = latest as WifiNetworkModel.Active
+            assertThat(latestActive.isPasspointAccessPoint).isFalse()
+            assertThat(latestActive.isOnlineSignUpForPasspointAccessPoint).isFalse()
+            assertThat(latestActive.passpointProviderFriendlyName).isNull()
+        }
+
+    @Test
+    fun wifiNetwork_unreachableLevel_inactiveNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(WIFI_LEVEL_UNREACHABLE)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+        }
+
+    @Test
+    fun wifiNetwork_levelTooHigh_inactiveNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(WIFI_LEVEL_MAX + 1)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+        }
+
+    @Test
+    fun wifiNetwork_levelTooLow_inactiveNetwork() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(WIFI_LEVEL_MIN - 1)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
+        }
+
+    @Test
+    fun wifiNetwork_levelIsMax_activeNetworkWithMaxLevel() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(WIFI_LEVEL_MAX)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isInstanceOf(WifiNetworkModel.Active::class.java)
+            assertThat((latest as WifiNetworkModel.Active).level).isEqualTo(WIFI_LEVEL_MAX)
+        }
+
+    @Test
+    fun wifiNetwork_levelIsMin_activeNetworkWithMinLevel() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(WIFI_LEVEL_MIN)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isInstanceOf(WifiNetworkModel.Active::class.java)
+            assertThat((latest as WifiNetworkModel.Active).level).isEqualTo(WIFI_LEVEL_MIN)
+        }
+
+    @Test
+    fun wifiNetwork_notHotspot_none() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply { whenever(this.isPrimaryNetwork).thenReturn(true) }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_unknown() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_UNKNOWN)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.UNKNOWN)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_phone() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_PHONE)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.PHONE)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_tablet() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_TABLET)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.TABLET)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_laptop() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_LAPTOP)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.LAPTOP)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_watch() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.WATCH)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_auto() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_AUTO)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.AUTO)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_invalid() =
+        testScope.runTest {
+            featureFlags.set(Flags.INSTANT_TETHER, true)
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(1234)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.INVALID)
+        }
+
+    @Test
+    fun wifiNetwork_hotspot_flagOff_valueNotUsed() =
+        testScope.runTest {
+            // WHEN the flag is off
+            featureFlags.set(Flags.INSTANT_TETHER, false)
+
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry = createHotspotWithType(NetworkProviderInfo.DEVICE_TYPE_WATCH)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+
+            // THEN NONE is always used, even if the wifi entry does have a hotspot device type
+            assertThat((latest as WifiNetworkModel.Active).hotspotDeviceType)
+                .isEqualTo(WifiNetworkModel.HotspotDeviceType.NONE)
         }
 
     @Test
@@ -258,6 +527,7 @@
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.level).thenReturn(3)
+                    whenever(this.subscriptionId).thenReturn(567)
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
@@ -265,7 +535,7 @@
             assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
             val latestMerged = latest as WifiNetworkModel.CarrierMerged
             assertThat(latestMerged.level).isEqualTo(3)
-            // numberOfLevels = maxSignalLevel + 1
+            assertThat(latestMerged.subscriptionId).isEqualTo(567)
         }
 
     @Test
@@ -288,30 +558,23 @@
             assertThat(latestMerged.numberOfLevels).isEqualTo(6)
         }
 
-    /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID.
     @Test
     fun wifiNetwork_carrierMergedButInvalidSubId_flowHasInvalid() =
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiInfo =
-                mock<WifiInfo>().apply {
-                    whenever(this.isPrimary).thenReturn(true)
-                    whenever(this.isCarrierMerged).thenReturn(true)
+            val wifiEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
                 }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
 
-            getNetworkCallback()
-                .onCapabilitiesChanged(
-                    NETWORK,
-                    createWifiNetworkCapabilities(wifiInfo),
-                )
+            getCallback().onWifiEntriesChanged()
 
             assertThat(latest).isInstanceOf(WifiNetworkModel.Invalid::class.java)
         }
 
-     */
-
     @Test
     fun wifiNetwork_notValidated_networkNotValidated() =
         testScope.runTest {
@@ -382,7 +645,7 @@
                 mock<WifiEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.level).thenReturn(3)
-                    whenever(this.ssid).thenReturn("AB")
+                    whenever(this.title).thenReturn("AB")
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
@@ -397,7 +660,7 @@
                 mock<WifiEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.level).thenReturn(4)
-                    whenever(this.ssid).thenReturn("CD")
+                    whenever(this.title).thenReturn("CD")
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(newWifiEntry)
             getCallback().onWifiEntriesChanged()
@@ -430,12 +693,12 @@
             val wifiEntry =
                 mock<WifiEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
-                    whenever(this.ssid).thenReturn(SSID)
+                    whenever(this.title).thenReturn(TITLE)
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
 
-            assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(SSID)
+            assertThat((latest as WifiNetworkModel.Active).ssid).isEqualTo(TITLE)
 
             // WHEN we lose our current network
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
@@ -480,7 +743,7 @@
                 mock<WifiEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.level).thenReturn(1)
-                    whenever(this.ssid).thenReturn(SSID)
+                    whenever(this.title).thenReturn(TITLE)
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
@@ -488,7 +751,7 @@
             assertThat(latest1 is WifiNetworkModel.Active).isTrue()
             val latest1Active = latest1 as WifiNetworkModel.Active
             assertThat(latest1Active.level).isEqualTo(1)
-            assertThat(latest1Active.ssid).isEqualTo(SSID)
+            assertThat(latest1Active.ssid).isEqualTo(TITLE)
 
             // WHEN we add a second subscriber after having already emitted a value
             val latest2 by collectLastValue(underTest.wifiNetwork)
@@ -497,7 +760,198 @@
             assertThat(latest2 is WifiNetworkModel.Active).isTrue()
             val latest2Active = latest2 as WifiNetworkModel.Active
             assertThat(latest2Active.level).isEqualTo(1)
-            assertThat(latest2Active.ssid).isEqualTo(SSID)
+            assertThat(latest2Active.ssid).isEqualTo(TITLE)
+        }
+
+    @Test
+    fun secondaryNetworks_activeEntriesEmpty_isEmpty() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf())
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEmpty()
+        }
+
+    @Test
+    fun secondaryNetworks_oneActiveEntry_hasOne() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val wifiEntry = mock<WifiEntry>()
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(1)
+        }
+
+    @Test
+    fun secondaryNetworks_multipleActiveEntries_hasMultiple() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val wifiEntry1 = mock<WifiEntry>()
+            val wifiEntry2 = mock<WifiEntry>()
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry1, wifiEntry2))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(2)
+        }
+
+    @Test
+    fun secondaryNetworks_mapsToInactive() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val inactiveEntry =
+                mock<WifiEntry>().apply { whenever(this.level).thenReturn(WIFI_LEVEL_UNREACHABLE) }
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(inactiveEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.Inactive::class.java)
+        }
+
+    @Test
+    fun secondaryNetworks_mapsToActive() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val activeEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(activeEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.Active::class.java)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+        }
+
+    @Test
+    fun secondaryNetworks_mapsToCarrierMerged() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val carrierMergedEntry =
+                mock<MergedCarrierEntry>().apply { whenever(this.level).thenReturn(3) }
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(carrierMergedEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(1)
+            assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.CarrierMerged::class.java)
+            assertThat((latest!![0] as WifiNetworkModel.CarrierMerged).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_mapsMultipleInOrder() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val activeEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            val carrierMergedEntry =
+                mock<MergedCarrierEntry>().apply { whenever(this.level).thenReturn(3) }
+            whenever(wifiPickerTracker.activeWifiEntries)
+                .thenReturn(listOf(activeEntry, carrierMergedEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest!![0]).isInstanceOf(WifiNetworkModel.Active::class.java)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+            assertThat(latest!![1]).isInstanceOf(WifiNetworkModel.CarrierMerged::class.java)
+            assertThat((latest!![1] as WifiNetworkModel.CarrierMerged).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_filtersOutConnectedEntry() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val connectedEntry = mock<WifiEntry>().apply { whenever(this.level).thenReturn(1) }
+            val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            val secondaryEntry2 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(3) }
+            // WHEN the active list has both a primary and secondary networks
+            whenever(wifiPickerTracker.activeWifiEntries)
+                .thenReturn(listOf(connectedEntry, secondaryEntry1, secondaryEntry2))
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(connectedEntry)
+
+            getCallback().onWifiEntriesChanged()
+
+            // THEN only the secondary networks are included
+            assertThat(latest).hasSize(2)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+            assertThat((latest!![1] as WifiNetworkModel.Active).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_noConnectedEntry_hasAllActiveEntries() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            val secondaryEntry2 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(3) }
+            whenever(wifiPickerTracker.activeWifiEntries)
+                .thenReturn(listOf(secondaryEntry1, secondaryEntry2))
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).hasSize(2)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+            assertThat((latest!![1] as WifiNetworkModel.Active).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_filtersOutPrimaryNetwork() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val primaryEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(1)
+                }
+            val secondaryEntry1 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(2) }
+            val secondaryEntry2 = mock<WifiEntry>().apply { whenever(this.level).thenReturn(3) }
+            // WHEN the active list has both a primary and secondary networks
+            whenever(wifiPickerTracker.activeWifiEntries)
+                .thenReturn(listOf(secondaryEntry1, primaryEntry, secondaryEntry2))
+
+            getCallback().onWifiEntriesChanged()
+
+            // THEN only the secondary networks are included
+            assertThat(latest).hasSize(2)
+            assertThat((latest!![0] as WifiNetworkModel.Active).level).isEqualTo(2)
+            assertThat((latest!![1] as WifiNetworkModel.Active).level).isEqualTo(3)
+        }
+
+    @Test
+    fun secondaryNetworks_flagOff_noNetworks() =
+        testScope.runTest {
+            featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, false)
+            val latest by collectLastValue(underTest.secondaryNetworks)
+
+            val wifiEntry = mock<WifiEntry>()
+            whenever(wifiPickerTracker.activeWifiEntries).thenReturn(listOf(wifiEntry))
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest).isEmpty()
         }
 
     @Test
@@ -541,40 +995,15 @@
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
         }
 
-    /* TODO(b/292534484): Re-enable this test once WifiTrackerLib gives us the subscription ID.
-       @Test
-       fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
-       testScope.runTest {
-           collectLastValue(underTest.wifiNetwork)
-
-           val wifiInfo =
-               mock<WifiInfo>().apply {
-                   whenever(this.isPrimary).thenReturn(true)
-                   whenever(this.isCarrierMerged).thenReturn(true)
-                   whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
-               }
-
-           getNetworkCallback()
-               .onCapabilitiesChanged(
-                   NETWORK,
-                   createWifiNetworkCapabilities(wifiInfo),
-               )
-           testScope.runCurrent()
-
-           assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
-       }
-
-    */
-
     @Test
-    fun isWifiConnectedWithValidSsid_activeNetwork_nullSsid_false() =
+    fun isWifiConnectedWithValidSsid_invalidNetwork_false() =
         testScope.runTest {
             collectLastValue(underTest.wifiNetwork)
 
             val wifiEntry =
-                mock<WifiEntry>().apply {
+                mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
-                    whenever(this.ssid).thenReturn(null)
+                    whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
@@ -584,14 +1013,14 @@
         }
 
     @Test
-    fun isWifiConnectedWithValidSsid_activeNetwork_unknownSsid_false() =
+    fun isWifiConnectedWithValidSsid_activeNetwork_nullTitle_false() =
         testScope.runTest {
             collectLastValue(underTest.wifiNetwork)
 
             val wifiEntry =
                 mock<WifiEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
-                    whenever(this.ssid).thenReturn(UNKNOWN_SSID)
+                    whenever(this.title).thenReturn(null)
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
@@ -601,14 +1030,31 @@
         }
 
     @Test
-    fun isWifiConnectedWithValidSsid_activeNetwork_validSsid_true() =
+    fun isWifiConnectedWithValidSsid_activeNetwork_unknownTitle_false() =
         testScope.runTest {
             collectLastValue(underTest.wifiNetwork)
 
             val wifiEntry =
                 mock<WifiEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
-                    whenever(this.ssid).thenReturn("fakeSsid")
+                    whenever(this.title).thenReturn(UNKNOWN_SSID)
+                }
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            getCallback().onWifiEntriesChanged()
+            testScope.runCurrent()
+
+            assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
+        }
+
+    @Test
+    fun isWifiConnectedWithValidSsid_activeNetwork_validTitle_true() =
+        testScope.runTest {
+            collectLastValue(underTest.wifiNetwork)
+
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.title).thenReturn("fakeSsid")
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
@@ -626,7 +1072,7 @@
             val wifiEntry =
                 mock<WifiEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
-                    whenever(this.ssid).thenReturn("fakeSsid")
+                    whenever(this.title).thenReturn("fakeSsid")
                 }
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
             getCallback().onWifiEntriesChanged()
@@ -643,23 +1089,74 @@
             assertThat(underTest.isWifiConnectedWithValidSsid()).isFalse()
         }
 
+    @Test
+    fun wifiActivity_callbackGivesNone_activityFlowHasNone() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiActivity)
+
+            getTrafficStateCallback()
+                .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE)
+
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+        }
+
+    @Test
+    fun wifiActivity_callbackGivesIn_activityFlowHasIn() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiActivity)
+
+            getTrafficStateCallback()
+                .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_IN)
+
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = false))
+        }
+
+    @Test
+    fun wifiActivity_callbackGivesOut_activityFlowHasOut() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiActivity)
+
+            getTrafficStateCallback()
+                .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT)
+
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = true))
+        }
+
+    @Test
+    fun wifiActivity_callbackGivesInout_activityFlowHasInAndOut() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiActivity)
+
+            getTrafficStateCallback()
+                .onStateChanged(WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT)
+
+            assertThat(latest)
+                .isEqualTo(DataActivityModel(hasActivityIn = true, hasActivityOut = true))
+        }
+
     private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
         testScope.runCurrent()
         return callbackCaptor.value
     }
 
-    private fun createRepo(): WifiRepositoryViaTrackerLib {
-        return WifiRepositoryViaTrackerLib(
-            testScope.backgroundScope,
-            executor,
-            wifiPickerTrackerFactory,
-            wifiManager,
-            logger,
-            tableLogger,
-        )
+    private fun getTrafficStateCallback(): WifiManager.TrafficStateCallback {
+        testScope.runCurrent()
+        val callbackCaptor = argumentCaptor<WifiManager.TrafficStateCallback>()
+        verify(wifiManager).registerTrafficStateCallback(any(), callbackCaptor.capture())
+        return callbackCaptor.value!!
+    }
+
+    private fun createHotspotWithType(@DeviceType type: Int): HotspotNetworkEntry {
+        return mock<HotspotNetworkEntry>().apply {
+            whenever(this.isPrimaryNetwork).thenReturn(true)
+            whenever(this.deviceType).thenReturn(type)
+        }
     }
 
     private companion object {
-        const val SSID = "AB"
+        const val TITLE = "AB"
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
index 4e0c309..ba035be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/shared/model/WifiNetworkModelTest.kt
@@ -136,7 +136,8 @@
                 networkId = 5,
                 isValidated = true,
                 level = 3,
-                ssid = "Test SSID"
+                ssid = "Test SSID",
+                hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.LAPTOP,
             )
 
         activeNetwork.logDiffs(prevVal = WifiNetworkModel.Inactive, logger)
@@ -146,6 +147,7 @@
         assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
         assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
         assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+        assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "LAPTOP"))
     }
     @Test
     fun logDiffs_activeToInactive_resetsAllActiveFields() {
@@ -165,6 +167,7 @@
         assertThat(logger.changes).contains(Pair(COL_VALIDATED, "false"))
         assertThat(logger.changes).contains(Pair(COL_LEVEL, LEVEL_DEFAULT.toString()))
         assertThat(logger.changes).contains(Pair(COL_SSID, "null"))
+        assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "null"))
     }
 
     @Test
@@ -175,7 +178,8 @@
                 networkId = 5,
                 isValidated = true,
                 level = 3,
-                ssid = "Test SSID"
+                ssid = "Test SSID",
+                hotspotDeviceType = WifiNetworkModel.HotspotDeviceType.AUTO,
             )
         val prevVal =
             WifiNetworkModel.CarrierMerged(
@@ -191,6 +195,7 @@
         assertThat(logger.changes).contains(Pair(COL_VALIDATED, "true"))
         assertThat(logger.changes).contains(Pair(COL_LEVEL, "3"))
         assertThat(logger.changes).contains(Pair(COL_SSID, "Test SSID"))
+        assertThat(logger.changes).contains(Pair(COL_HOTSPOT, "AUTO"))
     }
     @Test
     fun logDiffs_activeToCarrierMerged_logsAllFields() {
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 c886f9b..cdeb592 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
@@ -29,6 +29,9 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Intent;
+import android.hardware.usb.UsbManager;
+import android.hardware.usb.UsbPort;
+import android.hardware.usb.UsbPortStatus;
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -56,6 +59,9 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
@@ -65,8 +71,10 @@
     @Mock private BroadcastDispatcher mBroadcastDispatcher;
     @Mock private DemoModeController mDemoModeController;
     @Mock private View mView;
+    @Mock private UsbPort mUsbPort;
+    @Mock private UsbManager mUsbManager;
+    @Mock private UsbPortStatus mUsbPortStatus;
     private BatteryControllerImpl mBatteryController;
-
     private MockitoSession mMockitoSession;
 
     @Before
@@ -255,4 +263,38 @@
 
         Assert.assertFalse(mBatteryController.isBatteryDefender());
     }
+
+    @Test
+    public void complianceChanged_complianceIncompatible_outputsTrue() {
+        mContext.addMockSystemService(UsbManager.class, mUsbManager);
+        setupIncompatibleCharging();
+        Intent intent = new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+
+        mBatteryController.onReceive(getContext(), intent);
+
+        Assert.assertTrue(mBatteryController.isIncompatibleCharging());
+    }
+
+    @Test
+    public void complianceChanged_emptyComplianceWarnings_outputsFalse() {
+        mContext.addMockSystemService(UsbManager.class, mUsbManager);
+        setupIncompatibleCharging();
+        when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
+        Intent intent = new Intent(UsbManager.ACTION_USB_PORT_COMPLIANCE_CHANGED);
+
+        mBatteryController.onReceive(getContext(), intent);
+
+        Assert.assertFalse(mBatteryController.isIncompatibleCharging());
+    }
+
+    private void setupIncompatibleCharging() {
+        final List<UsbPort> usbPorts = new ArrayList<>();
+        usbPorts.add(mUsbPort);
+        when(mUsbManager.getPorts()).thenReturn(usbPorts);
+        when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
+        when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
+        when(mUsbPortStatus.isConnected()).thenReturn(true);
+        when(mUsbPortStatus.getComplianceWarnings())
+                .thenReturn(new int[]{UsbPortStatus.COMPLIANCE_WARNING_OTHER});
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index 14edf3d..e5bbead 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static android.app.Notification.FLAG_FSI_REQUESTED_BUT_DENIED;
+
 import static com.android.systemui.dump.LogBufferHelperKt.logcatLogBuffer;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -40,7 +42,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
-import android.service.notification.StatusBarNotification;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 
@@ -48,6 +49,7 @@
 
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.logging.testing.UiEventLoggerFake;
+import com.android.systemui.R;
 import com.android.systemui.statusbar.AlertingNotificationManager;
 import com.android.systemui.statusbar.AlertingNotificationManagerTest;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -63,16 +65,11 @@
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper
 public class HeadsUpManagerTest extends AlertingNotificationManagerTest {
-    private static final int TEST_A11Y_AUTO_DISMISS_TIME = 600;
-    private static final int TEST_A11Y_TIMEOUT_TIME = 5_000;
+    private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200;
+    private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000;
+    private static final int TEST_A11Y_TIMEOUT_TIME = 3_000;
 
-    private HeadsUpManager mHeadsUpManager;
-    private boolean mLivesPastNormalTime;
     private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake();
-    @Mock private HeadsUpManager.HeadsUpEntry mAlertEntry;
-    @Mock private NotificationEntry mEntry;
-    @Mock private StatusBarNotification mSbn;
-    @Mock private Notification mNotification;
     private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer()));
     @Mock private AccessibilityManagerWrapper mAccessibilityMgr;
 
@@ -83,27 +80,81 @@
                 AccessibilityManagerWrapper accessibilityManagerWrapper,
                 UiEventLogger uiEventLogger) {
             super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger);
+            mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME;
             mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
-            mStickyDisplayTime = TEST_STICKY_DISPLAY_TIME;
             mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME;
+            mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME;
         }
     }
 
+    private HeadsUpManager createHeadsUpManager() {
+        return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mAccessibilityMgr,
+                mUiEventLoggerFake);
+    }
+
     @Override
     protected AlertingNotificationManager createAlertingNotificationManager() {
-        return mHeadsUpManager;
+        return createHeadsUpManager();
     }
 
+    private NotificationEntry createStickyEntry(int id) {
+        final Notification notif = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFullScreenIntent(mock(PendingIntent.class), /* highPriority */ true)
+                .build();
+        return createEntry(id, notif);
+    }
+
+    private NotificationEntry createStickyForSomeTimeEntry(int id) {
+        final Notification notif = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFlag(FLAG_FSI_REQUESTED_BUT_DENIED, true)
+                .build();
+        return createEntry(id, notif);
+    }
+
+    private PendingIntent createFullScreenIntent() {
+        return PendingIntent.getActivity(
+                getContext(), 0, new Intent(getContext(), this.getClass()),
+                PendingIntent.FLAG_MUTABLE_UNAUDITED);
+    }
+
+    private NotificationEntry createFullScreenIntentEntry(int id) {
+        final Notification notif = new Notification.Builder(mContext, "")
+                .setSmallIcon(R.drawable.ic_person)
+                .setFullScreenIntent(createFullScreenIntent(), /* highPriority */ true)
+                .build();
+        return createEntry(id, notif);
+    }
+
+
+    private void useAccessibilityTimeout(boolean use) {
+        if (use) {
+            doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
+                    .getRecommendedTimeoutMillis(anyInt(), anyInt());
+        } else {
+            when(mAccessibilityMgr.getRecommendedTimeoutMillis(anyInt(), anyInt())).then(
+                    i -> i.getArgument(0));
+        }
+    }
+
+
     @Before
     @Override
     public void setUp() {
         initMocks(this);
-        when(mEntry.getSbn()).thenReturn(mSbn);
-        when(mEntry.getKey()).thenReturn("entryKey");
-        when(mSbn.getNotification()).thenReturn(mNotification);
         super.setUp();
-        mHeadsUpManager = new TestableHeadsUpManager(mContext, mLogger, mTestHandler,
-                mAccessibilityMgr, mUiEventLoggerFake);
+
+        assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME);
+        assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME);
+        assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME);
+
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan(
+                TEST_TIMEOUT_TIME);
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(
+                TEST_TIMEOUT_TIME);
+        assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan(
+                TEST_A11Y_TIMEOUT_TIME);
     }
 
     @After
@@ -114,193 +165,327 @@
 
     @Test
     public void testHunRemovedLogging() {
-        mAlertEntry.mEntry = mEntry;
-        mHeadsUpManager.onAlertEntryRemoved(mAlertEntry);
-        verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry));
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = mock(HeadsUpManager.HeadsUpEntry.class);
+        headsUpEntry.mEntry = notifEntry;
+
+        hum.onAlertEntryRemoved(headsUpEntry);
+
+        verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(notifEntry));
     }
 
     @Test
     public void testShouldHeadsUpBecomePinned_hasFSI_notUnpinned_true() {
-        // Set up NotifEntry with FSI
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
-        notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()),
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
 
         // Add notifEntry to ANM mAlertEntries map and make it NOT unpinned
-        mHeadsUpManager.showNotification(notifEntry);
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+        hum.showNotification(notifEntry);
+
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.wasUnpinned = false;
 
-        assertTrue(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+        assertTrue(hum.shouldHeadsUpBecomePinned(notifEntry));
     }
 
     @Test
     public void testShouldHeadsUpBecomePinned_wasUnpinned_false() {
-        // Set up NotifEntry with FSI
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
-        notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()),
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
 
         // Add notifEntry to ANM mAlertEntries map and make it unpinned
-        mHeadsUpManager.showNotification(notifEntry);
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+        hum.showNotification(notifEntry);
+
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.wasUnpinned = true;
 
-        assertFalse(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+        assertFalse(hum.shouldHeadsUpBecomePinned(notifEntry));
     }
 
     @Test
     public void testShouldHeadsUpBecomePinned_noFSI_false() {
-        // Set up NotifEntry with no FSI
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
 
-        assertFalse(mHeadsUpManager.shouldHeadsUpBecomePinned(notifEntry));
+        assertFalse(hum.shouldHeadsUpBecomePinned(entry));
     }
 
+
+    @Test
+    public void testShowNotification_autoDismissesIncludingTouchAcceptanceDelay() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        final int pastJustAutoDismissMillis =
+                TEST_TOUCH_ACCEPTANCE_TIME / 2 + TEST_AUTO_DISMISS_TIME;
+        verifyAlertingAtTime(hum, entry, true, pastJustAutoDismissMillis, "just auto dismiss");
+    }
+
+
+    @Test
+    public void testShowNotification_autoDismissesWithDefaultTimeout() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, false, pastDefaultTimeoutMillis, "default timeout");
+    }
+
+
+    @Test
+    public void testShowNotification_stickyForSomeTime_autoDismissesWithStickyTimeout() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_AUTO_DISMISS_TIME + TEST_STICKY_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+    }
+
+
+    @Test
+    public void testShowNotification_sticky_neverAutoDismisses() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createStickyEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        final int pastLongestAutoDismissMillis =
+                TEST_TOUCH_ACCEPTANCE_TIME + 2 * TEST_A11Y_AUTO_DISMISS_TIME;
+        final Boolean[] wasAlerting = {null};
+        final Runnable checkAlerting =
+                () -> wasAlerting[0] = hum.isAlerting(entry.getKey());
+        mTestHandler.postDelayed(checkAlerting, pastLongestAutoDismissMillis);
+        TestableLooper.get(this).processMessages(1);
+
+        assertTrue("Should still be alerting past longest auto-dismiss", wasAlerting[0]);
+        assertTrue("Should still be alerting after processing",
+                hum.isAlerting(entry.getKey()));
+    }
+
+
     @Test
     public void testShowNotification_autoDismissesWithAccessibilityTimeout() {
-        doReturn(TEST_A11Y_AUTO_DISMISS_TIME).when(mAccessibilityMgr)
-                .getRecommendedTimeoutMillis(anyInt(), anyInt());
-        mHeadsUpManager.showNotification(mEntry);
-        Runnable pastNormalTimeRunnable =
-                () -> mLivesPastNormalTime = mHeadsUpManager.isAlerting(mEntry.getKey());
-        mTestHandler.postDelayed(pastNormalTimeRunnable,
-                        (TEST_A11Y_AUTO_DISMISS_TIME + TEST_AUTO_DISMISS_TIME) / 2);
-        mTestHandler.postDelayed(mTestTimeoutRunnable, TEST_A11Y_TIMEOUT_TIME);
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(true);
 
-        TestableLooper.get(this).processMessages(2);
+        hum.showNotification(entry);
 
-        assertFalse("Test timed out", mTimedOut);
-        assertTrue("Heads up should live long enough", mLivesPastNormalTime);
-        assertFalse(mHeadsUpManager.isAlerting(mEntry.getKey()));
+        final int pastDefaultTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, true, pastDefaultTimeoutMillis, "default timeout");
+    }
+
+
+    @Test
+    public void testShowNotification_stickyForSomeTime_autoDismissesWithAccessibilityTimeout() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
+        useAccessibilityTimeout(true);
+
+        hum.showNotification(entry);
+
+        final int pastStickyTimeoutMillis = TEST_TOUCH_ACCEPTANCE_TIME
+                + (TEST_STICKY_AUTO_DISMISS_TIME + TEST_A11Y_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, true, pastStickyTimeoutMillis, "sticky timeout");
+    }
+
+
+    @Test
+    public void testRemoveNotification_beforeMinimumDisplayTime() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        // Try to remove but defer, since the notification has not been shown long enough.
+        final boolean removedImmediately = hum.removeNotification(
+                entry.getKey(), false /* releaseImmediately */);
+
+        assertFalse("HUN should not be removed before minimum display time", removedImmediately);
+        assertTrue("HUN should still be alerting before minimum display time",
+                hum.isAlerting(entry.getKey()));
+
+        final int pastMinimumDisplayTimeMillis =
+                (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
+        verifyAlertingAtTime(hum, entry, false, pastMinimumDisplayTimeMillis,
+                "minimum display time");
+    }
+
+
+    @Test
+    public void testRemoveNotification_afterMinimumDisplayTime() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+        useAccessibilityTimeout(false);
+
+        hum.showNotification(entry);
+
+        // After the minimum display time:
+        // 1. Check whether the notification is still alerting.
+        // 2. Try to remove it and check whether the remove succeeded.
+        // 3. Check whether it is still alerting after trying to remove it.
+        final Boolean[] livedPastMinimumDisplayTime = {null};
+        final Boolean[] removedAfterMinimumDisplayTime = {null};
+        final Boolean[] livedPastRemoveAfterMinimumDisplayTime = {null};
+        final Runnable pastMinimumDisplayTimeRunnable = () -> {
+            livedPastMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
+            removedAfterMinimumDisplayTime[0] = hum.removeNotification(
+                    entry.getKey(), /* releaseImmediately = */ false);
+            livedPastRemoveAfterMinimumDisplayTime[0] = hum.isAlerting(entry.getKey());
+        };
+        final int pastMinimumDisplayTimeMillis =
+                (TEST_MINIMUM_DISPLAY_TIME + TEST_AUTO_DISMISS_TIME) / 2;
+        mTestHandler.postDelayed(pastMinimumDisplayTimeRunnable, pastMinimumDisplayTimeMillis);
+        // Wait until the minimum display time has passed before attempting removal.
+        TestableLooper.get(this).processMessages(1);
+
+        assertTrue("HUN should live past minimum display time",
+                livedPastMinimumDisplayTime[0]);
+        assertTrue("HUN should be removed immediately past minimum display time",
+                removedAfterMinimumDisplayTime[0]);
+        assertFalse("HUN should not live after being removed past minimum display time",
+                livedPastRemoveAfterMinimumDisplayTime[0]);
+        assertFalse(hum.isAlerting(entry.getKey()));
+    }
+
+
+    @Test
+    public void testRemoveNotification_releaseImmediately() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createEntry(/* id = */ 0);
+
+        hum.showNotification(entry);
+
+        // Remove forcibly with releaseImmediately = true.
+        final boolean removedImmediately = hum.removeNotification(
+                entry.getKey(), /* releaseImmediately = */ true);
+
+        assertTrue(removedImmediately);
+        assertFalse(hum.isAlerting(entry.getKey()));
     }
 
 
     @Test
     public void testIsSticky_rowPinnedAndExpanded_true() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
-
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
         when(mRow.isPinned()).thenReturn(true);
         notifEntry.setRow(mRow);
 
-        mHeadsUpManager.showNotification(notifEntry);
+        hum.showNotification(notifEntry);
 
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.setExpanded(true);
 
-        assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+        assertTrue(hum.isSticky(notifEntry.getKey()));
     }
 
     @Test
     public void testIsSticky_remoteInputActive_true() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(notifEntry);
+        hum.showNotification(notifEntry);
 
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
-
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.remoteInputActive = true;
 
-        assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+        assertTrue(hum.isSticky(notifEntry.getKey()));
     }
 
     @Test
     public void testIsSticky_hasFullScreenIntent_true() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createFullScreenIntentEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(notifEntry);
+        hum.showNotification(notifEntry);
 
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
-
-        notifEntry.getSbn().getNotification().fullScreenIntent = PendingIntent.getActivity(
-                getContext(), 0, new Intent(getContext(), this.getClass()),
-                PendingIntent.FLAG_MUTABLE_UNAUDITED);
-
-        assertTrue(mHeadsUpManager.isSticky(notifEntry.getKey()));
+        assertTrue(hum.isSticky(notifEntry.getKey()));
     }
 
+
     @Test
-    public void testIsSticky_stickyAndNotDemoted_true() {
-        NotificationEntry alertEntry = new NotificationEntryBuilder()
-                .setSbn(createStickySbn(0))
-                .build();
+    public void testIsSticky_stickyForSomeTime_false() {
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry entry = createStickyForSomeTimeEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(alertEntry);
+        hum.showNotification(entry);
 
-        assertTrue(mHeadsUpManager.isSticky(alertEntry.getKey()));
+        assertFalse(hum.isSticky(entry.getKey()));
     }
 
+
     @Test
     public void testIsSticky_false() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(notifEntry);
+        hum.showNotification(notifEntry);
 
-        HeadsUpManager.HeadsUpEntry headsUpEntry =
-                mHeadsUpManager.getHeadsUpEntry(notifEntry.getKey());
+        final HeadsUpManager.HeadsUpEntry headsUpEntry = hum.getHeadsUpEntry(notifEntry.getKey());
         headsUpEntry.setExpanded(false);
         headsUpEntry.remoteInputActive = false;
 
-        assertFalse(mHeadsUpManager.isSticky(notifEntry.getKey()));
+        assertFalse(hum.isSticky(notifEntry.getKey()));
     }
 
     @Test
     public void testCompareTo_withNullEntries() {
-        NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
-        mHeadsUpManager.showNotification(alertEntry);
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
 
-        assertThat(mHeadsUpManager.compare(alertEntry, null)).isLessThan(0);
-        assertThat(mHeadsUpManager.compare(null, alertEntry)).isGreaterThan(0);
-        assertThat(mHeadsUpManager.compare(null, null)).isEqualTo(0);
+        hum.showNotification(alertEntry);
+
+        assertThat(hum.compare(alertEntry, null)).isLessThan(0);
+        assertThat(hum.compare(null, alertEntry)).isGreaterThan(0);
+        assertThat(hum.compare(null, null)).isEqualTo(0);
     }
 
     @Test
     public void testCompareTo_withNonAlertEntries() {
-        NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag("nae1").build();
-        NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag("nae2").build();
-        NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
-        mHeadsUpManager.showNotification(alertEntry);
+        final HeadsUpManager hum = createHeadsUpManager();
 
-        assertThat(mHeadsUpManager.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
-        assertThat(mHeadsUpManager.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
-        assertThat(mHeadsUpManager.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
+        final NotificationEntry nonAlertEntry1 = new NotificationEntryBuilder().setTag(
+                "nae1").build();
+        final NotificationEntry nonAlertEntry2 = new NotificationEntryBuilder().setTag(
+                "nae2").build();
+        final NotificationEntry alertEntry = new NotificationEntryBuilder().setTag("alert").build();
+        hum.showNotification(alertEntry);
+
+        assertThat(hum.compare(alertEntry, nonAlertEntry1)).isLessThan(0);
+        assertThat(hum.compare(nonAlertEntry1, alertEntry)).isGreaterThan(0);
+        assertThat(hum.compare(nonAlertEntry1, nonAlertEntry2)).isEqualTo(0);
     }
 
     @Test
     public void testAlertEntryCompareTo_ongoingCallLessThanActiveRemoteInput() {
-        HeadsUpManager.HeadsUpEntry ongoingCall = mHeadsUpManager.new HeadsUpEntry();
+        final HeadsUpManager hum = createHeadsUpManager();
+
+        final HeadsUpManager.HeadsUpEntry ongoingCall = hum.new HeadsUpEntry();
         ongoingCall.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewSbn(0,
+                .setSbn(createSbn(/* id = */ 0,
                         new Notification.Builder(mContext, "")
                                 .setCategory(Notification.CATEGORY_CALL)
                                 .setOngoing(true)))
                 .build());
 
-        HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
-        activeRemoteInput.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewNotification(1))
-                .build());
+        final HeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
+        activeRemoteInput.setEntry(createEntry(/* id = */ 1));
         activeRemoteInput.remoteInputActive = true;
 
         assertThat(ongoingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -309,20 +494,20 @@
 
     @Test
     public void testAlertEntryCompareTo_incomingCallLessThanActiveRemoteInput() {
-        HeadsUpManager.HeadsUpEntry incomingCall = mHeadsUpManager.new HeadsUpEntry();
-        Person person = new Person.Builder().setName("person").build();
-        PendingIntent intent = mock(PendingIntent.class);
+        final HeadsUpManager hum = createHeadsUpManager();
+
+        final HeadsUpManager.HeadsUpEntry incomingCall = hum.new HeadsUpEntry();
+        final Person person = new Person.Builder().setName("person").build();
+        final PendingIntent intent = mock(PendingIntent.class);
         incomingCall.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewSbn(0,
+                .setSbn(createSbn(/* id = */ 0,
                         new Notification.Builder(mContext, "")
                                 .setStyle(Notification.CallStyle
                                         .forIncomingCall(person, intent, intent))))
                 .build());
 
-        HeadsUpManager.HeadsUpEntry activeRemoteInput = mHeadsUpManager.new HeadsUpEntry();
-        activeRemoteInput.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewNotification(1))
-                .build());
+        final HeadsUpManager.HeadsUpEntry activeRemoteInput = hum.new HeadsUpEntry();
+        activeRemoteInput.setEntry(createEntry(/* id = */ 1));
         activeRemoteInput.remoteInputActive = true;
 
         assertThat(incomingCall.compareTo(activeRemoteInput)).isLessThan(0);
@@ -331,22 +516,18 @@
 
     @Test
     public void testPinEntry_logsPeek() {
-        // Needs full screen intent in order to be pinned
-        final PendingIntent fullScreenIntent = PendingIntent.getActivity(mContext, 0,
-                new Intent().setPackage(mContext.getPackageName()), PendingIntent.FLAG_MUTABLE);
+        final HeadsUpManager hum = createHeadsUpManager();
 
-        HeadsUpManager.HeadsUpEntry entryToPin = mHeadsUpManager.new HeadsUpEntry();
-        entryToPin.setEntry(new NotificationEntryBuilder()
-                .setSbn(createNewSbn(0,
-                        new Notification.Builder(mContext, "")
-                                .setFullScreenIntent(fullScreenIntent, true)))
-                .build());
+        // Needs full screen intent in order to be pinned
+        final HeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry();
+        entryToPin.setEntry(createFullScreenIntentEntry(/* id = */ 0));
+
         // Note: the standard way to show a notification would be calling showNotification rather
         // than onAlertEntryAdded. However, in practice showNotification in effect adds
         // the notification and then updates it; in order to not log twice, the entry needs
         // to have a functional ExpandableNotificationRow that can keep track of whether it's
         // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
-        mHeadsUpManager.onAlertEntryAdded(entryToPin);
+        hum.onAlertEntryAdded(entryToPin);
 
         assertEquals(1, mUiEventLoggerFake.numLogs());
         assertEquals(HeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
@@ -355,14 +536,15 @@
 
     @Test
     public void testSetUserActionMayIndirectlyRemove() {
-        NotificationEntry notifEntry = new NotificationEntryBuilder()
-                .setSbn(createNewNotification(/* id= */ 0))
-                .build();
+        final HeadsUpManager hum = createHeadsUpManager();
+        final NotificationEntry notifEntry = createEntry(/* id = */ 0);
 
-        mHeadsUpManager.showNotification(notifEntry);
-        assertFalse(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+        hum.showNotification(notifEntry);
 
-        mHeadsUpManager.setUserActionMayIndirectlyRemove(notifEntry);
-        assertTrue(mHeadsUpManager.canRemoveImmediately(notifEntry.getKey()));
+        assertFalse(hum.canRemoveImmediately(notifEntry.getKey()));
+
+        hum.setUserActionMayIndirectlyRemove(notifEntry);
+
+        assertTrue(hum.canRemoveImmediately(notifEntry.getKey()));
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
index 5cabcd4..cae892f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java
@@ -36,6 +36,7 @@
 import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 
 import dagger.Lazy;
@@ -67,6 +68,8 @@
     private Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy;
     @Mock
     private KeyguardUpdateMonitorLogger mLogger;
+    @Mock
+    private FeatureFlags mFeatureFlags;
 
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mUpdateCallbackCaptor;
@@ -80,7 +83,8 @@
                 mLockPatternUtils,
                 mKeyguardUnlockAnimationControllerLazy,
                 mLogger,
-                mDumpManager);
+                mDumpManager,
+                mFeatureFlags);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
index 871a48c..b698e70 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt
@@ -19,9 +19,11 @@
 import android.os.Handler
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
+import android.view.View
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.time.FakeSystemClock
@@ -59,6 +61,7 @@
 
     private var lastText: String? = null
 
+    private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
     private lateinit var systemClock: FakeSystemClock
     private lateinit var testableLooper: TestableLooper
     private lateinit var testableHandler: Handler
@@ -76,6 +79,8 @@
         systemClock = FakeSystemClock()
         systemClock.setCurrentTimeMillis(TIME_STAMP)
 
+        shadeExpansionStateManager = ShadeExpansionStateManager()
+
         `when`(view.longerPattern).thenReturn(LONG_PATTERN)
         `when`(view.shorterPattern).thenReturn(SHORT_PATTERN)
         `when`(view.handler).thenReturn(testableHandler)
@@ -99,6 +104,7 @@
         controller = VariableDateViewController(
                 systemClock,
                 broadcastDispatcher,
+                shadeExpansionStateManager,
                 testableHandler,
                 view
         )
@@ -121,7 +127,7 @@
 
     @Test
     fun testLotsOfSpaceUseLongText() {
-        onMeasureListenerCaptor.value.onMeasureAction(10000)
+        onMeasureListenerCaptor.value.onMeasureAction(10000, View.MeasureSpec.EXACTLY)
 
         testableLooper.processAllMessages()
         assertThat(lastText).isEqualTo(longText)
@@ -129,7 +135,7 @@
 
     @Test
     fun testSmallSpaceUseEmpty() {
-        onMeasureListenerCaptor.value.onMeasureAction(1)
+        onMeasureListenerCaptor.value.onMeasureAction(1, View.MeasureSpec.EXACTLY)
         testableLooper.processAllMessages()
 
         assertThat(lastText).isEmpty()
@@ -139,7 +145,7 @@
     fun testSpaceInBetweenUseShortText() {
         val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt()
 
-        onMeasureListenerCaptor.value.onMeasureAction(average)
+        onMeasureListenerCaptor.value.onMeasureAction(average, View.MeasureSpec.EXACTLY)
         testableLooper.processAllMessages()
 
         assertThat(lastText).isEqualTo(shortText)
@@ -147,10 +153,10 @@
 
     @Test
     fun testSwitchBackToLonger() {
-        onMeasureListenerCaptor.value.onMeasureAction(1)
+        onMeasureListenerCaptor.value.onMeasureAction(1, View.MeasureSpec.EXACTLY)
         testableLooper.processAllMessages()
 
-        onMeasureListenerCaptor.value.onMeasureAction(10000)
+        onMeasureListenerCaptor.value.onMeasureAction(10000, View.MeasureSpec.EXACTLY)
         testableLooper.processAllMessages()
 
         assertThat(lastText).isEqualTo(longText)
@@ -161,11 +167,41 @@
         `when`(view.freezeSwitching).thenReturn(true)
 
         val average = ((getTextLength(longText) + getTextLength(shortText)) / 2).toInt()
-        onMeasureListenerCaptor.value.onMeasureAction(average)
+        onMeasureListenerCaptor.value.onMeasureAction(average, View.MeasureSpec.EXACTLY)
         testableLooper.processAllMessages()
         assertThat(lastText).isEqualTo(longText)
 
-        onMeasureListenerCaptor.value.onMeasureAction(1)
+        onMeasureListenerCaptor.value.onMeasureAction(1, View.MeasureSpec.EXACTLY)
+        testableLooper.processAllMessages()
+        assertThat(lastText).isEqualTo(longText)
+    }
+
+    @Test
+    fun testQsExpansionTrue_ignoreAtMostMeasureRequests() {
+        shadeExpansionStateManager.onQsExpansionFractionChanged(0f)
+
+        onMeasureListenerCaptor.value.onMeasureAction(
+                getTextLength(shortText).toInt(),
+                View.MeasureSpec.EXACTLY
+            )
+        testableLooper.processAllMessages()
+
+        onMeasureListenerCaptor.value.onMeasureAction(10000, View.MeasureSpec.AT_MOST)
+        testableLooper.processAllMessages()
+        assertThat(lastText).isEqualTo(shortText)
+    }
+
+    @Test
+    fun testQsExpansionFalse_acceptAtMostMeasureRequests() {
+        shadeExpansionStateManager.onQsExpansionFractionChanged(1f)
+
+        onMeasureListenerCaptor.value.onMeasureAction(
+                getTextLength(shortText).toInt(),
+                View.MeasureSpec.EXACTLY
+        )
+        testableLooper.processAllMessages()
+
+        onMeasureListenerCaptor.value.onMeasureAction(10000, View.MeasureSpec.AT_MOST)
         testableLooper.processAllMessages()
         assertThat(lastText).isEqualTo(longText)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
index 2662da2..1404a4f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt
@@ -16,43 +16,128 @@
 
 package com.android.systemui.util
 
-import android.test.suitebuilder.annotation.SmallTest
-import androidx.test.runner.AndroidJUnit4
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
-class ListenerSetTest : SysuiTestCase() {
+open class ListenerSetTest : SysuiTestCase() {
 
-    var runnableSet: ListenerSet<Runnable> = ListenerSet()
+    private val runnableSet: IListenerSet<Runnable> = makeRunnableListenerSet()
 
-    @Before
-    fun setup() {
-        runnableSet = ListenerSet()
-    }
+    open fun makeRunnableListenerSet(): IListenerSet<Runnable> = ListenerSet()
 
     @Test
     fun addIfAbsent_doesNotDoubleAdd() {
         // setup & preconditions
         val runnable1 = Runnable { }
         val runnable2 = Runnable { }
-        assertThat(runnableSet.toList()).isEmpty()
+        assertThat(runnableSet).isEmpty()
 
         // Test that an element can be added
         assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
-        assertThat(runnableSet.toList()).containsExactly(runnable1)
+        assertThat(runnableSet).containsExactly(runnable1)
 
         // Test that a second element can be added
         assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
-        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
 
         // Test that re-adding the first element does nothing and returns false
         assertThat(runnableSet.addIfAbsent(runnable1)).isFalse()
-        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
+    }
+
+    @Test
+    fun isEmpty_changes() {
+        val runnable = Runnable { }
+        assertThat(runnableSet).isEmpty()
+        assertThat(runnableSet.isEmpty()).isTrue()
+        assertThat(runnableSet.isNotEmpty()).isFalse()
+
+        assertThat(runnableSet.addIfAbsent(runnable)).isTrue()
+        assertThat(runnableSet).isNotEmpty()
+        assertThat(runnableSet.isEmpty()).isFalse()
+        assertThat(runnableSet.isNotEmpty()).isTrue()
+
+        assertThat(runnableSet.remove(runnable)).isTrue()
+        assertThat(runnableSet).isEmpty()
+        assertThat(runnableSet.isEmpty()).isTrue()
+        assertThat(runnableSet.isNotEmpty()).isFalse()
+    }
+
+    @Test
+    fun size_changes() {
+        assertThat(runnableSet).isEmpty()
+        assertThat(runnableSet.size).isEqualTo(0)
+
+        assertThat(runnableSet.addIfAbsent(Runnable { })).isTrue()
+        assertThat(runnableSet.size).isEqualTo(1)
+
+        assertThat(runnableSet.addIfAbsent(Runnable { })).isTrue()
+        assertThat(runnableSet.size).isEqualTo(2)
+    }
+
+    @Test
+    fun contains_worksAsExpected() {
+        val runnable1 = Runnable { }
+        val runnable2 = Runnable { }
+        assertThat(runnableSet).isEmpty()
+        assertThat(runnable1 in runnableSet).isFalse()
+        assertThat(runnable2 in runnableSet).isFalse()
+        assertThat(runnableSet).doesNotContain(runnable1)
+        assertThat(runnableSet).doesNotContain(runnable2)
+
+        assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+        assertThat(runnable1 in runnableSet).isTrue()
+        assertThat(runnable2 in runnableSet).isFalse()
+        assertThat(runnableSet).contains(runnable1)
+        assertThat(runnableSet).doesNotContain(runnable2)
+
+        assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+        assertThat(runnable1 in runnableSet).isTrue()
+        assertThat(runnable2 in runnableSet).isTrue()
+        assertThat(runnableSet).contains(runnable1)
+        assertThat(runnableSet).contains(runnable2)
+
+        assertThat(runnableSet.remove(runnable1)).isTrue()
+        assertThat(runnable1 in runnableSet).isFalse()
+        assertThat(runnable2 in runnableSet).isTrue()
+        assertThat(runnableSet).doesNotContain(runnable1)
+        assertThat(runnableSet).contains(runnable2)
+    }
+
+    @Test
+    fun containsAll_worksAsExpected() {
+        val runnable1 = Runnable { }
+        val runnable2 = Runnable { }
+
+        assertThat(runnableSet).isEmpty()
+        assertThat(runnableSet.containsAll(listOf())).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable1))).isFalse()
+        assertThat(runnableSet.containsAll(listOf(runnable2))).isFalse()
+        assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isFalse()
+
+        assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+        assertThat(runnableSet.containsAll(listOf())).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable1))).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable2))).isFalse()
+        assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isFalse()
+
+        assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+        assertThat(runnableSet.containsAll(listOf())).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable1))).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable2))).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isTrue()
+
+        assertThat(runnableSet.remove(runnable1)).isTrue()
+        assertThat(runnableSet.containsAll(listOf())).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable1))).isFalse()
+        assertThat(runnableSet.containsAll(listOf(runnable2))).isTrue()
+        assertThat(runnableSet.containsAll(listOf(runnable1, runnable2))).isFalse()
     }
 
     @Test
@@ -60,22 +145,22 @@
         // setup and preconditions
         val runnable1 = Runnable { }
         val runnable2 = Runnable { }
-        assertThat(runnableSet.toList()).isEmpty()
+        assertThat(runnableSet).isEmpty()
         runnableSet.addIfAbsent(runnable1)
         runnableSet.addIfAbsent(runnable2)
-        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
 
         // Test that removing the first runnable only removes that one runnable
         assertThat(runnableSet.remove(runnable1)).isTrue()
-        assertThat(runnableSet.toList()).containsExactly(runnable2)
+        assertThat(runnableSet).containsExactly(runnable2)
 
         // Test that removing a non-present runnable does not error
         assertThat(runnableSet.remove(runnable1)).isFalse()
-        assertThat(runnableSet.toList()).containsExactly(runnable2)
+        assertThat(runnableSet).containsExactly(runnable2)
 
         // Test that removing the other runnable succeeds
         assertThat(runnableSet.remove(runnable2)).isTrue()
-        assertThat(runnableSet.toList()).isEmpty()
+        assertThat(runnableSet).isEmpty()
     }
 
     @Test
@@ -92,17 +177,17 @@
         val runnable2 = Runnable {
             runnablesCalled.add(2)
         }
-        assertThat(runnableSet.toList()).isEmpty()
+        assertThat(runnableSet).isEmpty()
         runnableSet.addIfAbsent(runnable1)
         runnableSet.addIfAbsent(runnable2)
-        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
 
         // Test that both runnables are called and 1 was removed
         for (runnable in runnableSet) {
             runnable.run()
         }
         assertThat(runnablesCalled).containsExactly(1, 2)
-        assertThat(runnableSet.toList()).containsExactly(runnable2)
+        assertThat(runnableSet).containsExactly(runnable2)
     }
 
     @Test
@@ -120,16 +205,16 @@
         val runnable2 = Runnable {
             runnablesCalled.add(2)
         }
-        assertThat(runnableSet.toList()).isEmpty()
+        assertThat(runnableSet).isEmpty()
         runnableSet.addIfAbsent(runnable1)
         runnableSet.addIfAbsent(runnable2)
-        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2)
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
 
         // Test that both original runnables are called and 99 was added but not called
         for (runnable in runnableSet) {
             runnable.run()
         }
         assertThat(runnablesCalled).containsExactly(1, 2)
-        assertThat(runnableSet.toList()).containsExactly(runnable1, runnable2, runnable99)
+        assertThat(runnableSet).containsExactly(runnable1, runnable2, runnable99)
     }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt
new file mode 100644
index 0000000..c89e317
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/NamedListenerSetTest.kt
@@ -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.systemui.util
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class NamedListenerSetTest : ListenerSetTest() {
+    override fun makeRunnableListenerSet(): IListenerSet<Runnable> = NamedListenerSet()
+
+    private val runnableSet = NamedListenerSet(NamedRunnable::name)
+
+    class NamedRunnable(val name: String, private val block: () -> Unit = {}) : Runnable {
+        override fun run() = block()
+    }
+
+    @Test
+    fun addIfAbsent_addsMultipleWithSameName_onlyIfInstanceIsAbsent() {
+        // setup & preconditions
+        val runnable1 = NamedRunnable("A")
+        val runnable2 = NamedRunnable("A")
+        assertThat(runnableSet).isEmpty()
+
+        // Test that an element can be added
+        assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+        assertThat(runnableSet).containsExactly(runnable1)
+
+        // Test that a second element can be added, even with the same name
+        assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
+
+        // Test that re-adding the first element does nothing and returns false
+        assertThat(runnableSet.addIfAbsent(runnable1)).isFalse()
+        assertThat(runnableSet).containsExactly(runnable1, runnable2)
+    }
+
+    @Test
+    fun forEachNamed_includesCorrectNames() {
+        val runnable1 = NamedRunnable("A")
+        val runnable2 = NamedRunnable("X")
+        val runnable3 = NamedRunnable("X")
+        assertThat(runnableSet).isEmpty()
+
+        assertThat(runnableSet.addIfAbsent(runnable1)).isTrue()
+        assertThat(runnableSet.toNamedPairs())
+            .containsExactly(
+                "A" to runnable1,
+            )
+
+        assertThat(runnableSet.addIfAbsent(runnable2)).isTrue()
+        assertThat(runnableSet.toNamedPairs())
+            .containsExactly(
+                "A" to runnable1,
+                "X" to runnable2,
+            )
+
+        assertThat(runnableSet.addIfAbsent(runnable3)).isTrue()
+        assertThat(runnableSet.toNamedPairs())
+            .containsExactly(
+                "A" to runnable1,
+                "X" to runnable2,
+                "X" to runnable3,
+            )
+
+        assertThat(runnableSet.remove(runnable1)).isTrue()
+        assertThat(runnableSet.toNamedPairs())
+            .containsExactly(
+                "X" to runnable2,
+                "X" to runnable3,
+            )
+
+        assertThat(runnableSet.remove(runnable2)).isTrue()
+        assertThat(runnableSet.toNamedPairs())
+            .containsExactly(
+                "X" to runnable3,
+            )
+    }
+
+    /**
+     * This private method uses [NamedListenerSet.forEachNamed] to produce a list of pairs in order
+     * to validate that method.
+     */
+    private fun <T : Any> NamedListenerSet<T>.toNamedPairs() =
+        sequence { forEachNamed { name, listener -> yield(name to listener) } }.toList()
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index c2e1ac7..7c98df6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -20,7 +20,8 @@
 import com.android.internal.widget.LockPatternView
 import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationResultModel
 import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -47,7 +48,7 @@
 
     private val _authenticationMethod =
         MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD)
-    val authenticationMethod: StateFlow<AuthenticationMethodModel> =
+    override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
         _authenticationMethod.asStateFlow()
 
     private var isLockscreenEnabled = true
@@ -154,13 +155,13 @@
         val DEFAULT_AUTHENTICATION_METHOD = AuthenticationMethodModel.Pin
         val PATTERN =
             listOf(
-                AuthenticationMethodModel.Pattern.PatternCoordinate(2, 0),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(2, 1),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(2, 2),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(1, 1),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(0, 0),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(0, 1),
-                AuthenticationMethodModel.Pattern.PatternCoordinate(0, 2),
+                AuthenticationPatternCoordinate(2, 0),
+                AuthenticationPatternCoordinate(2, 1),
+                AuthenticationPatternCoordinate(2, 2),
+                AuthenticationPatternCoordinate(1, 1),
+                AuthenticationPatternCoordinate(0, 0),
+                AuthenticationPatternCoordinate(0, 1),
+                AuthenticationPatternCoordinate(0, 2),
             )
         const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5
         const val THROTTLE_DURATION_MS = 30000
@@ -172,7 +173,6 @@
                 is AuthenticationMethodModel.Pin -> SecurityMode.PIN
                 is AuthenticationMethodModel.Password -> SecurityMode.Password
                 is AuthenticationMethodModel.Pattern -> SecurityMode.Pattern
-                is AuthenticationMethodModel.Swipe,
                 is AuthenticationMethodModel.None -> SecurityMode.None
             }
         }
@@ -208,8 +208,7 @@
             }
         }
 
-        private fun List<AuthenticationMethodModel.Pattern.PatternCoordinate>.toCells():
-            List<LockPatternView.Cell> {
+        private fun List<AuthenticationPatternCoordinate>.toCells(): List<LockPatternView.Cell> {
             return map { coordinate -> LockPatternView.Cell.of(coordinate.y, coordinate.x) }
         }
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
index 36fa7e6..013dbb4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt
@@ -19,45 +19,45 @@
 import java.io.PrintWriter
 
 class FakeFeatureFlags : FeatureFlags {
-    private val booleanFlags = mutableMapOf<Int, Boolean>()
-    private val stringFlags = mutableMapOf<Int, String>()
-    private val intFlags = mutableMapOf<Int, Int>()
-    private val knownFlagNames = mutableMapOf<Int, String>()
-    private val flagListeners = mutableMapOf<Int, MutableSet<FlagListenable.Listener>>()
-    private val listenerFlagIds = mutableMapOf<FlagListenable.Listener, MutableSet<Int>>()
+    private val booleanFlags = mutableMapOf<String, Boolean>()
+    private val stringFlags = mutableMapOf<String, String>()
+    private val intFlags = mutableMapOf<String, Int>()
+    private val knownFlagNames = mutableMapOf<String, String>()
+    private val flagListeners = mutableMapOf<String, MutableSet<FlagListenable.Listener>>()
+    private val listenerflagNames = mutableMapOf<FlagListenable.Listener, MutableSet<String>>()
 
     init {
         FlagsFactory.knownFlags.forEach { entry: Map.Entry<String, Flag<*>> ->
-            knownFlagNames[entry.value.id] = entry.key
+            knownFlagNames[entry.value.name] = entry.key
         }
     }
 
     fun set(flag: BooleanFlag, value: Boolean) {
-        if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+        if (booleanFlags.put(flag.name, value)?.let { value != it } != false) {
             notifyFlagChanged(flag)
         }
     }
 
     fun set(flag: ResourceBooleanFlag, value: Boolean) {
-        if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+        if (booleanFlags.put(flag.name, value)?.let { value != it } != false) {
             notifyFlagChanged(flag)
         }
     }
 
     fun set(flag: SysPropBooleanFlag, value: Boolean) {
-        if (booleanFlags.put(flag.id, value)?.let { value != it } != false) {
+        if (booleanFlags.put(flag.name, value)?.let { value != it } != false) {
             notifyFlagChanged(flag)
         }
     }
 
     fun set(flag: StringFlag, value: String) {
-        if (stringFlags.put(flag.id, value)?.let { value != it } == null) {
+        if (stringFlags.put(flag.name, value)?.let { value != it } == null) {
             notifyFlagChanged(flag)
         }
     }
 
     fun set(flag: ResourceStringFlag, value: String) {
-        if (stringFlags.put(flag.id, value)?.let { value != it } == null) {
+        if (stringFlags.put(flag.name, value)?.let { value != it } == null) {
             notifyFlagChanged(flag)
         }
     }
@@ -73,7 +73,7 @@
      *  and the flag value *does* matter, you'll notice when the flag is flipped and tests
      *  start failing.
      */
-    fun setDefault(flag: BooleanFlag) = booleanFlags.putIfAbsent(flag.id, flag.default)
+    fun setDefault(flag: BooleanFlag) = booleanFlags.putIfAbsent(flag.name, flag.default)
 
     /**
      * Set the given flag's default value if no other value has been set.
@@ -86,10 +86,10 @@
      *  and the flag value *does* matter, you'll notice when the flag is flipped and tests
      *  start failing.
      */
-    fun setDefault(flag: SysPropBooleanFlag) = booleanFlags.putIfAbsent(flag.id, flag.default)
+    fun setDefault(flag: SysPropBooleanFlag) = booleanFlags.putIfAbsent(flag.name, flag.default)
 
     private fun notifyFlagChanged(flag: Flag<*>) {
-        flagListeners[flag.id]?.let { listeners ->
+        flagListeners[flag.name]?.let { listeners ->
             listeners.forEach { listener ->
                 listener.onFlagChanged(
                     object : FlagListenable.FlagEvent {
@@ -101,30 +101,30 @@
         }
     }
 
-    override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.id)
+    override fun isEnabled(flag: UnreleasedFlag): Boolean = requireBooleanValue(flag.name)
 
-    override fun isEnabled(flag: ReleasedFlag): Boolean = requireBooleanValue(flag.id)
+    override fun isEnabled(flag: ReleasedFlag): Boolean = requireBooleanValue(flag.name)
 
-    override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.id)
+    override fun isEnabled(flag: ResourceBooleanFlag): Boolean = requireBooleanValue(flag.name)
 
-    override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.id)
+    override fun isEnabled(flag: SysPropBooleanFlag): Boolean = requireBooleanValue(flag.name)
 
-    override fun getString(flag: StringFlag): String = requireStringValue(flag.id)
+    override fun getString(flag: StringFlag): String = requireStringValue(flag.name)
 
-    override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.id)
+    override fun getString(flag: ResourceStringFlag): String = requireStringValue(flag.name)
 
-    override fun getInt(flag: IntFlag): Int = requireIntValue(flag.id)
+    override fun getInt(flag: IntFlag): Int = requireIntValue(flag.name)
 
-    override fun getInt(flag: ResourceIntFlag): Int = requireIntValue(flag.id)
+    override fun getInt(flag: ResourceIntFlag): Int = requireIntValue(flag.name)
 
     override fun addListener(flag: Flag<*>, listener: FlagListenable.Listener) {
-        flagListeners.getOrPut(flag.id) { mutableSetOf() }.add(listener)
-        listenerFlagIds.getOrPut(listener) { mutableSetOf() }.add(flag.id)
+        flagListeners.getOrPut(flag.name) { mutableSetOf() }.add(listener)
+        listenerflagNames.getOrPut(listener) { mutableSetOf() }.add(flag.name)
     }
 
     override fun removeListener(listener: FlagListenable.Listener) {
-        listenerFlagIds.remove(listener)?.let {
-                flagIds -> flagIds.forEach {
+        listenerflagNames.remove(listener)?.let {
+                flagNames -> flagNames.forEach {
                         id -> flagListeners[id]?.remove(listener)
                 }
         }
@@ -134,22 +134,22 @@
         // no-op
     }
 
-    private fun flagName(flagId: Int): String {
-        return knownFlagNames[flagId] ?: "UNKNOWN(id=$flagId)"
+    private fun flagName(flagName: String): String {
+        return knownFlagNames[flagName] ?: "UNKNOWN($flagName)"
     }
 
-    private fun requireBooleanValue(flagId: Int): Boolean {
-        return booleanFlags[flagId]
-            ?: error("Flag ${flagName(flagId)} was accessed as boolean but not specified.")
+    private fun requireBooleanValue(flagName: String): Boolean {
+        return booleanFlags[flagName]
+            ?: error("Flag ${flagName(flagName)} was accessed as boolean but not specified.")
     }
 
-    private fun requireStringValue(flagId: Int): String {
-        return stringFlags[flagId]
-            ?: error("Flag ${flagName(flagId)} was accessed as string but not specified.")
+    private fun requireStringValue(flagName: String): String {
+        return stringFlags[flagName]
+            ?: error("Flag ${flagName(flagName)} was accessed as string but not specified.")
     }
 
-    private fun requireIntValue(flagId: Int): Int {
-        return intFlags[flagId]
-            ?: error("Flag ${flagName(flagId)} was accessed as int but not specified.")
+    private fun requireIntValue(flagName: String): Int {
+        return intFlags[flagName]
+            ?: error("Flag ${flagName(flagName)} was accessed as int but not specified.")
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 15ce055..faebcaa 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -146,7 +146,6 @@
         _animateBottomAreaDozingTransitions.tryEmit(animate)
     }
 
-
     @Deprecated("Deprecated as part of b/278057014")
     override fun setBottomAreaAlpha(alpha: Float) {
         _bottomAreaAlpha.value = alpha
@@ -257,8 +256,11 @@
         goingToFullShade: Boolean,
         occlusionTransitionRunning: Boolean
     ) {
-        _keyguardRootViewVisibility.value = KeyguardRootViewVisibilityState(
-            statusBarState, goingToFullShade, occlusionTransitionRunning
-        )
+        _keyguardRootViewVisibility.value =
+            KeyguardRootViewVisibilityState(
+                statusBarState,
+                goingToFullShade,
+                occlusionTransitionRunning
+            )
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
new file mode 100644
index 0000000..823f29a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardSurfaceBehindRepository.kt
@@ -0,0 +1,29 @@
+/*
+ * 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 kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeKeyguardSurfaceBehindRepository : KeyguardSurfaceBehindRepository {
+    private val _isAnimatingSurface = MutableStateFlow(false)
+    override val isAnimatingSurface = _isAnimatingSurface.asStateFlow()
+
+    override fun setAnimatingSurface(animating: Boolean) {
+        _isAnimatingSurface.value = animating
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
index 312ade5..23faaf3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorFactory.kt
@@ -18,6 +18,8 @@
 
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.util.mockito.mock
+import dagger.Lazy
 import kotlinx.coroutines.CoroutineScope
 
 /**
@@ -30,18 +32,36 @@
     fun create(
         scope: CoroutineScope,
         repository: KeyguardTransitionRepository = FakeKeyguardTransitionRepository(),
+        keyguardInteractor: KeyguardInteractor =
+            KeyguardInteractorFactory.create().keyguardInteractor,
+        fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor> = Lazy {
+            mock<FromLockscreenTransitionInteractor>()
+        },
+        fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor> =
+            Lazy {
+                mock<FromPrimaryBouncerTransitionInteractor>()
+            },
     ): WithDependencies {
         return WithDependencies(
             repository = repository,
+            keyguardInteractor = keyguardInteractor,
+            fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+            fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
             KeyguardTransitionInteractor(
                 scope = scope,
                 repository = repository,
+                keyguardInteractor = { keyguardInteractor },
+                fromLockscreenTransitionInteractor = fromLockscreenTransitionInteractor,
+                fromPrimaryBouncerTransitionInteractor = fromPrimaryBouncerTransitionInteractor,
             )
         )
     }
 
     data class WithDependencies(
         val repository: KeyguardTransitionRepository,
+        val keyguardInteractor: KeyguardInteractor,
+        val fromLockscreenTransitionInteractor: Lazy<FromLockscreenTransitionInteractor>,
+        val fromPrimaryBouncerTransitionInteractor: Lazy<FromPrimaryBouncerTransitionInteractor>,
         val keyguardTransitionInteractor: KeyguardTransitionInteractor,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 6cffb66..dd45331 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -18,9 +18,11 @@
 
 import android.content.pm.UserInfo
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.model.AuthenticationMethodModel as DataLayerAuthenticationMethodModel
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.domain.model.AuthenticationMethodModel as DomainLayerAuthenticationMethodModel
 import com.android.systemui.bouncer.data.repository.BouncerRepository
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
@@ -32,7 +34,6 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.LockscreenSceneInteractor
 import com.android.systemui.keyguard.shared.model.WakeSleepReason
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
@@ -98,7 +99,7 @@
     fun fakeSceneContainerRepository(
         containerConfig: SceneContainerConfig = fakeSceneContainerConfig(),
     ): SceneContainerRepository {
-        return SceneContainerRepository(containerConfig)
+        return SceneContainerRepository(applicationScope(), containerConfig)
     }
 
     fun fakeSceneKeys(): List<SceneKey> {
@@ -124,6 +125,7 @@
         repository: SceneContainerRepository = fakeSceneContainerRepository()
     ): SceneInteractor {
         return SceneInteractor(
+            applicationScope = applicationScope(),
             repository = repository,
             logger = mock(),
         )
@@ -135,6 +137,7 @@
 
     fun authenticationInteractor(
         repository: AuthenticationRepository,
+        sceneInteractor: SceneInteractor = sceneInteractor(),
     ): AuthenticationInteractor {
         return AuthenticationInteractor(
             applicationScope = applicationScope(),
@@ -142,6 +145,7 @@
             backgroundDispatcher = testDispatcher,
             userRepository = userRepository,
             keyguardRepository = keyguardRepository,
+            sceneInteractor = sceneInteractor,
             clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
         )
     }
@@ -176,23 +180,14 @@
 
     fun bouncerViewModel(
         bouncerInteractor: BouncerInteractor,
+        authenticationInteractor: AuthenticationInteractor,
     ): BouncerViewModel {
         return BouncerViewModel(
             applicationContext = context,
             applicationScope = applicationScope(),
-            interactor = bouncerInteractor,
-            featureFlags = featureFlags,
-        )
-    }
-
-    fun lockScreenSceneInteractor(
-        authenticationInteractor: AuthenticationInteractor,
-        bouncerInteractor: BouncerInteractor,
-    ): LockscreenSceneInteractor {
-        return LockscreenSceneInteractor(
-            applicationScope = applicationScope(),
-            authenticationInteractor = authenticationInteractor,
             bouncerInteractor = bouncerInteractor,
+            authenticationInteractor = authenticationInteractor,
+            featureFlags = featureFlags,
         )
     }
 
@@ -209,5 +204,18 @@
                 RemoteUserInput(10f, 40f, RemoteUserInputAction.MOVE),
                 RemoteUserInput(10f, 40f, RemoteUserInputAction.UP),
             )
+
+        fun DomainLayerAuthenticationMethodModel.toDataLayer(): DataLayerAuthenticationMethodModel {
+            return when (this) {
+                DomainLayerAuthenticationMethodModel.None -> DataLayerAuthenticationMethodModel.None
+                DomainLayerAuthenticationMethodModel.Swipe ->
+                    DataLayerAuthenticationMethodModel.None
+                DomainLayerAuthenticationMethodModel.Pin -> DataLayerAuthenticationMethodModel.Pin
+                DomainLayerAuthenticationMethodModel.Password ->
+                    DataLayerAuthenticationMethodModel.Password
+                DomainLayerAuthenticationMethodModel.Pattern ->
+                    DataLayerAuthenticationMethodModel.Pattern
+            }
+        }
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
index 1403cea..3fd11a1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeDisplayTracker.kt
@@ -26,7 +26,7 @@
     override var defaultDisplayId: Int = Display.DEFAULT_DISPLAY
     override var allDisplays: Array<Display> = displayManager.displays
 
-    private val displayCallbacks: MutableList<DisplayTracker.Callback> = ArrayList()
+    val displayCallbacks: MutableList<DisplayTracker.Callback> = ArrayList()
     private val brightnessCallbacks: MutableList<DisplayTracker.Callback> = ArrayList()
     override fun addDisplayChangeCallback(callback: DisplayTracker.Callback, executor: Executor) {
         displayCallbacks.add(callback)
@@ -43,12 +43,12 @@
         brightnessCallbacks.remove(callback)
     }
 
-    fun setDefaultDisplay(displayId: Int) {
-        defaultDisplayId = displayId
+    override fun getDisplay(displayId: Int): Display {
+        return allDisplays.filter { display -> display.displayId == displayId }[0]
     }
 
-    fun setDisplays(displays: Array<Display>) {
-        allDisplays = displays
+    fun setDefaultDisplay(displayId: Int) {
+        defaultDisplayId = displayId
     }
 
     fun triggerOnDisplayAdded(displayId: Int) {
diff --git a/services/Android.bp b/services/Android.bp
index 53dc068..9264172 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -115,7 +115,6 @@
         ":services.profcollect-sources",
         ":services.restrictions-sources",
         ":services.searchui-sources",
-        ":services.selectiontoolbar-sources",
         ":services.smartspace-sources",
         ":services.soundtrigger-sources",
         ":services.systemcaptions-sources",
@@ -174,7 +173,6 @@
         "services.profcollect",
         "services.restrictions",
         "services.searchui",
-        "services.selectiontoolbar",
         "services.smartspace",
         "services.soundtrigger",
         "services.systemcaptions",
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 37abe1b..1827a5b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -54,8 +54,8 @@
 import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_SERVED_FROM_CACHED_RESPONSE;
 import static com.android.server.autofill.FillResponseEventLogger.AVAILABLE_COUNT_WHEN_FILL_REQUEST_FAILED_OR_TIMEOUT;
 import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_AUTOFILL_PROVIDER;
-import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_UNKNOWN;
 import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_PCC;
+import static com.android.server.autofill.FillResponseEventLogger.DETECTION_PREFER_UNKNOWN;
 import static com.android.server.autofill.FillResponseEventLogger.HAVE_SAVE_TRIGGER_ID;
 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_FAILURE;
 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SESSION_DESTROYED;
@@ -135,6 +135,7 @@
 import android.service.autofill.CompositeUserData;
 import android.service.autofill.Dataset;
 import android.service.autofill.Dataset.DatasetEligibleReason;
+import android.service.autofill.Field;
 import android.service.autofill.FieldClassification;
 import android.service.autofill.FieldClassification.Match;
 import android.service.autofill.FieldClassificationUserData;
@@ -190,6 +191,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -1539,6 +1541,17 @@
                 return;
             }
 
+            if (mSessionFlags.mShowingSaveUi) {
+                // Even though the session has not yet been destroyed at this point, after the
+                // saveUi gets closed, the session will be destroyed and AutofillManager will reset
+                // its state. Processing the fill request will result in a great chance of corrupt
+                // state in Autofill.
+                Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
+                        + id + " is showing saveUi");
+                mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED);
+                mFillResponseEventLogger.logAndEndEvent();
+                return;
+            }
 
             requestLog = mRequestLogs.get(requestId);
             if (requestLog != null) {
@@ -1817,11 +1830,11 @@
      */
     private static class DatasetComputationContainer {
         // List of all autofill ids that have a corresponding datasets
-        Set<AutofillId> mAutofillIds = new ArraySet<>();
+        Set<AutofillId> mAutofillIds = new LinkedHashSet<>();
         // Set of datasets. Kept separately, to be able to be used directly for composing
         // FillResponse.
         Set<Dataset> mDatasets = new LinkedHashSet<>();
-        ArrayMap<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new ArrayMap<>();
+        Map<AutofillId, Set<Dataset>> mAutofillIdToDatasetMap = new LinkedHashMap<>();
 
         public String toString() {
             final StringBuilder builder = new StringBuilder("DatasetComputationContainer[");
@@ -1859,7 +1872,7 @@
                 // for now to keep safe. TODO(b/266379948): Revisit this logic.
 
                 Set<Dataset> datasets = c2.mAutofillIdToDatasetMap.get(id);
-                Set<Dataset> copyDatasets = new ArraySet<>(datasets);
+                Set<Dataset> copyDatasets = new LinkedHashSet<>(datasets);
                 c1.mAutofillIds.add(id);
                 c1.mAutofillIdToDatasetMap.put(id, copyDatasets);
                 c1.mDatasets.addAll(copyDatasets);
@@ -1875,6 +1888,13 @@
         }
     }
 
+    /**
+     * Computes datasets that are eligible to be shown based on provider detections.
+     * Datasets are populated in the provided container for them to be later merged with the
+     * PCC eligible datasets based on preference strategy.
+     * @param response
+     * @param container
+     */
     private void computeDatasetsForProviderAndUpdateContainer(
             FillResponse response, DatasetComputationContainer container) {
         @DatasetEligibleReason int globalPickReason = PICK_REASON_UNKNOWN;
@@ -1886,9 +1906,9 @@
         }
         List<Dataset> datasets = response.getDatasets();
         if (datasets == null) return;
-        ArrayMap<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new ArrayMap<>();
+        Map<AutofillId, Set<Dataset>> autofillIdToDatasetMap = new LinkedHashMap<>();
         Set<Dataset> eligibleDatasets = new LinkedHashSet<>();
-        Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
+        Set<AutofillId> eligibleAutofillIds = new LinkedHashSet<>();
         for (Dataset dataset : response.getDatasets()) {
             if (dataset.getFieldIds() == null || dataset.getFieldIds().isEmpty()) continue;
             @DatasetEligibleReason int pickReason = globalPickReason;
@@ -1964,7 +1984,7 @@
                 eligibleAutofillIds.add(id);
                 Set<Dataset> datasetForIds = autofillIdToDatasetMap.get(id);
                 if (datasetForIds == null) {
-                    datasetForIds = new ArraySet<>();
+                    datasetForIds = new LinkedHashSet<>();
                 }
                 datasetForIds.add(dataset);
                 autofillIdToDatasetMap.put(id, datasetForIds);
@@ -1975,23 +1995,30 @@
         container.mAutofillIds = eligibleAutofillIds;
     }
 
+    /**
+     * Computes datasets that are eligible to be shown based on PCC detections.
+     * Datasets are populated in the provided container for them to be later merged with the
+     * provider eligible datasets based on preference strategy.
+     * @param response
+     * @param container
+     */
     private void computeDatasetsForPccAndUpdateContainer(
             FillResponse response, DatasetComputationContainer container) {
         List<Dataset> datasets = response.getDatasets();
         if (datasets == null) return;
 
         synchronized (mLock) {
-            ArrayMap<String, Set<AutofillId>> hintsToAutofillIdMap =
+            Map<String, Set<AutofillId>> hintsToAutofillIdMap =
                     mClassificationState.mHintsToAutofillIdMap;
 
             // TODO(266379948): Handle group hints too.
-            ArrayMap<String, Set<AutofillId>> groupHintsToAutofillIdMap =
+            Map<String, Set<AutofillId>> groupHintsToAutofillIdMap =
                     mClassificationState.mGroupHintsToAutofillIdMap;
 
-            ArrayMap<AutofillId, Set<Dataset>> map = new ArrayMap<>();
+            Map<AutofillId, Set<Dataset>> map = new LinkedHashMap<>();
 
             Set<Dataset> eligibleDatasets = new LinkedHashSet<>();
-            Set<AutofillId> eligibleAutofillIds = new ArraySet<>();
+            Set<AutofillId> eligibleAutofillIds = new LinkedHashSet<>();
 
             for (int i = 0; i < datasets.size(); i++) {
 
@@ -2007,13 +2034,35 @@
                 ArrayList<InlinePresentation> fieldInlinePresentations = new ArrayList<>();
                 ArrayList<InlinePresentation> fieldInlineTooltipPresentations = new ArrayList<>();
                 ArrayList<Dataset.DatasetFieldFilter> fieldFilters = new ArrayList<>();
-                Set<AutofillId> datasetAutofillIds = new ArraySet<>();
+                Set<AutofillId> datasetAutofillIds = new LinkedHashSet<>();
+
+                boolean isDatasetAvailable = false;
+                Set<AutofillId> additionalDatasetAutofillIds = new LinkedHashSet<>();
+                Set<AutofillId> additionalEligibleAutofillIds = new LinkedHashSet<>();
 
                 for (int j = 0; j < dataset.getAutofillDatatypes().size(); j++) {
                     if (dataset.getAutofillDatatypes().get(j) == null) {
+                        // TODO : revisit pickReason logic
                         if (dataset.getFieldIds() != null && dataset.getFieldIds().get(j) != null) {
                             pickReason = PICK_REASON_PCC_DETECTION_PREFERRED_WITH_PROVIDER;
                         }
+                        // Check if the autofill id at this index is detected by PCC.
+                        // If not, add that id here, otherwise, we can have duplicates when later
+                        // merging with provider datasets.
+                        // Howover, this doesn't make datasetAvailable for PCC on its own.
+                        // For that, there has to be a datatype detected by PCC, and the dataset
+                        // for that datatype provided by the provider.
+                        AutofillId autofillId = dataset.getFieldIds().get(j);
+                        if (!mClassificationState.mClassificationCombinedHintsMap
+                                .containsKey(autofillId)) {
+                            additionalEligibleAutofillIds.add(autofillId);
+                            additionalDatasetAutofillIds.add(autofillId);
+                            // For each of the field, copy over values.
+                            copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
+                                    fieldPresentations, fieldDialogPresentations,
+                                    fieldInlinePresentations, fieldInlineTooltipPresentations,
+                                    fieldFilters);
+                        }
                         continue;
                     }
                     String hint = dataset.getAutofillDatatypes().get(j);
@@ -2021,22 +2070,18 @@
                     if (hintsToAutofillIdMap.containsKey(hint)) {
                         ArrayList<AutofillId> tempIds =
                                 new ArrayList<>(hintsToAutofillIdMap.get(hint));
-
+                        if (tempIds.isEmpty()) {
+                            continue;
+                        }
+                        isDatasetAvailable = true;
                         for (AutofillId autofillId : tempIds) {
                             eligibleAutofillIds.add(autofillId);
                             datasetAutofillIds.add(autofillId);
                             // For each of the field, copy over values.
-                            fieldIds.add(autofillId);
-                            fieldValues.add(dataset.getFieldValues().get(j));
-                            //  TODO(b/266379948): might need to make it more efficient by not
-                            //  copying over value if it didn't exist. This would require creating
-                            //  a getter for the presentations arraylist.
-                            fieldPresentations.add(dataset.getFieldPresentation(j));
-                            fieldDialogPresentations.add(dataset.getFieldDialogPresentation(j));
-                            fieldInlinePresentations.add(dataset.getFieldInlinePresentation(j));
-                            fieldInlineTooltipPresentations.add(
-                                    dataset.getFieldInlineTooltipPresentation(j));
-                            fieldFilters.add(dataset.getFilter(j));
+                            copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues,
+                                    fieldPresentations, fieldDialogPresentations,
+                                    fieldInlinePresentations, fieldInlineTooltipPresentations,
+                                    fieldFilters);
                         }
                     }
                     // TODO(b/266379948):  handle the case:
@@ -2045,34 +2090,38 @@
                     // TODO(b/266379948):  also handle the case where there could be more types in
                     // the dataset, provided by the provider, however, they aren't applicable.
                 }
-                Dataset newDataset =
-                        new Dataset(
-                                fieldIds,
-                                fieldValues,
-                                fieldPresentations,
-                                fieldDialogPresentations,
-                                fieldInlinePresentations,
-                                fieldInlineTooltipPresentations,
-                                fieldFilters,
-                                new ArrayList<>(),
-                                dataset.getFieldContent(),
-                                null,
-                                null,
-                                null,
-                                null,
-                                dataset.getId(),
-                                dataset.getAuthentication());
-                newDataset.setEligibleReasonReason(pickReason);
-                eligibleDatasets.add(newDataset);
-                Set<Dataset> newDatasets;
-                for (AutofillId autofillId : datasetAutofillIds) {
-                    if (map.containsKey(autofillId)) {
-                        newDatasets = map.get(autofillId);
-                    } else {
-                        newDatasets = new ArraySet<>();
+                if (isDatasetAvailable) {
+                    datasetAutofillIds.addAll(additionalDatasetAutofillIds);
+                    eligibleAutofillIds.addAll(additionalEligibleAutofillIds);
+                    Dataset newDataset =
+                            new Dataset(
+                                    fieldIds,
+                                    fieldValues,
+                                    fieldPresentations,
+                                    fieldDialogPresentations,
+                                    fieldInlinePresentations,
+                                    fieldInlineTooltipPresentations,
+                                    fieldFilters,
+                                    new ArrayList<>(),
+                                    dataset.getFieldContent(),
+                                    null,
+                                    null,
+                                    null,
+                                    null,
+                                    dataset.getId(),
+                                    dataset.getAuthentication());
+                    newDataset.setEligibleReasonReason(pickReason);
+                    eligibleDatasets.add(newDataset);
+                    Set<Dataset> newDatasets;
+                    for (AutofillId autofillId : datasetAutofillIds) {
+                        if (map.containsKey(autofillId)) {
+                            newDatasets = map.get(autofillId);
+                        } else {
+                            newDatasets = new LinkedHashSet<>();
+                        }
+                        newDatasets.add(newDataset);
+                        map.put(autofillId, newDatasets);
                     }
-                    newDatasets.add(newDataset);
-                    map.put(autofillId, newDatasets);
                 }
             }
             container.mAutofillIds = eligibleAutofillIds;
@@ -2081,6 +2130,31 @@
         }
     }
 
+    private void copyFieldsFromDataset(
+            Dataset dataset,
+            int index,
+            AutofillId autofillId,
+            ArrayList<AutofillId> fieldIds,
+            ArrayList<AutofillValue> fieldValues,
+            ArrayList<RemoteViews> fieldPresentations,
+            ArrayList<RemoteViews> fieldDialogPresentations,
+            ArrayList<InlinePresentation> fieldInlinePresentations,
+            ArrayList<InlinePresentation> fieldInlineTooltipPresentations,
+            ArrayList<Dataset.DatasetFieldFilter> fieldFilters) {
+        // copy over values
+        fieldIds.add(autofillId);
+        fieldValues.add(dataset.getFieldValues().get(index));
+        //  TODO(b/266379948): might need to make it more efficient by not
+        //  copying over value if it didn't exist. This would require creating
+        //  a getter for the presentations arraylist.
+        fieldPresentations.add(dataset.getFieldPresentation(index));
+        fieldDialogPresentations.add(dataset.getFieldDialogPresentation(index));
+        fieldInlinePresentations.add(dataset.getFieldInlinePresentation(index));
+        fieldInlineTooltipPresentations.add(
+                dataset.getFieldInlineTooltipPresentation(index));
+        fieldFilters.add(dataset.getFilter(index));
+    }
+
     @GuardedBy("mLock")
     private void processNullResponseOrFallbackLocked(int requestId, int flags) {
         if (!mSessionFlags.mClientSuggestionsEnabled) {
@@ -2649,10 +2723,7 @@
                     if (sDebug) Slog.d(TAG,  "Updating client state from auth dataset");
                     mClientState = newClientState;
                 }
-                Dataset dataset = (Dataset) result;
-                FillResponse temp = new FillResponse.Builder().addDataset(dataset).build();
-                temp = getEffectiveFillResponse(temp);
-                dataset = temp.getDatasets().get(0);
+                Dataset dataset = getEffectiveDatasetForAuthentication((Dataset) result);
                 final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx);
                 if (!isAuthResultDatasetEphemeral(oldDataset, data)) {
                     authenticatedResponse.getDatasets().set(datasetIdx, dataset);
@@ -2678,6 +2749,39 @@
         }
     }
 
+    Dataset getEffectiveDatasetForAuthentication(Dataset authenticatedDataset) {
+        FillResponse response = new FillResponse.Builder().addDataset(authenticatedDataset).build();
+        response = getEffectiveFillResponse(response);
+        if (DBG) {
+            Slog.d(TAG, "DBG: authenticated effective response: " + response);
+        }
+        if (response == null || response.getDatasets().size() == 0) {
+            Log.wtf(TAG, "No datasets in fill response on authentication. response = "
+                    + (response == null ? "null" : response.toString()));
+            return authenticatedDataset;
+        }
+        List<Dataset> datasets = response.getDatasets();
+        Dataset result = response.getDatasets().get(0);
+        if (datasets.size() > 1) {
+            Dataset.Builder builder = new Dataset.Builder();
+            for (Dataset dataset : datasets) {
+                if (!dataset.getFieldIds().isEmpty()) {
+                    for (int i = 0; i < dataset.getFieldIds().size(); i++) {
+                        builder.setField(dataset.getFieldIds().get(i),
+                                new Field.Builder().setValue(dataset.getFieldValues().get(i))
+                                        .build());
+                    }
+                }
+            }
+            result = builder.setId(authenticatedDataset.getId()).build();
+        }
+
+        if (DBG) {
+            Slog.d(TAG, "DBG: authenticated effective dataset after auth: " + result);
+        }
+        return result;
+    }
+
     /**
      * Returns whether the dataset returned from the authentication result is ephemeral or not.
      * See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more
@@ -4204,7 +4308,15 @@
                     // We don't send an empty response to IME so that it doesn't cause UI flicker
                     // on the IME side if it arrives before the input view is finished on the IME.
                     mInlineSessionController.resetInlineFillUiLocked();
-                    mCurrentViewId = null;
+
+                    if ((viewState.getState() &
+                            ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) != 0) {
+                        // View was exited before Inline Request sent back, do not set it to
+                        // null yet to let onHandleAssistData finish processing
+                    } else {
+                        mCurrentViewId = null;
+                    }
+
 
                     mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
                                 NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 631b453..fd45d24 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -281,9 +281,9 @@
         final long timestamp = System.currentTimeMillis();
 
         final AssociationInfo association = new AssociationInfo(id, userId, packageName,
-                macAddress, displayName, deviceProfile, associatedDevice, selfManaged,
-                /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE,
-                /* systemDataSyncFlags */ 0);
+                /* tag */ null, macAddress, displayName, deviceProfile, associatedDevice,
+                selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
+                timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
 
         if (deviceProfile != null) {
             // If the "Device Profile" is specified, make the companion application a holder of the
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index c5501f1..996c68b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -668,7 +668,6 @@
             addOnAssociationsChangedListener_enforcePermission();
 
             enforceCallerIsSystemOrCanInteractWithUserId(getContext(), userId);
-
             mListeners.register(listener, userId);
         }
 
@@ -1015,6 +1014,29 @@
         }
 
         @Override
+        public void setAssociationTag(int associationId, String tag) {
+            AssociationInfo association = getAssociationWithCallerChecks(associationId);
+            association = AssociationInfo.builder(association).setTag(tag).build();
+            mAssociationStore.updateAssociation(association);
+        }
+
+        @Override
+        public void clearAssociationTag(int associationId) {
+            setAssociationTag(associationId, null);
+        }
+
+        @Override
+        public byte[] getBackupPayload(int userId) {
+            // TODO(b/286124853): back up CDM data
+            return new byte[0];
+        }
+
+        @Override
+        public void applyRestoredPayload(byte[] payload, int userId) {
+            // TODO(b/286124853): restore CDM data
+        }
+
+        @Override
         public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
                 String[] args, ShellCallback callback, ResultReceiver resultReceiver)
                 throws RemoteException {
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index 54798f4..b4b9379 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -171,6 +171,7 @@
     private static final String XML_TAG_ASSOCIATION = "association";
     private static final String XML_TAG_PREVIOUSLY_USED_IDS = "previously-used-ids";
     private static final String XML_TAG_PACKAGE = "package";
+    private static final String XML_TAG_TAG = "tag";
     private static final String XML_TAG_ID = "id";
 
     private static final String XML_ATTR_PERSISTENCE_VERSION = "persistence-version";
@@ -421,6 +422,7 @@
         requireStartOfTag(parser, XML_TAG_ASSOCIATION);
 
         final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
+        final String tag = readStringAttribute(parser, XML_TAG_TAG);
         final String deviceAddress = readStringAttribute(parser, LEGACY_XML_ATTR_DEVICE);
 
         if (appPackage == null || deviceAddress == null) return;
@@ -429,7 +431,7 @@
         final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
         final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
 
-        out.add(new AssociationInfo(associationId, userId, appPackage,
+        out.add(new AssociationInfo(associationId, userId, appPackage, tag,
                 MacAddress.fromString(deviceAddress), null, profile, null,
                 /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved,
                 Long.MAX_VALUE, /* systemDataSyncFlags */ 0));
@@ -456,6 +458,7 @@
         final int associationId = readIntAttribute(parser, XML_ATTR_ID);
         final String profile = readStringAttribute(parser, XML_ATTR_PROFILE);
         final String appPackage = readStringAttribute(parser, XML_ATTR_PACKAGE);
+        final String tag = readStringAttribute(parser, XML_TAG_TAG);
         final MacAddress macAddress = stringToMacAddress(
                 readStringAttribute(parser, XML_ATTR_MAC_ADDRESS));
         final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME);
@@ -469,7 +472,7 @@
                 XML_ATTR_SYSTEM_DATA_SYNC_FLAGS, 0);
 
         final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
-                appPackage, macAddress, displayName, profile, selfManaged, notify, revoked,
+                appPackage, tag, macAddress, displayName, profile, selfManaged, notify, revoked,
                 timeApproved, lastTimeConnected, systemDataSyncFlags);
         if (associationInfo != null) {
             out.add(associationInfo);
@@ -518,6 +521,7 @@
         writeIntAttribute(serializer, XML_ATTR_ID, a.getId());
         writeStringAttribute(serializer, XML_ATTR_PROFILE, a.getDeviceProfile());
         writeStringAttribute(serializer, XML_ATTR_PACKAGE, a.getPackageName());
+        writeStringAttribute(serializer, XML_TAG_TAG, a.getTag());
         writeStringAttribute(serializer, XML_ATTR_MAC_ADDRESS, a.getDeviceMacAddressAsString());
         writeStringAttribute(serializer, XML_ATTR_DISPLAY_NAME, a.getDisplayName());
         writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
@@ -565,17 +569,17 @@
     }
 
     private static AssociationInfo createAssociationInfoNoThrow(int associationId,
-            @UserIdInt int userId, @NonNull String appPackage, @Nullable MacAddress macAddress,
-            @Nullable CharSequence displayName, @Nullable String profile, boolean selfManaged,
-            boolean notify, boolean revoked, long timeApproved, long lastTimeConnected,
-            int systemDataSyncFlags) {
+            @UserIdInt int userId, @NonNull String appPackage, @Nullable String tag,
+            @Nullable MacAddress macAddress, @Nullable CharSequence displayName,
+            @Nullable String profile, boolean selfManaged, boolean notify, boolean revoked,
+            long timeApproved, long lastTimeConnected, int systemDataSyncFlags) {
         AssociationInfo associationInfo = null;
         try {
             // We do not persist AssociatedDevice, which means that AssociationInfo retrieved from
             // datastore is not guaranteed to be identical to the one from initial association.
-            associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress,
-                    displayName, profile, null, selfManaged, notify, revoked,
-                    timeApproved, lastTimeConnected, systemDataSyncFlags);
+            associationInfo = new AssociationInfo(associationId, userId, appPackage, tag,
+                    macAddress, displayName, profile, null, selfManaged, notify,
+                    revoked, timeApproved, lastTimeConnected, systemDataSyncFlags);
         } catch (Exception e) {
             if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
         }
diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
index f45a1c4..8fea078 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -281,43 +281,30 @@
             case DEVICE_EVENT_BLE_APPEARED:
             case DEVICE_EVENT_BT_CONNECTED:
             case DEVICE_EVENT_SELF_MANAGED_APPEARED:
-                final boolean alreadyPresent = isDevicePresent(associationId);
                 final boolean added = presentDevicesForSource.add(associationId);
+
                 if (!added) {
                     Slog.w(TAG, "Association with id "
                             + associationId + " is ALREADY reported as "
                             + "present by this source, event=" + event);
-                    return;
                 }
-                // For backward compatibility, do not send the onDeviceAppeared() callback
-                // if it already reported BLE device status.
-                if (event == DEVICE_EVENT_BT_CONNECTED && alreadyPresent) {
-                    Slog.i(TAG, "Ignore sending onDeviceAppeared callback, "
-                            + "device id (" + associationId + ") already present.");
-                } else {
-                    mCallback.onDeviceAppeared(associationId);
-                }
+
+                mCallback.onDeviceAppeared(associationId);
 
                 break;
             case DEVICE_EVENT_BLE_DISAPPEARED:
             case DEVICE_EVENT_BT_DISCONNECTED:
             case DEVICE_EVENT_SELF_MANAGED_DISAPPEARED:
                 final boolean removed = presentDevicesForSource.remove(associationId);
+
                 if (!removed) {
-                    Log.w(TAG, "Association with id " + associationId + " was NOT reported "
+                    Slog.w(TAG, "Association with id " + associationId + " was NOT reported "
                             + "as present by this source, event= " + event);
 
                     return;
                 }
-                final boolean stillPresent = isDevicePresent(associationId);
-                // For backward compatibility, do not send the onDeviceDisappeared()
-                // callback when ble scan still presenting.
-                if (stillPresent) {
-                    Slog.i(TAG, "Ignore sending onDeviceDisappeared callback, "
-                            + "device id (" + associationId + ") is still present.");
-                } else {
-                    mCallback.onDeviceDisappeared(associationId);
-                }
+
+                mCallback.onDeviceDisappeared(associationId);
 
                 break;
             default:
diff --git a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
index d862a54..253ef43 100644
--- a/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
+++ b/services/companion/java/com/android/server/companion/virtual/TEST_MAPPING
@@ -33,16 +33,27 @@
       ]
     },
     {
-      "name": "CtsVirtualDevicesTestCases",
+      "name": "CtsHardwareTestCases",
       "options": [
         {
           "include-filter": "android.hardware.input.cts.tests"
         },
         {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
         }
       ],
       "file_patterns": ["Virtual[^/]*\\.java"]
+    },
+    {
+      "name": "CtsAccessibilityServiceTestCases",
+      "options": [
+        {
+          "include-filter": "android.accessibilityservice.cts.AccessibilityDisplayProxyTest"
+        },
+        {
+          "exclude-annotation": "android.support.test.filters.FlakyTest"
+        }
+      ]
     }
   ]
 }
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 402fbb8..ee6c28e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -106,9 +106,7 @@
     private static final String TAG = "VirtualDeviceImpl";
 
     private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS =
-            DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
-                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
-                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+            DisplayManager.VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 9f263c8..c2774e5 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -98,7 +98,10 @@
 
 java_library_static {
     name: "services.core.unboosted",
-    defaults: ["platform_service_defaults"],
+    defaults: [
+        "platform_service_defaults",
+        "android.hardware.power-java_static",
+    ],
     srcs: [
         ":android.hardware.biometrics.face-V3-java-source",
         ":android.hardware.tv.hdmi.connection-V1-java-source",
@@ -152,7 +155,7 @@
         "android.hardware.boot-V1.0-java", // HIDL
         "android.hardware.boot-V1.1-java", // HIDL
         "android.hardware.boot-V1.2-java", // HIDL
-        "android.hardware.boot-V1-java",   // AIDL
+        "android.hardware.boot-V1-java", // AIDL
         "android.hardware.broadcastradio-V2.0-java", // HIDL
         "android.hardware.broadcastradio-V1-java", // AIDL
         "android.hardware.health-V1.0-java", // HIDL
@@ -176,7 +179,6 @@
         "android.hardware.ir-V1-java",
         "android.hardware.rebootescrow-V1-java",
         "android.hardware.power.stats-V2-java",
-        "android.hardware.power-V4-java",
         "android.hidl.manager-V1.2-java",
         "cbor-java",
         "icu4j_calendar_astronomer",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 846283c..fc51e2e 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1369,6 +1369,13 @@
     public abstract void setPackageStoppedState(@NonNull String packageName, boolean stopped,
             @UserIdInt int userId);
 
+    /**
+     * Tells PackageManager when a component (except BroadcastReceivers) of the package is used
+     * and the package should get out of stopped state and be enabled.
+     */
+    public abstract void notifyComponentUsed(@NonNull String packageName,
+            @UserIdInt int userId, @NonNull String recentCallingPackage);
+
     /** @deprecated For legacy shell command only. */
     @Deprecated
     public abstract void legacyDumpProfiles(@NonNull String packageName,
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 41cca49..03022b0 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -1,7 +1,13 @@
 {
     "presubmit": [
         {
-            "name": "CtsLocationFineTestCases"
+            "name": "CtsLocationFineTestCases",
+            "options": [
+                {
+                    // TODO: Wait for test to deflake - b/293934372
+                    "exclude-filter":"android.location.cts.fine.ScanningSettingsTest"
+                }
+             ]
         },
         {
             "name": "CtsLocationCoarseTestCases"
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index da2588b..cb246f6 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2501,12 +2501,12 @@
                         FGS_STOP_REASON_STOP_FOREGROUND,
                         FGS_TYPE_POLICY_CHECK_UNKNOWN);
 
-                // foregroundServiceType is used in logFGSStateChangeLocked(), so we can't clear it
-                // earlier.
-                r.foregroundServiceType = 0;
                 synchronized (mFGSLogger) {
                     mFGSLogger.logForegroundServiceStop(r.appInfo.uid, r);
                 }
+                // foregroundServiceType is used in logFGSStateChangeLocked(), so we can't clear it
+                // earlier.
+                r.foregroundServiceType = 0;
                 r.mFgsNotificationWasDeferred = false;
                 signalForegroundServiceObserversLocked(r);
                 resetFgsRestrictionLocked(r);
@@ -5069,7 +5069,7 @@
             boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen,
             boolean enqueueOomAdj)
             throws TransactionTooLargeException {
-        if (r.app != null && r.app.getThread() != null) {
+        if (r.app != null && r.app.isThreadReady()) {
             sendServiceArgsLocked(r, execInFg, false);
             return null;
         }
@@ -5116,10 +5116,9 @@
                     r.packageName, r.userId, UsageEvents.Event.APP_COMPONENT_USED);
         }
 
-        // Service is now being launched, its package can't be stopped.
         try {
-            mAm.mPackageManagerInt.setPackageStoppedState(
-                    r.packageName, false, r.userId);
+            mAm.mPackageManagerInt.notifyComponentUsed(
+                    r.packageName, r.userId, r.mRecentCallingPackage);
         } catch (IllegalArgumentException e) {
             Slog.w(TAG, "Failed trying to unstop package "
                     + r.packageName + ": " + e);
@@ -5141,7 +5140,7 @@
                 final IApplicationThread thread = app.getThread();
                 final int pid = app.getPid();
                 final UidRecord uidRecord = app.getUidRecord();
-                if (thread != null) {
+                if (app.isThreadReady()) {
                     try {
                         if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
@@ -5173,7 +5172,7 @@
                     final int pid = app.getPid();
                     final UidRecord uidRecord = app.getUidRecord();
                     r.isolationHostProc = app;
-                    if (thread != null) {
+                    if (app.isThreadReady()) {
                         try {
                             if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
                                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
@@ -5573,7 +5572,7 @@
 
         boolean oomAdjusted = false;
         // Tell the service that it has been unbound.
-        if (r.app != null && r.app.getThread() != null) {
+        if (r.app != null && r.app.isThreadReady()) {
             for (int i = r.bindings.size() - 1; i >= 0; i--) {
                 IntentBindRecord ibr = r.bindings.valueAt(i);
                 if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Bringing down binding " + ibr
@@ -5715,7 +5714,7 @@
             mAm.mBatteryStatsService.noteServiceStopLaunch(r.appInfo.uid, r.name.getPackageName(),
                     r.name.getClassName());
             stopServiceAndUpdateAllowlistManagerLocked(r);
-            if (r.app.getThread() != null) {
+            if (r.app.isThreadReady()) {
                 // Bump the process to the top of LRU list
                 mAm.updateLruProcessLocked(r.app, false, null);
                 updateServiceForegroundLocked(r.app.mServices, false);
@@ -5879,7 +5878,7 @@
         if (!c.serviceDead) {
             if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Disconnecting binding " + b.intent
                     + ": shouldUnbind=" + b.intent.hasBound);
-            if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0
+            if (s.app != null && s.app.isThreadReady() && b.intent.apps.size() == 0
                     && b.intent.hasBound) {
                 try {
                     bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE);
@@ -6381,7 +6380,7 @@
                     sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true,
                             sr.getLastStartId(), baseIntent, null, 0, null, null,
                             ActivityManager.PROCESS_STATE_UNKNOWN));
-                    if (sr.app != null && sr.app.getThread() != null) {
+                    if (sr.app != null && sr.app.isThreadReady()) {
                         // We always run in the foreground, since this is called as
                         // part of the "remove task" UI operation.
                         try {
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 3fd7d7e..c20f0aa 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1032,7 +1032,7 @@
     private static final String KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION =
             "enable_wait_for_finish_attach_application";
 
-    private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = false;
+    private static final boolean DEFAULT_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION = true;
 
     /** @see #KEY_ENABLE_WAIT_FOR_FINISH_ATTACH_APPLICATION */
     public volatile boolean mEnableWaitForFinishAttachApplication =
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c1f2f67..a53c2fb 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3095,6 +3095,22 @@
         }
     }
 
+    /**
+     * Enforces that the uid of the caller matches the uid of the package.
+     *
+     * @param packageName the name of the package to match uid against.
+     * @param callingUid the uid of the caller.
+     * @throws SecurityException if the calling uid doesn't match uid of the package.
+     */
+    private void enforceCallingPackage(String packageName, int callingUid) {
+        final int userId = UserHandle.getUserId(callingUid);
+        final int packageUid = getPackageManagerInternal().getPackageUid(packageName,
+                /*flags=*/ 0, userId);
+        if (packageUid != callingUid) {
+            throw new SecurityException(packageName + " does not belong to uid " + callingUid);
+        }
+    }
+
     @Override
     public void setPackageScreenCompatMode(String packageName, int mode) {
         mActivityTaskManager.setPackageScreenCompatMode(packageName, mode);
@@ -13704,13 +13720,16 @@
     // A backup agent has just come up
     @Override
     public void backupAgentCreated(String agentPackageName, IBinder agent, int userId) {
+        final int callingUid = Binder.getCallingUid();
+        enforceCallingPackage(agentPackageName, callingUid);
+
         // Resolve the target user id and enforce permissions.
-        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
+        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), callingUid,
                 userId, /* allowAll */ false, ALLOW_FULL_ONLY, "backupAgentCreated", null);
         if (DEBUG_BACKUP) {
             Slog.v(TAG_BACKUP, "backupAgentCreated: " + agentPackageName + " = " + agent
                     + " callingUserId = " + UserHandle.getCallingUserId() + " userId = " + userId
-                    + " callingUid = " + Binder.getCallingUid() + " uid = " + Process.myUid());
+                    + " callingUid = " + callingUid + " uid = " + Process.myUid());
         }
 
         synchronized(this) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index c0ac1f8..d3bfc9a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -764,37 +764,37 @@
                     out.println(
                             "Error: Activity not started, unable to "
                                     + "resolve " + intent.toString());
-                    break;
+                    return 1;
                 case ActivityManager.START_CLASS_NOT_FOUND:
                     out.println(NO_CLASS_ERROR_CODE);
                     out.println("Error: Activity class " +
                             intent.getComponent().toShortString()
                             + " does not exist.");
-                    break;
+                    return 1;
                 case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
                     out.println(
                             "Error: Activity not started, you requested to "
                                     + "both forward and receive its result");
-                    break;
+                    return 1;
                 case ActivityManager.START_PERMISSION_DENIED:
                     out.println(
                             "Error: Activity not started, you do not "
                                     + "have permission to access it.");
-                    break;
+                    return 1;
                 case ActivityManager.START_NOT_VOICE_COMPATIBLE:
                     out.println(
                             "Error: Activity not started, voice control not allowed for: "
                                     + intent);
-                    break;
+                    return 1;
                 case ActivityManager.START_NOT_CURRENT_USER_ACTIVITY:
                     out.println(
                             "Error: Not allowed to start background user activity"
                                     + " that shouldn't be displayed for all users.");
-                    break;
+                    return 1;
                 default:
                     out.println(
                             "Error: Activity not started, unknown error code " + res);
-                    break;
+                    return 1;
             }
             out.flush();
             if (mWaitOption && launched) {
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index e26ee9c..65fd54a 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -505,12 +505,11 @@
                                     cpr.appInfo.packageName, userId, Event.APP_COMPONENT_USED);
                         }
 
-                        // Content provider is now in use, its package can't be stopped.
                         try {
                             checkTime(startTime,
                                     "getContentProviderImpl: before set stopped state");
-                            mService.mPackageManagerInt.setPackageStoppedState(
-                                    cpr.appInfo.packageName, false, userId);
+                            mService.mPackageManagerInt.notifyComponentUsed(
+                                    cpr.appInfo.packageName, userId, callingPackage);
                             checkTime(startTime, "getContentProviderImpl: after set stopped state");
                         } catch (IllegalArgumentException e) {
                             Slog.w(TAG, "Failed trying to unstop package "
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 9fdb833..e84fed7 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -163,6 +164,12 @@
                 WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
 
         sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
+                DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
+                SystemUiDeviceConfigFlags.KEY_REMOTEVIEWS_ADAPTER_CONVERSION, boolean.class,
+                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT));
+
+        sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
                 TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
                 TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
                 TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 786e1cc..f6859d1 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -141,6 +141,10 @@
         // grab the appropriate types
         final IntArray apiTypes =
                 convertFgsTypeToApiTypes(record.foregroundServiceType);
+        if (apiTypes.size() == 0) {
+            Slog.w(TAG, "Foreground service start for UID: "
+                    + uid + " does not have any types");
+        }
         // now we need to iterate through the types
         // and insert the new record as needed
         final IntArray apiTypesFound = new IntArray();
@@ -201,6 +205,9 @@
         // and also clean up the start calls stack by UID
         final IntArray apiTypes = convertFgsTypeToApiTypes(record.foregroundServiceType);
         final UidState uidState = mUids.get(uid);
+        if (apiTypes.size() == 0) {
+            Slog.w(TAG, "FGS stop call for: " + uid + " has no types!");
+        }
         if (uidState == null) {
             Slog.w(TAG, "FGS stop call being logged with no start call for UID for UID "
                     + uid
@@ -460,16 +467,17 @@
     public void logFgsApiEvent(ServiceRecord r, int fgsState,
             @FgsApiState int apiState,
             @ForegroundServiceApiType int apiType, long timestamp) {
-        long apiDurationBeforeFgsStart = r.createRealTime - timestamp;
-        long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime;
+        long apiDurationBeforeFgsStart = 0;
+        long apiDurationAfterFgsEnd = 0;
         UidState uidState = mUids.get(r.appInfo.uid);
-        if (uidState != null) {
-            if (uidState.mFirstFgsTimeStamp.contains(apiType)) {
-                apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp;
-            }
-            if (uidState.mLastFgsTimeStamp.contains(apiType)) {
-                apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
-            }
+        if (uidState == null) {
+            return;
+        }
+        if (uidState.mFirstFgsTimeStamp.contains(apiType)) {
+            apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp;
+        }
+        if (uidState.mLastFgsTimeStamp.contains(apiType)) {
+            apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
         }
         final int[] apiTypes = new int[1];
         apiTypes[0] = apiType;
@@ -525,10 +533,11 @@
             @ForegroundServiceApiType int apiType, long timestamp) {
         long apiDurationAfterFgsEnd = 0;
         UidState uidState = mUids.get(uid);
-        if (uidState != null) {
-            if (uidState.mLastFgsTimeStamp.contains(apiType)) {
-                apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
-            }
+        if (uidState == null) {
+            return;
+        }
+        if (uidState.mLastFgsTimeStamp.contains(apiType)) {
+            apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
         }
         final int[] apiTypes = new int[1];
         apiTypes[0] = apiType;
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 7037fec..e2edd8a 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -774,6 +774,11 @@
     }
 
     @GuardedBy("mService")
+    boolean isThreadReady() {
+        return mThread != null && !mPendingFinishAttach;
+    }
+
+    @GuardedBy("mService")
     long getStartSeq() {
         return mStartSeq;
     }
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index f7bbc8b..8a8e2af 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -286,6 +286,12 @@
                 + ")");
     }
 
+    private String getFgsInfoForWtf() {
+        return " cmp: " + this.getComponentName().toShortString()
+                + " sdk: " + this.appInfo.targetSdkVersion
+                ;
+    }
+
     void maybeLogFgsLogicChange() {
         final int origWiu = reasonOr(mAllowWhileInUsePermissionInFgsReasonNoBinding,
                 mAllowWIUInBindService);
@@ -311,7 +317,8 @@
                 + " OS:" // Orig-start
                 + changeMessage(mAllowStartForegroundNoBinding, mAllowStartInBindService)
                 + " NS:" // New-start
-                + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings);
+                + changeMessage(mAllowStartForegroundNoBinding, mAllowStartByBindings)
+                + getFgsInfoForWtf();
         Slog.wtf(TAG_SERVICE, message);
     }
 
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 0af9b2b..575db01 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -88,11 +88,14 @@
       "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"],
       "name": "FrameworksServicesTests",
       "options": [
-        { "include-filter": "com.android.server.am.BatteryStatsServiceTest" },
-        { "include-filter": "com.android.server.power.stats.BatteryStatsTests" }
+        { "include-filter": "com.android.server.am.BatteryStatsServiceTest" }
       ]
     },
     {
+      "file_patterns": ["Battery[^/]*\\.java", "MeasuredEnergy[^/]*\\.java"],
+      "name": "PowerStatsTests"
+    },
+    {
       "file_patterns": ["Broadcast.*"],
       "name": "FrameworksMockingServicesTests",
       "options": [
@@ -111,27 +114,14 @@
       ]
     },
     {
-      "name": "CtsUsageStatsTestCases",
+      "name": "CtsBRSTestCases",
       "file_patterns": [
         "ActivityManagerService\\.java",
         "BroadcastQueue\\.java"
       ],
       "options": [
-        {
-          "include-filter": "android.app.usage.cts.BroadcastResponseStatsTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.FlakyTest"
-        },
-        {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.MediumTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
-        }
+        { "exclude-annotation": "androidx.test.filters.FlakyTest" },
+        { "exclude-annotation": "org.junit.Ignore" }
       ]
     }
   ],
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 80d14a2..81397b4 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -129,8 +129,6 @@
     private static final String EVENT_ON_USER_SWITCHING = "ON_USER_SWITCHING";
     private static final String EVENT_ON_USER_STOPPING = "ON_USER_STOPPING";
 
-    private static final boolean DEBUG = false;
-
     static final int WRITE_SETTINGS = 1;
     static final int REMOVE_SETTINGS = 2;
     static final int POPULATE_GAME_MODE_SETTINGS = 3;
@@ -407,6 +405,7 @@
         @Override
         public void onPropertiesChanged(Properties properties) {
             final String[] packageNames = properties.getKeyset().toArray(new String[0]);
+            Slog.v(TAG, "Device config changed for packages: " + Arrays.toString(packageNames));
             updateConfigsForUser(ActivityManager.getCurrentUser(), true /*checkGamePackage*/,
                     packageNames);
         }
@@ -1861,15 +1860,11 @@
                     final GamePackageConfiguration config =
                             new GamePackageConfiguration(mPackageManager, packageName, userId);
                     if (config.isActive()) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Adding config: " + config.toString());
-                        }
+                        Slog.v(TAG, "Adding config: " + config.toString());
                         mConfigs.put(packageName, config);
                     } else {
-                        if (DEBUG) {
-                            Slog.w(TAG, "Inactive package config for "
+                        Slog.v(TAG, "Inactive package config for "
                                     + config.getPackageName() + ":" + config.toString());
-                        }
                         mConfigs.remove(packageName);
                     }
                 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index af8aa916..d89171d 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -435,7 +435,7 @@
         // LE Audio it stays the same and we must trigger the proper stream volume alignment, if
         // LE Audio communication device is activated after the audio system has already switched to
         // MODE_IN_CALL mode.
-        if (isBluetoothLeAudioRequested()) {
+        if (isBluetoothLeAudioRequested() && device != null) {
             final int streamType = mAudioService.getBluetoothContextualVolumeStream();
             final int leAudioVolIndex = getVssVolumeForDevice(streamType, device.getInternalType());
             final int leAudioMaxVolIndex = getMaxVssVolumeForStream(streamType);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 5a92cb4..13e3fc7 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -2070,12 +2070,18 @@
     @GuardedBy("mDevicesLock")
     private void makeLeAudioDeviceAvailable(
             AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) {
-        final String address = btInfo.mDevice.getAddress();
-        final String name = BtHelper.getName(btInfo.mDevice);
         final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10;
         final int device = btInfo.mAudioSystemDevice;
 
         if (device != AudioSystem.DEVICE_NONE) {
+            final String address = btInfo.mDevice.getAddress();
+            String name = BtHelper.getName(btInfo.mDevice);
+
+            // The BT Stack does not provide a name for LE Broadcast devices
+            if (device == AudioSystem.DEVICE_OUT_BLE_BROADCAST && name.equals("")) {
+                name = "Broadcast";
+            }
+
             /* Audio Policy sees Le Audio similar to A2DP. Let's make sure
              * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
              */
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 851c5c3..1578193 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -334,9 +334,8 @@
                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
         mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES,
                 SAFE_MEDIA_VOLUME_UNINITIALIZED);
-        // TODO(b/278265907): enable A2DP when we can distinguish A2DP headsets
-        // mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-        //        SAFE_MEDIA_VOLUME_UNINITIALIZED);
+        mSafeMediaVolumeDevices.append(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
+                SAFE_MEDIA_VOLUME_UNINITIALIZED);
     }
 
     float getOutputRs2UpperBound() {
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index b5d5cbe..de4979a 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -61,6 +61,7 @@
     private static final String PEOPLE_HELPER = "people";
     private static final String APP_LOCALES_HELPER = "app_locales";
     private static final String APP_GENDER_HELPER = "app_gender";
+    private static final String COMPANION_HELPER = "companion";
 
     // These paths must match what the WallpaperManagerService uses.  The leaf *_FILENAME
     // are also used in the full-backup file format, so must not change unless steps are
@@ -95,7 +96,8 @@
                     PERMISSION_HELPER,
                     NOTIFICATION_HELPER,
                     SYNC_SETTINGS_HELPER,
-                    APP_LOCALES_HELPER);
+                    APP_LOCALES_HELPER,
+                    COMPANION_HELPER);
 
     /** Helpers that are enabled for full, non-system users. */
     private static final Set<String> sEligibleHelpersForNonSystemUser =
@@ -132,6 +134,7 @@
         addHelperIfEligibleForUser(APP_LOCALES_HELPER, new AppSpecificLocalesBackupHelper(mUserId));
         addHelperIfEligibleForUser(APP_GENDER_HELPER,
                 new AppGrammaticalGenderBackupHelper(mUserId));
+        addHelperIfEligibleForUser(COMPANION_HELPER, new CompanionBackupHelper(mUserId));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java
index cb5e7f1..2ae3118 100644
--- a/services/core/java/com/android/server/biometrics/AuthSession.java
+++ b/services/core/java/com/android/server/biometrics/AuthSession.java
@@ -150,6 +150,10 @@
     // Timestamp when hardware authentication occurred
     private long mAuthenticatedTimeMs;
 
+    @NonNull
+    private final OperationContextExt mOperationContext;
+
+
     AuthSession(@NonNull Context context,
             @NonNull BiometricContext biometricContext,
             @NonNull IStatusBarService statusBarService,
@@ -215,6 +219,7 @@
         mFingerprintSensorProperties = fingerprintSensorProperties;
         mCancelled = false;
         mBiometricFrameworkStatsLogger = logger;
+        mOperationContext = new OperationContextExt(true /* isBP */);
 
         try {
             mClientReceiver.asBinder().linkToDeath(this, 0 /* flags */);
@@ -581,6 +586,8 @@
         } else {
             Slog.d(TAG, "delaying fingerprint sensor start");
         }
+
+        mBiometricContext.updateContext(mOperationContext, isCrypto());
     }
 
     // call once anytime after onDialogAnimatedIn() to indicate it's appropriate to start the
@@ -743,12 +750,12 @@
                         + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
                         + ", RequireConfirmation: " + mPreAuthInfo.confirmationRequested
                         + ", State: " + FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__CONFIRMED
-                        + ", Latency: " + latency);
+                        + ", Latency: " + latency
+                        + ", SessionId: " + mOperationContext.getId());
             }
 
             mBiometricFrameworkStatsLogger.authenticate(
-                    mBiometricContext.updateContext(new OperationContextExt(true /* isBP */),
-                            isCrypto()),
+                    mOperationContext,
                     statsModality(),
                     BiometricsProtoEnums.ACTION_UNKNOWN,
                     BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
@@ -780,13 +787,13 @@
                         + ", Client: " + BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT
                         + ", Reason: " + reason
                         + ", Error: " + error
-                        + ", Latency: " + latency);
+                        + ", Latency: " + latency
+                        + ", SessionId: " + mOperationContext.getId());
             }
             // Auth canceled
             if (error != 0) {
                 mBiometricFrameworkStatsLogger.error(
-                        mBiometricContext.updateContext(new OperationContextExt(true /* isBP */),
-                                isCrypto()),
+                        mOperationContext,
                         statsModality(),
                         BiometricsProtoEnums.ACTION_AUTHENTICATE,
                         BiometricsProtoEnums.CLIENT_BIOMETRIC_PROMPT,
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStats.java b/services/core/java/com/android/server/biometrics/AuthenticationStats.java
new file mode 100644
index 0000000..137a418
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStats.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.biometrics;
+
+/**
+ * Utility class for on-device biometric authentication data, including total authentication,
+ * rejections, and the number of sent enrollment notifications.
+ */
+public class AuthenticationStats {
+
+    private final int mUserId;
+    private int mTotalAttempts;
+    private int mRejectedAttempts;
+    private int mEnrollmentNotifications;
+    private final int mModality;
+
+    public AuthenticationStats(final int userId, int totalAttempts, int rejectedAttempts,
+            int enrollmentNotifications, final int modality) {
+        mUserId = userId;
+        mTotalAttempts = totalAttempts;
+        mRejectedAttempts = rejectedAttempts;
+        mEnrollmentNotifications = enrollmentNotifications;
+        mModality = modality;
+    }
+
+    public AuthenticationStats(final int userId, final int modality) {
+        mUserId = userId;
+        mTotalAttempts = 0;
+        mRejectedAttempts = 0;
+        mEnrollmentNotifications = 0;
+        mModality = modality;
+    }
+
+    public int getUserId() {
+        return mUserId;
+    }
+
+    public int getTotalAttempts() {
+        return mTotalAttempts;
+    }
+
+    public int getRejectedAttempts() {
+        return mRejectedAttempts;
+    }
+
+    public int getEnrollmentNotifications() {
+        return mEnrollmentNotifications;
+    }
+
+    public int getModality() {
+        return mModality;
+    }
+
+    /** Calculate FRR. */
+    public float getFrr() {
+        if (mTotalAttempts > 0) {
+            return mRejectedAttempts / (float) mTotalAttempts;
+        } else {
+            return -1.0f;
+        }
+    }
+
+    /** Update total authentication attempts and rejections. */
+    public void authenticate(boolean authenticated) {
+        if (!authenticated) {
+            mRejectedAttempts++;
+        }
+        mTotalAttempts++;
+    }
+
+    /** Reset total authentication attempts and rejections. */
+    public void resetData() {
+        mTotalAttempts = 0;
+        mRejectedAttempts = 0;
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
new file mode 100644
index 0000000..c9cd814
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/AuthenticationStatsCollector.java
@@ -0,0 +1,106 @@
+/*
+ * 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.biometrics;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Calculate and collect on-device False Rejection Rates (FRR).
+ * FRR = All [given biometric modality] unlock failures / all [given biometric modality] unlock
+ * attempts.
+ */
+public class AuthenticationStatsCollector {
+
+    private static final String TAG = "AuthenticationStatsCollector";
+
+    // The minimum number of attempts that will calculate the FRR and trigger the notification.
+    private static final int MINIMUM_ATTEMPTS = 500;
+    // The maximum number of eligible biometric enrollment notification can be sent.
+    private static final int MAXIMUM_ENROLLMENT_NOTIFICATIONS = 2;
+
+    private final float mThreshold;
+    private final int mModality;
+
+    @NonNull private final Map<Integer, AuthenticationStats> mUserAuthenticationStatsMap;
+
+    public AuthenticationStatsCollector(@NonNull Context context, int modality) {
+        mThreshold = context.getResources()
+                .getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1);
+        mUserAuthenticationStatsMap = new HashMap<>();
+        mModality = modality;
+    }
+
+    /** Update total authentication and rejected attempts. */
+    public void authenticate(int userId, boolean authenticated) {
+        // Check if this is a new user.
+        if (!mUserAuthenticationStatsMap.containsKey(userId)) {
+            mUserAuthenticationStatsMap.put(userId, new AuthenticationStats(userId, mModality));
+        }
+
+        AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+
+        authenticationStats.authenticate(authenticated);
+
+        persistDataIfNeeded(userId);
+        sendNotificationIfNeeded(userId);
+    }
+
+    private void sendNotificationIfNeeded(int userId) {
+        AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+        if (authenticationStats.getTotalAttempts() >= MINIMUM_ATTEMPTS) {
+            // Send notification if FRR exceeds the threshold
+            if (authenticationStats.getEnrollmentNotifications() < MAXIMUM_ENROLLMENT_NOTIFICATIONS
+                    && authenticationStats.getFrr() >= mThreshold) {
+                // TODO(wenhuiy): Send notifications.
+            }
+
+            authenticationStats.resetData();
+        }
+    }
+
+    private void persistDataIfNeeded(int userId) {
+        AuthenticationStats authenticationStats = mUserAuthenticationStatsMap.get(userId);
+        if (authenticationStats.getTotalAttempts() % 50 == 0) {
+            // TODO(wenhuiy): Persist data.
+        }
+    }
+
+    /**
+     * Only being used in tests. Callers should not make any changes to the returned
+     * authentication stats.
+     *
+     * @return AuthenticationStats of the user, or null if the stats doesn't exist.
+     */
+    @Nullable
+    @VisibleForTesting
+    AuthenticationStats getAuthenticationStatsForUser(int userId) {
+        return mUserAuthenticationStatsMap.getOrDefault(userId, null);
+    }
+
+    @VisibleForTesting
+    void setAuthenticationStatsForUser(int userId, AuthenticationStats authenticationStats) {
+        mUserAuthenticationStatsMap.put(userId, authenticationStats);
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 279aaf9..1898b80 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1308,10 +1308,13 @@
                                 .getString(R.string.biometric_dialog_default_subtitle));
                     } else if (hasEligibleFingerprintSensor) {
                         promptInfo.setSubtitle(getContext()
-                                .getString(R.string.biometric_dialog_fingerprint_subtitle));
+                                .getString(R.string.fingerprint_dialog_default_subtitle));
                     } else if (hasEligibleFaceSensor) {
                         promptInfo.setSubtitle(getContext()
-                                .getString(R.string.biometric_dialog_face_subtitle));
+                                .getString(R.string.face_dialog_default_subtitle));
+                    } else {
+                        promptInfo.setSubtitle(getContext()
+                                .getString(R.string.screen_lock_dialog_default_subtitle));
                     }
                 }
 
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index c76a2e3..87037af 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -27,6 +27,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.Utils;
 
 /**
@@ -41,6 +42,7 @@
     private final int mStatsAction;
     private final int mStatsClient;
     private final BiometricFrameworkStatsLogger mSink;
+    @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
     @NonNull private final ALSProbe mALSProbe;
 
     private long mFirstAcquireTimeMs;
@@ -49,7 +51,8 @@
     /** Get a new logger with all unknown fields (for operations that do not require logs). */
     public static BiometricLogger ofUnknown(@NonNull Context context) {
         return new BiometricLogger(context, BiometricsProtoEnums.MODALITY_UNKNOWN,
-                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN);
+                BiometricsProtoEnums.ACTION_UNKNOWN, BiometricsProtoEnums.CLIENT_UNKNOWN,
+                null /* AuthenticationStatsCollector */);
     }
 
     /**
@@ -64,26 +67,32 @@
      * @param statsClient One of {@link BiometricsProtoEnums} CLIENT_* constants.
      */
     public BiometricLogger(
-            @NonNull Context context, int statsModality, int statsAction, int statsClient) {
+            @NonNull Context context, int statsModality, int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         this(statsModality, statsAction, statsClient,
                 BiometricFrameworkStatsLogger.getInstance(),
+                authenticationStatsCollector,
                 context.getSystemService(SensorManager.class));
     }
 
     @VisibleForTesting
     BiometricLogger(
             int statsModality, int statsAction, int statsClient,
-            BiometricFrameworkStatsLogger logSink, SensorManager sensorManager) {
+            BiometricFrameworkStatsLogger logSink,
+            @NonNull AuthenticationStatsCollector statsCollector,
+            SensorManager sensorManager) {
         mStatsModality = statsModality;
         mStatsAction = statsAction;
         mStatsClient = statsClient;
         mSink = logSink;
+        mAuthenticationStatsCollector = statsCollector;
         mALSProbe = new ALSProbe(sensorManager);
     }
 
     /** Creates a new logger with the action replaced with the new action. */
     public BiometricLogger swapAction(@NonNull Context context, int statsAction) {
-        return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient);
+        return new BiometricLogger(context, mStatsModality, statsAction, mStatsClient,
+                null /* AuthenticationStatsCollector */);
     }
 
     /** Disable logging metrics and only log critical events, such as system health issues. */
@@ -192,10 +201,13 @@
     public void logOnAuthenticated(Context context, OperationContextExt operationContext,
             boolean authenticated, boolean requireConfirmation, int targetUserId,
             boolean isBiometricPrompt) {
+        // Do not log metrics when fingerprint enrollment reason is ENROLL_FIND_SENSOR
         if (!mShouldLogMetrics) {
             return;
         }
 
+        mAuthenticationStatsCollector.authenticate(targetUserId, authenticated);
+
         int authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__UNKNOWN;
         if (!authenticated) {
             authState = FrameworkStatsLog.BIOMETRIC_AUTHENTICATED__STATE__REJECTED;
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 33ed63c..a7d160c 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
@@ -49,6 +49,7 @@
 import android.view.Surface;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -112,6 +113,8 @@
     private final BiometricContext mBiometricContext;
     @NonNull
     private final AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull
+    private final AuthenticationStatsCollector mAuthenticationStatsCollector;
     @Nullable
     private IFace mDaemon;
 
@@ -173,6 +176,9 @@
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
         mDaemon = daemon;
 
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                BiometricsProtoEnums.MODALITY_FACE);
+
         for (SensorProps prop : props) {
             final int sensorId = prop.commonProps.sensorId;
 
@@ -283,7 +289,8 @@
                     mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFaceSensors.get(sensorId).getAuthenticatorIds());
 
@@ -341,7 +348,8 @@
             final FaceInvalidationClient client = new FaceInvalidationClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(), userId, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFaceSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
@@ -372,7 +380,8 @@
                     mFaceSensors.get(sensorId).getLazySession(), token,
                     new ClientMonitorCallbackConverter(receiver), userId, opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
@@ -386,7 +395,8 @@
                     mFaceSensors.get(sensorId).getLazySession(), token, userId,
                     opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, challenge);
             scheduleForSensor(sensorId, client);
         });
@@ -407,7 +417,8 @@
                     opPackageName, id, FaceUtils.getInstance(sensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, maxTemplatesPerUser, debugConsent);
             scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
                             mBiometricStateCallback, new ClientMonitorCallback() {
@@ -443,7 +454,8 @@
             final FaceDetectClient client = new FaceDetectClient(mContext,
                     mFaceSensors.get(sensorId).getLazySession(),
                     token, id, callback, options,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric);
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
@@ -471,7 +483,8 @@
                     mContext, mFaceSensors.get(sensorId).getLazySession(), token, requestId,
                     callback, operationId, restricted, options, cookie,
                     false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
                     mUsageStats, mFaceSensors.get(sensorId).getLockoutCache(),
                     allowBackgroundAuthentication, Utils.getCurrentStrength(sensorId));
@@ -540,7 +553,8 @@
                     new ClientMonitorCallbackConverter(receiver), faceIds, userId,
                     opPackageName, FaceUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFaceSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -554,7 +568,8 @@
                     mContext, mFaceSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken,
                     mFaceSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
                     Utils.getCurrentStrength(sensorId));
@@ -624,7 +639,8 @@
                             mFaceSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext,
                             FaceUtils.getInstance(sensorId),
                             mFaceSensors.get(sensorId).getAuthenticatorIds());
@@ -636,9 +652,10 @@
         });
     }
 
-    private BiometricLogger createLogger(int statsAction, int statsClient) {
+    private BiometricLogger createLogger(int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
-                statsAction, statsClient);
+                statsAction, statsClient, authenticationStatsCollector);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
index 1e33c96..10991d5 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/hidl/Face10.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -124,6 +125,7 @@
     @Nullable private IBiometricsFace mDaemon;
     @NonNull private final HalResultController mHalResultController;
     @NonNull private final BiometricContext mBiometricContext;
+    @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     private int mCurrentUserId = UserHandle.USER_NULL;
@@ -364,6 +366,9 @@
             mCurrentUserId = UserHandle.USER_NULL;
         });
 
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                BiometricsProtoEnums.MODALITY_FACE);
+
         try {
             ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
         } catch (RemoteException e) {
@@ -554,7 +559,7 @@
                     mLazyDaemon, token, new ClientMonitorCallbackConverter(receiver), userId,
                     opPackageName, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, sSystemClock.millis());
             mGeneratedChallengeCache = client;
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -586,7 +591,7 @@
             final FaceRevokeChallengeClient client = new FaceRevokeChallengeClient(mContext,
                     mLazyDaemon, token, userId, opPackageName, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -617,7 +622,7 @@
                     opPackageName, id, FaceUtils.getLegacyInstance(mSensorId), disabledFeatures,
                     ENROLL_TIMEOUT_SEC, previewSurface, mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext);
 
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -677,7 +682,8 @@
             final FaceAuthenticationClient client = new FaceAuthenticationClient(mContext,
                     mLazyDaemon, token, requestId, receiver, operationId, restricted,
                     options, cookie, false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric, mLockoutTracker,
                     mUsageStats, allowBackgroundAuthentication,
                     Utils.getCurrentStrength(mSensorId));
@@ -713,7 +719,7 @@
                     new ClientMonitorCallbackConverter(receiver), faceId, userId, opPackageName,
                     FaceUtils.getLegacyInstance(mSensorId), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -731,7 +737,7 @@
                     opPackageName,
                     FaceUtils.getLegacyInstance(mSensorId), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -750,7 +756,7 @@
             final FaceResetLockoutClient client = new FaceResetLockoutClient(mContext,
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -821,7 +827,7 @@
             final FaceInternalCleanupClient client = new FaceInternalCleanupClient(mContext,
                     mLazyDaemon, userId, mContext.getOpPackageName(), mSensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext,
                     FaceUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCompositeCallback(callback,
@@ -953,7 +959,7 @@
         final FaceUpdateActiveUserClient client = new FaceUpdateActiveUserClient(mContext,
                 mLazyDaemon, targetUserId, mContext.getOpPackageName(), mSensorId,
                 createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                        BiometricsProtoEnums.CLIENT_UNKNOWN),
+                        BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                 mBiometricContext, hasEnrolled, mAuthenticatorIds);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
@@ -968,9 +974,10 @@
         });
     }
 
-    private BiometricLogger createLogger(int statsAction, int statsClient) {
+    private BiometricLogger createLogger(int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FACE,
-                statsAction, statsClient);
+                statsAction, statsClient, authenticationStatsCollector);
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 0421d78..2d062db 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -57,6 +57,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
@@ -122,6 +123,7 @@
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
     private AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -181,6 +183,9 @@
         mAuthSessionCoordinator = mBiometricContext.getAuthSessionCoordinator();
         mDaemon = daemon;
 
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
         final List<SensorLocationInternal> workaroundLocations = getWorkaroundSensorProps(context);
 
         for (SensorProps prop : props) {
@@ -338,7 +343,8 @@
                             mFingerprintSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext,
                             mFingerprintSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client);
@@ -363,7 +369,8 @@
                     mContext, mFingerprintSensors.get(sensorId).getLazySession(), userId,
                     mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, hardwareAuthToken,
                     mFingerprintSensors.get(sensorId).getLockoutCache(), mLockoutResetDispatcher,
                     Utils.getCurrentStrength(sensorId));
@@ -380,7 +387,7 @@
                             mFingerprintSensors.get(sensorId).getLazySession(), token,
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
                             sensorId, createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                             mBiometricContext);
             scheduleForSensor(sensorId, client);
         });
@@ -395,7 +402,8 @@
                             mFingerprintSensors.get(sensorId).getLazySession(), token,
                             userId, opPackageName, sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext, challenge);
             scheduleForSensor(sensorId, client);
         });
@@ -415,7 +423,7 @@
                     new ClientMonitorCallbackConverter(receiver), userId, hardwareAuthToken,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFingerprintSensors.get(sensorId).getSensorProperties(),
                     mUdfpsOverlayController, mSidefpsController,
@@ -455,7 +463,8 @@
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
                     mFingerprintSensors.get(sensorId).getLazySession(), token, id, callback,
                     options,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
@@ -477,7 +486,8 @@
                     mContext, mFingerprintSensors.get(sensorId).getLazySession(), token, requestId,
                     callback, operationId, restricted, options, cookie,
                     false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mFingerprintSensors.get(sensorId).getLockoutCache(),
                     mUdfpsOverlayController, mSidefpsController,
@@ -566,7 +576,8 @@
                     new ClientMonitorCallbackConverter(receiver), fingerprintIds, userId,
                     opPackageName, FingerprintUtils.getInstance(sensorId), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext,
                     mFingerprintSensors.get(sensorId).getAuthenticatorIds());
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
@@ -588,7 +599,8 @@
                             mFingerprintSensors.get(sensorId).getLazySession(), userId,
                             mContext.getOpPackageName(), sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext,
                             FingerprintUtils.getInstance(sensorId),
                             mFingerprintSensors.get(sensorId).getAuthenticatorIds());
@@ -600,9 +612,10 @@
         });
     }
 
-    private BiometricLogger createLogger(int statsAction, int statsClient) {
+    private BiometricLogger createLogger(int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                statsAction, statsClient);
+                statsAction, statsClient, authenticationStatsCollector);
     }
 
     @Override
@@ -635,7 +648,8 @@
                     new FingerprintInvalidationClient(mContext,
                             mFingerprintSensors.get(sensorId).getLazySession(), userId, sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext,
                             mFingerprintSensors.get(sensorId).getAuthenticatorIds(), callback);
             scheduleForSensor(sensorId, client);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 92b216d..4b07dca 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -52,6 +52,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.SensorServiceStateProto;
 import com.android.server.biometrics.SensorStateProto;
 import com.android.server.biometrics.UserStateProto;
@@ -123,6 +124,7 @@
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
     @NonNull private final BiometricContext mBiometricContext;
+    @NonNull private final AuthenticationStatsCollector mAuthenticationStatsCollector;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
     private int mCurrentUserId = UserHandle.USER_NULL;
@@ -351,6 +353,9 @@
             mCurrentUserId = UserHandle.USER_NULL;
         });
 
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                BiometricsProtoEnums.MODALITY_FINGERPRINT);
+
         try {
             ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG);
         } catch (RemoteException e) {
@@ -497,7 +502,8 @@
                 new FingerprintUpdateActiveUserClient(mContext, mLazyDaemon, targetUserId,
                         mContext.getOpPackageName(), mSensorProperties.sensorId,
                         createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                mAuthenticationStatsCollector),
                         mBiometricContext,
                         this::getCurrentUser, hasEnrolled, mAuthenticatorIds, force);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
@@ -544,7 +550,8 @@
             final FingerprintResetLockoutClient client = new FingerprintResetLockoutClient(mContext,
                     userId, mContext.getOpPackageName(), sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, mLockoutTracker);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -559,7 +566,8 @@
                             new ClientMonitorCallbackConverter(receiver), userId, opPackageName,
                             mSensorProperties.sensorId,
                             createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                                    BiometricsProtoEnums.CLIENT_UNKNOWN),
+                                    BiometricsProtoEnums.CLIENT_UNKNOWN,
+                                    mAuthenticationStatsCollector),
                             mBiometricContext);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -573,7 +581,8 @@
                     mContext, mLazyDaemon, token, userId, opPackageName,
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_UNKNOWN,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN,
+                            mAuthenticationStatsCollector),
                     mBiometricContext);
             mScheduler.scheduleClientMonitor(client);
         });
@@ -594,7 +603,7 @@
                     FingerprintUtils.getLegacyInstance(mSensorId), ENROLL_TIMEOUT_SEC,
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -639,7 +648,8 @@
             final boolean isStrongBiometric = Utils.isStrongBiometric(mSensorProperties.sensorId);
             final FingerprintDetectClient client = new FingerprintDetectClient(mContext,
                     mLazyDaemon, token, id, listener, options,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, mUdfpsOverlayController, isStrongBiometric);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -660,7 +670,8 @@
             final FingerprintAuthenticationClient client = new FingerprintAuthenticationClient(
                     mContext, mLazyDaemon, token, requestId, listener, operationId,
                     restricted, options, cookie, false /* requireConfirmation */,
-                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
+                    createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient,
+                            mAuthenticationStatsCollector),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
                     mUdfpsOverlayController, mSidefpsController,
@@ -706,7 +717,7 @@
                     userId, opPackageName, FingerprintUtils.getLegacyInstance(mSensorId),
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -726,7 +737,7 @@
                     FingerprintUtils.getLegacyInstance(mSensorId),
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_REMOVE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext, mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -741,7 +752,7 @@
                     mContext, mLazyDaemon, userId, mContext.getOpPackageName(),
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_ENUMERATE,
-                            BiometricsProtoEnums.CLIENT_UNKNOWN),
+                            BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
                     mBiometricContext,
                     FingerprintUtils.getLegacyInstance(mSensorId), mAuthenticatorIds);
             mScheduler.scheduleClientMonitor(client, callback);
@@ -762,9 +773,10 @@
                 mBiometricStateCallback));
     }
 
-    private BiometricLogger createLogger(int statsAction, int statsClient) {
+    private BiometricLogger createLogger(int statsAction, int statsClient,
+            AuthenticationStatsCollector authenticationStatsCollector) {
         return new BiometricLogger(mContext, BiometricsProtoEnums.MODALITY_FINGERPRINT,
-                statsAction, statsClient);
+                statsAction, statsClient, authenticationStatsCollector);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index da51569..1c1b69b 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -134,6 +134,13 @@
     }
 
     /**
+     * Helper methods to create builder
+     */
+    public static Builder builder() {
+        return new Builder();
+    }
+
+    /**
      * A DisplayBrightnessState's builder class.
      */
     public static class Builder {
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index e5965ef..c131226 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -222,8 +222,14 @@
  *          <minimum>120</minimum>
  *          <maximum>120</maximum>
  *        </refreshRate>
- *        <thermalStatusLimit>light</thermalStatusLimit>
  *        <allowInLowPowerMode>false</allowInLowPowerMode>
+ *        <minimumHdrPercentOfScreen>0.6</minimumHdrPercentOfScreen>
+ *        <sdrHdrRatioMap>
+ *          <point>
+ *            <sdrNits>2.000</sdrNits>
+ *            <hdrRatio>4.000</hdrRatio>
+ *          </point>
+ *        </sdrHdrRatioMap>
  *      </highBrightnessMode>
  *
  *      <luxThrottling>
@@ -276,6 +282,10 @@
  *      <lightSensor>
  *        <type>android.sensor.light</type>
  *        <name>1234 Ambient Light Sensor</name>
+ *        <refreshRate>
+ *          <minimum>60</minimum>
+ *          <maximum>120</maximum>
+ *        </refreshRate>
  *      </lightSensor>
  *      <screenOffBrightnessSensor>
  *        <type>com.google.sensor.binned_brightness</type>
@@ -1568,37 +1578,40 @@
     public String toString() {
         return "DisplayDeviceConfig{"
                 + "mLoadedFrom=" + mLoadedFrom
-                + ", mBacklight=" + Arrays.toString(mBacklight)
+                + "\n"
+                + "mBacklight=" + Arrays.toString(mBacklight)
                 + ", mNits=" + Arrays.toString(mNits)
                 + ", mRawBacklight=" + Arrays.toString(mRawBacklight)
                 + ", mRawNits=" + Arrays.toString(mRawNits)
                 + ", mInterpolationType=" + mInterpolationType
-                + ", mBrightness=" + Arrays.toString(mBrightness)
-                + ", mBrightnessToBacklightSpline=" + mBrightnessToBacklightSpline
+                + "mBrightness=" + Arrays.toString(mBrightness)
+                + "\n"
+                + "mBrightnessToBacklightSpline=" + mBrightnessToBacklightSpline
                 + ", mBacklightToBrightnessSpline=" + mBacklightToBrightnessSpline
                 + ", mNitsToBacklightSpline=" + mNitsToBacklightSpline
                 + ", mBacklightMinimum=" + mBacklightMinimum
                 + ", mBacklightMaximum=" + mBacklightMaximum
                 + ", mBrightnessDefault=" + mBrightnessDefault
                 + ", mQuirks=" + mQuirks
-                + ", isHbmEnabled=" + mIsHighBrightnessModeEnabled
-                + ", mLuxThrottlingData=" + mLuxThrottlingData
+                + ", mIsHighBrightnessModeEnabled=" + mIsHighBrightnessModeEnabled
+                + "\n"
+                + "mLuxThrottlingData=" + mLuxThrottlingData
                 + ", mHbmData=" + mHbmData
                 + ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
                 + ", mThermalBrightnessThrottlingDataMapByThrottlingId="
                 + mThermalBrightnessThrottlingDataMapByThrottlingId
                 + "\n"
-                + ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
+                + "mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
                 + ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
                 + ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease
                 + ", mBrightnessRampSlowIncrease=" + mBrightnessRampSlowIncrease
                 + ", mBrightnessRampDecreaseMaxMillis=" + mBrightnessRampDecreaseMaxMillis
                 + ", mBrightnessRampIncreaseMaxMillis=" + mBrightnessRampIncreaseMaxMillis
                 + "\n"
-                + ", mAmbientHorizonLong=" + mAmbientHorizonLong
+                + "mAmbientHorizonLong=" + mAmbientHorizonLong
                 + ", mAmbientHorizonShort=" + mAmbientHorizonShort
                 + "\n"
-                + ", mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
+                + "mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold
                 + ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle
                 + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold
                 + ", mScreenBrighteningMinThresholdIdle=" + mScreenBrighteningMinThresholdIdle
@@ -1608,7 +1621,7 @@
                 + ", mAmbientLuxBrighteningMinThresholdIdle="
                 + mAmbientLuxBrighteningMinThresholdIdle
                 + "\n"
-                + ", mScreenBrighteningLevels=" + Arrays.toString(
+                + "mScreenBrighteningLevels=" + Arrays.toString(
                 mScreenBrighteningLevels)
                 + ", mScreenBrighteningPercentages=" + Arrays.toString(
                 mScreenBrighteningPercentages)
@@ -1625,7 +1638,7 @@
                 + ", mAmbientDarkeningPercentages=" + Arrays.toString(
                 mAmbientDarkeningPercentages)
                 + "\n"
-                + ", mAmbientBrighteningLevelsIdle=" + Arrays.toString(
+                + "mAmbientBrighteningLevelsIdle=" + Arrays.toString(
                 mAmbientBrighteningLevelsIdle)
                 + ", mAmbientBrighteningPercentagesIdle=" + Arrays.toString(
                 mAmbientBrighteningPercentagesIdle)
@@ -1642,7 +1655,7 @@
                 + ", mScreenDarkeningPercentagesIdle=" + Arrays.toString(
                 mScreenDarkeningPercentagesIdle)
                 + "\n"
-                + ", mAmbientLightSensor=" + mAmbientLightSensor
+                + "mAmbientLightSensor=" + mAmbientLightSensor
                 + ", mScreenOffBrightnessSensor=" + mScreenOffBrightnessSensor
                 + ", mProximitySensor=" + mProximitySensor
                 + ", mRefreshRateLimitations= " + Arrays.toString(mRefreshRateLimitations.toArray())
@@ -1656,7 +1669,7 @@
                 + ", mDdcAutoBrightnessAvailable= " + mDdcAutoBrightnessAvailable
                 + ", mAutoBrightnessAvailable= " + mAutoBrightnessAvailable
                 + "\n"
-                + ", mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate
+                + "mDefaultLowBlockingZoneRefreshRate= " + mDefaultLowBlockingZoneRefreshRate
                 + ", mDefaultHighBlockingZoneRefreshRate= " + mDefaultHighBlockingZoneRefreshRate
                 + ", mDefaultPeakRefreshRate= " + mDefaultPeakRefreshRate
                 + ", mDefaultRefreshRate= " + mDefaultRefreshRate
@@ -1665,7 +1678,7 @@
                 + ", mDefaultRefreshRateInHbmSunlight= " + mDefaultRefreshRateInHbmSunlight
                 + ", mRefreshRateThrottlingMap= " + mRefreshRateThrottlingMap
                 + "\n"
-                + ", mLowDisplayBrightnessThresholds= "
+                + "mLowDisplayBrightnessThresholds= "
                 + Arrays.toString(mLowDisplayBrightnessThresholds)
                 + ", mLowAmbientBrightnessThresholds= "
                 + Arrays.toString(mLowAmbientBrightnessThresholds)
@@ -1674,10 +1687,10 @@
                 + ", mHighAmbientBrightnessThresholds= "
                 + Arrays.toString(mHighAmbientBrightnessThresholds)
                 + "\n"
-                + ", mScreenOffBrightnessSensorValueToLux=" + Arrays.toString(
+                + "mScreenOffBrightnessSensorValueToLux=" + Arrays.toString(
                 mScreenOffBrightnessSensorValueToLux)
                 + "\n"
-                + ", mUsiVersion= " + mHostUsiVersion
+                + "mUsiVersion= " + mHostUsiVersion
                 + "}";
     }
 
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 19dffeb..fe445a6 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -39,6 +39,7 @@
 import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
 import static android.hardware.display.HdrConversionMode.HDR_CONVERSION_UNSUPPORTED;
+import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.os.Process.ROOT_UID;
 
@@ -593,7 +594,7 @@
         DisplayManagerGlobal.invalidateLocalDisplayInfoCaches();
 
         publishBinderService(Context.DISPLAY_SERVICE, new BinderService(),
-                true /*allowIsolated*/);
+                true /*allowIsolated*/, DUMP_FLAG_PRIORITY_CRITICAL);
         publishLocalService(DisplayManagerInternal.class, new LocalService());
     }
 
@@ -1546,6 +1547,7 @@
                 if (displayId != Display.INVALID_DISPLAY && virtualDevice != null && dwpc != null) {
                     mDisplayWindowPolicyControllers.put(
                             displayId, Pair.create(virtualDevice, dwpc));
+                    Slog.d(TAG, "Virtual Display: successfully created virtual display");
                 }
             }
 
@@ -1592,19 +1594,25 @@
                     if (!getProjectionService().setContentRecordingSession(session, projection)) {
                         // Unable to start mirroring, so release VirtualDisplay. Projection service
                         // handles stopping the projection.
+                        Slog.w(TAG, "Content Recording: failed to start mirroring - "
+                                + "releasing virtual display " + displayId);
                         releaseVirtualDisplayInternal(callback.asBinder());
                         return Display.INVALID_DISPLAY;
                     } else if (projection != null) {
                         // Indicate that this projection has been used to record, and can't be used
                         // again.
+                        Slog.d(TAG, "Content Recording: notifying MediaProjection of successful"
+                                + " VirtualDisplay creation.");
                         projection.notifyVirtualDisplayCreated(displayId);
                     }
                 } catch (RemoteException e) {
                     Slog.e(TAG, "Unable to tell MediaProjectionManagerService to set the "
                             + "content recording session", e);
+                    return displayId;
                 }
+                Slog.d(TAG, "Virtual Display: successfully set up virtual display "
+                        + displayId);
             }
-
             return displayId;
         } finally {
             Binder.restoreCallingIdentity(secondToken);
@@ -1628,10 +1636,13 @@
             return -1;
         }
 
+
+        Slog.d(TAG, "Virtual Display: creating DisplayDevice with VirtualDisplayAdapter");
         DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(
                 callback, projection, callingUid, packageName, surface, flags,
                 virtualDisplayConfig);
         if (device == null) {
+            Slog.w(TAG, "Virtual Display: VirtualDisplayAdapter failed to create DisplayDevice");
             return -1;
         }
 
@@ -1709,6 +1720,7 @@
 
             DisplayDevice device =
                     mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken);
+            Slog.d(TAG, "Virtual Display: Display Device released");
             if (device != null) {
                 // TODO: multi-display - handle virtual displays the same as other display adapters.
                 mDisplayDeviceRepo.onDisplayDeviceEvent(device,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 5213d31..0f89a6e 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -254,13 +254,6 @@
     // The doze screen brightness.
     private final float mScreenBrightnessDozeConfig;
 
-    // The dim screen brightness.
-    private final float mScreenBrightnessDimConfig;
-
-    // The minimum dim amount to use if the screen brightness is already below
-    // mScreenBrightnessDimConfig.
-    private final float mScreenBrightnessMinimumDimAmount;
-
     // True if auto-brightness should be used.
     private boolean mUseSoftwareAutoBrightnessConfig;
 
@@ -349,7 +342,7 @@
     private boolean mDozing;
 
     private boolean mAppliedDimming;
-    private boolean mAppliedLowPower;
+
     private boolean mAppliedThrottling;
 
     // Reason for which the brightness was last changed. See {@link BrightnessReason} for more
@@ -529,11 +522,6 @@
         // DOZE AND DIM SETTINGS
         mScreenBrightnessDozeConfig = BrightnessUtils.clampAbsoluteBrightness(
                 pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DOZE));
-        mScreenBrightnessDimConfig = BrightnessUtils.clampAbsoluteBrightness(
-                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
-        mScreenBrightnessMinimumDimAmount = resources.getFloat(
-                R.dimen.config_screenBrightnessMinimumDimAmountFloat);
-
         loadBrightnessRampRates();
         mSkipScreenOnBrightnessRamp = resources.getBoolean(
                 R.bool.config_skipScreenOnBrightnessRamp);
@@ -565,7 +553,7 @@
                 mUniqueDisplayId,
                 mThermalBrightnessThrottlingDataId,
                 mDisplayDeviceConfig
-        ));
+        ), mContext);
         // Seed the cached brightness
         saveBrightnessInfo(getScreenBrightnessSetting());
         mAutomaticBrightnessStrategy =
@@ -1426,6 +1414,7 @@
         // Note throttling effectively changes the allowed brightness range, so, similarly to HBM,
         // we broadcast this change through setting.
         final float unthrottledBrightnessState = brightnessState;
+
         if (mBrightnessThrottler.isThrottled()) {
             mTempBrightnessEvent.setThermalMax(mBrightnessThrottler.getBrightnessCap());
             brightnessState = Math.min(brightnessState, mBrightnessThrottler.getBrightnessCap());
@@ -1449,42 +1438,12 @@
             mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessState);
         }
 
-        // Apply dimming by at least some minimum amount when user activity
-        // timeout is about to expire.
-        if (mPowerRequest.policy == DisplayPowerRequest.POLICY_DIM) {
-            if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
-                brightnessState = Math.max(
-                        Math.min(brightnessState - mScreenBrightnessMinimumDimAmount,
-                                mScreenBrightnessDimConfig),
-                        PowerManager.BRIGHTNESS_MIN);
-                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_DIMMED);
-            }
-            if (!mAppliedDimming) {
-                slowChange = false;
-            }
-            mAppliedDimming = true;
-        } else if (mAppliedDimming) {
-            slowChange = false;
-            mAppliedDimming = false;
-        }
-        // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
-        // as long as it is above the minimum threshold.
-        if (mPowerRequest.lowPowerMode) {
-            if (brightnessState > PowerManager.BRIGHTNESS_MIN) {
-                final float brightnessFactor =
-                        Math.min(mPowerRequest.screenLowPowerBrightnessFactor, 1);
-                final float lowPowerBrightnessFloat = (brightnessState * brightnessFactor);
-                brightnessState = Math.max(lowPowerBrightnessFloat, PowerManager.BRIGHTNESS_MIN);
-                mBrightnessReasonTemp.addModifier(BrightnessReason.MODIFIER_LOW_POWER);
-            }
-            if (!mAppliedLowPower) {
-                slowChange = false;
-            }
-            mAppliedLowPower = true;
-        } else if (mAppliedLowPower) {
-            slowChange = false;
-            mAppliedLowPower = false;
-        }
+        DisplayBrightnessState clampedState = mBrightnessClamperController.clamp(mPowerRequest,
+                brightnessState, slowChange);
+
+        brightnessState = clampedState.getBrightness();
+        slowChange = clampedState.isSlowChange();
+        mBrightnessReasonTemp.addModifier(clampedState.getBrightnessReason().getModifier());
 
         // The current brightness to use has been calculated at this point, and HbmController should
         // be notified so that it can accurately calculate HDR or HBM levels. We specifically do it
@@ -1542,8 +1501,6 @@
             // allowed range.
             float animateValue = clampScreenBrightness(brightnessState);
 
-            animateValue = mBrightnessClamperController.clamp(animateValue);
-
             // If there are any HDR layers on the screen, we have a special brightness value that we
             // use instead. We still preserve the calculated brightness for Standard Dynamic Range
             // (SDR) layers, but the main brightness value will be the one for HDR.
@@ -2379,7 +2336,6 @@
         pw.println();
         pw.println("Display Power Controller Configuration:");
         pw.println("  mScreenBrightnessDozeConfig=" + mScreenBrightnessDozeConfig);
-        pw.println("  mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
         pw.println("  mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
         pw.println("  mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
         pw.println("  mColorFadeFadesConfig=" + mColorFadeFadesConfig);
@@ -2411,7 +2367,6 @@
         pw.println("  mPowerRequest=" + mPowerRequest);
         pw.println("  mBrightnessReason=" + mBrightnessReason);
         pw.println("  mAppliedDimming=" + mAppliedDimming);
-        pw.println("  mAppliedLowPower=" + mAppliedLowPower);
         pw.println("  mAppliedThrottling=" + mAppliedThrottling);
         pw.println("  mDozing=" + mDozing);
         pw.println("  mSkipRampState=" + skipRampStateToString(mSkipRampState));
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 4edc8bc..9c271ff5 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -441,6 +441,9 @@
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_ALWAYS_UNLOCKED) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_ALWAYS_UNLOCKED;
             }
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0) {
+                mBaseDisplayInfo.flags |= Display.FLAG_ROTATES_WITH_CONTENT;
+            }
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_TOUCH_FEEDBACK_DISABLED) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_TOUCH_FEEDBACK_DISABLED;
             }
diff --git a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
index 6191861..6936112 100644
--- a/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
@@ -147,9 +147,12 @@
         try {
             if (projection != null) {
                 projection.registerCallback(mediaProjectionCallback);
+                Slog.d(TAG, "Virtual Display: registered media projection callback for new "
+                        + "VirtualDisplayDevice");
             }
             appToken.linkToDeath(device, 0);
         } catch (RemoteException ex) {
+            Slog.e(TAG, "Virtual Display: error while setting up VirtualDisplayDevice", ex);
             mVirtualDisplayDevices.remove(appToken);
             device.destroyLocked(false);
             return null;
@@ -445,6 +448,7 @@
         }
 
         public void stopLocked() {
+            Slog.d(TAG, "Virtual Display: stopping device " + mName);
             setSurfaceLocked(null);
             mStopped = true;
         }
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
index 9345a3d..54a280f 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java
@@ -21,6 +21,9 @@
 
 import java.io.PrintWriter;
 
+/**
+ * Provides max allowed brightness
+ */
 abstract class BrightnessClamper<T> {
 
     protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
index d0f28c3..9b28989 100644
--- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java
@@ -20,6 +20,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.Context;
+import android.hardware.display.DisplayManagerInternal;
 import android.os.Handler;
 import android.os.HandlerExecutor;
 import android.os.PowerManager;
@@ -28,6 +30,7 @@
 import android.util.IndentingPrintWriter;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.DisplayDeviceConfig;
 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
@@ -42,7 +45,7 @@
  */
 public class BrightnessClamperController {
 
-    private static final boolean ENABLED = false;
+    private static final boolean THERMAL_ENABLED = false;
 
     private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
     private final Handler mHandler;
@@ -50,6 +53,8 @@
 
     private final Executor mExecutor;
     private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers = new ArrayList<>();
+
+    private final List<BrightnessModifier> mModifiers = new ArrayList<>();
     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
             properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
     private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
@@ -57,13 +62,13 @@
     private Type mClamperType = null;
 
     public BrightnessClamperController(Handler handler,
-            ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
-        this(new Injector(), handler, clamperChangeListener, data);
+            ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) {
+        this(new Injector(), handler, clamperChangeListener, data, context);
     }
 
     @VisibleForTesting
     BrightnessClamperController(Injector injector, Handler handler,
-            ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
+            ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) {
         mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
         mHandler = handler;
         mClamperChangeListenerExternal = clamperChangeListener;
@@ -77,11 +82,13 @@
             }
         };
 
-        if (ENABLED) {
+        if (THERMAL_ENABLED) {
             mClampers.add(
                     new BrightnessThermalClamper(handler, clamperChangeListenerInternal, data));
-            start();
         }
+        mModifiers.add(new DisplayDimModifier(context));
+        mModifiers.add(new BrightnessLowPowerModeModifier());
+        start();
     }
 
     /**
@@ -95,8 +102,19 @@
      * Applies clamping
      * Called in DisplayControllerHandler
      */
-    public float clamp(float value) {
-        return Math.min(value, mBrightnessCap);
+    public DisplayBrightnessState clamp(DisplayManagerInternal.DisplayPowerRequest request,
+            float brightnessValue, boolean slowChange) {
+        float cappedBrightness = Math.min(brightnessValue, mBrightnessCap);
+
+        DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+        builder.setIsSlowChange(slowChange);
+        builder.setBrightness(cappedBrightness);
+
+        for (int i = 0; i < mModifiers.size(); i++) {
+            mModifiers.get(i).apply(request, builder);
+        }
+
+        return builder.build();
     }
 
     /**
@@ -108,6 +126,7 @@
         writer.println("  mClamperType: " + mClamperType);
         IndentingPrintWriter ipw = new IndentingPrintWriter(writer, "    ");
         mClampers.forEach(clamper -> clamper.dump(ipw));
+        mModifiers.forEach(modifier -> modifier.dump(ipw));
     }
 
     /**
@@ -144,8 +163,10 @@
     }
 
     private void start() {
-        mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
-                mExecutor, mOnPropertiesChangedListener);
+        if (!mClampers.isEmpty()) {
+            mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
+                    mExecutor, mOnPropertiesChangedListener);
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
new file mode 100644
index 0000000..b478952
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifier.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.util.IndentingPrintWriter;
+
+import com.android.server.display.brightness.BrightnessReason;
+
+import java.io.PrintWriter;
+
+class BrightnessLowPowerModeModifier extends BrightnessModifier {
+
+    @Override
+    boolean shouldApply(DisplayManagerInternal.DisplayPowerRequest request) {
+        return request.lowPowerMode;
+    }
+
+
+    @Override
+    float getBrightnessAdjusted(float currentBrightness,
+            DisplayManagerInternal.DisplayPowerRequest request) {
+        final float brightnessFactor =
+                Math.min(request.screenLowPowerBrightnessFactor, 1);
+        return Math.max((currentBrightness * brightnessFactor), PowerManager.BRIGHTNESS_MIN);
+    }
+
+    @Override
+    int getModifier() {
+        return BrightnessReason.MODIFIER_LOW_POWER;
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+        pw.println("BrightnessLowPowerModeModifier:");
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
+        super.dump(ipw);
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
new file mode 100644
index 0000000..112e63d
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessModifier.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import com.android.server.display.DisplayBrightnessState;
+
+import java.io.PrintWriter;
+
+/**
+ * Modifies current brightness based on request
+ */
+abstract class BrightnessModifier {
+
+    private boolean mApplied = false;
+
+    abstract boolean shouldApply(DisplayManagerInternal.DisplayPowerRequest request);
+
+    abstract float getBrightnessAdjusted(float currentBrightness,
+            DisplayManagerInternal.DisplayPowerRequest request);
+
+    abstract int getModifier();
+
+    void apply(DisplayManagerInternal.DisplayPowerRequest request,
+            DisplayBrightnessState.Builder stateBuilder) {
+        // If low power mode is enabled, scale brightness by screenLowPowerBrightnessFactor
+        // as long as it is above the minimum threshold.
+        if (shouldApply(request)) {
+            float value = stateBuilder.getBrightness();
+            if (value > PowerManager.BRIGHTNESS_MIN) {
+                stateBuilder.setBrightness(getBrightnessAdjusted(value, request));
+                stateBuilder.getBrightnessReason().addModifier(getModifier());
+            }
+            if (!mApplied) {
+                stateBuilder.setIsSlowChange(false);
+            }
+            mApplied = true;
+        } else if (mApplied) {
+            stateBuilder.setIsSlowChange(false);
+            mApplied = false;
+        }
+    }
+
+    void dump(PrintWriter pw) {
+        pw.println("BrightnessModifier:");
+        pw.println("  mApplied=" + mApplied);
+    }
+}
diff --git a/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
new file mode 100644
index 0000000..4ff7bdb
--- /dev/null
+++ b/services/core/java/com/android/server/display/brightness/clamper/DisplayDimModifier.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+import android.util.IndentingPrintWriter;
+
+import com.android.internal.R;
+import com.android.server.display.brightness.BrightnessReason;
+import com.android.server.display.brightness.BrightnessUtils;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+
+class DisplayDimModifier extends BrightnessModifier {
+
+    // The dim screen brightness.
+    private final float mScreenBrightnessDimConfig;
+
+    // The minimum dim amount to use if the screen brightness is already below
+    // mScreenBrightnessDimConfig.
+    private final float mScreenBrightnessMinimumDimAmount;
+
+    DisplayDimModifier(Context context) {
+        PowerManager pm = Objects.requireNonNull(context.getSystemService(PowerManager.class));
+        Resources resources = context.getResources();
+
+        mScreenBrightnessDimConfig = BrightnessUtils.clampAbsoluteBrightness(
+                pm.getBrightnessConstraint(PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM));
+        mScreenBrightnessMinimumDimAmount = resources.getFloat(
+                R.dimen.config_screenBrightnessMinimumDimAmountFloat);
+    }
+
+
+    @Override
+    boolean shouldApply(DisplayManagerInternal.DisplayPowerRequest request) {
+        return request.policy == DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+    }
+
+    @Override
+    float getBrightnessAdjusted(float currentBrightness,
+            DisplayManagerInternal.DisplayPowerRequest request) {
+        return Math.max(
+                Math.min(currentBrightness - mScreenBrightnessMinimumDimAmount,
+                        mScreenBrightnessDimConfig),
+                PowerManager.BRIGHTNESS_MIN);
+    }
+
+    @Override
+    int getModifier() {
+        return BrightnessReason.MODIFIER_DIMMED;
+    }
+
+    @Override
+    public void dump(PrintWriter pw) {
+        pw.println("DisplayDimModifier:");
+        pw.println("  mScreenBrightnessDimConfig=" + mScreenBrightnessDimConfig);
+        pw.println("  mScreenBrightnessMinimumDimAmount=" + mScreenBrightnessMinimumDimAmount);
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
+        super.dump(ipw);
+    }
+}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
index 7ae7d5c..53c0217 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecAtomWriter.java
@@ -222,6 +222,21 @@
         );
     }
 
+    /**
+     * Writes a HdmiSoundbarModeStatusReported atom representing a Dynamic soundbar mode status
+     * change.
+     * @param isSupported         Whether the hardware supports ARC.
+     * @param isEnabled           Whether DSM is enabled.
+     * @param enumLogReason       The event that triggered the log.
+     */
+    public void dsmStatusChanged(boolean isSupported, boolean isEnabled, int enumLogReason) {
+        FrameworkStatsLog.write(
+                FrameworkStatsLog.HDMI_SOUNDBAR_MODE_STATUS_REPORTED,
+                isSupported,
+                isEnabled,
+                enumLogReason);
+    }
+
     private int earcStateToEnum(int earcState) {
         switch (earcState) {
             case HDMI_EARC_STATUS_IDLE:
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index dd45307..5abb2b5 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -694,13 +694,13 @@
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_ENABLED);
 
         mSoundbarModeFeatureFlagEnabled = mDeviceConfig.getBoolean(
-                Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE, false);
+                Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE, true);
         mEarcTxFeatureFlagEnabled = mDeviceConfig.getBoolean(
-                Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX, false);
+                Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX, true);
         mTransitionFromArcToEarcTxEnabled = mDeviceConfig.getBoolean(
-                Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX, false);
+                Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX, true);
         mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled = mDeviceConfig.getBoolean(
-                Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI, false);
+                Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI, true);
 
         synchronized (mLock) {
             mEarcEnabled = (mHdmiCecConfig.getIntValue(
@@ -857,7 +857,7 @@
                         public void onPropertiesChanged(DeviceConfig.Properties properties) {
                             mEarcTxFeatureFlagEnabled = properties.getBoolean(
                                     Constants.DEVICE_CONFIG_FEATURE_FLAG_ENABLE_EARC_TX,
-                                    false);
+                                    true);
                             boolean earcEnabledSetting = mHdmiCecConfig.getIntValue(
                                     HdmiControlManager.SETTING_NAME_EARC_ENABLED)
                                     == EARC_FEATURE_ENABLED;
@@ -891,7 +891,7 @@
                     public void onPropertiesChanged(DeviceConfig.Properties properties) {
                         mSoundbarModeFeatureFlagEnabled = properties.getBoolean(
                                 Constants.DEVICE_CONFIG_FEATURE_FLAG_SOUNDBAR_MODE,
-                                false);
+                                true);
                         boolean soundbarModeSetting = mHdmiCecConfig.getIntValue(
                                 HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
                                 == SOUNDBAR_MODE_ENABLED;
@@ -917,7 +917,7 @@
                     public void onPropertiesChanged(DeviceConfig.Properties properties) {
                         mTransitionFromArcToEarcTxEnabled = properties.getBoolean(
                                 Constants.DEVICE_CONFIG_FEATURE_FLAG_TRANSITION_ARC_TO_EARC_TX,
-                                false);
+                                true);
                     }
                 });
 
@@ -927,7 +927,7 @@
                     public void onPropertiesChanged(DeviceConfig.Properties properties) {
                         mNumericSoundbarVolumeUiOnTvFeatureFlagEnabled = properties.getBoolean(
                                 Constants.DEVICE_CONFIG_FEATURE_FLAG_TV_NUMERIC_SOUNDBAR_VOLUME_UI,
-                                false);
+                                true);
                         checkAndUpdateAbsoluteVolumeBehavior();
                     }
                 });
@@ -1064,13 +1064,18 @@
      */
     @VisibleForTesting
     public void setSoundbarMode(final int settingValue) {
+        boolean isArcSupported = isArcSupported();
         HdmiCecLocalDevicePlayback playback = playback();
         HdmiCecLocalDeviceAudioSystem audioSystem = audioSystem();
+        getAtomWriter().dsmStatusChanged(isArcSupported,
+                settingValue == SOUNDBAR_MODE_ENABLED,
+                HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED);
+
         if (playback == null) {
             Slog.w(TAG, "Device type not compatible to change soundbar mode.");
             return;
         }
-        if (!SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)) {
+        if (!isArcSupported) {
             Slog.w(TAG, "Device type doesn't support ARC.");
             return;
         }
@@ -1269,11 +1274,8 @@
     @ServiceThreadOnly
     private List<Integer> getCecLocalDeviceTypes() {
         ArrayList<Integer> allLocalDeviceTypes = new ArrayList<>(mCecLocalDevices);
-        if (mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
-                == SOUNDBAR_MODE_ENABLED
-                && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
-                && SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true)
-                && mSoundbarModeFeatureFlagEnabled) {
+        if (isDsmEnabled() && !allLocalDeviceTypes.contains(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM)
+                && isArcSupported() && mSoundbarModeFeatureFlagEnabled) {
             allLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
         }
         return allLocalDeviceTypes;
@@ -3589,6 +3591,16 @@
         }
     }
 
+    private boolean isDsmEnabled() {
+        return mHdmiCecConfig.getIntValue(HdmiControlManager.CEC_SETTING_NAME_SOUNDBAR_MODE)
+                == SOUNDBAR_MODE_ENABLED;
+    }
+
+    @VisibleForTesting
+    protected boolean isArcSupported() {
+        return SystemProperties.getBoolean(Constants.PROPERTY_ARC_SUPPORT, true);
+    }
+
     @ServiceThreadOnly
     int getPowerStatus() {
         assertRunOnServiceThread();
@@ -3705,6 +3717,9 @@
             int earcStatus = getEarcStatus();
             getAtomWriter().earcStatusChanged(isEarcSupported(), isEarcEnabled(),
                     earcStatus, earcStatus, HdmiStatsEnums.LOG_REASON_WAKE);
+        } else if (isPlaybackDevice()) {
+            getAtomWriter().dsmStatusChanged(isArcSupported(), isDsmEnabled(),
+                    HdmiStatsEnums.LOG_REASON_DSM_WAKE);
         }
         // TODO: Initialize MHL local devices.
     }
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 5bdf263..a5162c0 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -169,7 +169,9 @@
     @Override
     @MainThread
     public void onInputDeviceAdded(int deviceId) {
-        onInputDeviceChanged(deviceId);
+        // Logging keyboard configuration data to statsd whenever input device is added. Currently
+        // only logging for New Settings UI where we are using IME to decide the layout information.
+        onInputDeviceChangedInternal(deviceId, true /* shouldLogConfiguration */);
     }
 
     @Override
@@ -182,6 +184,10 @@
     @Override
     @MainThread
     public void onInputDeviceChanged(int deviceId) {
+        onInputDeviceChangedInternal(deviceId, false /* shouldLogConfiguration */);
+    }
+
+    private void onInputDeviceChangedInternal(int deviceId, boolean shouldLogConfiguration) {
         final InputDevice inputDevice = getInputDevice(deviceId);
         if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
             return;
@@ -243,18 +249,15 @@
             synchronized (mDataStore) {
                 try {
                     final String key = keyboardIdentifier.toString();
-                    boolean isFirstConfiguration = !mDataStore.hasInputDeviceEntry(key);
                     if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
                         // Need to show the notification only if layout selection changed
                         // from the previous configuration
                         needToShowNotification = true;
+                    }
 
-                        // Logging keyboard configuration data to statsd only if the
-                        // configuration changed from the previous configuration. Currently
-                        // only logging for New Settings UI where we are using IME to decide
-                        // the layout information.
+                    if (shouldLogConfiguration) {
                         logKeyboardConfigurationEvent(inputDevice, imeInfoList, layoutInfoList,
-                                isFirstConfiguration);
+                                !mDataStore.hasInputDeviceEntry(key));
                     }
                 } finally {
                     mDataStore.saveIfNeeded();
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 4b30ae5..08e5977 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -21,10 +21,10 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.content.ComponentName;
 import android.content.Intent;
 import android.hardware.input.KeyboardLayout;
 import android.icu.util.ULocale;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -41,9 +41,7 @@
 import java.lang.annotation.Retention;
 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.Set;
 
@@ -59,6 +57,7 @@
 
     @Retention(SOURCE)
     @IntDef(prefix = {"LAYOUT_SELECTION_CRITERIA_"}, value = {
+            LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
             LAYOUT_SELECTION_CRITERIA_USER,
             LAYOUT_SELECTION_CRITERIA_DEVICE,
             LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD,
@@ -67,23 +66,26 @@
     public @interface LayoutSelectionCriteria {
     }
 
+    /** Unspecified layout selection criteria */
+    public static final int LAYOUT_SELECTION_CRITERIA_UNSPECIFIED = 0;
+
     /** Manual selection by user */
-    public static final int LAYOUT_SELECTION_CRITERIA_USER = 0;
+    public static final int LAYOUT_SELECTION_CRITERIA_USER = 1;
 
     /** Auto-detection based on device provided language tag and layout type */
-    public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 1;
+    public static final int LAYOUT_SELECTION_CRITERIA_DEVICE = 2;
 
     /** Auto-detection based on IME provided language tag and layout type */
-    public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 2;
+    public static final int LAYOUT_SELECTION_CRITERIA_VIRTUAL_KEYBOARD = 3;
 
     /** Default selection */
-    public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 3;
+    public static final int LAYOUT_SELECTION_CRITERIA_DEFAULT = 4;
 
     @VisibleForTesting
-    static final String DEFAULT_LAYOUT = "Default";
+    static final String DEFAULT_LAYOUT_NAME = "Default";
 
     @VisibleForTesting
-    static final String DEFAULT_LANGUAGE_TAG = "None";
+    public static final String DEFAULT_LANGUAGE_TAG = "None";
 
     public enum KeyboardLogEvent {
         UNSPECIFIED(
@@ -536,23 +538,23 @@
                             mLayoutSelectionCriteriaList.get(i);
                     InputMethodSubtype imeSubtype = mImeSubtypeList.get(i);
                     String keyboardLanguageTag = mInputDevice.getKeyboardLanguageTag();
-                    keyboardLanguageTag = keyboardLanguageTag == null ? DEFAULT_LANGUAGE_TAG
-                            : keyboardLanguageTag;
+                    keyboardLanguageTag = TextUtils.isEmpty(keyboardLanguageTag)
+                            ? DEFAULT_LANGUAGE_TAG : keyboardLanguageTag;
                     int keyboardLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
                             mInputDevice.getKeyboardLayoutType());
 
                     ULocale pkLocale = imeSubtype.getPhysicalKeyboardHintLanguageTag();
-                    String canonicalizedLanguageTag =
-                            imeSubtype.getCanonicalizedLanguageTag().equals("")
-                            ? DEFAULT_LANGUAGE_TAG : imeSubtype.getCanonicalizedLanguageTag();
                     String imeLanguageTag = pkLocale != null ? pkLocale.toLanguageTag()
-                            : canonicalizedLanguageTag;
+                            : imeSubtype.getCanonicalizedLanguageTag();
+                    imeLanguageTag = TextUtils.isEmpty(imeLanguageTag) ? DEFAULT_LANGUAGE_TAG
+                            : imeLanguageTag;
                     int imeLayoutType = KeyboardLayout.LayoutType.getLayoutTypeEnumValue(
                             imeSubtype.getPhysicalKeyboardHintLayoutType());
 
                     // Sanitize null values
                     String keyboardLayoutName =
-                            selectedLayout == null ? DEFAULT_LAYOUT : selectedLayout.getLabel();
+                            selectedLayout == null ? DEFAULT_LAYOUT_NAME
+                                    : selectedLayout.getLabel();
 
                     configurationList.add(
                             new LayoutConfiguration(keyboardLayoutType, keyboardLanguageTag,
@@ -601,6 +603,8 @@
     private static String getStringForSelectionCriteria(
             @LayoutSelectionCriteria int layoutSelectionCriteria) {
         switch (layoutSelectionCriteria) {
+            case LAYOUT_SELECTION_CRITERIA_UNSPECIFIED:
+                return "LAYOUT_SELECTION_CRITERIA_UNSPECIFIED";
             case LAYOUT_SELECTION_CRITERIA_USER:
                 return "LAYOUT_SELECTION_CRITERIA_USER";
             case LAYOUT_SELECTION_CRITERIA_DEVICE:
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 9ad4628..2e0274b 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -23,6 +23,7 @@
 import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_FORCED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
+import static android.view.MotionEvent.TOOL_TYPE_UNKNOWN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
@@ -44,6 +45,7 @@
 import android.util.Printer;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
+import android.view.MotionEvent;
 import android.view.WindowManager;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputMethod;
@@ -351,7 +353,8 @@
 
     void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) {
         final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
-        if (state != null && newState.hasEditorFocused()) {
+        if (state != null && newState.hasEditorFocused()
+                && newState.mToolType != MotionEvent.TOOL_TYPE_STYLUS) {
             // Inherit the last requested IME visible state when the target window is still
             // focused with an editor.
             newState.setRequestedImeVisible(state.mRequestedImeVisible);
@@ -652,14 +655,23 @@
      * A class that represents the current state of the IME target window.
      */
     static class ImeTargetWindowState {
+
         ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags,
                 boolean imeFocusChanged, boolean hasFocusedEditor,
                 boolean isStartInputByGainFocus) {
+            this(softInputModeState, windowFlags, imeFocusChanged, hasFocusedEditor,
+                    isStartInputByGainFocus, TOOL_TYPE_UNKNOWN);
+        }
+
+        ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags,
+                boolean imeFocusChanged, boolean hasFocusedEditor,
+                boolean isStartInputByGainFocus, @MotionEvent.ToolType int toolType) {
             mSoftInputModeState = softInputModeState;
             mWindowFlags = windowFlags;
             mImeFocusChanged = imeFocusChanged;
             mHasFocusedEditor = hasFocusedEditor;
             mIsStartInputByGainFocus = isStartInputByGainFocus;
+            mToolType = toolType;
         }
 
         /**
@@ -670,6 +682,11 @@
         private final int mWindowFlags;
 
         /**
+         * {@link MotionEvent#getToolType(int)} that was used to click editor.
+         */
+        private final int mToolType;
+
+        /**
          * {@code true} means the IME focus changed from the previous window, {@code false}
          * otherwise.
          */
@@ -718,6 +735,10 @@
             return mWindowFlags;
         }
 
+        int getToolType() {
+            return mToolType;
+        }
+
         private void setImeDisplayId(int imeDisplayId) {
             mImeDisplayId = imeDisplayId;
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 1d222e5..20c7029 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -40,6 +40,7 @@
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethod;
 import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -295,7 +296,12 @@
                     }
                     if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
                     final InputMethodInfo info = mMethodMap.get(mSelectedMethodId);
+                    boolean supportsStylusHwChanged =
+                            mSupportsStylusHw != info.supportsStylusHandwriting();
                     mSupportsStylusHw = info.supportsStylusHandwriting();
+                    if (supportsStylusHwChanged) {
+                        InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
+                    }
                     mService.initializeImeLocked(mCurMethod, mCurToken);
                     mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
                     mService.reRequestCurrentClientSessionLocked();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 4ce7e24..2fc4829 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -2315,8 +2315,6 @@
             mCurClient = null;
             ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
             mCurStatsToken = null;
-            InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
-
             mMenuController.hideInputMethodMenuLocked();
         }
     }
@@ -3794,11 +3792,14 @@
         final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
         final boolean startInputByWinGainedFocus =
                 (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
+        final int toolType = editorInfo != null
+                ? editorInfo.getInitialToolType() : MotionEvent.TOOL_TYPE_UNKNOWN;
 
         // Init the focused window state (e.g. whether the editor has focused or IME focus has
         // changed from another window).
-        final ImeTargetWindowState windowState = new ImeTargetWindowState(softInputMode,
-                windowFlags, !sameWindowFocused, isTextEditor, startInputByWinGainedFocus);
+        final ImeTargetWindowState windowState = new ImeTargetWindowState(
+                softInputMode, windowFlags, !sameWindowFocused, isTextEditor,
+                startInputByWinGainedFocus, toolType);
         mVisibilityStateComputer.setWindowState(windowToken, windowState);
 
         if (sameWindowFocused && isTextEditor) {
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 595b2e4..74b7f08 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -1750,13 +1750,6 @@
         }
 
         @Override
-        public void sendNiResponse(int notifId, int userResponse) {
-            if (mGnssManagerService != null) {
-                mGnssManagerService.sendNiResponse(notifId, userResponse);
-            }
-        }
-
-        @Override
         public @Nullable LocationTime getGnssTimeMillis() {
             LocationProviderManager gpsManager = getLocationProviderManager(GPS_PROVIDER);
             if (gpsManager == null) {
diff --git a/services/core/java/com/android/server/location/TEST_MAPPING b/services/core/java/com/android/server/location/TEST_MAPPING
index 214d2f3..f5deb2b 100644
--- a/services/core/java/com/android/server/location/TEST_MAPPING
+++ b/services/core/java/com/android/server/location/TEST_MAPPING
@@ -1,7 +1,13 @@
 {
   "presubmit": [
     {
-      "name": "CtsLocationFineTestCases"
+      "name": "CtsLocationFineTestCases",
+      "options": [
+          {
+             // TODO: Wait for test to deflake - b/293934372
+             "exclude-filter":"android.location.cts.fine.ScanningSettingsTest"
+          }
+      ]
     },
     {
       "name": "CtsLocationCoarseTestCases"
@@ -16,4 +22,4 @@
       }]
     }
   ]
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
index ed5c130..e97a12a 100644
--- a/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/gnss/GnssLocationProvider.java
@@ -62,7 +62,6 @@
 import android.database.ContentObserver;
 import android.location.GnssCapabilities;
 import android.location.GnssStatus;
-import android.location.INetInitiatedListener;
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
@@ -109,7 +108,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.location.GpsNetInitiatedHandler;
-import com.android.internal.location.GpsNetInitiatedHandler.GpsNiNotification;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.HexDump;
 import com.android.server.FgThread;
@@ -396,7 +394,6 @@
         mC2KServerPort = mGnssConfiguration.getC2KPort(TCP_MIN_PORT);
         mNIHandler.setEmergencyExtensionSeconds(mGnssConfiguration.getEsExtensionSec());
         mSuplEsEnabled = mGnssConfiguration.getSuplEs(0) == 1;
-        mNIHandler.setSuplEsEnabled(mSuplEsEnabled);
         if (mGnssVisibilityControl != null) {
             mGnssVisibilityControl.onConfigurationUpdated(mGnssConfiguration);
         }
@@ -465,7 +462,6 @@
                     }
                 };
         mNIHandler = new GpsNetInitiatedHandler(context,
-                mNetInitiatedListener,
                 emergencyCallCallback,
                 mSuplEsEnabled);
         // Trigger PSDS data download when the network comes up after booting.
@@ -1435,96 +1431,6 @@
         updateRequirements();
     }
 
-    //=============================================================
-    // NI Client support
-    //=============================================================
-    private final INetInitiatedListener mNetInitiatedListener = new INetInitiatedListener.Stub() {
-        // Sends a response for an NI request to HAL.
-        @Override
-        public boolean sendNiResponse(int notificationId, int userResponse) {
-            // TODO Add Permission check
-
-            if (DEBUG) {
-                Log.d(TAG, "sendNiResponse, notifId: " + notificationId
-                        + ", response: " + userResponse);
-            }
-            mGnssNative.sendNiResponse(notificationId, userResponse);
-
-            FrameworkStatsLog.write(FrameworkStatsLog.GNSS_NI_EVENT_REPORTED,
-                    FrameworkStatsLog.GNSS_NI_EVENT_REPORTED__EVENT_TYPE__NI_RESPONSE,
-                    notificationId,
-                    /* niType= */ 0,
-                    /* needNotify= */ false,
-                    /* needVerify= */ false,
-                    /* privacyOverride= */ false,
-                    /* timeout= */ 0,
-                    /* defaultResponse= */ 0,
-                    /* requestorId= */ null,
-                    /* text= */ null,
-                    /* requestorIdEncoding= */ 0,
-                    /* textEncoding= */ 0,
-                    mSuplEsEnabled,
-                    isGpsEnabled(),
-                    userResponse);
-
-            return true;
-        }
-    };
-
-    public INetInitiatedListener getNetInitiatedListener() {
-        return mNetInitiatedListener;
-    }
-
-    /** Reports a NI notification. */
-    private void reportNiNotification(int notificationId, int niType, int notifyFlags, int timeout,
-            int defaultResponse, String requestorId, String text, int requestorIdEncoding,
-            int textEncoding) {
-        Log.i(TAG, "reportNiNotification: entered");
-        Log.i(TAG, "notificationId: " + notificationId
-                + ", niType: " + niType
-                + ", notifyFlags: " + notifyFlags
-                + ", timeout: " + timeout
-                + ", defaultResponse: " + defaultResponse);
-
-        Log.i(TAG, "requestorId: " + requestorId
-                + ", text: " + text
-                + ", requestorIdEncoding: " + requestorIdEncoding
-                + ", textEncoding: " + textEncoding);
-
-        GpsNiNotification notification = new GpsNiNotification();
-
-        notification.notificationId = notificationId;
-        notification.niType = niType;
-        notification.needNotify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_NOTIFY) != 0;
-        notification.needVerify = (notifyFlags & GpsNetInitiatedHandler.GPS_NI_NEED_VERIFY) != 0;
-        notification.privacyOverride =
-                (notifyFlags & GpsNetInitiatedHandler.GPS_NI_PRIVACY_OVERRIDE) != 0;
-        notification.timeout = timeout;
-        notification.defaultResponse = defaultResponse;
-        notification.requestorId = requestorId;
-        notification.text = text;
-        notification.requestorIdEncoding = requestorIdEncoding;
-        notification.textEncoding = textEncoding;
-
-        mNIHandler.handleNiNotification(notification);
-        FrameworkStatsLog.write(FrameworkStatsLog.GNSS_NI_EVENT_REPORTED,
-                FrameworkStatsLog.GNSS_NI_EVENT_REPORTED__EVENT_TYPE__NI_REQUEST,
-                notification.notificationId,
-                notification.niType,
-                notification.needNotify,
-                notification.needVerify,
-                notification.privacyOverride,
-                notification.timeout,
-                notification.defaultResponse,
-                notification.requestorId,
-                notification.text,
-                notification.requestorIdEncoding,
-                notification.textEncoding,
-                mSuplEsEnabled,
-                isGpsEnabled(),
-                /* userResponse= */ 0);
-    }
-
     private void demandUtcTimeInjection() {
         if (DEBUG) Log.d(TAG, "demandUtcTimeInjection");
         postWithWakeLockHeld(mNetworkTimeHelper::demandUtcTimeInjection);
@@ -1829,14 +1735,6 @@
     }
 
     @Override
-    public void onReportNiNotification(int notificationId, int niType, int notifyFlags,
-            int timeout, int defaultResponse, String requestorId, String text,
-            int requestorIdEncoding, int textEncoding) {
-        reportNiNotification(notificationId, niType, notifyFlags, timeout,
-                defaultResponse, requestorId, text, requestorIdEncoding, textEncoding);
-    }
-
-    @Override
     public void onRequestSetID(@GnssNative.AGpsCallbacks.AgpsSetIdFlags int flags) {
         TelephonyManager phone = (TelephonyManager)
                 mContext.getSystemService(Context.TELEPHONY_SERVICE);
diff --git a/services/core/java/com/android/server/location/gnss/GnssManagerService.java b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
index c962bc4..133704d 100644
--- a/services/core/java/com/android/server/location/gnss/GnssManagerService.java
+++ b/services/core/java/com/android/server/location/gnss/GnssManagerService.java
@@ -38,7 +38,6 @@
 import android.location.util.identity.CallerIdentity;
 import android.os.BatteryStats;
 import android.os.Binder;
-import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.IndentingPrintWriter;
@@ -275,17 +274,6 @@
     }
 
     /**
-     * Send Ni Response, indicating a location request initiated by a network carrier.
-     */
-    public void sendNiResponse(int notifId, int userResponse) {
-        try {
-            mGnssLocationProvider.getNetInitiatedListener().sendNiResponse(notifId, userResponse);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * Dump info for debugging.
      */
     public void dump(FileDescriptor fd, IndentingPrintWriter ipw, String[] args) {
diff --git a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
index 7618419..bdd4885 100644
--- a/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
+++ b/services/core/java/com/android/server/location/gnss/hal/GnssNative.java
@@ -284,9 +284,6 @@
 
     /** Callbacks for notifications. */
     public interface NotificationCallbacks {
-        void onReportNiNotification(int notificationId, int niType, int notifyFlags,
-                int timeout, int defaultResponse, String requestorId, String text,
-                int requestorIdEncoding, int textEncoding);
         void onReportNfwNotification(String proxyAppPackageName, byte protocolStack,
                 String otherProtocolStackName, byte requestor, String requestorId,
                 byte responseType, boolean inEmergencyMode, boolean isCachedLocation);
@@ -933,14 +930,6 @@
     }
 
     /**
-     * Send a network initiated respnse.
-     */
-    public void sendNiResponse(int notificationId, int userResponse) {
-        Preconditions.checkState(mRegistered);
-        mGnssHal.sendNiResponse(notificationId, userResponse);
-    }
-
-    /**
      * Request an eventual update of GNSS power statistics.
      */
     public void requestPowerStats() {
@@ -1244,16 +1233,6 @@
     }
 
     @NativeEntryPoint
-    void reportNiNotification(int notificationId, int niType, int notifyFlags,
-            int timeout, int defaultResponse, String requestorId, String text,
-            int requestorIdEncoding, int textEncoding) {
-        Binder.withCleanCallingIdentity(
-                () -> mNotificationCallbacks.onReportNiNotification(notificationId, niType,
-                        notifyFlags, timeout, defaultResponse, requestorId, text,
-                        requestorIdEncoding, textEncoding));
-    }
-
-    @NativeEntryPoint
     void requestSetID(int flags) {
         Binder.withCleanCallingIdentity(() -> mAGpsCallbacks.onRequestSetID(flags));
     }
@@ -1488,10 +1467,6 @@
             return native_is_gnss_visibility_control_supported();
         }
 
-        protected void sendNiResponse(int notificationId, int userResponse) {
-            native_send_ni_response(notificationId, userResponse);
-        }
-
         protected void requestPowerStats() {
             native_request_power_stats();
         }
@@ -1648,8 +1623,6 @@
 
     private static native boolean native_is_gnss_visibility_control_supported();
 
-    private static native void native_send_ni_response(int notificationId, int userResponse);
-
     // power stats APIs
 
     private static native void native_request_power_stats();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index b3ef869..c3abfc1 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -192,9 +192,40 @@
 import javax.crypto.spec.GCMParameterSpec;
 
 /**
- * Keeps the lock pattern/password data and related settings for each user. Used by
- * LockPatternUtils. Needs to be a service because Settings app also needs to be able to save
- * lockscreen information for secondary users.
+ * LockSettingsService (LSS) mainly has the following responsibilities:
+ * <p>
+ * <ul>
+ *   <li>Provide APIs to verify and change the Lock Screen Knowledge Factor (LSKF) ("lockscreen
+ *   credential") of each user.  Unlock users when their correct LSKF is given.</li>
+ *
+ *   <li>Store other lockscreen related settings, such as some Keyguard (UI) settings.</li>
+ *
+ *   <li>Manage each user's synthetic password (SP), which is their main cryptographic secret.
+ *   See {@link SyntheticPasswordManager}.</li>
+ *
+ *   <li>Protect each user's SP using their LSKF.  Use the Gatekeeper or Weaver HAL to ensure that
+ *   guesses of the LSKF are ratelimited by the TEE or secure element.</li>
+ *
+ *   <li>Protect each user's data using their SP.  For example, use the SP to encrypt/decrypt the
+ *   user's credential-encrypted (CE) key for file-based encryption (FBE).</li>
+ *
+ *   <li>Generate, protect, and use profile passwords for managed profiles.</li>
+ *
+ *   <li>Support unlocking the SP by alternative means: resume-on-reboot (reboot escrow) for easier
+ *   OTA updates, and escrow tokens when set up by the Device Policy Controller (DPC).</li>
+ *
+ *   <li>Implement part of the Factory Reset Protection (FRP) and Repair Mode features by storing
+ *   the information needed to verify a user's LSKF on the persist or metadata partition.</li>
+ *
+ *   <li>Support insider attack resistance using the AuthSecret HAL.</li>
+ *
+ *   <li>Implement "recoverable keystore", a feature that enables end-to-end encrypted backups.
+ *   See {@link android.security.keystore.recovery.RecoveryController}.</li>
+ * </ul>
+ * <p>
+ * The main clients of LockSettingsService are Keyguard (i.e. the lockscreen UI, which is part of
+ * System UI), the Settings app (com.android.settings), and other parts of system_server.  Most
+ * methods are protected by ACCESS_KEYGUARD_SECURE_STORAGE which only system processes can have.
  *
  * @hide
  */
@@ -1284,7 +1315,6 @@
         return getCredentialTypeInternal(userId);
     }
 
-    // TODO: this is a hot path, can we optimize it?
     /**
      * Returns the credential type of the user, can be one of {@link #CREDENTIAL_TYPE_NONE},
      * {@link #CREDENTIAL_TYPE_PATTERN}, {@link #CREDENTIAL_TYPE_PIN} and
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java
index 06db6b8..ef56a1e 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/ApplicationKeyStorage.java
@@ -138,7 +138,7 @@
         } catch (android.security.KeyStoreException e) {
             if (e.getNumericErrorCode()
                     == android.security.KeyStoreException.ERROR_KEY_DOES_NOT_EXIST) {
-                Log.e(TAG, "Failed to get grant for KeyStore key - key not found", e);
+                Log.w(TAG, "Failed to get grant for KeyStore key - key not found");
                 throw new ServiceSpecificException(ERROR_KEY_NOT_FOUND, e.getMessage());
             }
             Log.e(TAG, "Failed to get grant for KeyStore key.", e);
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 7c3ef19..e7bd68e 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -225,6 +225,7 @@
         mMediaRouter.rebindAsUser(to.getUserIdentifier());
         synchronized (mLock) {
             if (mProjectionGrant != null) {
+                Slog.d(TAG, "Content Recording: Stopped MediaProjection due to user switching");
                 mProjectionGrant.stop();
             }
         }
@@ -260,6 +261,8 @@
         }
 
         synchronized (mLock) {
+            Slog.d(TAG,
+                    "Content Recording: Stopped MediaProjection due to foreground service change");
             if (mProjectionGrant != null) {
                 mProjectionGrant.stop();
             }
@@ -268,6 +271,8 @@
 
     private void startProjectionLocked(final MediaProjection projection) {
         if (mProjectionGrant != null) {
+            Slog.d(TAG, "Content Recording: Stopped MediaProjection to start new "
+                    + "incoming projection");
             mProjectionGrant.stop();
         }
         if (mMediaRouteInfo != null) {
@@ -279,6 +284,8 @@
     }
 
     private void stopProjectionLocked(final MediaProjection projection) {
+        Slog.d(TAG, "Content Recording: Stopped active MediaProjection and "
+                + "dispatching stop to callbacks");
         mProjectionToken = null;
         mProjectionGrant = null;
         dispatchStop(projection);
@@ -351,6 +358,13 @@
             if (!setSessionSucceeded) {
                 // Unable to start mirroring, so tear down this projection.
                 if (mProjectionGrant != null) {
+                    String projectionType = incomingSession != null
+                            ? ContentRecordingSession.recordContentToString(
+                                    incomingSession.getContentToRecord()) : "none";
+                    Slog.w(TAG, "Content Recording: Stopped MediaProjection due to failing to set "
+                            + "ContentRecordingSession - id= "
+                            + mProjectionGrant.getVirtualDisplayId() + "type=" + projectionType);
+
                     mProjectionGrant.stop();
                 }
                 return false;
@@ -464,6 +478,9 @@
                     // The grant may now be null if setting the session failed.
                     if (mProjectionGrant != null) {
                         // Always stop the projection.
+                        Slog.w(TAG, "Content Recording: Stopped MediaProjection due to user "
+                                + "consent result of CANCEL - "
+                                + "id= " + mProjectionGrant.getVirtualDisplayId());
                         mProjectionGrant.stop();
                     }
                     break;
@@ -666,6 +683,7 @@
             try {
                 synchronized (mLock) {
                     if (mProjectionGrant != null) {
+                        Slog.d(TAG, "Content Recording: Stopping active projection");
                         mProjectionGrant.stop();
                     }
                 }
@@ -864,6 +882,10 @@
                     MEDIA_PROJECTION_TOKEN_EVENT_CREATED);
         }
 
+        int getVirtualDisplayId() {
+            return mVirtualDisplayId;
+        }
+
         @Override // Binder call
         public boolean canProjectVideo() {
             return mType == MediaProjectionManager.TYPE_MIRRORING ||
@@ -937,12 +959,11 @@
                 registerCallback(mCallback);
                 try {
                     mToken = callback.asBinder();
-                    mDeathEater = new IBinder.DeathRecipient() {
-                        @Override
-                        public void binderDied() {
-                            mCallbackDelegate.remove(callback);
-                            stop();
-                        }
+                    mDeathEater = () -> {
+                        Slog.d(TAG, "Content Recording: MediaProjection stopped by Binder death - "
+                                + "id= " + mVirtualDisplayId);
+                        mCallbackDelegate.remove(callback);
+                        stop();
                     };
                     mToken.linkToDeath(mDeathEater, 0);
                 } catch (RemoteException e) {
@@ -1012,6 +1033,9 @@
                         Binder.restoreCallingIdentity(token);
                     }
                 }
+                Slog.d(TAG, "Content Recording: handling stopping this projection token"
+                        + " createTime= " + mCreateTimeMs
+                        + " countStarts= " + mCountStarts);
                 stopProjectionLocked(this);
                 mToken.unlinkToDeath(mDeathEater, 0);
                 mToken = null;
@@ -1125,6 +1149,8 @@
                 if ((type & MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) != 0) {
                     mMediaRouteInfo = info;
                     if (mProjectionGrant != null) {
+                        Slog.d(TAG, "Content Recording: Stopped MediaProjection due to "
+                                + "route type of REMOTE_DISPLAY not selected");
                         mProjectionGrant.stop();
                     }
                 }
@@ -1296,7 +1322,7 @@
             try {
                 mCallback.onStart(mInfo);
             } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to notify media projection has stopped", e);
+                Slog.w(TAG, "Failed to notify media projection has started", e);
             }
         }
     }
@@ -1370,7 +1396,8 @@
                 return "TYPE_MIRRORING";
             case MediaProjectionManager.TYPE_PRESENTATION:
                 return "TYPE_PRESENTATION";
+            default:
+                return Integer.toString(type);
         }
-        return Integer.toString(type);
     }
 }
diff --git a/services/core/java/com/android/server/notification/ConditionProviders.java b/services/core/java/com/android/server/notification/ConditionProviders.java
index bfc4f53..2da1a68 100644
--- a/services/core/java/com/android/server/notification/ConditionProviders.java
+++ b/services/core/java/com/android/server/notification/ConditionProviders.java
@@ -441,6 +441,58 @@
         return info == null ? null : (IConditionProvider) info.service;
     }
 
+    void resetDefaultFromConfig() {
+        synchronized (mDefaultsLock) {
+            mDefaultComponents.clear();
+            mDefaultPackages.clear();
+        }
+        loadDefaultsFromConfig();
+    }
+
+    boolean removeDefaultFromConfig(int userId) {
+        boolean removed = false;
+        String defaultDndDenied = mContext.getResources().getString(
+                R.string.config_defaultDndDeniedPackages);
+        if (defaultDndDenied != null) {
+            String[] dnds = defaultDndDenied.split(ManagedServices.ENABLED_SERVICES_SEPARATOR);
+            for (int i = 0; i < dnds.length; i++) {
+                if (TextUtils.isEmpty(dnds[i])) {
+                    continue;
+                }
+                removed |= removePackageFromApprovedLists(userId, dnds[i], "remove from config");
+            }
+        }
+        return removed;
+    }
+
+    private boolean removePackageFromApprovedLists(int userId, String pkg, String reason) {
+        boolean removed = false;
+        synchronized (mApproved) {
+            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.get(
+                    userId);
+            if (approvedByType != null) {
+                int approvedByTypeSize = approvedByType.size();
+                for (int i = 0; i < approvedByTypeSize; i++) {
+                    final ArraySet<String> approved = approvedByType.valueAt(i);
+                    int approvedSize = approved.size();
+                    for (int j = approvedSize - 1; j >= 0; j--) {
+                        final String packageOrComponent = approved.valueAt(j);
+                        final String packageName = getPackageName(packageOrComponent);
+                        if (TextUtils.equals(pkg, packageName)) {
+                            approved.removeAt(j);
+                            removed = true;
+                            if (DEBUG) {
+                                Slog.v(TAG, "Removing " + packageOrComponent
+                                        + " from approved list; " + reason);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return removed;
+    }
+
     private static class ConditionRecord {
         public final Uri id;
         public final ComponentName component;
diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java
index 446c4f7..0e76dfb 100644
--- a/services/core/java/com/android/server/notification/NotificationComparator.java
+++ b/services/core/java/com/android/server/notification/NotificationComparator.java
@@ -25,7 +25,7 @@
 import android.content.IntentFilter;
 import android.telecom.TelecomManager;
 
-import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.NotificationMessagingUtil;
 
 import java.util.Comparator;
@@ -34,21 +34,23 @@
 /**
  * Sorts notifications individually into attention-relevant order.
  */
-public class NotificationComparator
-        implements Comparator<NotificationRecord> {
+class NotificationComparator implements Comparator<NotificationRecord> {
 
     private final Context mContext;
     private final NotificationMessagingUtil mMessagingUtil;
-    private final boolean mSortByInterruptiveness;
     private String mDefaultPhoneApp;
 
+    /**
+     * Lock that must be held during a sort() call that uses this {@link Comparator}, AND to make
+     * any changes to the state of this object that could affect the results of {@link #compare}.
+     */
+    public final Object mStateLock = new Object();
+
     public NotificationComparator(Context context) {
         mContext = context;
         mContext.registerReceiver(mPhoneAppBroadcastReceiver,
                 new IntentFilter(TelecomManager.ACTION_DEFAULT_DIALER_CHANGED));
-        mMessagingUtil = new NotificationMessagingUtil(mContext);
-        mSortByInterruptiveness = !SystemUiSystemPropertiesFlags.getResolver().isEnabled(
-                SystemUiSystemPropertiesFlags.NotificationFlags.NO_SORT_BY_INTERRUPTIVENESS);
+        mMessagingUtil = new NotificationMessagingUtil(mContext, mStateLock);
     }
 
     @Override
@@ -139,14 +141,6 @@
             return -1 * Integer.compare(leftPriority, rightPriority);
         }
 
-        if (mSortByInterruptiveness) {
-            final boolean leftInterruptive = left.isInterruptive();
-            final boolean rightInterruptive = right.isInterruptive();
-            if (leftInterruptive != rightInterruptive) {
-                return -1 * Boolean.compare(leftInterruptive, rightInterruptive);
-            }
-        }
-
         // then break ties by time, most recent first
         return -1 * Long.compare(left.getRankingTimeMs(), right.getRankingTimeMs());
     }
@@ -224,8 +218,13 @@
     private final BroadcastReceiver mPhoneAppBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            mDefaultPhoneApp =
-                    intent.getStringExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
+            BackgroundThread.getExecutor().execute(() -> {
+                synchronized (mStateLock) {
+                    mDefaultPhoneApp =
+                            intent.getStringExtra(
+                                    TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME);
+                }
+            });
         }
     };
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
old mode 100644
new mode 100755
index 009e097..e782ea9
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -832,12 +832,32 @@
             allowNotificationListener(userId, cn);
         }
 
+        allowDndPackages(userId);
+
+        setDefaultAssistantForUser(userId);
+    }
+
+    @VisibleForTesting
+    void allowDndPackages(int userId) {
         ArraySet<String> defaultDnds = mConditionProviders.getDefaultPackages();
         for (int i = 0; i < defaultDnds.size(); i++) {
             allowDndPackage(userId, defaultDnds.valueAt(i));
         }
+        if (!isDNDMigrationDone(userId)) {
+            setDNDMigrationDone(userId);
+        }
+    }
 
-        setDefaultAssistantForUser(userId);
+    @VisibleForTesting
+    boolean isDNDMigrationDone(int userId) {
+        return Settings.Secure.getIntForUser(getContext().getContentResolver(),
+                Settings.Secure.DND_CONFIGS_MIGRATED, 0, userId) == 1;
+    }
+
+    @VisibleForTesting
+    void setDNDMigrationDone(int userId) {
+        Settings.Secure.putIntForUser(getContext().getContentResolver(),
+                Settings.Secure.DND_CONFIGS_MIGRATED, 1, userId);
     }
 
     protected void migrateDefaultNAS() {
@@ -1024,6 +1044,24 @@
     }
 
     @VisibleForTesting
+    void resetDefaultDndIfNecessary() {
+        boolean removed = false;
+        final List<UserInfo> activeUsers = mUm.getAliveUsers();
+        for (UserInfo userInfo : activeUsers) {
+            int userId = userInfo.getUserHandle().getIdentifier();
+            if (isDNDMigrationDone(userId)) {
+                continue;
+            }
+            removed |= mConditionProviders.removeDefaultFromConfig(userId);
+            mConditionProviders.resetDefaultFromConfig();
+            allowDndPackages(userId);
+        }
+        if (removed) {
+            handleSavePolicyFile();
+        }
+    }
+
+    @VisibleForTesting
     protected void loadPolicyFile() {
         if (DBG) Slog.d(TAG, "loadPolicyFile");
         synchronized (mPolicyFile) {
@@ -1031,6 +1069,13 @@
             try {
                 infile = mPolicyFile.openRead();
                 readPolicyXml(infile, false /*forRestore*/, UserHandle.USER_ALL);
+
+                // We re-load the default dnd packages to allow the newly added and denined.
+                final boolean isWatch = mPackageManagerClient.hasSystemFeature(
+                        PackageManager.FEATURE_WATCH);
+                if (isWatch) {
+                    resetDefaultDndIfNecessary();
+                }
             } catch (FileNotFoundException e) {
                 // No data yet
                 // Load default managed services approvals
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 1bb1092..3f799dc 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -2608,7 +2608,12 @@
                             for (NotificationChannel channel : r.channels.values()) {
                                 if (!channel.isSoundRestored()) {
                                     Uri uri = channel.getSound();
-                                    Uri restoredUri = channel.restoreSoundUri(mContext, uri, true);
+                                    Uri restoredUri =
+                                            channel.restoreSoundUri(
+                                                    mContext,
+                                                    uri,
+                                                    true,
+                                                    channel.getAudioAttributes().getUsage());
                                     if (Settings.System.DEFAULT_NOTIFICATION_URI.equals(
                                             restoredUri)) {
                                         Log.w(TAG,
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index b4347e1..773d10b 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -103,8 +103,11 @@
             notificationList.get(i).setGlobalSortKey(null);
         }
 
-        // rank each record individually
-        Collections.sort(notificationList, mPreliminaryComparator);
+        // Rank each record individually.
+        // Lock comparator state for consistent compare() results.
+        synchronized (mPreliminaryComparator.mStateLock) {
+            notificationList.sort(mPreliminaryComparator);
+        }
 
         synchronized (mProxyByGroupTmp) {
             // record individual ranking result and nominate proxies for each group
diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java
index 5b7b0c1..f56a67c 100644
--- a/services/core/java/com/android/server/notification/ZenModeFiltering.java
+++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java
@@ -55,7 +55,7 @@
 
     public ZenModeFiltering(Context context) {
         mContext = context;
-        mMessagingUtil = new NotificationMessagingUtil(mContext);
+        mMessagingUtil = new NotificationMessagingUtil(mContext, null);
     }
 
     public ZenModeFiltering(Context context, NotificationMessagingUtil messagingUtil) {
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 1b52725..5b05b48 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -24,6 +24,7 @@
 import static android.content.Intent.ACTION_MAIN;
 import static android.content.Intent.CATEGORY_DEFAULT;
 import static android.content.Intent.CATEGORY_HOME;
+import static android.content.pm.ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
 import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
 import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
@@ -1514,11 +1515,17 @@
             ai.uid = UserHandle.getUid(userId, ps.getAppId());
             ai.primaryCpuAbi = ps.getPrimaryCpuAbiLegacy();
             ai.secondaryCpuAbi = ps.getSecondaryCpuAbiLegacy();
+            ai.volumeUuid = ps.getVolumeUuid();
+            ai.storageUuid = StorageManager.convert(ai.volumeUuid);
+            if (ps.isDefaultToDeviceProtectedStorage()) {
+                ai.privateFlags |= PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
+            }
             ai.setVersionCode(ps.getVersionCode());
             ai.flags = ps.getFlags();
             ai.privateFlags = ps.getPrivateFlags();
             pi.applicationInfo = PackageInfoUtils.generateDelegateApplicationInfo(
                     ai, flags, state, userId);
+            pi.signingInfo = ps.getSigningInfo();
 
             if (DEBUG_PACKAGE_INFO) {
                 Log.v(TAG, "ps.pkg is n/a for ["
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index a5a4594..b5ec136 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -470,7 +470,6 @@
                         // We need to set it back to 'installed' so the uninstall
                         // broadcasts will be sent correctly.
                         if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete");
-                        ps.setPkg(null);
                         ps.setInstalled(true, userId);
                         mPm.mSettings.writeKernelMappingLPr(ps);
                         clearPackageStateAndReturn = false;
diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java
index 39cd888..8bd2982 100644
--- a/services/core/java/com/android/server/pm/DexOptHelper.java
+++ b/services/core/java/com/android/server/pm/DexOptHelper.java
@@ -1050,7 +1050,7 @@
                 context.unregisterReceiver(this);
                 artManager.scheduleBackgroundDexoptJob();
             }
-        }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+        }, new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 7e1560a..31869b8 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2417,7 +2417,8 @@
             final InstallRequest installRequest = reconciledPkg.mInstallRequest;
             final boolean instantApp = ((installRequest.getScanFlags() & SCAN_AS_INSTANT_APP) != 0);
             final boolean isApex = ((installRequest.getScanFlags() & SCAN_AS_APEX) != 0);
-            final AndroidPackage pkg = installRequest.getScannedPackageSetting().getPkg();
+            final PackageSetting ps = installRequest.getScannedPackageSetting();
+            final AndroidPackage pkg = ps.getPkg();
             final String packageName = pkg.getPackageName();
             final String codePath = pkg.getPath();
             final boolean onIncremental = mIncrementalManager != null
@@ -2517,7 +2518,7 @@
                 // Compile the layout resources.
                 if (SystemProperties.getBoolean(PRECOMPILE_LAYOUTS, false)) {
                     Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "compileLayouts");
-                    mViewCompiler.compileLayouts(pkg);
+                    mViewCompiler.compileLayouts(ps, pkg.getBaseApkPath());
                     Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                 }
 
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 12f3aa9..11660a59 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -86,7 +86,10 @@
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.ServiceManager;
+import android.os.ShellCallback;
+import android.os.ShellCommand;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -127,6 +130,9 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
+import java.util.function.BiConsumer;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 /**
  * Service that manages requests and callbacks for launchers that support
@@ -215,7 +221,8 @@
 
         final LauncherAppsServiceInternal mInternal;
 
-        private RemoteCallbackList<IDumpCallback> mDumpCallbacks = new RemoteCallbackList<>();
+        @NonNull
+        private final RemoteCallbackList<IDumpCallback> mDumpCallbacks = new RemoteCallbackList<>();
 
         public LauncherAppsImpl(Context context) {
             mContext = context;
@@ -372,7 +379,12 @@
                 filter.addDataScheme("package");
                 mContext.registerReceiverAsUser(mPackageRemovedListener, UserHandle.ALL, filter,
                         /* broadcastPermission= */ null, mCallbackHandler);
-                mPackageMonitor.register(mContext, UserHandle.ALL, mCallbackHandler);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    mPackageMonitor.register(mContext, UserHandle.ALL, mCallbackHandler);
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
                 mIsWatchingPackageBroadcasts = true;
             }
         }
@@ -1462,46 +1474,124 @@
                     getActivityOptionsForLauncher(opts), user.getIdentifier());
         }
 
+        @Override
+        public void onShellCommand(FileDescriptor in, @NonNull FileDescriptor out,
+                @NonNull FileDescriptor err, @Nullable String[] args, ShellCallback cb,
+                @Nullable ResultReceiver receiver) {
+            final int callingUid = injectBinderCallingUid();
+            if (!(callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID)) {
+                throw new SecurityException("Caller must be shell");
+            }
+
+            final long token = injectClearCallingIdentity();
+            try {
+                int status = (new LauncherAppsShellCommand())
+                        .exec(this, in, out, err, args, cb, receiver);
+                if (receiver != null) {
+                    receiver.send(status, null);
+                }
+            } finally {
+                injectRestoreCallingIdentity(token);
+            }
+        }
+
+        /** Handles Shell commands for LauncherAppsService */
+        private class LauncherAppsShellCommand extends ShellCommand {
+            @Override
+            public int onCommand(@Nullable String cmd) {
+                if ("dump-view-hierarchies".equals(cmd)) {
+                    dumpViewCaptureDataToShell();
+                    return 0;
+                } else {
+                    return handleDefaultCommands(cmd);
+                }
+            }
+
+            private void dumpViewCaptureDataToShell() {
+                try (ZipOutputStream zipOs = new ZipOutputStream(getRawOutputStream())) {
+                    forEachViewCaptureWindow((fileName, is) -> {
+                        try {
+                            zipOs.putNextEntry(new ZipEntry("FS" + fileName));
+                            is.transferTo(zipOs);
+                            zipOs.closeEntry();
+                        } catch (IOException e) {
+                            getErrPrintWriter().write("Failed to output " + fileName
+                                    + " data to shell: " + e.getMessage());
+                        }
+                    });
+                } catch (IOException e) {
+                    getErrPrintWriter().write("Failed to create or close zip output stream: "
+                            + e.getMessage());
+                }
+            }
+
+            @Override
+            public void onHelp() {
+                final PrintWriter pw = getOutPrintWriter();
+                pw.println("Usage: cmd launcherapps COMMAND [options ...]");
+                pw.println();
+                pw.println("cmd launcherapps dump-view-hierarchies");
+                pw.println("    Output captured view hierarchies. Files will be generated in ");
+                pw.println("    `"  + WM_TRACE_DIR + "`. After pulling the data to your device,");
+                pw.println("     you can upload / visualize it at `go/winscope`.");
+                pw.println();
+            }
+        }
 
         /**
          * Using a pipe, outputs view capture data to the wmtrace dir
          */
-        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw,
+                @Nullable String[] args) {
             super.dump(fd, pw, args);
 
             // Before the wmtrace directory is picked up by dumpstate service, some processes need
             // to write their data to that location. They can do that via these dumpCallbacks.
-            int i = mDumpCallbacks.beginBroadcast();
-            while (i > 0) {
-                i--;
-                dumpDataToWmTrace((String) mDumpCallbacks.getBroadcastCookie(i) + "_" + i,
-                        mDumpCallbacks.getBroadcastItem(i));
+            forEachViewCaptureWindow(this::dumpViewCaptureDataToWmTrace);
+        }
+
+        private void dumpViewCaptureDataToWmTrace(@NonNull String fileName,
+                @NonNull InputStream is) {
+            Path outPath = Paths.get(fileName);
+            try {
+                Files.copy(is, outPath, StandardCopyOption.REPLACE_EXISTING);
+                Files.setPosixFilePermissions(outPath, WM_TRACE_FILE_PERMISSIONS);
+            } catch (IOException e) {
+                Log.d(TAG, "failed to write data to " + fileName + " in wmtrace dir", e);
+            }
+        }
+
+        /**
+         * IDumpCallback.onDump alerts the in-process ViewCapture instance to start sending data
+         * to LauncherAppsService via the pipe's input provided. This data (as well as an output
+         * file name) is provided to the consumer via an InputStream to output where it wants (for
+         * example, the winscope trace directory or the shell's stdout).
+         */
+        private void forEachViewCaptureWindow(
+                @NonNull BiConsumer<String, InputStream> outputtingConsumer) {
+            for (int i = mDumpCallbacks.beginBroadcast() - 1; i >= 0; i--) {
+                String packageName = (String) mDumpCallbacks.getBroadcastCookie(i);
+                String fileName = WM_TRACE_DIR + packageName + "_" + i + VC_FILE_SUFFIX;
+
+                try {
+                    // Order is important here. OnDump needs to be called before the BiConsumer
+                    // accepts & starts blocking on reading the input stream.
+                    ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
+                    mDumpCallbacks.getBroadcastItem(i).onDump(pipe[1]);
+
+                    InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0]);
+                    outputtingConsumer.accept(fileName, is);
+                    is.close();
+                } catch (Exception e) {
+                    Log.d(TAG, "failed to pipe view capture data", e);
+                }
             }
             mDumpCallbacks.finishBroadcast();
         }
 
-        private void dumpDataToWmTrace(String name, IDumpCallback cb) {
-            ParcelFileDescriptor[] pipe;
-            try {
-                pipe = ParcelFileDescriptor.createPipe();
-                cb.onDump(pipe[1]);
-            } catch (IOException | RemoteException e) {
-                Log.d(TAG, "failed to pipe view capture data", e);
-                return;
-            }
-
-            Path path = Paths.get(WM_TRACE_DIR + Paths.get(name + VC_FILE_SUFFIX).getFileName());
-            try (InputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pipe[0])) {
-                Files.copy(is, path, StandardCopyOption.REPLACE_EXISTING);
-                Files.setPosixFilePermissions(path, WM_TRACE_FILE_PERMISSIONS);
-            } catch (IOException e) {
-                Log.d(TAG, "failed to write data to file in wmtrace dir", e);
-            }
-        }
-
         @RequiresPermission(READ_FRAME_BUFFER)
         @Override
-        public void registerDumpCallback(IDumpCallback cb) {
+        public void registerDumpCallback(@NonNull IDumpCallback cb) {
             int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
             if (PERMISSION_GRANTED == status) {
                 String name = mContext.getPackageManager().getNameForUid(Binder.getCallingUid());
@@ -1513,7 +1603,7 @@
 
         @RequiresPermission(READ_FRAME_BUFFER)
         @Override
-        public void unRegisterDumpCallback(IDumpCallback cb) {
+        public void unRegisterDumpCallback(@NonNull IDumpCallback cb) {
             int status = checkCallingOrSelfPermissionForPreflight(mContext, READ_FRAME_BUFFER);
             if (PERMISSION_GRANTED == status) {
                 mDumpCallbacks.unregister(cb);
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 4e75210..82587c5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -745,6 +745,13 @@
         mService.setPackageStoppedState(snapshot(), packageName, stopped, userId);
     }
 
+    @Override
+    public void notifyComponentUsed(@NonNull String packageName,
+            @UserIdInt int userId, @NonNull String recentCallingPackage) {
+        mService.notifyComponentUsed(snapshot(), packageName, userId,
+                recentCallingPackage);
+    }
+
     @NonNull
     @Override
     @Deprecated
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 770ed8b..6bcfcfe 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4582,6 +4582,13 @@
         }
     }
 
+    void notifyComponentUsed(@NonNull Computer snapshot, @NonNull String packageName,
+            @UserIdInt int userId, @NonNull String recentCallingPackage) {
+        PackageManagerService.this
+                .setPackageStoppedState(snapshot, packageName, false /* stopped */,
+                        userId);
+    }
+
     public class IPackageManagerImpl extends IPackageManagerBase {
 
         public IPackageManagerImpl() {
@@ -6244,7 +6251,11 @@
         @Override
         public void registerPackageMonitorCallback(@NonNull IRemoteCallback callback, int userId) {
             int uid = Binder.getCallingUid();
-            mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, userId, uid);
+            int targetUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), uid,
+                    userId, true, true, "registerPackageMonitorCallback",
+                    mContext.getPackageName());
+            mPackageMonitorCallbackHelper.registerPackageMonitorCallback(callback, targetUserId,
+                    uid);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index cf4d1b0..dee31ec 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -20,6 +20,7 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -80,9 +81,38 @@
  * @hide
  */
 @DataClass(genGetters = true, genConstructor = false, genSetters = false, genBuilder = false)
-@DataClass.Suppress({"getSnapshot", })
+@DataClass.Suppress({"getSnapshot", "getBooleans"})
 public class PackageSetting extends SettingBase implements PackageStateInternal {
 
+    // Use a bitset to store boolean data to save memory
+    private static class Booleans {
+        @IntDef({
+                INSTALL_PERMISSION_FIXED,
+                DEFAULT_TO_DEVICE_PROTECTED_STORAGE,
+                UPDATE_AVAILABLE,
+                FORCE_QUERYABLE_OVERRIDE
+        })
+        public @interface Flags {
+        }
+        private static final int INSTALL_PERMISSION_FIXED = 1;
+        private static final int DEFAULT_TO_DEVICE_PROTECTED_STORAGE = 1 << 1;
+        private static final int UPDATE_AVAILABLE = 1 << 2;
+        private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 3;
+    }
+    private int mBooleans;
+
+    private void setBoolean(@Booleans.Flags int flag, boolean value) {
+        if (value) {
+            mBooleans |= flag;
+        } else {
+            mBooleans &= ~flag;
+        }
+    }
+
+    private boolean getBoolean(@Booleans.Flags int flag) {
+        return (mBooleans & flag) != 0;
+    }
+
     /**
      * The shared user ID lets us link this object to {@link SharedUserSetting}.
      */
@@ -160,8 +190,6 @@
     @NonNull
     private PackageSignatures signatures;
 
-    private boolean installPermissionsFixed;
-
     @NonNull
     private PackageKeySetData keySetData = new PackageKeySetData();
 
@@ -179,11 +207,6 @@
     /** @see PackageState#getCategoryOverride() */
     private int categoryOverride = ApplicationInfo.CATEGORY_UNDEFINED;
 
-    /** @see PackageState#isUpdateAvailable() */
-    private boolean updateAvailable;
-
-    private boolean forceQueryableOverride;
-
     @NonNull
     private final PackageStateUnserialized pkgState = new PackageStateUnserialized(this);
 
@@ -259,7 +282,8 @@
         this.mRealName = realPkgName;
     }
 
-    PackageSetting(@NonNull PackageSetting original, boolean sealedSnapshot)  {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public PackageSetting(@NonNull PackageSetting original, boolean sealedSnapshot)  {
         super(original);
         copyPackageSetting(original, sealedSnapshot);
         if (sealedSnapshot) {
@@ -363,7 +387,7 @@
     }
 
     public PackageSetting setForceQueryableOverride(boolean forceQueryableOverride) {
-        this.forceQueryableOverride = forceQueryableOverride;
+        setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, forceQueryableOverride);
         onChanged();
         return this;
     }
@@ -487,13 +511,20 @@
         return this;
     }
 
+    public PackageSetting setDefaultToDeviceProtectedStorage(
+            boolean defaultToDeviceProtectedStorage) {
+        setBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE, defaultToDeviceProtectedStorage);
+        onChanged();
+        return this;
+    }
+
     @Override
     public boolean isExternalStorage() {
         return (getFlags() & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
     }
 
     public PackageSetting setUpdateAvailable(boolean updateAvailable) {
-        this.updateAvailable = updateAvailable;
+        setBoolean(Booleans.UPDATE_AVAILABLE, updateAvailable);
         onChanged();
         return this;
     }
@@ -585,7 +616,7 @@
     }
 
     public PackageSetting setInstallPermissionsFixed(boolean installPermissionsFixed) {
-        this.installPermissionsFixed = installPermissionsFixed;
+        setBoolean(Booleans.INSTALL_PERMISSION_FIXED, installPermissionsFixed);
         return this;
     }
 
@@ -635,6 +666,7 @@
 
     public void copyPackageSetting(PackageSetting other, boolean sealedSnapshot) {
         super.copySettingBase(other);
+        mBooleans = other.mBooleans;
         mSharedUserAppId = other.mSharedUserAppId;
         mLoadingProgress = other.mLoadingProgress;
         mLoadingCompletedTime = other.mLoadingCompletedTime;
@@ -652,13 +684,10 @@
         lastUpdateTime = other.lastUpdateTime;
         versionCode = other.versionCode;
         signatures = other.signatures;
-        installPermissionsFixed = other.installPermissionsFixed;
         keySetData = new PackageKeySetData(other.keySetData);
         installSource = other.installSource;
         volumeUuid = other.volumeUuid;
         categoryOverride = other.categoryOverride;
-        updateAvailable = other.updateAvailable;
-        forceQueryableOverride = other.forceQueryableOverride;
         mDomainSetId = other.mDomainSetId;
         mAppMetadataFilePath = other.mAppMetadataFilePath;
 
@@ -1282,7 +1311,7 @@
     @NonNull
     @Override
     public List<SharedLibrary> getSharedLibraryDependencies() {
-        return (List<SharedLibrary>) (List<?>) pkgState.getUsesLibraryInfos();
+        return Collections.unmodifiableList(pkgState.getUsesLibraryInfos());
     }
 
     @NonNull
@@ -1294,7 +1323,7 @@
     @NonNull
     @Override
     public List<String> getUsesLibraryFiles() {
-        return pkgState.getUsesLibraryFiles();
+        return Collections.unmodifiableList(pkgState.getUsesLibraryFiles());
     }
 
     @NonNull
@@ -1474,6 +1503,32 @@
         return getAndroidPackage() != null && getAndroidPackage().isApex();
     }
 
+    @Override
+    public boolean isForceQueryableOverride() {
+        return getBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE);
+    }
+
+    /**
+     * @see PackageState#isUpdateAvailable()
+     */
+    @Override
+    public boolean isUpdateAvailable() {
+        return getBoolean(Booleans.UPDATE_AVAILABLE);
+    }
+
+    @Override
+    public boolean isInstallPermissionsFixed() {
+        return getBoolean(Booleans.INSTALL_PERMISSION_FIXED);
+    }
+
+    /**
+     * @see PackageState#isDefaultToDeviceProtectedStorage()
+     */
+    @Override
+    public boolean isDefaultToDeviceProtectedStorage() {
+        return getBoolean(Booleans.DEFAULT_TO_DEVICE_PROTECTED_STORAGE);
+    }
+
 
 
     // Code below generated by codegen v1.0.23.
@@ -1576,11 +1631,6 @@
     }
 
     @DataClass.Generated.Member
-    public boolean isInstallPermissionsFixed() {
-        return installPermissionsFixed;
-    }
-
-    @DataClass.Generated.Member
     public @NonNull PackageKeySetData getKeySetData() {
         return keySetData;
     }
@@ -1606,19 +1656,6 @@
         return categoryOverride;
     }
 
-    /**
-     * @see PackageState#isUpdateAvailable()
-     */
-    @DataClass.Generated.Member
-    public boolean isUpdateAvailable() {
-        return updateAvailable;
-    }
-
-    @DataClass.Generated.Member
-    public boolean isForceQueryableOverride() {
-        return forceQueryableOverride;
-    }
-
     @DataClass.Generated.Member
     public @NonNull PackageStateUnserialized getPkgState() {
         return pkgState;
@@ -1635,10 +1672,10 @@
     }
 
     @DataClass.Generated(
-            time = 1688743336932L,
+            time = 1691185420362L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java",
-            inputSignatures = "private  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate  boolean installPermissionsFixed\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate  boolean updateAvailable\nprivate  boolean forceQueryableOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
+            inputSignatures = "private  int mBooleans\nprivate  int mSharedUserAppId\nprivate @android.annotation.Nullable java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mimeGroups\nprivate @java.lang.Deprecated @android.annotation.Nullable java.util.Set<java.lang.String> mOldCodePaths\nprivate @android.annotation.Nullable java.lang.String[] usesSdkLibraries\nprivate @android.annotation.Nullable long[] usesSdkLibrariesVersionsMajor\nprivate @android.annotation.Nullable java.lang.String[] usesStaticLibraries\nprivate @android.annotation.Nullable long[] usesStaticLibrariesVersions\nprivate @android.annotation.Nullable @java.lang.Deprecated java.lang.String legacyNativeLibraryPath\nprivate @android.annotation.NonNull java.lang.String mName\nprivate @android.annotation.Nullable java.lang.String mRealName\nprivate  int mAppId\nprivate @android.annotation.Nullable com.android.server.pm.parsing.pkg.AndroidPackageInternal pkg\nprivate @android.annotation.NonNull java.io.File mPath\nprivate @android.annotation.NonNull java.lang.String mPathString\nprivate  float mLoadingProgress\nprivate  long mLoadingCompletedTime\nprivate @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate  long mLastModifiedTime\nprivate  long lastUpdateTime\nprivate  long versionCode\nprivate @android.annotation.NonNull com.android.server.pm.PackageSignatures signatures\nprivate @android.annotation.NonNull com.android.server.pm.PackageKeySetData keySetData\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserStateImpl> mUserStates\nprivate @android.annotation.NonNull com.android.server.pm.InstallSource installSource\nprivate @android.annotation.Nullable java.lang.String volumeUuid\nprivate  int categoryOverride\nprivate final @android.annotation.NonNull com.android.server.pm.pkg.PackageStateUnserialized pkgState\nprivate @android.annotation.NonNull java.util.UUID mDomainSetId\nprivate @android.annotation.Nullable java.lang.String mAppMetadataFilePath\nprivate final @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.PackageSetting> makeCache()\npublic  com.android.server.pm.PackageSetting snapshot()\npublic  void dumpDebug(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\npublic  com.android.server.pm.PackageSetting setAppId(int)\npublic  com.android.server.pm.PackageSetting setCpuAbiOverride(java.lang.String)\npublic  com.android.server.pm.PackageSetting setFirstInstallTimeFromReplaced(com.android.server.pm.pkg.PackageStateInternal,int[])\npublic  com.android.server.pm.PackageSetting setFirstInstallTime(long,int)\npublic  com.android.server.pm.PackageSetting setForceQueryableOverride(boolean)\npublic  com.android.server.pm.PackageSetting setInstallerPackage(java.lang.String,int)\npublic  com.android.server.pm.PackageSetting setUpdateOwnerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setInstallSource(com.android.server.pm.InstallSource)\n  com.android.server.pm.PackageSetting removeInstallerPackage(java.lang.String)\npublic  com.android.server.pm.PackageSetting setIsOrphaned(boolean)\npublic  com.android.server.pm.PackageSetting setKeySetData(com.android.server.pm.PackageKeySetData)\npublic  com.android.server.pm.PackageSetting setLastModifiedTime(long)\npublic  com.android.server.pm.PackageSetting setLastUpdateTime(long)\npublic  com.android.server.pm.PackageSetting setLongVersionCode(long)\npublic  boolean setMimeGroup(java.lang.String,android.util.ArraySet<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPkg(com.android.server.pm.pkg.AndroidPackage)\npublic  com.android.server.pm.PackageSetting setPkgStateLibraryFiles(java.util.Collection<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setPrimaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSecondaryCpuAbi(java.lang.String)\npublic  com.android.server.pm.PackageSetting setSignatures(com.android.server.pm.PackageSignatures)\npublic  com.android.server.pm.PackageSetting setVolumeUuid(java.lang.String)\npublic  com.android.server.pm.PackageSetting setDefaultToDeviceProtectedStorage(boolean)\npublic @java.lang.Override boolean isExternalStorage()\npublic  com.android.server.pm.PackageSetting setUpdateAvailable(boolean)\npublic  void setSharedUserAppId(int)\npublic @java.lang.Override int getSharedUserAppId()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override java.lang.String toString()\nprotected  void copyMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  void updateFrom(com.android.server.pm.PackageSetting)\n  com.android.server.pm.PackageSetting updateMimeGroups(java.util.Set<java.lang.String>)\npublic @java.lang.Deprecated @java.lang.Override com.android.server.pm.permission.LegacyPermissionState getLegacyPermissionState()\npublic  com.android.server.pm.PackageSetting setInstallPermissionsFixed(boolean)\npublic  boolean isPrivileged()\npublic  boolean isOem()\npublic  boolean isVendor()\npublic  boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic  boolean isSystemExt()\npublic  boolean isOdm()\npublic  boolean isSystem()\npublic  android.content.pm.SigningDetails getSigningDetails()\npublic  com.android.server.pm.PackageSetting setSigningDetails(android.content.pm.SigningDetails)\npublic  void copyPackageSetting(com.android.server.pm.PackageSetting,boolean)\n @com.android.internal.annotations.VisibleForTesting com.android.server.pm.pkg.PackageUserStateImpl modifyUserState(int)\npublic  com.android.server.pm.pkg.PackageUserStateImpl getOrCreateUserState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateInternal readUserState(int)\n  void setEnabled(int,int,java.lang.String)\n  int getEnabled(int)\n  void setInstalled(boolean,int)\n  boolean getInstalled(int)\n  int getInstallReason(int)\n  void setInstallReason(int,int)\n  int getUninstallReason(int)\n  void setUninstallReason(int,int)\n @android.annotation.NonNull android.content.pm.overlay.OverlayPaths getOverlayPaths(int)\n  boolean setOverlayPathsForLibrary(java.lang.String,android.content.pm.overlay.OverlayPaths,int)\n  boolean isAnyInstalled(int[])\n  int[] queryInstalledUsers(int[],boolean)\n  long getCeDataInode(int)\n  void setCeDataInode(long,int)\n  boolean getStopped(int)\n  void setStopped(boolean,int)\n  boolean getNotLaunched(int)\n  void setNotLaunched(boolean,int)\n  boolean getHidden(int)\n  void setHidden(boolean,int)\n  int getDistractionFlags(int)\n  void setDistractionFlags(int,int)\npublic  boolean getInstantApp(int)\n  void setInstantApp(boolean,int)\n  boolean getVirtualPreload(int)\n  void setVirtualPreload(boolean,int)\n  void setUserState(int,long,int,boolean,boolean,boolean,boolean,int,android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>,boolean,boolean,java.lang.String,android.util.ArraySet<java.lang.String>,android.util.ArraySet<java.lang.String>,int,int,java.lang.String,java.lang.String,long,int,com.android.server.pm.pkg.ArchiveState)\n  void setUserState(int,com.android.server.pm.pkg.PackageUserStateInternal)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponents(int)\n  com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponents(int)\n  void setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setEnabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  void setDisabledComponentsCopy(com.android.server.utils.WatchedArraySet<java.lang.String>,int)\n  com.android.server.pm.pkg.PackageUserStateImpl modifyUserStateComponents(int,boolean,boolean)\n  void addDisabledComponent(java.lang.String,int)\n  void addEnabledComponent(java.lang.String,int)\n  boolean enableComponentLPw(java.lang.String,int)\n  boolean disableComponentLPw(java.lang.String,int)\n  boolean restoreComponentLPw(java.lang.String,int)\n  int getCurrentEnabledStateLPr(java.lang.String,int)\n  void removeUser(int)\npublic  int[] getNotInstalledUserIds()\n  void writePackageUserPermissionsProto(android.util.proto.ProtoOutputStream,long,java.util.List<android.content.pm.UserInfo>,com.android.server.pm.permission.LegacyPermissionDataProvider)\nprotected  void writeUsersInfoToProto(android.util.proto.ProtoOutputStream,long)\nprivate static  void writeArchiveState(android.util.proto.ProtoOutputStream,com.android.server.pm.pkg.ArchiveState)\n  com.android.server.pm.PackageSetting setPath(java.io.File)\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideNonLocalizedLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer,int)\npublic  void resetOverrideComponentLabelIcon(int)\npublic @android.annotation.Nullable java.lang.String getSplashScreenTheme(int)\npublic  boolean isIncremental()\npublic  boolean isLoading()\npublic  com.android.server.pm.PackageSetting setLoadingProgress(float)\npublic  com.android.server.pm.PackageSetting setLoadingCompletedTime(long)\npublic  com.android.server.pm.PackageSetting setAppMetadataFilePath(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override long getVersionCode()\npublic @android.annotation.Nullable @java.lang.Override java.util.Map<java.lang.String,java.util.Set<java.lang.String>> getMimeGroups()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String getPackageName()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.pm.pkg.AndroidPackage getAndroidPackage()\npublic @android.annotation.NonNull android.content.pm.SigningInfo getSigningInfo()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesSdkLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesSdkLibrariesVersionsMajor()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getUsesStaticLibraries()\npublic @android.annotation.NonNull @java.lang.Override long[] getUsesStaticLibrariesVersions()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.server.pm.pkg.SharedLibrary> getSharedLibraryDependencies()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryInfo(android.content.pm.SharedLibraryInfo)\npublic @android.annotation.NonNull @java.lang.Override java.util.List<java.lang.String> getUsesLibraryFiles()\npublic @android.annotation.NonNull com.android.server.pm.PackageSetting addUsesLibraryFile(java.lang.String)\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @android.annotation.NonNull @java.lang.Override long[] getLastPackageUsageTime()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getApexModuleName()\npublic  com.android.server.pm.PackageSetting setDomainSetId(java.util.UUID)\npublic  com.android.server.pm.PackageSetting setCategoryOverride(int)\npublic  com.android.server.pm.PackageSetting setLegacyNativeLibraryPath(java.lang.String)\npublic  com.android.server.pm.PackageSetting setMimeGroups(java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)\npublic  com.android.server.pm.PackageSetting setOldCodePaths(java.util.Set<java.lang.String>)\npublic  com.android.server.pm.PackageSetting setUsesSdkLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesSdkLibrariesVersionsMajor(long[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibraries(java.lang.String[])\npublic  com.android.server.pm.PackageSetting setUsesStaticLibrariesVersions(long[])\npublic  com.android.server.pm.PackageSetting setApexModuleName(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageStateUnserialized getTransientState()\npublic @android.annotation.NonNull android.util.SparseArray<? extends PackageUserStateInternal> getUserStates()\npublic  com.android.server.pm.PackageSetting addMimeTypes(java.lang.String,java.util.Set<java.lang.String>)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbi()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbi()\npublic @android.annotation.Nullable @java.lang.Override java.lang.String getSeInfo()\npublic @android.annotation.Nullable java.lang.String getPrimaryCpuAbiLegacy()\npublic @android.annotation.Nullable java.lang.String getSecondaryCpuAbiLegacy()\npublic @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy @java.lang.Override int getHiddenApiEnforcementPolicy()\npublic @java.lang.Override boolean isApex()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isDefaultToDeviceProtectedStorage()\nclass PackageSetting extends com.android.server.pm.SettingBase implements [com.android.server.pm.pkg.PackageStateInternal]\nprivate static final  int INSTALL_PERMISSION_FIXED\nprivate static final  int DEFAULT_TO_DEVICE_PROTECTED_STORAGE\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genConstructor=false, genSetters=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 59314a2..6d3b26c 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -135,6 +135,7 @@
         cacher.cleanCachedResult(codePath);
     }
 
+    // Used for system apps only
     public void removePackage(AndroidPackage pkg, boolean chatty) {
         synchronized (mPm.mInstallLock) {
             removePackageLI(pkg, chatty);
@@ -284,6 +285,13 @@
         }
 
         removePackageLI(deletedPs.getPackageName(), (flags & PackageManager.DELETE_CHATTY) != 0);
+        if (!deletedPs.isSystem()) {
+            // A non-system app's AndroidPackage object has been removed from the service.
+            // Explicitly nullify the corresponding app's PackageSetting's pkg object to
+            // prevent any future usage of it, in case the PackageSetting object will remain because
+            // of DELETE_KEEP_DATA.
+            deletedPs.setPkg(null);
+        }
 
         if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
             final AndroidPackage resolvedPkg;
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 1cd44e6..f4dca3f 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -462,6 +462,8 @@
                             + " to " + volumeUuid);
             pkgSetting.setVolumeUuid(volumeUuid);
         }
+        pkgSetting.setDefaultToDeviceProtectedStorage(
+                parsedPackage.isDefaultToDeviceProtectedStorage());
 
         SharedLibraryInfo sdkLibraryInfo = null;
         if (!TextUtils.isEmpty(parsedPackage.getSdkLibraryName())) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index e5ad01f..c6135e0 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -2914,29 +2914,28 @@
             FileUtils.setPermissions(fstr.getFD(), 0640, SYSTEM_UID, PACKAGE_INFO_GID);
 
             StringBuilder sb = new StringBuilder();
-            for (final PackageSetting pkg : mPackages.values()) {
+            for (final PackageSetting ps : mPackages.values()) {
                 // TODO(b/135203078): This doesn't handle multiple users
-                final String dataPath = pkg.getPkg() == null ? null :
-                        PackageInfoUtils.getDataDir(pkg.getPkg(), UserHandle.USER_SYSTEM)
-                                .getAbsolutePath();
+                final String dataPath = PackageInfoUtils.getDataDir(ps, UserHandle.USER_SYSTEM)
+                        .getAbsolutePath();
 
-                if (pkg.getPkg() == null || dataPath == null) {
-                    if (!"android".equals(pkg.getPackageName())) {
-                        Slog.w(TAG, "Skipping " + pkg + " due to missing metadata");
+                if (ps.getPkg() == null || dataPath == null) {
+                    if (!"android".equals(ps.getPackageName())) {
+                        Slog.w(TAG, "Skipping " + ps + " due to missing metadata");
                     }
                     continue;
                 }
-                if (pkg.getPkg().isApex()) {
+                if (ps.getPkg().isApex()) {
                     // Don't persist APEX which doesn't have a valid app id and will cause parsing
                     // error in libpackagelistparser
                     continue;
                 }
 
-                final boolean isDebug = pkg.getPkg().isDebuggable();
+                final boolean isDebug = ps.getPkg().isDebuggable();
                 final IntArray gids = new IntArray();
                 for (final int userId : userIds) {
                     gids.addAll(mPermissionDataProvider.getGidsForUid(UserHandle.getUid(userId,
-                            pkg.getAppId())));
+                            ps.getAppId())));
                 }
 
                 // Avoid any application that has a space in its path.
@@ -2964,13 +2963,13 @@
                 //   system/core/libpackagelistparser
                 //
                 sb.setLength(0);
-                sb.append(pkg.getPkg().getPackageName());
+                sb.append(ps.getPkg().getPackageName());
                 sb.append(" ");
-                sb.append(pkg.getPkg().getUid());
+                sb.append(ps.getPkg().getUid());
                 sb.append(isDebug ? " 1 " : " 0 ");
                 sb.append(dataPath);
                 sb.append(" ");
-                sb.append(pkg.getSeInfo());
+                sb.append(ps.getSeInfo());
                 sb.append(" ");
                 final int gidsSize = gids.size();
                 if (gids != null && gids.size() > 0) {
@@ -2983,19 +2982,19 @@
                     sb.append("none");
                 }
                 sb.append(" ");
-                sb.append(pkg.getPkg().isProfileableByShell() ? "1" : "0");
+                sb.append(ps.getPkg().isProfileableByShell() ? "1" : "0");
                 sb.append(" ");
-                sb.append(pkg.getPkg().getLongVersionCode());
+                sb.append(ps.getPkg().getLongVersionCode());
                 sb.append(" ");
-                sb.append(pkg.getPkg().isProfileable() ? "1" : "0");
+                sb.append(ps.getPkg().isProfileable() ? "1" : "0");
                 sb.append(" ");
-                if (pkg.isSystem()) {
+                if (ps.isSystem()) {
                     sb.append("@system");
-                } else if (pkg.isProduct()) {
+                } else if (ps.isProduct()) {
                     sb.append("@product");
-                } else if (pkg.getInstallSource().mInstallerPackageName != null
-                           && !pkg.getInstallSource().mInstallerPackageName.isEmpty()) {
-                    sb.append(pkg.getInstallSource().mInstallerPackageName);
+                } else if (ps.getInstallSource().mInstallerPackageName != null
+                           && !ps.getInstallSource().mInstallerPackageName.isEmpty()) {
+                    sb.append(ps.getInstallSource().mInstallerPackageName);
                 } else {
                     sb.append("@null");
                 }
@@ -3123,6 +3122,8 @@
         if (pkg.getVolumeUuid() != null) {
             serializer.attribute(null, "volumeUuid", pkg.getVolumeUuid());
         }
+        serializer.attributeBoolean(null, "defaultToDeviceProtectedStorage",
+                pkg.isDefaultToDeviceProtectedStorage());
         if (pkg.getCategoryOverride() != ApplicationInfo.CATEGORY_UNDEFINED) {
             serializer.attributeInt(null, "categoryHint", pkg.getCategoryOverride());
         }
@@ -3911,6 +3912,7 @@
         String installInitiatingPackageName = null;
         boolean installInitiatorUninstalled = false;
         String volumeUuid = null;
+        boolean defaultToDeviceProtectedStorage = false;
         boolean updateAvailable = false;
         int categoryHint = ApplicationInfo.CATEGORY_UNDEFINED;
         int pkgFlags = 0;
@@ -3960,6 +3962,8 @@
             installInitiatorUninstalled = parser.getAttributeBoolean(null,
                     "installInitiatorUninstalled", false);
             volumeUuid = parser.getAttributeValue(null, "volumeUuid");
+            defaultToDeviceProtectedStorage = parser.getAttributeBoolean(
+                    null, "defaultToDeviceProtectedStorage", false);
             categoryHint = parser.getAttributeInt(null, "categoryHint",
                     ApplicationInfo.CATEGORY_UNDEFINED);
             appMetadataFilePath = parser.getAttributeValue(null, "appMetadataFilePath");
@@ -4099,6 +4103,7 @@
                     installInitiatorUninstalled);
             packageSetting.setInstallSource(installSource)
                     .setVolumeUuid(volumeUuid)
+                    .setDefaultToDeviceProtectedStorage(defaultToDeviceProtectedStorage)
                     .setCategoryOverride(categoryHint)
                     .setLegacyNativeLibraryPath(legacyNativeLibraryPathStr)
                     .setPrimaryCpuAbi(primaryCpuAbiString)
@@ -4886,6 +4891,8 @@
             pw.print("]");
         }
         pw.println();
+        File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
+        pw.print(prefix); pw.print("  dataDir="); pw.println(dataDir.getAbsolutePath());
         if (pkg != null) {
             pw.print(prefix); pw.print("  versionName="); pw.println(pkg.getVersionName());
             pw.print(prefix); pw.print("  usesNonSdkApi="); pw.println(pkg.isNonSdkApiRequested());
@@ -4917,8 +4924,6 @@
                 pw.append(prefix).append("  queriesIntents=")
                         .println(ps.getPkg().getQueriesIntents());
             }
-            File dataDir = PackageInfoUtils.getDataDir(pkg, UserHandle.myUserId());
-            pw.print(prefix); pw.print("  dataDir="); pw.println(dataDir.getAbsolutePath());
             pw.print(prefix); pw.print("  supportsScreens=[");
             boolean first = true;
             if (pkg.isSmallScreensSupported()) {
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 4ba174d..04d1da6 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -113,6 +113,9 @@
         },
         {
           "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest"
         }
       ]
     }
@@ -134,6 +137,14 @@
     },
     {
       "name": "CtsAppEnumerationTestCases"
+    },
+    {
+      "name": "CtsPackageManagerTestCases",
+      "options": [
+        {
+          "include-filter": "android.content.pm.cts.PackageManagerShellCommandMultiUserTest"
+        }
+      ]
     }
   ],
   "imports": [
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index ac52f9f..385dfcb8 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -4882,6 +4882,7 @@
                     UserManager.USER_OPERATION_ERROR_LOW_STORAGE);
         }
 
+        final boolean isMainUser = (flags & UserInfo.FLAG_MAIN) != 0;
         final boolean isProfile = userTypeDetails.isProfile();
         final boolean isGuest = UserManager.isUserTypeGuest(userType);
         final boolean isRestricted = UserManager.isUserTypeRestricted(userType);
@@ -5028,6 +5029,10 @@
                 }
             } else {
                 userTypeDetails.addDefaultRestrictionsTo(restrictions);
+                if (isMainUser) {
+                    restrictions.remove(UserManager.DISALLOW_OUTGOING_CALLS);
+                    restrictions.remove(UserManager.DISALLOW_SMS);
+                }
             }
             synchronized (mRestrictionsLock) {
                 mBaseUserRestrictions.updateRestrictions(userId, restrictions);
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index f7967c0..d5231b5 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -295,7 +295,8 @@
                         .setMediaSharedWithParent(false)
                         .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
                         .setCrossProfileIntentFilterAccessControl(
-                                UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM));
+                                UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM)
+                        .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index d88b66b..31856f1 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -61,7 +61,7 @@
 import com.android.server.pm.PackageManagerServiceUtils;
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.PackageStateInternal;
 
 import dalvik.system.DexFile;
 import dalvik.system.VMRuntime;
@@ -542,14 +542,14 @@
     /**
      * Compile layout resources in a given package.
      */
-    public boolean compileLayouts(@NonNull PackageState packageState, @NonNull AndroidPackage pkg) {
+    public boolean compileLayouts(@NonNull PackageStateInternal ps, @NonNull AndroidPackage pkg) {
         try {
             final String packageName = pkg.getPackageName();
             final String apkPath = pkg.getSplits().get(0).getPath();
             // TODO(b/143971007): Use a cross-user directory
-            File dataDir = PackageInfoUtils.getDataDir(pkg, UserHandle.myUserId());
+            File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
             final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
-            if (packageState.isPrivileged() || pkg.isUseEmbeddedDex()
+            if (ps.isPrivileged() || pkg.isUseEmbeddedDex()
                     || pkg.isDefaultToDeviceProtectedStorage()) {
                 // Privileged apps prefer to load trusted code so they don't use compiled views.
                 // If the app is not privileged but prefers code integrity, also avoid compiling
diff --git a/services/core/java/com/android/server/pm/dex/ViewCompiler.java b/services/core/java/com/android/server/pm/dex/ViewCompiler.java
index 9ce648f..6405ea5 100644
--- a/services/core/java/com/android/server/pm/dex/ViewCompiler.java
+++ b/services/core/java/com/android/server/pm/dex/ViewCompiler.java
@@ -23,7 +23,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.pm.Installer;
 import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageStateInternal;
 
 import java.io.File;
 
@@ -37,12 +37,11 @@
         mInstaller = installer;
     }
 
-    public boolean compileLayouts(AndroidPackage pkg) {
+    public boolean compileLayouts(PackageStateInternal ps, String apkPath) {
         try {
-            final String packageName = pkg.getPackageName();
-            final String apkPath = pkg.getBaseApkPath();
+            final String packageName = ps.getPackageName();
             // TODO(b/143971007): Use a cross-user directory
-            File dataDir = PackageInfoUtils.getDataDir(pkg, UserHandle.myUserId());
+            File dataDir = PackageInfoUtils.getDataDir(ps, UserHandle.myUserId());
             final String outDexFile = dataDir.getAbsolutePath() + "/code_cache/compiled_view.dex";
             Log.i("PackageManager", "Compiling layouts in " + packageName + " (" + apkPath +
                 ") to " + outDexFile);
@@ -50,7 +49,7 @@
             try {
                 synchronized (mInstallLock) {
                     return mInstaller.compileLayouts(apkPath, packageName, outDexFile,
-                        pkg.getUid());
+                        ps.getAppId());
                 }
             } finally {
                 Binder.restoreCallingIdentity(callingId);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index d55f85c..94e9599 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -1082,18 +1082,18 @@
     }
 
     @NonNull
-    public static File getDataDir(AndroidPackage pkg, int userId) {
-        if ("android".equals(pkg.getPackageName())) {
+    public static File getDataDir(PackageStateInternal ps, int userId) {
+        if ("android".equals(ps.getPackageName())) {
             return Environment.getDataSystemDirectory();
         }
 
-        if (pkg.isDefaultToDeviceProtectedStorage()
+        if (ps.isDefaultToDeviceProtectedStorage()
                 && PackageManager.APPLY_DEFAULT_TO_DEVICE_PROTECTED_STORAGE) {
-            return Environment.getDataUserDePackageDirectory(pkg.getVolumeUuid(), userId,
-                    pkg.getPackageName());
+            return Environment.getDataUserDePackageDirectory(ps.getVolumeUuid(), userId,
+                    ps.getPackageName());
         } else {
-            return Environment.getDataUserCePackageDirectory(pkg.getVolumeUuid(), userId,
-                    pkg.getPackageName());
+            return Environment.getDataUserCePackageDirectory(ps.getVolumeUuid(), userId,
+                    ps.getPackageName());
         }
     }
 
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 7897195..b01a89e 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -72,7 +72,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.function.QuadFunction;
 import com.android.internal.util.function.TriFunction;
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
@@ -94,6 +93,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiFunction;
 
 /**
  * Manages all permissions and handles permissions related tasks.
@@ -233,11 +233,11 @@
         }
 
         if (checkPermissionDelegate == null) {
-            return mPermissionManagerServiceImpl.checkPermission(packageName, permissionName,
-                    deviceId, userId);
+            return mPermissionManagerServiceImpl.checkPermission(
+                    packageName, permissionName, userId);
         }
-        return checkPermissionDelegate.checkPermission(packageName, permissionName,
-                deviceId, userId, mPermissionManagerServiceImpl::checkPermission);
+        return checkPermissionDelegate.checkPermission(packageName, permissionName, userId,
+                mPermissionManagerServiceImpl::checkPermission);
     }
 
     @Override
@@ -254,10 +254,10 @@
         }
 
         if (checkPermissionDelegate == null)  {
-            return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName, deviceId);
+            return mPermissionManagerServiceImpl.checkUidPermission(uid, permissionName);
         }
         return checkPermissionDelegate.checkUidPermission(uid, permissionName,
-                deviceId, mPermissionManagerServiceImpl::checkUidPermission);
+                mPermissionManagerServiceImpl::checkUidPermission);
     }
 
     @Override
@@ -511,14 +511,14 @@
     public int getPermissionFlags(String packageName, String permissionName, int deviceId,
             int userId) {
         return mPermissionManagerServiceImpl
-                .getPermissionFlags(packageName, permissionName, deviceId, userId);
+                .getPermissionFlags(packageName, permissionName, userId);
     }
 
     @Override
     public void updatePermissionFlags(String packageName, String permissionName, int flagMask,
             int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
         mPermissionManagerServiceImpl.updatePermissionFlags(packageName, permissionName, flagMask,
-                flagValues, checkAdjustPolicyFlagPermission, deviceId, userId);
+                flagValues, checkAdjustPolicyFlagPermission, userId);
     }
 
     @Override
@@ -560,15 +560,14 @@
     @Override
     public void grantRuntimePermission(String packageName, String permissionName, int deviceId,
             int userId) {
-        mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName,
-                deviceId, userId);
+        mPermissionManagerServiceImpl.grantRuntimePermission(packageName, permissionName, userId);
     }
 
     @Override
     public void revokeRuntimePermission(String packageName, String permissionName, int deviceId,
             int userId, String reason) {
         mPermissionManagerServiceImpl.revokeRuntimePermission(packageName, permissionName,
-                deviceId, userId, reason);
+                userId, reason);
     }
 
     @Override
@@ -581,14 +580,14 @@
     public boolean shouldShowRequestPermissionRationale(String packageName, String permissionName,
             int deviceId, int userId) {
         return mPermissionManagerServiceImpl.shouldShowRequestPermissionRationale(packageName,
-                permissionName, deviceId, userId);
+                permissionName, userId);
     }
 
     @Override
     public boolean isPermissionRevokedByPolicy(String packageName, String permissionName,
             int deviceId, int userId) {
-        return mPermissionManagerServiceImpl.isPermissionRevokedByPolicy(packageName,
-                permissionName, deviceId, userId);
+        return mPermissionManagerServiceImpl
+                .isPermissionRevokedByPolicy(packageName, permissionName, userId);
     }
 
     @Override
@@ -869,7 +868,6 @@
          *
          * @param packageName the name of the package to be checked
          * @param permissionName the name of the permission to be checked
-         * @param deviceId The device ID
          * @param userId the user ID
          * @param superImpl the original implementation that can be delegated to
          * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
@@ -878,21 +876,20 @@
          * @see android.content.pm.PackageManager#checkPermission(String, String)
          */
         int checkPermission(@NonNull String packageName, @NonNull String permissionName,
-                int deviceId, @UserIdInt int userId,
-                @NonNull QuadFunction<String, String, Integer, Integer, Integer> superImpl);
+                @UserIdInt int userId,
+                @NonNull TriFunction<String, String, Integer, Integer> superImpl);
 
         /**
          * Check whether the given UID has been granted the specified permission.
          *
          * @param uid the UID to be checked
          * @param permissionName the name of the permission to be checked
-         * @param deviceId The device ID
          * @param superImpl the original implementation that can be delegated to
          * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} if the package has
          * the permission, or {@link android.content.pm.PackageManager#PERMISSION_DENIED} otherwise
          */
-        int checkUidPermission(int uid, @NonNull String permissionName, int deviceId,
-                TriFunction<Integer, String, Integer, Integer> superImpl);
+        int checkUidPermission(int uid, @NonNull String permissionName,
+                BiFunction<Integer, String, Integer> superImpl);
 
         /**
          * @return list of delegated permissions
@@ -921,32 +918,31 @@
 
         @Override
         public int checkPermission(@NonNull String packageName, @NonNull String permissionName,
-                int deviceId, int userId,
-                @NonNull QuadFunction<String, String, Integer, Integer, Integer> superImpl) {
+                int userId, @NonNull TriFunction<String, String, Integer, Integer> superImpl) {
             if (mDelegatedPackageName.equals(packageName)
                     && isDelegatedPermission(permissionName)) {
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    return superImpl.apply("com.android.shell", permissionName, deviceId, userId);
+                    return superImpl.apply("com.android.shell", permissionName, userId);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(packageName, permissionName, deviceId, userId);
+            return superImpl.apply(packageName, permissionName, userId);
         }
 
         @Override
-        public int checkUidPermission(int uid, @NonNull String permissionName, int deviceId,
-                @NonNull TriFunction<Integer, String, Integer, Integer> superImpl) {
+        public int checkUidPermission(int uid, @NonNull String permissionName,
+                @NonNull BiFunction<Integer, String, Integer> superImpl) {
             if (uid == mDelegatedUid && isDelegatedPermission(permissionName)) {
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    return superImpl.apply(Process.SHELL_UID, permissionName, deviceId);
+                    return superImpl.apply(Process.SHELL_UID, permissionName);
                 } finally {
                     Binder.restoreCallingIdentity(identity);
                 }
             }
-            return superImpl.apply(uid, permissionName, deviceId);
+            return superImpl.apply(uid, permissionName);
         }
 
         @Override
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 6764e08..4353c57 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -681,7 +681,7 @@
     }
 
     @Override
-    public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) {
+    public int getPermissionFlags(String packageName, String permName, int userId) {
         final int callingUid = Binder.getCallingUid();
         return getPermissionFlagsInternal(packageName, permName, callingUid, userId);
     }
@@ -724,7 +724,7 @@
 
     @Override
     public void updatePermissionFlags(String packageName, String permName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
         final int callingUid = Binder.getCallingUid();
         boolean overridePolicy = false;
 
@@ -908,12 +908,8 @@
         }
     }
 
-    private int checkPermission(String pkgName, String permName, int userId) {
-        return checkPermission(pkgName, permName, Context.DEVICE_ID_DEFAULT, userId);
-    }
-
     @Override
-    public int checkPermission(String pkgName, String permName, int deviceId, int userId) {
+    public int checkPermission(String pkgName, String permName, int userId) {
         if (!mUserManagerInt.exists(userId)) {
             return PackageManager.PERMISSION_DENIED;
         }
@@ -979,12 +975,8 @@
         return true;
     }
 
-    private int checkUidPermission(int uid, String permName) {
-        return checkUidPermission(uid, permName, Context.DEVICE_ID_DEFAULT);
-    }
-
     @Override
-    public int checkUidPermission(int uid, String permName, int deviceId) {
+    public int checkUidPermission(int uid, String permName) {
         final int userId = UserHandle.getUserId(uid);
         if (!mUserManagerInt.exists(userId)) {
             return PackageManager.PERMISSION_DENIED;
@@ -1303,8 +1295,7 @@
     }
 
     @Override
-    public void grantRuntimePermission(String packageName, String permName, int deviceId,
-            int userId) {
+    public void grantRuntimePermission(String packageName, String permName, final int userId) {
         final int callingUid = Binder.getCallingUid();
         final boolean overridePolicy =
                 checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
@@ -1477,11 +1468,11 @@
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String permName, int deviceId,
-            int userId, String reason) {
+    public void revokeRuntimePermission(String packageName, String permName, int userId,
+            String reason) {
         final int callingUid = Binder.getCallingUid();
         final boolean overridePolicy =
-                checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY, deviceId)
+                checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
                         == PackageManager.PERMISSION_GRANTED;
 
         revokeRuntimePermissionInternal(packageName, permName, overridePolicy, callingUid, userId,
@@ -1868,7 +1859,7 @@
 
     @Override
     public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
-            int deviceId, @UserIdInt int userId) {
+            @UserIdInt int userId) {
         final int callingUid = Binder.getCallingUid();
         if (UserHandle.getCallingUserId() != userId) {
             mContext.enforceCallingPermission(
@@ -1931,8 +1922,7 @@
     }
 
     @Override
-    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
-            int userId) {
+    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
         if (UserHandle.getCallingUserId() != userId) {
             mContext.enforceCallingPermission(
                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
@@ -2069,8 +2059,8 @@
                     continue;
                 }
                 boolean isSystemOrPolicyFixed = (getPermissionFlags(newPackage.getPackageName(),
-                        permInfo.name, Context.DEVICE_ID_DEFAULT, userId) & (
-                        FLAG_PERMISSION_SYSTEM_FIXED | FLAG_PERMISSION_POLICY_FIXED)) != 0;
+                        permInfo.name, userId) & (FLAG_PERMISSION_SYSTEM_FIXED
+                        | FLAG_PERMISSION_POLICY_FIXED)) != 0;
                 if (isSystemOrPolicyFixed) {
                     continue;
                 }
@@ -2236,8 +2226,7 @@
                 for (final int userId : userIds) {
                     final int permissionState = checkPermission(packageName, permName,
                             userId);
-                    final int flags = getPermissionFlags(packageName, permName,
-                            Context.DEVICE_ID_DEFAULT, userId);
+                    final int flags = getPermissionFlags(packageName, permName, userId);
                     final int flagMask = FLAG_PERMISSION_SYSTEM_FIXED
                             | FLAG_PERMISSION_POLICY_FIXED
                             | FLAG_PERMISSION_GRANTED_BY_DEFAULT
@@ -5133,7 +5122,8 @@
 
     @NonNull
     @Override
-    public Set<String> getGrantedPermissions(@NonNull String packageName, @UserIdInt int userId) {
+    public Set<String> getGrantedPermissions(@NonNull String packageName,
+            @UserIdInt int userId) {
         Objects.requireNonNull(packageName, "packageName");
         Preconditions.checkArgumentNonNegative(userId, "userId");
         return getGrantedPermissionsInternal(packageName, userId);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 2d824aa..128f847 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -25,6 +25,7 @@
 import android.content.pm.PermissionInfo;
 import android.content.pm.permission.SplitPermissionInfoParcelable;
 import android.permission.IOnPermissionsChangeListener;
+import android.permission.PermissionManager;
 import android.permission.PermissionManagerInternal;
 
 import com.android.server.pm.pkg.AndroidPackage;
@@ -136,16 +137,14 @@
     void removePermission(String permName);
 
     /**
-     * Gets the permission state flags associated with a permission.
+     * Gets the state flags associated with a permission.
      *
      * @param packageName the package name for which to get the flags
      * @param permName the permission for which to get the flags
-     * @param deviceId The device for which to get the flags
      * @param userId the user for which to get permission flags
      * @return the permission flags
      */
-    int getPermissionFlags(String packageName, String permName, int deviceId,
-            @UserIdInt int userId);
+    int getPermissionFlags(String packageName, String permName, int userId);
 
     /**
      * Updates the flags associated with a permission by replacing the flags in the specified mask
@@ -155,11 +154,10 @@
      * @param permName The permission for which to update the flags
      * @param flagMask The flags which to replace
      * @param flagValues The flags with which to replace
-     * @param deviceId The device for which to update the permission flags
      * @param userId The user for which to update the permission flags
      */
-    void updatePermissionFlags(String packageName, String permName, int flagMask, int flagValues,
-            boolean checkAdjustPolicyFlagPermission, int deviceId, @UserIdInt int userId);
+    void updatePermissionFlags(String packageName, String permName, int flagMask,
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId);
 
     /**
      * Update the permission flags for all packages and runtime permissions of a user in order
@@ -293,13 +291,11 @@
      *
      * @param packageName the package to which to grant the permission
      * @param permName the permission name to grant
-     * @param deviceId the device for which to grant the permission
      * @param userId the user for which to grant the permission
      *
-     * @see #revokeRuntimePermission(String, String, int, int, String)
+     * @see #revokeRuntimePermission(String, String, android.os.UserHandle, String)
      */
-    void grantRuntimePermission(String packageName, String permName, int deviceId,
-            @UserIdInt int userId);
+    void grantRuntimePermission(String packageName, String permName, int userId);
 
     /**
      * Revoke a runtime permission that was previously granted by
@@ -314,14 +310,13 @@
      *
      * @param packageName the package from which to revoke the permission
      * @param permName the permission name to revoke
-     * @param deviceId the device for which to revoke the permission
      * @param userId the user for which to revoke the permission
      * @param reason the reason for the revoke, or {@code null} for unspecified
      *
-     * @see #grantRuntimePermission(String, String, int, int)
+     * @see #grantRuntimePermission(String, String, android.os.UserHandle)
      */
-    void revokeRuntimePermission(String packageName, String permName, int deviceId,
-            @UserIdInt int userId, String reason);
+    void revokeRuntimePermission(String packageName, String permName, int userId,
+            String reason);
 
     /**
      * Revoke the POST_NOTIFICATIONS permission, without killing the app. This method must ONLY BE
@@ -338,29 +333,24 @@
      * does not clearly communicate to the user what would be the benefit from grating this
      * permission.
      *
-     * @param packageName the package name
      * @param permName a permission your app wants to request
-     * @param deviceId the device for which to check the permission
-     * @param userId the user for which to check the permission
      * @return whether you can show permission rationale UI
      */
     boolean shouldShowRequestPermissionRationale(String packageName, String permName,
-            int deviceId, @UserIdInt int userId);
+            @UserIdInt int userId);
 
     /**
-     * Checks whether a particular permission has been revoked for a package by policy. Typically,
+     * Checks whether a particular permissions has been revoked for a package by policy. Typically
      * the device owner or the profile owner may apply such a policy. The user cannot grant policy
      * revoked permissions, hence the only way for an app to get such a permission is by a policy
      * change.
      *
      * @param packageName the name of the package you are checking against
      * @param permName the name of the permission you are checking for
-     * @param deviceId the device for which you are checking the permission
-     * @param userId the device for which you are checking the permission
+     *
      * @return whether the permission is restricted by policy
      */
-    boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
-            @UserIdInt int userId);
+    boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId);
 
     /**
      * Get set of permissions that have been split into more granular or dependent permissions.
@@ -383,25 +373,14 @@
     List<SplitPermissionInfoParcelable> getSplitPermissions();
 
     /**
-     * Check whether a permission is granted or not to a package.
-     *
-     * @param pkgName package name
-     * @param permName permission name
-     * @param deviceId device ID
-     * @param userId user ID
-     * @return permission result {@link PackageManager.PermissionResult}
+     * TODO:theianchen add doc describing this is the old checkPermissionImpl
      */
-    int checkPermission(String pkgName, String permName, int deviceId, @UserIdInt int userId);
+    int checkPermission(String pkgName, String permName, int userId);
 
     /**
-     * Check whether a permission is granted or not to an UID.
-     *
-     * @param uid UID
-     * @param permName permission name
-     * @param deviceId device ID
-     * @return permission result {@link PackageManager.PermissionResult}
+     * TODO:theianchen add doc describing this is the old checkUidPermissionImpl
      */
-    int checkUidPermission(int uid, String permName, int deviceId);
+    int checkUidPermission(int uid, String permName);
 
     /**
      * Get all the package names requesting app op permissions.
@@ -421,11 +400,15 @@
             @UserIdInt int userId);
 
     /**
-     * Reset the runtime permission state changes for a package for all devices.
+     * Reset the runtime permission state changes for a package.
      *
      * TODO(zhanghai): Turn this into package change callback?
+     *
+     * @param pkg the package
+     * @param userId the user ID
      */
-    void resetRuntimePermissions(@NonNull AndroidPackage pkg, @UserIdInt int userId);
+    void resetRuntimePermissions(@NonNull AndroidPackage pkg,
+            @UserIdInt int userId);
 
     /**
      * Reset the runtime permission state changes for all packages in a user.
@@ -466,8 +449,8 @@
     /**
      * Get all the permissions granted to a package.
      *
-     * @param packageName package name
-     * @param userId user ID
+     * @param packageName the name of the package
+     * @param userId the user ID
      * @return the names of the granted permissions
      */
     @NonNull
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
index dacb8c6..7f98e21 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
@@ -120,21 +120,21 @@
     }
 
     @Override
-    public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) {
+    public int getPermissionFlags(String packageName, String permName, int userId) {
         Log.i(LOG_TAG, "getPermissionFlags(packageName = " + packageName + ", permName = "
-                + permName + ", deviceId = " + deviceId +  ", userId = " + userId + ")");
-        return mService.getPermissionFlags(packageName, permName, deviceId, userId);
+                + permName + ", userId = " + userId + ")");
+        return mService.getPermissionFlags(packageName, permName, userId);
     }
 
     @Override
     public void updatePermissionFlags(String packageName, String permName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
         Log.i(LOG_TAG, "updatePermissionFlags(packageName = " + packageName + ", permName = "
                 + permName + ", flagMask = " + flagMask + ", flagValues = " + flagValues
                 + ", checkAdjustPolicyFlagPermission = " + checkAdjustPolicyFlagPermission
-                + ", deviceId = " + deviceId + ", userId = " + userId + ")");
+                + ", userId = " + userId + ")");
         mService.updatePermissionFlags(packageName, permName, flagMask, flagValues,
-                checkAdjustPolicyFlagPermission, deviceId, userId);
+                checkAdjustPolicyFlagPermission, userId);
     }
 
     @Override
@@ -182,20 +182,18 @@
     }
 
     @Override
-    public void grantRuntimePermission(String packageName, String permName, int deviceId,
-            int userId) {
+    public void grantRuntimePermission(String packageName, String permName, int userId) {
         Log.i(LOG_TAG, "grantRuntimePermission(packageName = " + packageName + ", permName = "
-                + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")");
-        mService.grantRuntimePermission(packageName, permName, deviceId, userId);
+                + permName + ", userId = " + userId + ")");
+        mService.grantRuntimePermission(packageName, permName, userId);
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String permName, int deviceId,
-            int userId, String reason) {
+    public void revokeRuntimePermission(String packageName, String permName, int userId,
+            String reason) {
         Log.i(LOG_TAG, "revokeRuntimePermission(packageName = " + packageName + ", permName = "
-                + permName + ", deviceId = " + deviceId + ", userId = " + userId
-                + ", reason = " + reason + ")");
-        mService.revokeRuntimePermission(packageName, permName, deviceId, userId, reason);
+                + permName + ", userId = " + userId + ", reason = " + reason + ")");
+        mService.revokeRuntimePermission(packageName, permName, userId, reason);
     }
 
     @Override
@@ -207,20 +205,17 @@
 
     @Override
     public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
-            int deviceId, int userId) {
+            int userId) {
         Log.i(LOG_TAG, "shouldShowRequestPermissionRationale(packageName = " + packageName
-                + ", permName = " + permName + ", deviceId = " + deviceId
-                +  ", userId = " + userId + ")");
-        return mService.shouldShowRequestPermissionRationale(packageName, permName, deviceId,
-                userId);
+                + ", permName = " + permName + ", userId = " + userId + ")");
+        return mService.shouldShowRequestPermissionRationale(packageName, permName, userId);
     }
 
     @Override
-    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
-            int userId) {
+    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
         Log.i(LOG_TAG, "isPermissionRevokedByPolicy(packageName = " + packageName + ", permName = "
-                + permName + ", deviceId = " + deviceId + ", userId = " + userId + ")");
-        return mService.isPermissionRevokedByPolicy(packageName, permName, deviceId, userId);
+                + permName + ", userId = " + userId + ")");
+        return mService.isPermissionRevokedByPolicy(packageName, permName, userId);
     }
 
     @Override
@@ -230,17 +225,16 @@
     }
 
     @Override
-    public int checkPermission(String pkgName, String permName, int deviceId, int userId) {
+    public int checkPermission(String pkgName, String permName, int userId) {
         Log.i(LOG_TAG, "checkPermission(pkgName = " + pkgName + ", permName = " + permName
-                + ", deviceId = " + deviceId + ", userId = " + userId + ")");
-        return mService.checkPermission(pkgName, permName, deviceId, userId);
+                + ", userId = " + userId + ")");
+        return mService.checkPermission(pkgName, permName, userId);
     }
 
     @Override
-    public int checkUidPermission(int uid, String permName, int deviceId) {
-        Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " + permName
-                + ", deviceId = " + deviceId + ")");
-        return mService.checkUidPermission(uid, permName, deviceId);
+    public int checkUidPermission(int uid, String permName) {
+        Log.i(LOG_TAG, "checkUidPermission(uid = " + uid + ", permName = " + permName + ")");
+        return mService.checkUidPermission(uid, permName);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
index 35d165b..d4c6d42 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
@@ -153,10 +153,9 @@
     }
 
     @Override
-    public int getPermissionFlags(String packageName, String permName, int deviceId,
-            @UserIdInt int userId) {
-        int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, deviceId, userId);
-        int newVal = mNewImplementation.getPermissionFlags(packageName, permName, deviceId, userId);
+    public int getPermissionFlags(String packageName, String permName, int userId) {
+        int oldVal = mOldImplementation.getPermissionFlags(packageName, permName, userId);
+        int newVal = mNewImplementation.getPermissionFlags(packageName, permName, userId);
 
         if (!Objects.equals(oldVal, newVal)) {
             signalImplDifference("getPermissionFlags");
@@ -166,12 +165,11 @@
 
     @Override
     public void updatePermissionFlags(String packageName, String permName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId,
-            @UserIdInt int userId) {
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
         mOldImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues,
-                checkAdjustPolicyFlagPermission, deviceId, userId);
+                checkAdjustPolicyFlagPermission, userId);
         mNewImplementation.updatePermissionFlags(packageName, permName, flagMask, flagValues,
-                checkAdjustPolicyFlagPermission, deviceId, userId);
+                checkAdjustPolicyFlagPermission, userId);
     }
 
     @Override
@@ -236,17 +234,16 @@
     }
 
     @Override
-    public void grantRuntimePermission(String packageName, String permName, int deviceId,
-            @UserIdInt int userId) {
-        mOldImplementation.grantRuntimePermission(packageName, permName, deviceId, userId);
-        mNewImplementation.grantRuntimePermission(packageName, permName, deviceId, userId);
+    public void grantRuntimePermission(String packageName, String permName, int userId) {
+        mOldImplementation.grantRuntimePermission(packageName, permName, userId);
+        mNewImplementation.grantRuntimePermission(packageName, permName, userId);
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String permName, int deviceId,
-            @UserIdInt int userId, String reason) {
-        mOldImplementation.revokeRuntimePermission(packageName, permName, deviceId, userId, reason);
-        mNewImplementation.revokeRuntimePermission(packageName, permName, deviceId, userId, reason);
+    public void revokeRuntimePermission(String packageName, String permName, int userId,
+            String reason) {
+        mOldImplementation.grantRuntimePermission(packageName, permName, userId);
+        mNewImplementation.grantRuntimePermission(packageName, permName, userId);
     }
 
     @Override
@@ -258,11 +255,11 @@
 
     @Override
     public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
-            int deviceId, @UserIdInt int userId) {
-        boolean oldVal = mOldImplementation.shouldShowRequestPermissionRationale(packageName,
-                permName, deviceId,  userId);
-        boolean newVal = mNewImplementation.shouldShowRequestPermissionRationale(packageName,
-                permName, deviceId, userId);
+            int userId) {
+        boolean oldVal = mOldImplementation
+                .shouldShowRequestPermissionRationale(packageName, permName, userId);
+        boolean newVal = mNewImplementation
+                .shouldShowRequestPermissionRationale(packageName, permName, userId);
 
         if (!Objects.equals(oldVal, newVal)) {
             signalImplDifference("shouldShowRequestPermissionRationale");
@@ -271,12 +268,11 @@
     }
 
     @Override
-    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
-            @UserIdInt int userId) {
-        boolean oldVal = mOldImplementation.isPermissionRevokedByPolicy(packageName, permName,
-                deviceId, userId);
+    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
+        boolean oldVal = mOldImplementation
+                .isPermissionRevokedByPolicy(packageName, permName, userId);
         boolean newVal = mNewImplementation.isPermissionRevokedByPolicy(packageName, permName,
-                deviceId, userId);
+                userId);
 
         if (!Objects.equals(oldVal, newVal)) {
             signalImplDifference("isPermissionRevokedByPolicy");
@@ -296,9 +292,9 @@
     }
 
     @Override
-    public int checkPermission(String pkgName, String permName, int deviceId, int userId) {
-        int oldVal = mOldImplementation.checkPermission(pkgName, permName, deviceId, userId);
-        int newVal = mNewImplementation.checkPermission(pkgName, permName, deviceId, userId);
+    public int checkPermission(String pkgName, String permName, int userId) {
+        int oldVal = mOldImplementation.checkPermission(pkgName, permName, userId);
+        int newVal = mNewImplementation.checkPermission(pkgName, permName, userId);
 
         if (!Objects.equals(oldVal, newVal)) {
             signalImplDifference("checkPermission");
@@ -307,9 +303,9 @@
     }
 
     @Override
-    public int checkUidPermission(int uid, String permName, int deviceId) {
-        int oldVal = mOldImplementation.checkUidPermission(uid, permName, deviceId);
-        int newVal = mNewImplementation.checkUidPermission(uid, permName, deviceId);
+    public int checkUidPermission(int uid, String permName) {
+        int oldVal = mOldImplementation.checkUidPermission(uid, permName);
+        int newVal = mNewImplementation.checkUidPermission(uid, permName);
 
         if (!Objects.equals(oldVal, newVal)) {
             signalImplDifference("checkUidPermission");
@@ -376,7 +372,7 @@
 
     @NonNull
     @Override
-    public Set<String> getGrantedPermissions(@NonNull String packageName, @UserIdInt int userId) {
+    public Set<String> getGrantedPermissions(@NonNull String packageName, int userId) {
         Set<String> oldVal = mOldImplementation.getGrantedPermissions(packageName, userId);
         Set<String> newVal = mNewImplementation.getGrantedPermissions(packageName, userId);
 
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
index cbeede0..4e72fae 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTracingDecorator.java
@@ -158,10 +158,10 @@
     }
 
     @Override
-    public int getPermissionFlags(String packageName, String permName, int deviceId, int userId) {
+    public int getPermissionFlags(String packageName, String permName, int userId) {
         Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#getPermissionFlags");
         try {
-            return mService.getPermissionFlags(packageName, permName, deviceId, userId);
+            return mService.getPermissionFlags(packageName, permName, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
@@ -169,12 +169,12 @@
 
     @Override
     public void updatePermissionFlags(String packageName, String permName, int flagMask,
-            int flagValues, boolean checkAdjustPolicyFlagPermission, int deviceId, int userId) {
+            int flagValues, boolean checkAdjustPolicyFlagPermission, int userId) {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingPermissionManagerServiceImpl#updatePermissionFlags");
         try {
             mService.updatePermissionFlags(packageName, permName, flagMask, flagValues,
-                    checkAdjustPolicyFlagPermission, deviceId, userId);
+                    checkAdjustPolicyFlagPermission, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
@@ -253,24 +253,23 @@
     }
 
     @Override
-    public void grantRuntimePermission(String packageName, String permName, int deviceId,
-            int userId) {
+    public void grantRuntimePermission(String packageName, String permName, int userId) {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingPermissionManagerServiceImpl#grantRuntimePermission");
         try {
-            mService.grantRuntimePermission(packageName, permName, deviceId, userId);
+            mService.grantRuntimePermission(packageName, permName, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String permName, int deviceId,
-            int userId, String reason) {
+    public void revokeRuntimePermission(String packageName, String permName, int userId,
+            String reason) {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingPermissionManagerServiceImpl#revokeRuntimePermission");
         try {
-            mService.revokeRuntimePermission(packageName, permName, deviceId, userId, reason);
+            mService.revokeRuntimePermission(packageName, permName, userId, reason);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
@@ -289,24 +288,22 @@
 
     @Override
     public boolean shouldShowRequestPermissionRationale(String packageName, String permName,
-            int deviceId, int userId) {
+            int userId) {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingPermissionManagerServiceImpl#shouldShowRequestPermissionRationale");
         try {
-            return mService.shouldShowRequestPermissionRationale(
-                    packageName, permName, deviceId, userId);
+            return mService.shouldShowRequestPermissionRationale(packageName, permName, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
     }
 
     @Override
-    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int deviceId,
-            int userId) {
+    public boolean isPermissionRevokedByPolicy(String packageName, String permName, int userId) {
         Trace.traceBegin(TRACE_TAG,
                 "TaggedTracingPermissionManagerServiceImpl#isPermissionRevokedByPolicy");
         try {
-            return mService.isPermissionRevokedByPolicy(packageName, permName, deviceId, userId);
+            return mService.isPermissionRevokedByPolicy(packageName, permName, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
@@ -324,20 +321,20 @@
     }
 
     @Override
-    public int checkPermission(String pkgName, String permName, int deviceId, int userId) {
+    public int checkPermission(String pkgName, String permName, int userId) {
         Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkPermission");
         try {
-            return mService.checkPermission(pkgName, permName, deviceId, userId);
+            return mService.checkPermission(pkgName, permName, userId);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
     }
 
     @Override
-    public int checkUidPermission(int uid, String permName, int deviceId) {
+    public int checkUidPermission(int uid, String permName) {
         Trace.traceBegin(TRACE_TAG, "TaggedTracingPermissionManagerServiceImpl#checkUidPermission");
         try {
-            return mService.checkUidPermission(uid, permName, deviceId);
+            return mService.checkUidPermission(uid, permName);
         } finally {
             Trace.traceEnd(TRACE_TAG);
         }
diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java
index 2c37876..3f347e4 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageState.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageState.java
@@ -59,11 +59,6 @@
 public interface PackageState {
 
     /*
-     * Until immutability or read-only caching is enabled, {@link PackageSetting} cannot be
-     * returned directly, so {@link PackageStateImpl} is used to temporarily copy the data.
-     * This is a relatively expensive operation since it has to create an object for every package,
-     * but it's much lighter than the alternative of generating {@link PackageInfo} objects.
-     * <p>
      * TODO: Documentation
      * TODO: Currently missing, should be exposed as API?
      *   - keySetData
@@ -350,6 +345,12 @@
     String getVolumeUuid();
 
     /**
+     * @see AndroidPackage#isDefaultToDeviceProtectedStorage()
+     * @hide
+     */
+    boolean isDefaultToDeviceProtectedStorage();
+
+    /**
      * @see AndroidPackage#isExternalStorage()
      * @hide
      */
diff --git a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
deleted file mode 100644
index ba274e0..0000000
--- a/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
+++ /dev/null
@@ -1,759 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm.pkg;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
-import android.content.pm.SigningInfo;
-import android.content.pm.overlay.OverlayPaths;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.util.SparseArray;
-
-import com.android.internal.util.DataClass;
-import com.android.server.pm.PackageManagerService;
-import com.android.server.pm.PackageSetting;
-import com.android.server.pm.Settings;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Consumer;
-
-/**
- * Because a {@link PackageSetting} cannot be returned from {@link Settings} without holding the
- * {@link PackageManagerService#mLock}, this class serves as a memory snapshot of the state of a
- * single package, for use with {@link PackageManagerInternal#getPackageState(String)} and {@link
- * PackageManagerInternal#forEachPackageState(boolean, Consumer)}.
- *
- * @hide
- */
-@DataClass(genConstructor = false)
-@DataClass.Suppress({"mUserStates"})
-public class PackageStateImpl implements PackageState {
-
-    public static PackageState copy(@NonNull PackageStateInternal pkgSetting) {
-        return new PackageStateImpl(pkgSetting, pkgSetting.getPkg());
-    }
-
-    private static class Booleans {
-        @IntDef({
-                SYSTEM,
-                EXTERNAL_STORAGE,
-                PRIVILEGED,
-                OEM,
-                VENDOR,
-                PRODUCT,
-                SYSTEM_EXT,
-                REQUIRED_FOR_SYSTEM_USER,
-                ODM,
-                FORCE_QUERYABLE_OVERRIDE,
-                HIDDEN_UNTIL_INSTALLED,
-                INSTALL_PERMISSIONS_FIXED,
-                UPDATE_AVAILABLE,
-                UPDATED_SYSTEM_APP,
-                APK_IN_UPDATED_APEX,
-        })
-        public @interface Flags {
-        }
-
-        private static final int SYSTEM = 1;
-        private static final int EXTERNAL_STORAGE = 1 << 1;
-        private static final int PRIVILEGED = 1 << 2;
-        private static final int OEM = 1 << 3;
-        private static final int VENDOR = 1 << 4;
-        private static final int PRODUCT = 1 << 5;
-        private static final int SYSTEM_EXT = 1 << 6;
-        private static final int REQUIRED_FOR_SYSTEM_USER = 1 << 7;
-        private static final int ODM = 1 << 8;
-        private static final int FORCE_QUERYABLE_OVERRIDE = 1 << 9;
-        private static final int HIDDEN_UNTIL_INSTALLED = 1 << 10;
-        private static final int INSTALL_PERMISSIONS_FIXED = 1 << 11;
-        private static final int UPDATE_AVAILABLE = 1 << 12;
-        private static final int UPDATED_SYSTEM_APP = 1 << 13;
-        private static final int APK_IN_UPDATED_APEX = 1 << 14;
-    }
-
-    private int mBooleans;
-
-    private void setBoolean(@Booleans.Flags int flag, boolean value) {
-        if (value) {
-            mBooleans |= flag;
-        } else {
-            mBooleans &= ~flag;
-        }
-    }
-
-    private boolean getBoolean(@Booleans.Flags int flag) {
-        return (mBooleans & flag) != 0;
-    }
-
-    @Nullable
-    private final AndroidPackage mAndroidPackage;
-
-    @NonNull
-    private final String mPackageName;
-    @Nullable
-    private final String mVolumeUuid;
-    private final int mAppId;
-    private final int mCategoryOverride;
-    @Nullable
-    private final String mCpuAbiOverride;
-    @ApplicationInfo.HiddenApiEnforcementPolicy
-    private final int mHiddenApiEnforcementPolicy;
-    private final long mLastModifiedTime;
-    private final long mLastUpdateTime;
-    private final long mLongVersionCode;
-    @NonNull
-    private final Map<String, Set<String>> mMimeGroups;
-    @NonNull
-    private final File mPath;
-    @Nullable
-    private final String mPrimaryCpuAbi;
-    @Nullable
-    private final String mSecondaryCpuAbi;
-    @Nullable
-    private final String mSeInfo;
-    private final boolean mHasSharedUser;
-    private final int mSharedUserAppId;
-    @NonNull
-    private final String[] mUsesSdkLibraries;
-    @NonNull
-    private final long[] mUsesSdkLibrariesVersionsMajor;
-    @NonNull
-    private final String[] mUsesStaticLibraries;
-    @NonNull
-    private final long[] mUsesStaticLibrariesVersions;
-    @NonNull
-    private final List<SharedLibrary> mUsesLibraries;
-    @NonNull
-    private final List<String> mUsesLibraryFiles;
-    @NonNull
-    private final long[] mLastPackageUsageTime;
-    @NonNull
-    private final SigningInfo mSigningInfo;
-    @NonNull
-    private final SparseArray<PackageUserState> mUserStates;
-    @Nullable
-    private final String mApexModuleName;
-
-    private PackageStateImpl(@NonNull PackageState pkgState, @Nullable AndroidPackage pkg) {
-        mAndroidPackage = pkg;
-
-        setBoolean(Booleans.SYSTEM, pkgState.isSystem());
-        setBoolean(Booleans.EXTERNAL_STORAGE, pkgState.isExternalStorage());
-        setBoolean(Booleans.PRIVILEGED, pkgState.isPrivileged());
-        setBoolean(Booleans.OEM, pkgState.isOem());
-        setBoolean(Booleans.VENDOR, pkgState.isVendor());
-        setBoolean(Booleans.PRODUCT, pkgState.isProduct());
-        setBoolean(Booleans.SYSTEM_EXT, pkgState.isSystemExt());
-        setBoolean(Booleans.REQUIRED_FOR_SYSTEM_USER, pkgState.isRequiredForSystemUser());
-        setBoolean(Booleans.ODM, pkgState.isOdm());
-
-        mPackageName = pkgState.getPackageName();
-        mVolumeUuid = pkgState.getVolumeUuid();
-        mAppId = pkgState.getAppId();
-        mCategoryOverride = pkgState.getCategoryOverride();
-        mCpuAbiOverride = pkgState.getCpuAbiOverride();
-        mHiddenApiEnforcementPolicy = pkgState.getHiddenApiEnforcementPolicy();
-        mLastModifiedTime = pkgState.getLastModifiedTime();
-        mLastUpdateTime = pkgState.getLastUpdateTime();
-        mLongVersionCode = pkgState.getVersionCode();
-        mMimeGroups = Collections.unmodifiableMap(pkgState.getMimeGroups());
-        mPath = pkgState.getPath();
-        mPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
-        mSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
-        mSeInfo = pkgState.getSeInfo();
-        mHasSharedUser = pkgState.hasSharedUser();
-        mSharedUserAppId = pkgState.getSharedUserAppId();
-        mUsesSdkLibraries = pkgState.getUsesSdkLibraries();
-        mUsesSdkLibrariesVersionsMajor = pkgState.getUsesSdkLibrariesVersionsMajor();
-        mUsesStaticLibraries = pkgState.getUsesStaticLibraries();
-        mUsesStaticLibrariesVersions = pkgState.getUsesStaticLibrariesVersions();
-        mUsesLibraries = Collections.unmodifiableList(pkgState.getSharedLibraryDependencies());
-        mUsesLibraryFiles = Collections.unmodifiableList(pkgState.getUsesLibraryFiles());
-        setBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE, pkgState.isForceQueryableOverride());
-        setBoolean(Booleans.HIDDEN_UNTIL_INSTALLED, pkgState.isHiddenUntilInstalled());
-        setBoolean(Booleans.INSTALL_PERMISSIONS_FIXED, pkgState.isInstallPermissionsFixed());
-        setBoolean(Booleans.UPDATE_AVAILABLE, pkgState.isUpdateAvailable());
-        mLastPackageUsageTime = pkgState.getLastPackageUsageTime();
-        setBoolean(Booleans.UPDATED_SYSTEM_APP, pkgState.isUpdatedSystemApp());
-        setBoolean(Booleans.APK_IN_UPDATED_APEX, pkgState.isApkInUpdatedApex());
-        mSigningInfo = pkgState.getSigningInfo();
-
-        SparseArray<? extends PackageUserState> userStates = pkgState.getUserStates();
-        int userStatesSize = userStates.size();
-        mUserStates = new SparseArray<>(userStatesSize);
-        for (int index = 0; index < userStatesSize; index++) {
-            mUserStates.put(userStates.keyAt(index),
-                    UserStateImpl.copy(userStates.valueAt(index)));
-        }
-
-        mApexModuleName = pkgState.getApexModuleName();
-    }
-
-    @NonNull
-    @Override
-    public PackageUserState getStateForUser(@NonNull UserHandle user) {
-        PackageUserState userState = getUserStates().get(user.getIdentifier());
-        return userState == null ? PackageUserState.DEFAULT : userState;
-    }
-
-    @Override
-    public boolean isExternalStorage() {
-        return getBoolean(Booleans.EXTERNAL_STORAGE);
-    }
-
-    @Override
-    public boolean isForceQueryableOverride() {
-        return getBoolean(Booleans.FORCE_QUERYABLE_OVERRIDE);
-    }
-
-    @Override
-    public boolean isHiddenUntilInstalled() {
-        return getBoolean(Booleans.HIDDEN_UNTIL_INSTALLED);
-    }
-
-    @Override
-    public boolean isInstallPermissionsFixed() {
-        return getBoolean(Booleans.INSTALL_PERMISSIONS_FIXED);
-    }
-
-    @Override
-    public boolean isOdm() {
-        return getBoolean(Booleans.ODM);
-    }
-
-    @Override
-    public boolean isOem() {
-        return getBoolean(Booleans.OEM);
-    }
-
-    @Override
-    public boolean isPrivileged() {
-        return getBoolean(Booleans.PRIVILEGED);
-    }
-
-    @Override
-    public boolean isProduct() {
-        return getBoolean(Booleans.PRODUCT);
-    }
-
-    @Override
-    public boolean isRequiredForSystemUser() {
-        return getBoolean(Booleans.REQUIRED_FOR_SYSTEM_USER);
-    }
-
-    @Override
-    public boolean isSystem() {
-        return getBoolean(Booleans.SYSTEM);
-    }
-
-    @Override
-    public boolean isSystemExt() {
-        return getBoolean(Booleans.SYSTEM_EXT);
-    }
-
-    @Override
-    public boolean isUpdateAvailable() {
-        return getBoolean(Booleans.UPDATE_AVAILABLE);
-    }
-
-    @Override
-    public boolean isUpdatedSystemApp() {
-        return getBoolean(Booleans.UPDATED_SYSTEM_APP);
-    }
-
-    @Override
-    public boolean isApkInUpdatedApex() {
-        return getBoolean(Booleans.APK_IN_UPDATED_APEX);
-    }
-
-    @Override
-    public boolean isVendor() {
-        return getBoolean(Booleans.VENDOR);
-    }
-
-    @Override
-    public long getVersionCode() {
-        return mLongVersionCode;
-    }
-
-    @Override
-    public boolean hasSharedUser() {
-        return mHasSharedUser;
-    }
-
-    @Override
-    public boolean isApex() {
-        return getAndroidPackage() != null && getAndroidPackage().isApex();
-    }
-
-    /**
-     * @hide
-     */
-    @DataClass(genConstructor = false)
-    public static class UserStateImpl implements PackageUserState {
-
-        public static PackageUserState copy(@NonNull PackageUserState state) {
-            return new UserStateImpl(state);
-        }
-
-        private static class Booleans {
-            @IntDef({
-                    HIDDEN,
-                    INSTALLED,
-                    INSTANT_APP,
-                    NOT_LAUNCHED,
-                    STOPPED,
-                    SUSPENDED,
-                    VIRTUAL_PRELOAD,
-            })
-            public @interface Flags {
-            }
-
-            private static final int HIDDEN = 1;
-            private static final int INSTALLED = 1 << 1;
-            private static final int INSTANT_APP = 1 << 2;
-            private static final int NOT_LAUNCHED = 1 << 3;
-            private static final int STOPPED = 1 << 4;
-            private static final int SUSPENDED = 1 << 5;
-            private static final int VIRTUAL_PRELOAD = 1 << 6;
-        }
-
-        private int mBooleans;
-
-        private void setBoolean(@Booleans.Flags int flag, boolean value) {
-            if (value) {
-                mBooleans |= flag;
-            } else {
-                mBooleans &= ~flag;
-            }
-        }
-
-        private boolean getBoolean(@Booleans.Flags int flag) {
-            return (mBooleans & flag) != 0;
-        }
-
-        private final long mCeDataInode;
-        @NonNull
-        private final ArraySet<String> mDisabledComponents;
-        @PackageManager.DistractionRestriction
-        private final int mDistractionFlags;
-        @NonNull
-        private final ArraySet<String> mEnabledComponents;
-        private final int mEnabledState;
-        @Nullable
-        private final String mHarmfulAppWarning;
-        @PackageManager.InstallReason
-        private final int mInstallReason;
-        @Nullable
-        private final String mLastDisableAppCaller;
-        @NonNull
-        private final OverlayPaths mOverlayPaths;
-        @NonNull
-        private final Map<String, OverlayPaths> mSharedLibraryOverlayPaths;
-        @PackageManager.UninstallReason
-        private final int mUninstallReason;
-        @Nullable
-        private final String mSplashScreenTheme;
-        @PackageManager.UserMinAspectRatio
-        private final int mMinAspectRatio;
-        private final long mFirstInstallTimeMillis;
-        @Nullable
-        private final ArchiveState mArchiveState;
-
-        private UserStateImpl(@NonNull PackageUserState userState) {
-            mCeDataInode = userState.getCeDataInode();
-            mDisabledComponents = userState.getDisabledComponents();
-            mDistractionFlags = userState.getDistractionFlags();
-            mEnabledComponents = userState.getEnabledComponents();
-            mEnabledState = userState.getEnabledState();
-            mHarmfulAppWarning = userState.getHarmfulAppWarning();
-            mInstallReason = userState.getInstallReason();
-            mLastDisableAppCaller = userState.getLastDisableAppCaller();
-            mOverlayPaths = userState.getOverlayPaths();
-            mSharedLibraryOverlayPaths = userState.getSharedLibraryOverlayPaths();
-            mUninstallReason = userState.getUninstallReason();
-            mSplashScreenTheme = userState.getSplashScreenTheme();
-            mMinAspectRatio = userState.getMinAspectRatio();
-            setBoolean(Booleans.HIDDEN, userState.isHidden());
-            setBoolean(Booleans.INSTALLED, userState.isInstalled());
-            setBoolean(Booleans.INSTANT_APP, userState.isInstantApp());
-            setBoolean(Booleans.NOT_LAUNCHED, userState.isNotLaunched());
-            setBoolean(Booleans.STOPPED, userState.isStopped());
-            setBoolean(Booleans.SUSPENDED, userState.isSuspended());
-            setBoolean(Booleans.VIRTUAL_PRELOAD, userState.isVirtualPreload());
-            mFirstInstallTimeMillis = userState.getFirstInstallTimeMillis();
-            mArchiveState = userState.getArchiveState();
-        }
-
-        @Override
-        public boolean isHidden() {
-            return getBoolean(Booleans.HIDDEN);
-        }
-
-        @Override
-        public boolean isInstalled() {
-            return getBoolean(Booleans.INSTALLED);
-        }
-
-        @Override
-        public boolean isInstantApp() {
-            return getBoolean(Booleans.INSTANT_APP);
-        }
-
-        @Override
-        public boolean isNotLaunched() {
-            return getBoolean(Booleans.NOT_LAUNCHED);
-        }
-
-        @Override
-        public boolean isStopped() {
-            return getBoolean(Booleans.STOPPED);
-        }
-
-        @Override
-        public boolean isSuspended() {
-            return getBoolean(Booleans.SUSPENDED);
-        }
-
-        @Override
-        public boolean isVirtualPreload() {
-            return getBoolean(Booleans.VIRTUAL_PRELOAD);
-        }
-
-        @Override
-        public boolean isComponentEnabled(String componentName) {
-            return mEnabledComponents.contains(componentName);
-        }
-
-        @Override
-        public boolean isComponentDisabled(String componentName) {
-            return mDisabledComponents.contains(componentName);
-        }
-
-        @Override
-        public OverlayPaths getAllOverlayPaths() {
-            if (mOverlayPaths == null && mSharedLibraryOverlayPaths == null) {
-                return null;
-            }
-            final OverlayPaths.Builder newPaths = new OverlayPaths.Builder();
-            newPaths.addAll(mOverlayPaths);
-            if (mSharedLibraryOverlayPaths != null) {
-                for (final OverlayPaths libOverlayPaths : mSharedLibraryOverlayPaths.values()) {
-                    newPaths.addAll(libOverlayPaths);
-                }
-            }
-            return newPaths.build();
-        }
-
-
-
-        // Code below generated by codegen v1.0.23.
-        //
-        // DO NOT MODIFY!
-        // CHECKSTYLE:OFF Generated code
-        //
-        // To regenerate run:
-        // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
-        //
-        // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-        //   Settings > Editor > Code Style > Formatter Control
-        //@formatter:off
-
-
-        @DataClass.Generated.Member
-        public int getBooleans() {
-            return mBooleans;
-        }
-
-        @DataClass.Generated.Member
-        public long getCeDataInode() {
-            return mCeDataInode;
-        }
-
-        @DataClass.Generated.Member
-        public @NonNull ArraySet<String> getDisabledComponents() {
-            return mDisabledComponents;
-        }
-
-        @DataClass.Generated.Member
-        public @PackageManager.DistractionRestriction int getDistractionFlags() {
-            return mDistractionFlags;
-        }
-
-        @DataClass.Generated.Member
-        public @NonNull ArraySet<String> getEnabledComponents() {
-            return mEnabledComponents;
-        }
-
-        @DataClass.Generated.Member
-        public int getEnabledState() {
-            return mEnabledState;
-        }
-
-        @DataClass.Generated.Member
-        public @Nullable String getHarmfulAppWarning() {
-            return mHarmfulAppWarning;
-        }
-
-        @DataClass.Generated.Member
-        public @PackageManager.InstallReason int getInstallReason() {
-            return mInstallReason;
-        }
-
-        @DataClass.Generated.Member
-        public @Nullable String getLastDisableAppCaller() {
-            return mLastDisableAppCaller;
-        }
-
-        @DataClass.Generated.Member
-        public @NonNull OverlayPaths getOverlayPaths() {
-            return mOverlayPaths;
-        }
-
-        @DataClass.Generated.Member
-        public @NonNull Map<String,OverlayPaths> getSharedLibraryOverlayPaths() {
-            return mSharedLibraryOverlayPaths;
-        }
-
-        @DataClass.Generated.Member
-        public @PackageManager.UninstallReason int getUninstallReason() {
-            return mUninstallReason;
-        }
-
-        @DataClass.Generated.Member
-        public @Nullable String getSplashScreenTheme() {
-            return mSplashScreenTheme;
-        }
-
-        @DataClass.Generated.Member
-        public @PackageManager.UserMinAspectRatio int getMinAspectRatio() {
-            return mMinAspectRatio;
-        }
-
-        @DataClass.Generated.Member
-        public long getFirstInstallTimeMillis() {
-            return mFirstInstallTimeMillis;
-        }
-
-        @DataClass.Generated.Member
-        public @Nullable ArchiveState getArchiveState() {
-            return mArchiveState;
-        }
-
-        @DataClass.Generated.Member
-        public @NonNull UserStateImpl setBooleans( int value) {
-            mBooleans = value;
-            return this;
-        }
-
-        @DataClass.Generated(
-                time = 1689171425723L,
-                codegenVersion = "1.0.23",
-                sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-                inputSignatures = "private  int mBooleans\nprivate final  long mCeDataInode\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mDisabledComponents\nprivate final @android.content.pm.PackageManager.DistractionRestriction int mDistractionFlags\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledComponents\nprivate final  int mEnabledState\nprivate final @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate final @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate final @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate final @android.annotation.NonNull android.content.pm.overlay.OverlayPaths mOverlayPaths\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate final @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate final @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate final @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate final  long mFirstInstallTimeMillis\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\npublic static  com.android.server.pm.pkg.PackageUserState copy(com.android.server.pm.pkg.PackageUserState)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isSuspended()\npublic @java.lang.Override boolean isVirtualPreload()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\nclass UserStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageUserState]\nprivate static final  int HIDDEN\nprivate static final  int INSTALLED\nprivate static final  int INSTANT_APP\nprivate static final  int NOT_LAUNCHED\nprivate static final  int STOPPED\nprivate static final  int SUSPENDED\nprivate static final  int VIRTUAL_PRELOAD\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
-        @Deprecated
-        private void __metadata() {}
-
-
-        //@formatter:on
-        // End of generated code
-
-    }
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    @DataClass.Generated.Member
-    public int getBooleans() {
-        return mBooleans;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable AndroidPackage getAndroidPackage() {
-        return mAndroidPackage;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull String getPackageName() {
-        return mPackageName;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable String getVolumeUuid() {
-        return mVolumeUuid;
-    }
-
-    @DataClass.Generated.Member
-    public int getAppId() {
-        return mAppId;
-    }
-
-    @DataClass.Generated.Member
-    public int getCategoryOverride() {
-        return mCategoryOverride;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable String getCpuAbiOverride() {
-        return mCpuAbiOverride;
-    }
-
-    @DataClass.Generated.Member
-    public @ApplicationInfo.HiddenApiEnforcementPolicy int getHiddenApiEnforcementPolicy() {
-        return mHiddenApiEnforcementPolicy;
-    }
-
-    @DataClass.Generated.Member
-    public long getLastModifiedTime() {
-        return mLastModifiedTime;
-    }
-
-    @DataClass.Generated.Member
-    public long getLastUpdateTime() {
-        return mLastUpdateTime;
-    }
-
-    @DataClass.Generated.Member
-    public long getLongVersionCode() {
-        return mLongVersionCode;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull Map<String,Set<String>> getMimeGroups() {
-        return mMimeGroups;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull File getPath() {
-        return mPath;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable String getPrimaryCpuAbi() {
-        return mPrimaryCpuAbi;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable String getSecondaryCpuAbi() {
-        return mSecondaryCpuAbi;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable String getSeInfo() {
-        return mSeInfo;
-    }
-
-    @DataClass.Generated.Member
-    public boolean isHasSharedUser() {
-        return mHasSharedUser;
-    }
-
-    @DataClass.Generated.Member
-    public int getSharedUserAppId() {
-        return mSharedUserAppId;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull String[] getUsesSdkLibraries() {
-        return mUsesSdkLibraries;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull long[] getUsesSdkLibrariesVersionsMajor() {
-        return mUsesSdkLibrariesVersionsMajor;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull String[] getUsesStaticLibraries() {
-        return mUsesStaticLibraries;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull long[] getUsesStaticLibrariesVersions() {
-        return mUsesStaticLibrariesVersions;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull List<SharedLibrary> getSharedLibraryDependencies() {
-        return mUsesLibraries;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull List<String> getUsesLibraryFiles() {
-        return mUsesLibraryFiles;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull long[] getLastPackageUsageTime() {
-        return mLastPackageUsageTime;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull SigningInfo getSigningInfo() {
-        return mSigningInfo;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull SparseArray<PackageUserState> getUserStates() {
-        return mUserStates;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable String getApexModuleName() {
-        return mApexModuleName;
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull PackageStateImpl setBooleans( int value) {
-        mBooleans = value;
-        return this;
-    }
-
-    @DataClass.Generated(
-            time = 1689171425753L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageStateImpl.java",
-            inputSignatures = "private  int mBooleans\nprivate final @android.annotation.Nullable com.android.server.pm.pkg.AndroidPackage mAndroidPackage\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mVolumeUuid\nprivate final  int mAppId\nprivate final  int mCategoryOverride\nprivate final @android.annotation.Nullable java.lang.String mCpuAbiOverride\nprivate final @android.content.pm.ApplicationInfo.HiddenApiEnforcementPolicy int mHiddenApiEnforcementPolicy\nprivate final  long mLastModifiedTime\nprivate final  long mLastUpdateTime\nprivate final  long mLongVersionCode\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.util.Set<java.lang.String>> mMimeGroups\nprivate final @android.annotation.NonNull java.io.File mPath\nprivate final @android.annotation.Nullable java.lang.String mPrimaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSecondaryCpuAbi\nprivate final @android.annotation.Nullable java.lang.String mSeInfo\nprivate final  boolean mHasSharedUser\nprivate final  int mSharedUserAppId\nprivate final @android.annotation.NonNull java.lang.String[] mUsesSdkLibraries\nprivate final @android.annotation.NonNull long[] mUsesSdkLibrariesVersionsMajor\nprivate final @android.annotation.NonNull java.lang.String[] mUsesStaticLibraries\nprivate final @android.annotation.NonNull long[] mUsesStaticLibrariesVersions\nprivate final @android.annotation.NonNull java.util.List<com.android.server.pm.pkg.SharedLibrary> mUsesLibraries\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mUsesLibraryFiles\nprivate final @android.annotation.NonNull long[] mLastPackageUsageTime\nprivate final @android.annotation.NonNull android.content.pm.SigningInfo mSigningInfo\nprivate final @android.annotation.NonNull android.util.SparseArray<com.android.server.pm.pkg.PackageUserState> mUserStates\nprivate final @android.annotation.Nullable java.lang.String mApexModuleName\npublic static  com.android.server.pm.pkg.PackageState copy(com.android.server.pm.pkg.PackageStateInternal)\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserState getStateForUser(android.os.UserHandle)\npublic @java.lang.Override boolean isExternalStorage()\npublic @java.lang.Override boolean isForceQueryableOverride()\npublic @java.lang.Override boolean isHiddenUntilInstalled()\npublic @java.lang.Override boolean isInstallPermissionsFixed()\npublic @java.lang.Override boolean isOdm()\npublic @java.lang.Override boolean isOem()\npublic @java.lang.Override boolean isPrivileged()\npublic @java.lang.Override boolean isProduct()\npublic @java.lang.Override boolean isRequiredForSystemUser()\npublic @java.lang.Override boolean isSystem()\npublic @java.lang.Override boolean isSystemExt()\npublic @java.lang.Override boolean isUpdateAvailable()\npublic @java.lang.Override boolean isUpdatedSystemApp()\npublic @java.lang.Override boolean isApkInUpdatedApex()\npublic @java.lang.Override boolean isVendor()\npublic @java.lang.Override long getVersionCode()\npublic @java.lang.Override boolean hasSharedUser()\npublic @java.lang.Override boolean isApex()\nclass PackageStateImpl extends java.lang.Object implements [com.android.server.pm.pkg.PackageState]\nprivate static final  int SYSTEM\nprivate static final  int EXTERNAL_STORAGE\nprivate static final  int PRIVILEGED\nprivate static final  int OEM\nprivate static final  int VENDOR\nprivate static final  int PRODUCT\nprivate static final  int SYSTEM_EXT\nprivate static final  int REQUIRED_FOR_SYSTEM_USER\nprivate static final  int ODM\nprivate static final  int FORCE_QUERYABLE_OVERRIDE\nprivate static final  int HIDDEN_UNTIL_INSTALLED\nprivate static final  int INSTALL_PERMISSIONS_FIXED\nprivate static final  int UPDATE_AVAILABLE\nprivate static final  int UPDATED_SYSTEM_APP\nprivate static final  int APK_IN_UPDATED_APEX\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false)")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
-}
diff --git a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
index 6ac7c34..d8c8af6 100644
--- a/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
+++ b/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java
@@ -17,6 +17,7 @@
 package com.android.server.pm.pkg;
 
 import android.annotation.CurrentTimeMillisLong;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
@@ -45,9 +46,44 @@
 /** @hide */
 @DataClass(genConstructor = false, genBuilder = false, genEqualsHashCode = true)
 @DataClass.Suppress({"mOverlayPathsLock", "mOverlayPaths", "mSharedLibraryOverlayPathsLock",
-        "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths", "getWatchable"})
+        "mSharedLibraryOverlayPaths", "setOverlayPaths", "setCachedOverlayPaths", "getWatchable",
+        "getBooleans"
+})
 public class PackageUserStateImpl extends WatchableImpl implements PackageUserStateInternal,
         Snappable {
+    // Use a bitset to store boolean data to save memory
+    private static class Booleans {
+        @IntDef({
+                INSTALLED,
+                STOPPED,
+                NOT_LAUNCHED,
+                HIDDEN,
+                INSTANT_APP,
+                VIRTUAL_PRELOADED,
+        })
+        public @interface Flags {
+        }
+        private static final int INSTALLED = 1;
+        private static final int STOPPED = 1 << 1;
+        private static final int NOT_LAUNCHED = 1 << 2;
+        // Is the app restricted by owner / admin
+        private static final int HIDDEN = 1 << 3;
+        private static final int INSTANT_APP = 1 << 4;
+        private static final int VIRTUAL_PRELOADED = 1 << 5;
+    }
+    private int mBooleans;
+
+    private void setBoolean(@Booleans.Flags int flag, boolean value) {
+        if (value) {
+            mBooleans |= flag;
+        } else {
+            mBooleans &= ~flag;
+        }
+    }
+
+    private boolean getBoolean(@Booleans.Flags int flag) {
+        return (mBooleans & flag) != 0;
+    }
 
     @Nullable
     protected WatchedArraySet<String> mDisabledComponentsWatched;
@@ -55,13 +91,7 @@
     protected WatchedArraySet<String> mEnabledComponentsWatched;
 
     private long mCeDataInode;
-    private boolean mInstalled = true;
-    private boolean mStopped;
-    private boolean mNotLaunched;
-    private boolean mHidden; // Is the app restricted by owner / admin
     private int mDistractionFlags;
-    private boolean mInstantApp;
-    private boolean mVirtualPreload;
     @PackageManager.EnabledState
     private int mEnabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
     @PackageManager.InstallReason
@@ -122,15 +152,18 @@
         super();
         mWatchable = null;
         mSnapshot = makeCache();
+        setBoolean(Booleans.INSTALLED, true);
     }
 
     public PackageUserStateImpl(@NonNull Watchable watchable) {
         mWatchable = watchable;
         mSnapshot = makeCache();
+        setBoolean(Booleans.INSTALLED, true);
     }
 
     public PackageUserStateImpl(@NonNull Watchable watchable, PackageUserStateImpl other) {
         mWatchable = watchable;
+        mBooleans = other.mBooleans;
         mDisabledComponentsWatched = other.mDisabledComponentsWatched == null
                 ? null : other.mDisabledComponentsWatched.snapshot();
         mEnabledComponentsWatched =  other.mEnabledComponentsWatched == null
@@ -139,13 +172,7 @@
         mSharedLibraryOverlayPaths = other.mSharedLibraryOverlayPaths == null
                 ? null : other.mSharedLibraryOverlayPaths.snapshot();
         mCeDataInode = other.mCeDataInode;
-        mInstalled = other.mInstalled;
-        mStopped = other.mStopped;
-        mNotLaunched = other.mNotLaunched;
-        mHidden = other.mHidden;
         mDistractionFlags = other.mDistractionFlags;
-        mInstantApp = other.mInstantApp;
-        mVirtualPreload = other.mVirtualPreload;
         mEnabledState = other.mEnabledState;
         mInstallReason = other.mInstallReason;
         mUninstallReason = other.mUninstallReason;
@@ -418,25 +445,25 @@
     }
 
     public @NonNull PackageUserStateImpl setInstalled(boolean value) {
-        mInstalled = value;
+        setBoolean(Booleans.INSTALLED, value);
         onChanged();
         return this;
     }
 
     public @NonNull PackageUserStateImpl setStopped(boolean value) {
-        mStopped = value;
+        setBoolean(Booleans.STOPPED, value);
         onChanged();
         return this;
     }
 
     public @NonNull PackageUserStateImpl setNotLaunched(boolean value) {
-        mNotLaunched = value;
+        setBoolean(Booleans.NOT_LAUNCHED, value);
         onChanged();
         return this;
     }
 
     public @NonNull PackageUserStateImpl setHidden(boolean value) {
-        mHidden = value;
+        setBoolean(Booleans.HIDDEN, value);
         onChanged();
         return this;
     }
@@ -448,13 +475,13 @@
     }
 
     public @NonNull PackageUserStateImpl setInstantApp(boolean value) {
-        mInstantApp = value;
+        setBoolean(Booleans.INSTANT_APP, value);
         onChanged();
         return this;
     }
 
     public @NonNull PackageUserStateImpl setVirtualPreload(boolean value) {
-        mVirtualPreload = value;
+        setBoolean(Booleans.VIRTUAL_PRELOADED, value);
         onChanged();
         return this;
     }
@@ -613,6 +640,38 @@
     }
 
 
+    @Override
+    public boolean isInstalled() {
+        return getBoolean(Booleans.INSTALLED);
+    }
+
+    @Override
+    public boolean isStopped() {
+        return getBoolean(Booleans.STOPPED);
+    }
+
+    @Override
+    public boolean isNotLaunched() {
+        return getBoolean(Booleans.NOT_LAUNCHED);
+    }
+
+    @Override
+    public boolean isHidden() {
+        return getBoolean(Booleans.HIDDEN);
+    }
+
+    @Override
+    public boolean isInstantApp() {
+        return getBoolean(Booleans.INSTANT_APP);
+    }
+
+    @Override
+    public boolean isVirtualPreload() {
+        return getBoolean(Booleans.VIRTUAL_PRELOADED);
+    }
+
+
+
 
     // Code below generated by codegen v1.0.23.
     //
@@ -643,41 +702,11 @@
     }
 
     @DataClass.Generated.Member
-    public boolean isInstalled() {
-        return mInstalled;
-    }
-
-    @DataClass.Generated.Member
-    public boolean isStopped() {
-        return mStopped;
-    }
-
-    @DataClass.Generated.Member
-    public boolean isNotLaunched() {
-        return mNotLaunched;
-    }
-
-    @DataClass.Generated.Member
-    public boolean isHidden() {
-        return mHidden;
-    }
-
-    @DataClass.Generated.Member
     public int getDistractionFlags() {
         return mDistractionFlags;
     }
 
     @DataClass.Generated.Member
-    public boolean isInstantApp() {
-        return mInstantApp;
-    }
-
-    @DataClass.Generated.Member
-    public boolean isVirtualPreload() {
-        return mVirtualPreload;
-    }
-
-    @DataClass.Generated.Member
     public @PackageManager.EnabledState int getEnabledState() {
         return mEnabledState;
     }
@@ -746,6 +775,12 @@
     }
 
     @DataClass.Generated.Member
+    public @NonNull PackageUserStateImpl setBooleans( int value) {
+        mBooleans = value;
+        return this;
+    }
+
+    @DataClass.Generated.Member
     public @NonNull PackageUserStateImpl setDisabledComponentsWatched(@NonNull WatchedArraySet<String> value) {
         mDisabledComponentsWatched = value;
         return this;
@@ -791,16 +826,11 @@
         PackageUserStateImpl that = (PackageUserStateImpl) o;
         //noinspection PointlessBooleanExpression
         return true
+                && mBooleans == that.mBooleans
                 && Objects.equals(mDisabledComponentsWatched, that.mDisabledComponentsWatched)
                 && Objects.equals(mEnabledComponentsWatched, that.mEnabledComponentsWatched)
                 && mCeDataInode == that.mCeDataInode
-                && mInstalled == that.mInstalled
-                && mStopped == that.mStopped
-                && mNotLaunched == that.mNotLaunched
-                && mHidden == that.mHidden
                 && mDistractionFlags == that.mDistractionFlags
-                && mInstantApp == that.mInstantApp
-                && mVirtualPreload == that.mVirtualPreload
                 && mEnabledState == that.mEnabledState
                 && mInstallReason == that.mInstallReason
                 && mUninstallReason == that.mUninstallReason
@@ -825,16 +855,11 @@
         // int fieldNameHashCode() { ... }
 
         int _hash = 1;
+        _hash = 31 * _hash + mBooleans;
         _hash = 31 * _hash + Objects.hashCode(mDisabledComponentsWatched);
         _hash = 31 * _hash + Objects.hashCode(mEnabledComponentsWatched);
         _hash = 31 * _hash + Long.hashCode(mCeDataInode);
-        _hash = 31 * _hash + Boolean.hashCode(mInstalled);
-        _hash = 31 * _hash + Boolean.hashCode(mStopped);
-        _hash = 31 * _hash + Boolean.hashCode(mNotLaunched);
-        _hash = 31 * _hash + Boolean.hashCode(mHidden);
         _hash = 31 * _hash + mDistractionFlags;
-        _hash = 31 * _hash + Boolean.hashCode(mInstantApp);
-        _hash = 31 * _hash + Boolean.hashCode(mVirtualPreload);
         _hash = 31 * _hash + mEnabledState;
         _hash = 31 * _hash + mInstallReason;
         _hash = 31 * _hash + mUninstallReason;
@@ -854,10 +879,10 @@
     }
 
     @DataClass.Generated(
-            time = 1689171513404L,
+            time = 1691186062924L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/PackageUserStateImpl.java",
-            inputSignatures = "protected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  boolean mInstalled\nprivate  boolean mStopped\nprivate  boolean mNotLaunched\nprivate  boolean mHidden\nprivate  int mDistractionFlags\nprivate  boolean mInstantApp\nprivate  boolean mVirtualPreload\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
+            inputSignatures = "private  int mBooleans\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mDisabledComponentsWatched\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArraySet<java.lang.String> mEnabledComponentsWatched\nprivate  long mCeDataInode\nprivate  int mDistractionFlags\nprivate @android.content.pm.PackageManager.EnabledState int mEnabledState\nprivate @android.content.pm.PackageManager.InstallReason int mInstallReason\nprivate @android.content.pm.PackageManager.UninstallReason int mUninstallReason\nprivate @android.annotation.Nullable java.lang.String mHarmfulAppWarning\nprivate @android.annotation.Nullable java.lang.String mLastDisableAppCaller\nprivate @android.annotation.Nullable android.content.pm.overlay.OverlayPaths mOverlayPaths\nprotected @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths> mSharedLibraryOverlayPaths\nprivate @android.annotation.Nullable java.lang.String mSplashScreenTheme\nprivate @android.content.pm.PackageManager.UserMinAspectRatio int mMinAspectRatio\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams> mSuspendParams\nprivate @android.annotation.Nullable com.android.server.utils.WatchedArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>> mComponentLabelIconOverrideMap\nprivate @android.annotation.CurrentTimeMillisLong long mFirstInstallTimeMillis\nprivate @android.annotation.Nullable com.android.server.utils.Watchable mWatchable\nprivate @android.annotation.Nullable com.android.server.pm.pkg.ArchiveState mArchiveState\nfinal @android.annotation.NonNull com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> mSnapshot\nprivate  void setBoolean(int,boolean)\nprivate  boolean getBoolean(int)\nprivate  com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl> makeCache()\nprivate  void onChanged()\npublic @android.annotation.NonNull @java.lang.Override com.android.server.pm.pkg.PackageUserStateImpl snapshot()\npublic @android.annotation.Nullable boolean setOverlayPaths(android.content.pm.overlay.OverlayPaths)\npublic  boolean setSharedLibraryOverlayPaths(java.lang.String,android.content.pm.overlay.OverlayPaths)\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getDisabledComponentsNoCopy()\npublic @android.annotation.Nullable @java.lang.Override com.android.server.utils.WatchedArraySet<java.lang.String> getEnabledComponentsNoCopy()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getDisabledComponents()\npublic @android.annotation.NonNull @java.lang.Override android.util.ArraySet<java.lang.String> getEnabledComponents()\npublic @java.lang.Override boolean isComponentEnabled(java.lang.String)\npublic @java.lang.Override boolean isComponentDisabled(java.lang.String)\npublic @java.lang.Override android.content.pm.overlay.OverlayPaths getAllOverlayPaths()\npublic @com.android.internal.annotations.VisibleForTesting boolean overrideLabelAndIcon(android.content.ComponentName,java.lang.String,java.lang.Integer)\npublic  void resetOverrideComponentLabelIcon()\npublic @android.annotation.Nullable android.util.Pair<java.lang.String,java.lang.Integer> getOverrideLabelIconForComponent(android.content.ComponentName)\npublic @java.lang.Override boolean isSuspended()\npublic  com.android.server.pm.pkg.PackageUserStateImpl putSuspendParams(java.lang.String,com.android.server.pm.pkg.SuspendParams)\npublic  com.android.server.pm.pkg.PackageUserStateImpl removeSuspension(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(android.util.ArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDisabledComponents(com.android.server.utils.WatchedArraySet<java.lang.String>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setCeDataInode(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstalled(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setStopped(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setNotLaunched(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHidden(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setDistractionFlags(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstantApp(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setVirtualPreload(boolean)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setEnabledState(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setInstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setUninstallReason(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setHarmfulAppWarning(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setLastDisableAppCaller(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSharedLibraryOverlayPaths(android.util.ArrayMap<java.lang.String,android.content.pm.overlay.OverlayPaths>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSplashScreenTheme(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setMinAspectRatio(int)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setSuspendParams(android.util.ArrayMap<java.lang.String,com.android.server.pm.pkg.SuspendParams>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setComponentLabelIconOverrideMap(android.util.ArrayMap<android.content.ComponentName,android.util.Pair<java.lang.String,java.lang.Integer>>)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setFirstInstallTimeMillis(long)\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setArchiveState(com.android.server.pm.pkg.ArchiveState)\npublic @android.annotation.NonNull @java.lang.Override java.util.Map<java.lang.String,android.content.pm.overlay.OverlayPaths> getSharedLibraryOverlayPaths()\npublic @android.annotation.NonNull com.android.server.pm.pkg.PackageUserStateImpl setWatchable(com.android.server.utils.Watchable)\nprivate  boolean watchableEquals(com.android.server.utils.Watchable)\nprivate  int watchableHashCode()\nprivate  boolean snapshotEquals(com.android.server.utils.SnapshotCache<com.android.server.pm.pkg.PackageUserStateImpl>)\nprivate  int snapshotHashCode()\npublic @java.lang.Override boolean isInstalled()\npublic @java.lang.Override boolean isStopped()\npublic @java.lang.Override boolean isNotLaunched()\npublic @java.lang.Override boolean isHidden()\npublic @java.lang.Override boolean isInstantApp()\npublic @java.lang.Override boolean isVirtualPreload()\nclass PackageUserStateImpl extends com.android.server.utils.WatchableImpl implements [com.android.server.pm.pkg.PackageUserStateInternal, com.android.server.utils.Snappable]\nprivate static final  int INSTALLED\nprivate static final  int STOPPED\nprivate static final  int NOT_LAUNCHED\nprivate static final  int HIDDEN\nprivate static final  int INSTANT_APP\nprivate static final  int VIRTUAL_PRELOADED\nclass Booleans extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
index 0bb969f..a2177e8 100644
--- a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
+++ b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
@@ -17,13 +17,13 @@
 
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.os.Build;
 
 import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
 
 import libcore.io.IoUtils;
 
@@ -82,8 +82,8 @@
         }
 
         AssetManager assets = new AssetManager();
-        assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                0, 0, Build.VERSION.RESOURCES_SDK_INT);
+        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                Build.VERSION.RESOURCES_SDK_INT);
         assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
 
         mCachedAssetManager = assets;
diff --git a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
index 56d92fb..1a8c1996 100644
--- a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
+++ b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
@@ -80,8 +80,8 @@
 
     private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
         final AssetManager assets = new AssetManager();
-        assets.setConfiguration(0, 0, null, new String[0], 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                0, Build.VERSION.RESOURCES_SDK_INT);
+        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+                Build.VERSION.RESOURCES_SDK_INT);
         assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
         return assets;
     }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 75fcca5..1e6486a 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -18,7 +18,6 @@
 
 import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest.policyToString;
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
-import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT;
 import static android.os.PowerManager.GO_TO_SLEEP_REASON_DISPLAY_GROUPS_TURNED_OFF;
 import static android.os.PowerManager.GO_TO_SLEEP_REASON_DISPLAY_GROUP_REMOVED;
 import static android.os.PowerManager.WAKE_REASON_DISPLAY_GROUP_ADDED;
@@ -1280,7 +1279,7 @@
     @Override
     public void onStart() {
         publishBinderService(Context.POWER_SERVICE, mBinderService, /* allowIsolated= */ false,
-                DUMP_FLAG_PRIORITY_DEFAULT | DUMP_FLAG_PRIORITY_CRITICAL);
+                DUMP_FLAG_PRIORITY_CRITICAL);
         publishLocalService(PowerManagerInternal.class, mLocalService);
 
         Watchdog.getInstance().addMonitor(this);
diff --git a/services/core/java/com/android/server/power/TEST_MAPPING b/services/core/java/com/android/server/power/TEST_MAPPING
index 8374997..19086a1 100644
--- a/services/core/java/com/android/server/power/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/TEST_MAPPING
@@ -17,15 +17,6 @@
       ]
     },
     {
-      "name": "FrameworksServicesTests",
-      "options": [
-        {"include-filter": "com.android.server.power"},
-        {"exclude-filter": "com.android.server.power.BatteryStatsTests"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
-        {"exclude-annotation": "androidx.test.filters.FlakyTest"}
-      ]
-    },
-    {
       "name": "PowerServiceTests",
       "options": [
         {"include-filter": "com.android.server.power"},
@@ -48,15 +39,13 @@
     {
       "name": "FrameworksServicesTests",
       "options": [
-        {"include-filter": "com.android.server.power"},
-        {"exclude-filter": "com.android.server.power.BatteryStatsTests"}
+        {"include-filter": "com.android.server.power"}
       ]
     },
     {
       "name": "PowerServiceTests",
       "options": [
         {"include-filter": "com.android.server.power"},
-        {"exclude-filter": "com.android.server.power.BatteryStatsTests"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
     }
diff --git a/services/core/java/com/android/server/power/batterysaver/TEST_MAPPING b/services/core/java/com/android/server/power/batterysaver/TEST_MAPPING
index 17dba7d..c091b8e 100644
--- a/services/core/java/com/android/server/power/batterysaver/TEST_MAPPING
+++ b/services/core/java/com/android/server/power/batterysaver/TEST_MAPPING
@@ -4,7 +4,13 @@
       "name": "CtsLocationCoarseTestCases"
     },
     {
-      "name": "CtsLocationFineTestCases"
+      "name": "CtsLocationFineTestCases",
+      "options": [
+          {
+             // TODO: Wait for test to deflake - b/293934372
+             "exclude-filter":"android.location.cts.fine.ScanningSettingsTest"
+          }
+      ]
     },
     {
       "name": "CtsLocationNoneTestCases"
diff --git a/services/core/java/com/android/server/power/hint/HintManagerService.java b/services/core/java/com/android/server/power/hint/HintManagerService.java
index 1a91d25..33bed3d 100644
--- a/services/core/java/com/android/server/power/hint/HintManagerService.java
+++ b/services/core/java/com/android/server/power/hint/HintManagerService.java
@@ -171,6 +171,8 @@
     public static class NativeWrapper {
         private native void nativeInit();
 
+        private static native long nativeGetHintSessionPreferredRate();
+
         private static native long nativeCreateHintSession(int tgid, int uid, int[] tids,
                 long durationNanos);
 
@@ -190,13 +192,18 @@
 
         private static native void nativeSetThreads(long halPtr, int[] tids);
 
-        private static native long nativeGetHintSessionPreferredRate();
+        private static native void nativeSetMode(long halPtr, int mode, boolean enabled);
 
         /** Wrapper for HintManager.nativeInit */
         public void halInit() {
             nativeInit();
         }
 
+        /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */
+        public long halGetHintSessionPreferredRate() {
+            return nativeGetHintSessionPreferredRate();
+        }
+
         /** Wrapper for HintManager.nativeCreateHintSession */
         public long halCreateHintSession(int tgid, int uid, int[] tids, long durationNanos) {
             return nativeCreateHintSession(tgid, uid, tids, durationNanos);
@@ -234,15 +241,16 @@
             nativeSendHint(halPtr, hint);
         }
 
-        /** Wrapper for HintManager.nativeGetHintSessionPreferredRate */
-        public long halGetHintSessionPreferredRate() {
-            return nativeGetHintSessionPreferredRate();
-        }
-
         /** Wrapper for HintManager.nativeSetThreads */
         public void halSetThreads(long halPtr, int[] tids) {
             nativeSetThreads(halPtr, tids);
         }
+
+        /** Wrapper for HintManager.setMode */
+        public void halSetMode(long halPtr, int mode, boolean enabled) {
+            nativeSetMode(halPtr, mode, enabled);
+        }
+
     }
 
     @VisibleForTesting
@@ -304,7 +312,8 @@
         return mService;
     }
 
-    private boolean checkTidValid(int uid, int tgid, int [] tids) {
+    // returns the first invalid tid or null if not found
+    private Integer checkTidValid(int uid, int tgid, int [] tids) {
         // Make sure all tids belongs to the same UID (including isolated UID),
         // tids can belong to different application processes.
         List<Integer> isolatedPids = null;
@@ -326,19 +335,24 @@
             if (isolatedPids == null) {
                 // To avoid deadlock, do not call into AMS if the call is from system.
                 if (uid == Process.SYSTEM_UID) {
-                    return false;
+                    return threadId;
                 }
                 isolatedPids = mAmInternal.getIsolatedProcesses(uid);
                 if (isolatedPids == null) {
-                    return false;
+                    return threadId;
                 }
             }
             if (isolatedPids.contains(pidOfThreadId)) {
                 continue;
             }
-            return false;
+            return threadId;
         }
-        return true;
+        return null;
+    }
+
+    private String formatTidCheckErrMsg(int callingUid, int[] tids, Integer invalidTid) {
+        return "Tid" + invalidTid + " from list " + Arrays.toString(tids)
+                + " doesn't belong to the calling application" + callingUid;
     }
 
     @VisibleForTesting
@@ -356,8 +370,11 @@
             final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
             final long identity = Binder.clearCallingIdentity();
             try {
-                if (!checkTidValid(callingUid, callingTgid, tids)) {
-                    throw new SecurityException("Some tid doesn't belong to the application");
+                final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids);
+                if (invalidTid != null) {
+                    final String errMsg = formatTidCheckErrMsg(callingUid, tids, invalidTid);
+                    Slogf.w(TAG, errMsg);
+                    throw new SecurityException(errMsg);
                 }
 
                 long halSessionPtr = mNativeWrapper.halCreateHintSession(callingTgid, callingUid,
@@ -543,7 +560,7 @@
                 if (mHalSessionPtr == 0 || !updateHintAllowed()) {
                     return;
                 }
-                Preconditions.checkArgument(hint >= 0, "the hint ID the hint value should be"
+                Preconditions.checkArgument(hint >= 0, "the hint ID value should be"
                         + " greater than zero.");
                 mNativeWrapper.halSendHint(mHalSessionPtr, hint);
             }
@@ -561,8 +578,11 @@
                 final int callingTgid = Process.getThreadGroupLeader(Binder.getCallingPid());
                 final long identity = Binder.clearCallingIdentity();
                 try {
-                    if (!checkTidValid(callingUid, callingTgid, tids)) {
-                        throw new SecurityException("Some tid doesn't belong to the application.");
+                    final Integer invalidTid = checkTidValid(callingUid, callingTgid, tids);
+                    if (invalidTid != null) {
+                        final String errMsg = formatTidCheckErrMsg(callingUid, tids, invalidTid);
+                        Slogf.w(TAG, errMsg);
+                        throw new SecurityException(errMsg);
                     }
                 } finally {
                     Binder.restoreCallingIdentity(identity);
@@ -581,6 +601,18 @@
             return mThreadIds;
         }
 
+        @Override
+        public void setMode(int mode, boolean enabled) {
+            synchronized (mLock) {
+                if (mHalSessionPtr == 0 || !updateHintAllowed()) {
+                    return;
+                }
+                Preconditions.checkArgument(mode >= 0, "the mode Id value should be"
+                        + " greater than zero.");
+                mNativeWrapper.halSetMode(mHalSessionPtr, mode, enabled);
+            }
+        }
+
         private void onProcStateChanged() {
             updateHintAllowed();
         }
diff --git a/services/core/java/com/android/server/power/hint/OWNERS b/services/core/java/com/android/server/power/hint/OWNERS
new file mode 100644
index 0000000..c28c07a
--- /dev/null
+++ b/services/core/java/com/android/server/power/hint/OWNERS
@@ -0,0 +1,2 @@
+include /ADPF_OWNERS
+
diff --git a/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java
index bc39084..187b939 100644
--- a/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java
+++ b/services/core/java/com/android/server/security/rkp/RemoteProvisioningShellCommand.java
@@ -49,7 +49,7 @@
             + "  Show this message.\n"
             + "dump\n"
             + "  Dump service diagnostics.\n"
-            + "list [--min-version MIN_VERSION]\n"
+            + "list\n"
             + "  List the names of the IRemotelyProvisionedComponent instances.\n"
             + "csr [--challenge CHALLENGE] NAME\n"
             + "  Generate and print a base64-encoded CSR from the named\n"
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index f36ecf7..cb09aef 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -333,7 +333,6 @@
                 PackageManager.GET_SERVICES | PackageManager.GET_META_DATA,
                 userId);
         List<TvInputInfo> inputList = new ArrayList<>();
-        List<ComponentName> hardwareComponents = new ArrayList<>();
         for (ResolveInfo ri : services) {
             ServiceInfo si = ri.serviceInfo;
             if (!android.Manifest.permission.BIND_TV_INPUT.equals(si.permission)) {
@@ -344,17 +343,16 @@
 
             ComponentName component = new ComponentName(si.packageName, si.name);
             if (hasHardwarePermission(pm, component)) {
-                hardwareComponents.add(component);
                 ServiceState serviceState = userState.serviceStateMap.get(component);
                 if (serviceState == null) {
                     // New hardware input found. Create a new ServiceState and connect to the
                     // service to populate the hardware list.
                     serviceState = new ServiceState(component, userId);
                     userState.serviceStateMap.put(component, serviceState);
-                    updateServiceConnectionLocked(component, userId);
                 } else {
                     inputList.addAll(serviceState.hardwareInputMap.values());
                 }
+                updateServiceConnectionLocked(component, userId);
             } else {
                 try {
                     TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build();
@@ -417,15 +415,6 @@
             }
         }
 
-        // Clean up ServiceState corresponding to the removed hardware inputs
-        Iterator<ServiceState> it = userState.serviceStateMap.values().iterator();
-        while (it.hasNext()) {
-            ServiceState serviceState = it.next();
-            if (serviceState.isHardware && !hardwareComponents.contains(serviceState.component)) {
-                it.remove();
-            }
-        }
-
         userState.inputMap.clear();
         userState.inputMap = inputMap;
     }
@@ -890,10 +879,13 @@
                 sessionState.session = null;
             }
         }
-        logExternalInputEvent(FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__RELEASED,
-                mCurrentInputId, sessionState);
-        mCurrentInputId = null;
-        mCurrentSessionState = null;
+        if (mCurrentSessionState == sessionState) {
+            // only log when releasing the current on-screen session
+            logExternalInputEvent(FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__RELEASED,
+                    mCurrentInputId, sessionState);
+            mCurrentInputId = null;
+            mCurrentSessionState = null;
+        }
         removeSessionStateLocked(sessionToken, userId);
         return sessionState;
     }
@@ -3057,6 +3049,7 @@
     }
 
     private void logExternalInputEvent(int eventType, String inputId, SessionState sessionState) {
+        // TODO: handle recording sessions
         UserState userState = getOrCreateUserStateLocked(sessionState.userId);
         TvInputState tvInputState = userState.inputMap.get(inputId);
         TvInputInfo tvInputInfo = tvInputState.info;
@@ -3443,15 +3436,22 @@
                 synchronized (mLock) {
                     mTvInputHardwareManager.addHdmiInput(id, inputInfo);
                     addHardwareInputLocked(inputInfo);
-                    // catch the use case when a CEC device is unplugged from
-                    // an HDMI port, then plugged in to the same HDMI port.
-                    if (mCurrentInputId != null && mCurrentSessionState != null
-                            && mCurrentInputId.equals(inputInfo.getParentId())
-                            && inputInfo.getId().equals(mCurrentSessionState.inputId)) {
-                        logExternalInputEvent(
-                                FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
-                                inputInfo.getId(), mCurrentSessionState);
-                        mCurrentInputId = inputInfo.getId();
+                    if (mCurrentInputId != null && mCurrentSessionState != null) {
+                        if (TextUtils.equals(mCurrentInputId, inputInfo.getParentId())) {
+                            // catch the use case when a CEC device is plugged in an HDMI port,
+                            // and TV app does not explicitly call tune() to the added CEC input.
+                            logExternalInputEvent(
+                                    FrameworkStatsLog.EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED,
+                                    inputInfo.getId(), mCurrentSessionState);
+                            mCurrentInputId = inputInfo.getId();
+                        } else if (TextUtils.equals(mCurrentInputId, inputInfo.getId())) {
+                            // catch the use case when a CEC device disconnects itself
+                            // and reconnects to update info.
+                            logExternalInputEvent(
+                                    FrameworkStatsLog
+                                        .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__DEVICE_INFO_UPDATED,
+                                    mCurrentInputId, mCurrentSessionState);
+                        }
                     }
                 }
             } finally {
diff --git a/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
index 9bf046c..3b930f7 100644
--- a/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
+++ b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
@@ -28,6 +28,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
 import android.util.LongArrayQueue;
 import android.util.Slog;
 import android.util.TimeUtils;
@@ -36,7 +37,6 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
 
 import java.util.function.Consumer;
 import java.util.function.Function;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a7849c1..3125518 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -108,7 +108,6 @@
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Display.COLOR_MODE_DEFAULT;
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
@@ -2791,6 +2790,9 @@
             } else if (isEmbedded()) {
                 associateStartingWindowWithTaskIfNeeded();
             }
+            if (mTransitionController.isCollecting()) {
+                mStartingData.mTransitionId = mTransitionController.getCollectingTransitionId();
+            }
         }
     }
 
@@ -4703,26 +4705,11 @@
     }
 
     /**
-     * @return Whether we are allowed to show non-starting windows at the moment. We disallow
-     *         showing windows while the transition animation is playing in case we have windows
-     *         that have wide-color-gamut color mode set to avoid jank in the middle of the
-     *         animation.
+     * @return Whether we are allowed to show non-starting windows at the moment.
      */
     boolean canShowWindows() {
-        final boolean drawn = mTransitionController.isShellTransitionsEnabled()
+        return mTransitionController.isShellTransitionsEnabled()
                 ? mSyncState != SYNC_STATE_WAITING_FOR_DRAW : allDrawn;
-        final boolean animating = mTransitionController.isShellTransitionsEnabled()
-                ? mTransitionController.inPlayingTransition(this)
-                : isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION);
-        return drawn && !(animating && hasNonDefaultColorWindow());
-    }
-
-    /**
-     * @return true if we have a window that has a non-default color mode set; false otherwise.
-     */
-    private boolean hasNonDefaultColorWindow() {
-        return forAllWindows(ws -> ws.mAttrs.getColorMode() != COLOR_MODE_DEFAULT,
-                true /* topToBottom */);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivitySnapshotController.java b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
index 105b2bb..148bf9b 100644
--- a/services/core/java/com/android/server/wm/ActivitySnapshotController.java
+++ b/services/core/java/com/android/server/wm/ActivitySnapshotController.java
@@ -16,16 +16,14 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.SnapshotController.ACTIVITY_CLOSE;
-import static com.android.server.wm.SnapshotController.ACTIVITY_OPEN;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
-import static com.android.server.wm.SnapshotController.TASK_OPEN;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.os.Environment;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -35,7 +33,6 @@
 import com.android.server.LocalServices;
 import com.android.server.pm.UserManagerInternal;
 import com.android.server.wm.BaseAppSnapshotPersister.PersistInfoProvider;
-import com.android.server.wm.SnapshotController.TransitionState;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -62,12 +59,6 @@
     static final String SNAPSHOTS_DIRNAME = "activity_snapshots";
 
     /**
-     * The pending activities which should capture snapshot when process transition finish.
-     */
-    @VisibleForTesting
-    final ArraySet<ActivityRecord> mPendingCaptureActivity = new ArraySet<>();
-
-    /**
      * The pending activities which should remove snapshot from memory when process transition
      * finish.
      */
@@ -86,6 +77,10 @@
     @VisibleForTesting
     final ArraySet<ActivityRecord> mPendingLoadActivity = new ArraySet<>();
 
+    private final ArraySet<ActivityRecord> mOnBackPressedActivities = new ArraySet<>();
+
+    private final ArrayList<ActivityRecord> mTmpBelowActivities = new ArrayList<>();
+    private final ArrayList<WindowContainer> mTmpTransitionParticipants = new ArrayList<>();
     private final SnapshotPersistQueue mSnapshotPersistQueue;
     private final PersistInfoProvider mPersistInfoProvider;
     private final AppSnapshotLoader mSnapshotLoader;
@@ -117,20 +112,6 @@
         setSnapshotEnabled(snapshotEnabled);
     }
 
-    void systemReady() {
-        if (shouldDisableSnapshots()) {
-            return;
-        }
-        mService.mSnapshotController.registerTransitionStateConsumer(
-                ACTIVITY_OPEN, this::handleOpenActivityTransition);
-        mService.mSnapshotController.registerTransitionStateConsumer(
-                ACTIVITY_CLOSE, this::handleCloseActivityTransition);
-        mService.mSnapshotController.registerTransitionStateConsumer(
-                TASK_OPEN, this::handleOpenTaskTransition);
-        mService.mSnapshotController.registerTransitionStateConsumer(
-                TASK_CLOSE, this::handleCloseTaskTransition);
-    }
-
     @Override
     protected float initSnapshotScale() {
         final float config = mService.mContext.getResources().getFloat(
@@ -173,6 +154,7 @@
 
                         @Override
                         void write() {
+                            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "cleanUpUserFiles");
                             final File file = mPersistInfoProvider.getDirectory(userId);
                             if (file.exists()) {
                                 final File[] contents = file.listFiles();
@@ -182,15 +164,30 @@
                                     }
                                 }
                             }
+                            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                         }
                     });
         }
     }
 
+    void addOnBackPressedActivity(ActivityRecord ar) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        mOnBackPressedActivities.add(ar);
+    }
+
+    void clearOnBackPressedActivities() {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        mOnBackPressedActivities.clear();
+    }
+
     /**
-     * Prepare to handle on transition start. Clear all temporary fields.
+     * Prepare to collect any change for snapshots processing. Clear all temporary fields.
      */
-    void preTransitionStart() {
+    void beginSnapshotProcess() {
         if (shouldDisableSnapshots()) {
             return;
         }
@@ -198,18 +195,22 @@
     }
 
     /**
-     * on transition start has notified, start process data.
+     * End collect any change for snapshots processing, start process data.
      */
-    void postTransitionStart() {
+    void endSnapshotProcess() {
         if (shouldDisableSnapshots()) {
             return;
         }
-        onCommitTransition();
+        for (int i = mOnBackPressedActivities.size() - 1; i >= 0; --i) {
+            handleActivityTransition(mOnBackPressedActivities.valueAt(i));
+        }
+        mOnBackPressedActivities.clear();
+        mTmpTransitionParticipants.clear();
+        postProcess();
     }
 
     @VisibleForTesting
     void resetTmpFields() {
-        mPendingCaptureActivity.clear();
         mPendingRemoveActivity.clear();
         mPendingDeleteActivity.clear();
         mPendingLoadActivity.clear();
@@ -218,31 +219,13 @@
     /**
      * Start process all pending activities for a transition.
      */
-    private void onCommitTransition() {
+    private void postProcess() {
         if (DEBUG) {
-            Slog.d(TAG, "ActivitySnapshotController#onCommitTransition result:"
-                    + " capture " + mPendingCaptureActivity
+            Slog.d(TAG, "ActivitySnapshotController#postProcess result:"
                     + " remove " + mPendingRemoveActivity
                     + " delete " + mPendingDeleteActivity
                     + " load " + mPendingLoadActivity);
         }
-        // task snapshots
-        for (int i = mPendingCaptureActivity.size() - 1; i >= 0; i--) {
-            recordSnapshot(mPendingCaptureActivity.valueAt(i));
-        }
-        // clear mTmpRemoveActivity from cache
-        for (int i = mPendingRemoveActivity.size() - 1; i >= 0; i--) {
-            final ActivityRecord ar = mPendingRemoveActivity.valueAt(i);
-            final int code = getSystemHashCode(ar);
-            mCache.onIdRemoved(code);
-        }
-        // clear snapshot on cache and delete files
-        for (int i = mPendingDeleteActivity.size() - 1; i >= 0; i--) {
-            final ActivityRecord ar = mPendingDeleteActivity.valueAt(i);
-            final int code = getSystemHashCode(ar);
-            mCache.onIdRemoved(code);
-            removeIfUserSavedFileExist(code, ar.mUserId);
-        }
         // load snapshot to cache
         for (int i = mPendingLoadActivity.size() - 1; i >= 0; i--) {
             final ActivityRecord ar = mPendingLoadActivity.valueAt(i);
@@ -258,6 +241,8 @@
                             new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) {
                                 @Override
                                 void write() {
+                                    Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
+                                            "load_activity_snapshot");
                                     final TaskSnapshot snapshot = mSnapshotLoader.loadTask(code,
                                             userId, false /* loadLowResolutionBitmap */);
                                     synchronized (mService.getWindowManagerLock()) {
@@ -265,16 +250,36 @@
                                             mCache.putSnapshot(ar, snapshot);
                                         }
                                     }
+                                    Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                                 }
                             });
                 }
             }
         }
+        // clear mTmpRemoveActivity from cache
+        for (int i = mPendingRemoveActivity.size() - 1; i >= 0; i--) {
+            final ActivityRecord ar = mPendingRemoveActivity.valueAt(i);
+            final int code = getSystemHashCode(ar);
+            mCache.onIdRemoved(code);
+        }
+        // clear snapshot on cache and delete files
+        for (int i = mPendingDeleteActivity.size() - 1; i >= 0; i--) {
+            final ActivityRecord ar = mPendingDeleteActivity.valueAt(i);
+            final int code = getSystemHashCode(ar);
+            mCache.onIdRemoved(code);
+            removeIfUserSavedFileExist(code, ar.mUserId);
+        }
         // don't keep any reference
         resetTmpFields();
     }
 
-    private void recordSnapshot(ActivityRecord activity) {
+    void recordSnapshot(ActivityRecord activity) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "ActivitySnapshotController#recordSnapshot " + activity);
+        }
         final TaskSnapshot snapshot = recordSnapshotInner(activity, false /* allowSnapshotHome */);
         if (snapshot != null) {
             final int code = getSystemHashCode(activity);
@@ -285,15 +290,20 @@
     /**
      * Called when the visibility of an app changes outside the regular app transition flow.
      */
-    void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) {
+    void notifyAppVisibilityChanged(ActivityRecord ar, boolean visible) {
         if (shouldDisableSnapshots()) {
             return;
         }
+        final Task task = ar.getTask();
+        if (task == null) {
+            return;
+        }
+        // Doesn't need to capture activity snapshot when it converts from translucent.
         if (!visible) {
             resetTmpFields();
-            addBelowTopActivityIfExist(appWindowToken.getTask(), mPendingRemoveActivity,
+            addBelowActivityIfExist(ar, mPendingRemoveActivity, false,
                     "remove-snapshot");
-            onCommitTransition();
+            postProcess();
         }
     }
 
@@ -301,65 +311,146 @@
         return System.identityHashCode(activity);
     }
 
-    void handleOpenActivityTransition(TransitionState<ActivityRecord> transitionState) {
-        ArraySet<ActivityRecord> participant = transitionState.getParticipant(false /* open */);
-        for (ActivityRecord ar : participant) {
-            mPendingCaptureActivity.add(ar);
-            // remove the snapshot for the one below close
-            final ActivityRecord below = ar.getTask().getActivityBelow(ar);
-            if (below != null) {
-                mPendingRemoveActivity.add(below);
+    @VisibleForTesting
+    void handleTransitionFinish(@NonNull ArrayList<WindowContainer> windows) {
+        mTmpTransitionParticipants.clear();
+        mTmpTransitionParticipants.addAll(windows);
+        for (int i = mTmpTransitionParticipants.size() - 1; i >= 0; --i) {
+            final WindowContainer next = mTmpTransitionParticipants.get(i);
+            if (next.asTask() != null) {
+                handleTaskTransition(next.asTask());
+            } else if (next.asTaskFragment() != null) {
+                final TaskFragment tf = next.asTaskFragment();
+                final ActivityRecord ar = tf.getTopMostActivity();
+                if (ar != null) {
+                    handleActivityTransition(ar);
+                }
+            } else if (next.asActivityRecord() != null) {
+                handleActivityTransition(next.asActivityRecord());
             }
         }
     }
 
-    void handleCloseActivityTransition(TransitionState<ActivityRecord> transitionState) {
-        ArraySet<ActivityRecord> participant = transitionState.getParticipant(true /* open */);
-        for (ActivityRecord ar : participant) {
+    private void handleActivityTransition(@NonNull ActivityRecord ar) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
+        if (ar.isVisibleRequested()) {
             mPendingDeleteActivity.add(ar);
             // load next one if exists.
-            final ActivityRecord below = ar.getTask().getActivityBelow(ar);
-            if (below != null) {
-                mPendingLoadActivity.add(below);
-            }
+            addBelowActivityIfExist(ar, mPendingLoadActivity, true, "load-snapshot");
+        } else {
+            // remove the snapshot for the one below close
+            addBelowActivityIfExist(ar, mPendingRemoveActivity, true, "remove-snapshot");
         }
     }
 
-    void handleCloseTaskTransition(TransitionState<Task> closeTaskTransitionRecord) {
-        ArraySet<Task> participant = closeTaskTransitionRecord.getParticipant(false /* open */);
-        for (Task close : participant) {
-            // this is close task transition
-            // remove the N - 1 from cache
-            addBelowTopActivityIfExist(close, mPendingRemoveActivity, "remove-snapshot");
+    private void handleTaskTransition(Task task) {
+        if (shouldDisableSnapshots()) {
+            return;
         }
-    }
-
-    void handleOpenTaskTransition(TransitionState<Task> openTaskTransitionRecord) {
-        ArraySet<Task> participant = openTaskTransitionRecord.getParticipant(true /* open */);
-        for (Task open : participant) {
-            // this is close task transition
-            // remove the N - 1 from cache
-            addBelowTopActivityIfExist(open, mPendingLoadActivity, "load-snapshot");
+        final ActivityRecord topActivity = task.getTopMostActivity();
+        if (topActivity == null) {
+            return;
+        }
+        if (task.isVisibleRequested()) {
+            // this is open task transition
+            // load the N - 1 to cache
+            addBelowActivityIfExist(topActivity, mPendingLoadActivity, true, "load-snapshot");
             // Move the activities to top of mSavedFilesInOrder, so when purge happen, there
             // will trim the persisted files from the most non-accessed.
-            adjustSavedFileOrder(open);
+            adjustSavedFileOrder(task);
+        } else {
+            // this is close task transition
+            // remove the N - 1 from cache
+            addBelowActivityIfExist(topActivity, mPendingRemoveActivity, true, "remove-snapshot");
         }
     }
 
-    // Add the top -1 activity to a set if it exists.
-    private void addBelowTopActivityIfExist(Task task, ArraySet<ActivityRecord> set,
-            String debugMessage) {
-        final ActivityRecord topActivity = task.getTopMostActivity();
-        if (topActivity != null) {
-            final ActivityRecord below = task.getActivityBelow(topActivity);
-            if (below != null) {
-                set.add(below);
-                if (DEBUG) {
-                    Slog.d(TAG, "ActivitySnapshotController#addBelowTopActivityIfExist "
-                            + below + " from " + debugMessage);
-                }
+    /**
+     * Add the top -1 activity to a set if it exists.
+     * @param inTransition true if the activity must participant in transition.
+     */
+    private void addBelowActivityIfExist(ActivityRecord currentActivity,
+            ArraySet<ActivityRecord> set, boolean inTransition, String debugMessage) {
+        getActivityBelow(currentActivity, inTransition, mTmpBelowActivities);
+        for (int i = mTmpBelowActivities.size() - 1; i >= 0; --i) {
+            set.add(mTmpBelowActivities.get(i));
+            if (DEBUG) {
+                Slog.d(TAG, "ActivitySnapshotController#addBelowTopActivityIfExist "
+                        + mTmpBelowActivities.get(i) + " from " + debugMessage);
             }
         }
+        mTmpBelowActivities.clear();
+    }
+
+    private void getActivityBelow(ActivityRecord currentActivity, boolean inTransition,
+            ArrayList<ActivityRecord> result) {
+        final Task currentTask = currentActivity.getTask();
+        if (currentTask == null) {
+            return;
+        }
+        final ActivityRecord initPrev = currentTask.getActivityBelow(currentActivity);
+        if (initPrev == null) {
+            return;
+        }
+        final TaskFragment currTF = currentActivity.getTaskFragment();
+        final TaskFragment prevTF = initPrev.getTaskFragment();
+        final TaskFragment prevAdjacentTF = prevTF != null
+                ? prevTF.getAdjacentTaskFragment() : null;
+        if (currTF == prevTF && currTF != null || prevAdjacentTF == null) {
+            // Current activity and previous one is in the same task fragment, or
+            // previous activity is not in a task fragment, or
+            // previous activity's task fragment doesn't adjacent to any others.
+            if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
+                result.add(initPrev);
+            }
+            return;
+        }
+
+        if (prevAdjacentTF == currTF) {
+            // previous activity A is adjacent to current activity B.
+            // Try to find anyone below previous activityA, which are C and D if exists.
+            // A | B
+            // C (| D)
+            getActivityBelow(initPrev, inTransition, result);
+        } else {
+            // previous activity C isn't adjacent to current activity A.
+            // A
+            // B | C
+            final Task prevAdjacentTask = prevAdjacentTF.getTask();
+            if (prevAdjacentTask == currentTask) {
+                final int currentIndex = currTF != null
+                        ? currentTask.mChildren.indexOf(currTF)
+                        : currentTask.mChildren.indexOf(currentActivity);
+                final int prevAdjacentIndex =
+                        prevAdjacentTask.mChildren.indexOf(prevAdjacentTF);
+                // prevAdjacentTF already above currentActivity
+                if (prevAdjacentIndex > currentIndex) {
+                    return;
+                }
+            }
+            if (!inTransition || isInParticipant(initPrev, mTmpTransitionParticipants)) {
+                result.add(initPrev);
+            }
+            // prevAdjacentTF is adjacent to another one
+            final ActivityRecord prevAdjacentActivity = prevAdjacentTF.getTopMostActivity();
+            if (prevAdjacentActivity != null && (!inTransition
+                    || isInParticipant(prevAdjacentActivity, mTmpTransitionParticipants))) {
+                result.add(prevAdjacentActivity);
+            }
+        }
+    }
+
+    static boolean isInParticipant(ActivityRecord ar,
+            ArrayList<WindowContainer> transitionParticipants) {
+        for (int i = transitionParticipants.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = transitionParticipants.get(i);
+            if (ar == wc || ar.isDescendantOf(wc)) {
+                return true;
+            }
+        }
+        return false;
     }
 
     private void adjustSavedFileOrder(Task nextTopTask) {
@@ -376,6 +467,9 @@
 
     @Override
     void onAppRemoved(ActivityRecord activity) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
         super.onAppRemoved(activity);
         final int code = getSystemHashCode(activity);
         removeIfUserSavedFileExist(code, activity.mUserId);
@@ -386,6 +480,9 @@
 
     @Override
     void onAppDied(ActivityRecord activity) {
+        if (shouldDisableSnapshots()) {
+            return;
+        }
         super.onAppDied(activity);
         final int code = getSystemHashCode(activity);
         removeIfUserSavedFileExist(code, activity.mUserId);
@@ -440,7 +537,7 @@
     private void removeIfUserSavedFileExist(int code, int userId) {
         final UserSavedFile usf = getUserFiles(userId).get(code);
         if (usf != null) {
-            mUserSavedFiles.remove(code);
+            mUserSavedFiles.get(userId).remove(code);
             mSavedFilesInOrder.remove(usf);
             mPersister.removeSnap(code, userId);
         }
@@ -490,11 +587,13 @@
                     new SnapshotPersistQueue.WriteQueueItem(mPersistInfoProvider) {
                         @Override
                         void write() {
+                            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activity_remove_files");
                             for (int i = files.size() - 1; i >= 0; --i) {
                                 final UserSavedFile usf = files.get(i);
                                 mSnapshotPersistQueue.deleteSnapshot(
                                         usf.mFileId, usf.mUserId, mPersistInfoProvider);
                             }
+                            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
                         }
                     });
         }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 8673b90..6c848d1 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3029,9 +3029,11 @@
         // Set to activity manager directly to make sure the state can be seen by the subsequent
         // update of scheduling group.
         proc.setRunningAnimationUnsafe();
-        mH.removeMessages(H.UPDATE_PROCESS_ANIMATING_STATE, proc);
-        mH.sendMessageDelayed(mH.obtainMessage(H.UPDATE_PROCESS_ANIMATING_STATE, proc),
+        mH.sendMessage(mH.obtainMessage(H.ADD_WAKEFULNESS_ANIMATING_REASON, proc));
+        mH.removeMessages(H.REMOVE_WAKEFULNESS_ANIMATING_REASON, proc);
+        mH.sendMessageDelayed(mH.obtainMessage(H.REMOVE_WAKEFULNESS_ANIMATING_REASON, proc),
                 DOZE_ANIMATING_STATE_RETAIN_TIME_MS);
+        Trace.instant(TRACE_TAG_WINDOW_MANAGER, "requestWakefulnessAnimating");
     }
 
     @Override
@@ -5657,9 +5659,10 @@
 
     final class H extends Handler {
         static final int REPORT_TIME_TRACKER_MSG = 1;
-        static final int UPDATE_PROCESS_ANIMATING_STATE = 2;
         static final int END_POWER_MODE_UNKNOWN_VISIBILITY_MSG = 3;
         static final int RESUME_FG_APP_SWITCH_MSG = 4;
+        static final int ADD_WAKEFULNESS_ANIMATING_REASON = 5;
+        static final int REMOVE_WAKEFULNESS_ANIMATING_REASON = 6;
 
         static final int FIRST_ACTIVITY_TASK_MSG = 100;
         static final int FIRST_SUPERVISOR_TASK_MSG = 200;
@@ -5676,13 +5679,23 @@
                     tracker.deliverResult(mContext);
                 }
                 break;
-                case UPDATE_PROCESS_ANIMATING_STATE: {
+                case ADD_WAKEFULNESS_ANIMATING_REASON: {
                     final WindowProcessController proc = (WindowProcessController) msg.obj;
                     synchronized (mGlobalLock) {
-                        proc.updateRunningRemoteOrRecentsAnimation();
+                        proc.addAnimatingReason(
+                                WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
                     }
                 }
                 break;
+                case REMOVE_WAKEFULNESS_ANIMATING_REASON: {
+                    final WindowProcessController proc = (WindowProcessController) msg.obj;
+                    synchronized (mGlobalLock) {
+                        proc.removeAnimatingReason(
+                                WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
+                    }
+                    Trace.instant(TRACE_TAG_WINDOW_MANAGER, "finishWakefulnessAnimating");
+                }
+                break;
                 case END_POWER_MODE_UNKNOWN_VISIBILITY_MSG: {
                     synchronized (mGlobalLock) {
                         mRetainPowerModeAndTopProcessState = false;
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 4ce21bd..2eceecc 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -33,6 +33,7 @@
 
 import com.android.internal.R;
 
+import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.function.Consumer;
@@ -224,7 +225,15 @@
      * operation directly to avoid waiting until timeout.
      */
     void updateTargetWindows() {
-        if (mTransitionOp == OP_LEGACY || !mIsStartTransactionCommitted) return;
+        if (mTransitionOp == OP_LEGACY) return;
+        if (!mIsStartTransactionCommitted) {
+            if (mTimeoutRunnable == null && !mDisplayContent.hasTopFixedRotationLaunchingApp()
+                    && !mDisplayContent.isRotationChanging() && !mDisplayContent.inTransition()) {
+                Slog.d(TAG, "Cancel for no change");
+                mDisplayContent.finishAsyncRotationIfPossible();
+            }
+            return;
+        }
         for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
             final Operation op = mTargetWindowTokens.valueAt(i);
             if (op.mIsCompletionPending || op.mAction == Operation.ACTION_SEAMLESS) {
@@ -608,6 +617,16 @@
         return op.mAction != Operation.ACTION_SEAMLESS;
     }
 
+    void dump(PrintWriter pw, String prefix) {
+        pw.println(prefix + "AsyncRotationController");
+        prefix += "  ";
+        pw.println(prefix + "mTransitionOp=" + mTransitionOp);
+        pw.println(prefix + "mIsStartTransactionCommitted=" + mIsStartTransactionCommitted);
+        pw.println(prefix + "mIsSyncDrawRequested=" + mIsSyncDrawRequested);
+        pw.println(prefix + "mOriginalRotation=" + mOriginalRotation);
+        pw.println(prefix + "mTargetWindowTokens=" + mTargetWindowTokens);
+    }
+
     /** The operation to control the rotation appearance associated with window token. */
     private static class Operation {
         @Retention(RetentionPolicy.SOURCE)
@@ -635,5 +654,10 @@
         Operation(@Action int action) {
             mAction = action;
         }
+
+        @Override
+        public String toString() {
+            return "Operation{a=" + mAction + " pending=" + mIsCompletionPending + '}';
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 976641b..993c016 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -1177,6 +1177,8 @@
                 if (!composeAnimations(mCloseTarget, mOpenTarget, openActivity)) {
                     return null;
                 }
+                mCloseTarget.mTransitionController.mSnapshotController
+                        .mActivitySnapshotController.clearOnBackPressedActivities();
                 applyPreviewStrategy(mOpenAdaptor, openActivity);
 
                 final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback();
@@ -1222,6 +1224,8 @@
             // Call it again to make sure the activity could be visible while handling the pending
             // animation.
             activity.commitVisibility(true, true);
+            activity.mTransitionController.mSnapshotController
+                    .mActivitySnapshotController.addOnBackPressedActivity(activity);
         }
         activity.mLaunchTaskBehind = true;
 
@@ -1248,6 +1252,9 @@
         // Restore the launch-behind state.
         activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
         activity.mLaunchTaskBehind = false;
+        // Ignore all change
+        activity.mTransitionController.mSnapshotController
+                .mActivitySnapshotController.clearOnBackPressedActivities();
         ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
                 "Setting Activity.mLauncherTaskBehind to false. Activity=%s",
                 activity);
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 2ecbf8a..5aa7c97 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -87,7 +87,7 @@
     private int mLastOrientation = ORIENTATION_UNDEFINED;
 
     ContentRecorder(@NonNull DisplayContent displayContent) {
-        this(displayContent, new RemoteMediaProjectionManagerWrapper());
+        this(displayContent, new RemoteMediaProjectionManagerWrapper(displayContent.mDisplayId));
     }
 
     @VisibleForTesting
@@ -556,8 +556,14 @@
 
     private static final class RemoteMediaProjectionManagerWrapper implements
             MediaProjectionManagerWrapper {
+
+        private final int mDisplayId;
         @Nullable private IMediaProjectionManager mIMediaProjectionManager = null;
 
+        RemoteMediaProjectionManagerWrapper(int displayId) {
+            mDisplayId = displayId;
+        }
+
         @Override
         public void stopActiveProjection() {
             fetchMediaProjectionManager();
@@ -565,12 +571,15 @@
                 return;
             }
             try {
+                ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
+                        "Content Recording: stopping active projection for display %d",
+                        mDisplayId);
                 mIMediaProjectionManager.stopActiveProjection();
             } catch (RemoteException e) {
                 ProtoLog.e(WM_DEBUG_CONTENT_RECORDING,
                         "Content Recording: Unable to tell MediaProjectionManagerService to stop "
-                                + "the active projection: %s",
-                        e);
+                                + "the active projection for display %d: %s",
+                        mDisplayId, e);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index f24ba5a..b589085 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -117,10 +117,11 @@
             }
             incomingDisplayContent.setContentRecordingSession(incomingSession);
             // Updating scenario: Explicitly ask ContentRecorder to update, since no config or
-            // display change will trigger an update from the DisplayContent.
-            if (hasSessionUpdatedWithConsent) {
-                incomingDisplayContent.updateRecording();
-            }
+            // display change will trigger an update from the DisplayContent. There exists a
+            // scenario where a DisplayContent is created, but it's ContentRecordingSession hasn't
+            // been set yet due to a race condition. On creation, updateRecording fails to start
+            // recording, so now this call guarantees recording will be started from somewhere.
+            incomingDisplayContent.updateRecording();
         }
         // Takeover and stopping scenario: stop recording on the pre-existing session.
         if (mSession != null && !hasSessionUpdatedWithConsent) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 614493d..5c82dba 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3453,6 +3453,9 @@
                 if (mFixedRotationLaunchingApp != null) {
                     setSeamlessTransitionForFixedRotation(controller.getCollectingTransition());
                 }
+            } else if (mAsyncRotationController != null && !isRotationChanging()) {
+                Slog.i(TAG, "Finish AsyncRotation for previous intermediate change");
+                finishAsyncRotationIfPossible();
             }
             return;
         }
@@ -3626,6 +3629,9 @@
         if (mFixedRotationLaunchingApp != null) {
             pw.println("  mFixedRotationLaunchingApp=" + mFixedRotationLaunchingApp);
         }
+        if (mAsyncRotationController != null) {
+            mAsyncRotationController.dump(pw, prefix);
+        }
 
         pw.println();
         pw.print(prefix + "mHoldScreenWindow="); pw.print(mHoldScreenWindow);
@@ -6494,6 +6500,13 @@
     }
 
     /**
+     * @return whether the physical display has a fixed orientation and cannot be rotated.
+     */
+    boolean isDisplayOrientationFixed() {
+        return (mDisplayInfo.flags & Display.FLAG_ROTATES_WITH_CONTENT) == 0;
+    }
+
+    /**
      * @return whether AOD is showing on this display
      */
     boolean isAodShowing() {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 70edf3a..9ef25b6 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -434,7 +434,8 @@
         final boolean isTv = mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_LEANBACK);
         mDefaultFixedToUserRotation =
-                (isCar || isTv || mService.mIsPc || mDisplayContent.forceDesktopMode())
+                (isCar || isTv || mService.mIsPc || mDisplayContent.forceDesktopMode()
+                        || mDisplayContent.isDisplayOrientationFixed())
                 // For debug purposes the next line turns this feature off with:
                 // $ adb shell setprop config.override_forced_orient true
                 // $ adb shell wm size reset
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 20595ea..73fdfe0 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -25,6 +25,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.gui.StalledTransactionInfo;
 import android.os.Debug;
 import android.os.IBinder;
 import android.util.Slog;
@@ -96,7 +97,7 @@
     @Override
     public void notifyNoFocusedWindowAnr(@NonNull InputApplicationHandle applicationHandle) {
         TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(
-                timeoutMessage("Application does not have a focused window"));
+                timeoutMessage(OptionalInt.empty(), "Application does not have a focused window"));
         mService.mAnrController.notifyAppUnresponsive(applicationHandle, timeoutRecord);
     }
 
@@ -104,7 +105,7 @@
     public void notifyWindowUnresponsive(@NonNull IBinder token, @NonNull OptionalInt pid,
             String reason) {
         TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
-                timeoutMessage(reason));
+                timeoutMessage(pid, reason));
         mService.mAnrController.notifyWindowUnresponsive(token, pid, timeoutRecord);
     }
 
@@ -354,11 +355,21 @@
         mService.mInputManager.setInputDispatchMode(mInputDispatchEnabled, mInputDispatchFrozen);
     }
 
-    private String timeoutMessage(String reason) {
-        if (reason == null) {
-            return "Input dispatching timed out";
+    private String timeoutMessage(OptionalInt pid, String reason) {
+        String message = (reason == null) ? "Input dispatching timed out."
+                : String.format("Input dispatching timed out (%s).", reason);
+        if (pid.isEmpty()) {
+            return message;
         }
-        return "Input dispatching timed out (" + reason + ")";
+        StalledTransactionInfo stalledTransactionInfo =
+                SurfaceControl.getStalledTransactionInfo(pid.getAsInt());
+        if (stalledTransactionInfo == null) {
+            return message;
+        }
+        return String.format("%s Buffer processing for the associated surface is stuck due to an "
+                + "unsignaled fence (window=%s, bufferId=0x%016X, frameNumber=%s). This "
+                + "potentially indicates a GPU hang.", message, stalledTransactionInfo.layerName,
+                stalledTransactionInfo.bufferId, stalledTransactionInfo.frameNumber);
     }
 
     void dump(PrintWriter pw, String prefix) {
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 3551370..f9fa9e6 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -20,7 +20,6 @@
 import static android.view.SurfaceControl.HIDDEN;
 import static android.window.TaskConstants.TASK_CHILD_LAYER_LETTERBOX_BACKGROUND;
 
-import android.content.Context;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -256,11 +255,11 @@
         private final GestureDetector mDoubleTapDetector;
         private final DoubleTapListener mDoubleTapListener;
 
-        TapEventReceiver(InputChannel inputChannel, Context context) {
+        TapEventReceiver(InputChannel inputChannel, WindowManagerService wmService) {
             super(inputChannel, UiThread.getHandler().getLooper());
-            mDoubleTapListener = new DoubleTapListener();
+            mDoubleTapListener = new DoubleTapListener(wmService);
             mDoubleTapDetector = new GestureDetector(
-                    context, mDoubleTapListener, UiThread.getHandler());
+                    wmService.mContext, mDoubleTapListener, UiThread.getHandler());
         }
 
         @Override
@@ -271,14 +270,24 @@
     }
 
     private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
+        private final WindowManagerService mWmService;
+
+        private DoubleTapListener(WindowManagerService wmService) {
+            mWmService = wmService;
+        }
+
         @Override
         public boolean onDoubleTapEvent(MotionEvent e) {
-            if (e.getAction() == MotionEvent.ACTION_UP) {
-                mDoubleTapCallbackX.accept((int) e.getRawX());
-                mDoubleTapCallbackY.accept((int) e.getRawY());
-                return true;
+            synchronized (mWmService.mGlobalLock) {
+                // This check prevents late events to be handled in case the Letterbox has been
+                // already destroyed and so mOuter.isEmpty() is true.
+                if (!mOuter.isEmpty() && e.getAction() == MotionEvent.ACTION_UP) {
+                    mDoubleTapCallbackX.accept((int) e.getRawX());
+                    mDoubleTapCallbackY.accept((int) e.getRawY());
+                    return true;
+                }
+                return false;
             }
-            return false;
         }
     }
 
@@ -294,7 +303,7 @@
             mWmService = win.mWmService;
             final String name = namePrefix + (win.mActivityRecord != null ? win.mActivityRecord : win);
             mClientChannel = mWmService.mInputManager.createInputChannel(name);
-            mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService.mContext);
+            mInputEventReceiver = new TapEventReceiver(mClientChannel, mWmService);
 
             mToken = mClientChannel.getToken();
 
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 7d3c87a..ba242ec 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -63,6 +63,8 @@
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
 import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
 
@@ -215,6 +217,11 @@
     @Nullable
     private final Boolean mBooleanPropertyAllowForceResizeOverride;
 
+    @Nullable
+    private final Boolean mBooleanPropertyAllowUserAspectRatioOverride;
+    @Nullable
+    private final Boolean mBooleanPropertyAllowUserAspectRatioFullscreenOverride;
+
     /*
      * WindowContainerListener responsible to make translucent activities inherit
      * constraints from the first opaque activity beneath them. It's null for not
@@ -335,6 +342,15 @@
                         /* gatingCondition */ null,
                         PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES);
 
+        mBooleanPropertyAllowUserAspectRatioOverride =
+                readComponentProperty(packageManager, mActivityRecord.packageName,
+                        () -> mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled(),
+                        PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE);
+        mBooleanPropertyAllowUserAspectRatioFullscreenOverride =
+                readComponentProperty(packageManager, mActivityRecord.packageName,
+                        () -> mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled(),
+                        PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
+
         mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
         mIsOverrideToPortraitOrientationEnabled =
                 isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
@@ -1109,7 +1125,8 @@
     }
 
     boolean shouldApplyUserMinAspectRatioOverride() {
-        if (!mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
+        if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+                || !mLetterboxConfiguration.isUserAppAspectRatioSettingsEnabled()
                 || mActivityRecord.mDisplayContent == null
                 || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
             return false;
@@ -1122,7 +1139,9 @@
     }
 
     boolean shouldApplyUserFullscreenOverride() {
-        if (!mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
+        if (FALSE.equals(mBooleanPropertyAllowUserAspectRatioOverride)
+                || FALSE.equals(mBooleanPropertyAllowUserAspectRatioFullscreenOverride)
+                || !mLetterboxConfiguration.isUserAppAspectRatioFullscreenEnabled()
                 || mActivityRecord.mDisplayContent == null
                 || !mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()) {
             return false;
@@ -1151,7 +1170,8 @@
         }
     }
 
-    private int getUserMinAspectRatioOverrideCode() {
+    @VisibleForTesting
+    int getUserMinAspectRatioOverrideCode() {
         try {
             return mActivityRecord.mAtmService.getPackageManager()
                     .getUserMinAspectRatio(mActivityRecord.packageName, mActivityRecord.mUserId);
diff --git a/services/core/java/com/android/server/wm/SnapshotController.java b/services/core/java/com/android/server/wm/SnapshotController.java
index badcfa9..37f9730 100644
--- a/services/core/java/com/android/server/wm/SnapshotController.java
+++ b/services/core/java/com/android/server/wm/SnapshotController.java
@@ -16,185 +16,36 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.TRANSIT_CLOSE;
 import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
-import android.annotation.IntDef;
-import android.util.ArraySet;
-import android.util.Slog;
-import android.util.SparseArray;
+import android.os.Trace;
 import android.view.WindowManager;
 
-import com.android.internal.annotations.VisibleForTesting;
-
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.function.Consumer;
 
 /**
  * Integrates common functionality from TaskSnapshotController and ActivitySnapshotController.
  */
 class SnapshotController {
-    private static final boolean DEBUG = false;
-    private static final String TAG = AbsAppSnapshotController.TAG;
-
-    static final int ACTIVITY_OPEN = 1;
-    static final int ACTIVITY_CLOSE = 2;
-    static final int TASK_OPEN = 4;
-    static final int TASK_CLOSE = 8;
-    /** @hide */
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef(
-            value = {ACTIVITY_OPEN,
-                    ACTIVITY_CLOSE,
-                    TASK_OPEN,
-                    TASK_CLOSE})
-    @interface TransitionStateType {}
-
     private final SnapshotPersistQueue mSnapshotPersistQueue;
     final TaskSnapshotController mTaskSnapshotController;
     final ActivitySnapshotController mActivitySnapshotController;
 
-    private final ArraySet<Task> mTmpCloseTasks = new ArraySet<>();
-    private final ArraySet<Task> mTmpOpenTasks = new ArraySet<>();
-
-    private final SparseArray<TransitionState> mTmpOpenCloseRecord = new SparseArray<>();
-    private final ArraySet<Integer> mTmpAnalysisRecord = new ArraySet<>();
-    private final SparseArray<ArrayList<Consumer<TransitionState>>> mTransitionStateConsumer =
-            new SparseArray<>();
-    private int mActivatedType;
-
-    private final ActivityOrderCheck mActivityOrderCheck = new ActivityOrderCheck();
-    private final ActivityOrderCheck.AnalysisResult mResultHandler = (type, close, open) -> {
-        addTransitionRecord(type, true/* open */, open);
-        addTransitionRecord(type, false/* open */, close);
-    };
-
-    private static class ActivityOrderCheck {
-        private ActivityRecord mOpenActivity;
-        private ActivityRecord mCloseActivity;
-        private int mOpenIndex = -1;
-        private int mCloseIndex = -1;
-
-        private void reset() {
-            mOpenActivity = null;
-            mCloseActivity = null;
-            mOpenIndex = -1;
-            mCloseIndex = -1;
-        }
-
-        private void setTarget(boolean open, ActivityRecord ar, int index) {
-            if (open) {
-                mOpenActivity = ar;
-                mOpenIndex = index;
-            } else {
-                mCloseActivity = ar;
-                mCloseIndex = index;
-            }
-        }
-
-        void analysisOrder(ArraySet<ActivityRecord> closeApps,
-                ArraySet<ActivityRecord> openApps, Task task, AnalysisResult result) {
-            for (int j = closeApps.size() - 1; j >= 0; j--) {
-                final ActivityRecord ar = closeApps.valueAt(j);
-                if (ar.getTask() == task) {
-                    setTarget(false, ar, task.mChildren.indexOf(ar));
-                    break;
-                }
-            }
-            for (int j = openApps.size() - 1; j >= 0; j--) {
-                final ActivityRecord ar = openApps.valueAt(j);
-                if (ar.getTask() == task) {
-                    setTarget(true, ar, task.mChildren.indexOf(ar));
-                    break;
-                }
-            }
-            if (mOpenIndex > mCloseIndex && mCloseIndex != -1) {
-                result.onCheckResult(ACTIVITY_OPEN, mCloseActivity, mOpenActivity);
-            } else if (mOpenIndex < mCloseIndex && mOpenIndex != -1) {
-                result.onCheckResult(ACTIVITY_CLOSE, mCloseActivity, mOpenActivity);
-            }
-            reset();
-        }
-        private interface AnalysisResult {
-            void onCheckResult(@TransitionStateType int type,
-                    ActivityRecord close, ActivityRecord open);
-        }
-    }
-
-    private void addTransitionRecord(int type, boolean open, WindowContainer target) {
-        TransitionState record = mTmpOpenCloseRecord.get(type);
-        if (record == null) {
-            record =  new TransitionState();
-            mTmpOpenCloseRecord.set(type, record);
-        }
-        record.addParticipant(target, open);
-        mTmpAnalysisRecord.add(type);
-    }
-
-    private void clearRecord() {
-        mTmpOpenCloseRecord.clear();
-        mTmpAnalysisRecord.clear();
-    }
-
-    static class TransitionState<TYPE extends WindowContainer> {
-        private final ArraySet<TYPE> mOpenParticipant = new ArraySet<>();
-        private final ArraySet<TYPE> mCloseParticipant = new ArraySet<>();
-
-        void addParticipant(TYPE target, boolean open) {
-            final ArraySet<TYPE> participant = open
-                    ? mOpenParticipant : mCloseParticipant;
-            participant.add(target);
-        }
-
-        ArraySet<TYPE> getParticipant(boolean open) {
-            return open ? mOpenParticipant : mCloseParticipant;
-        }
-    }
-
     SnapshotController(WindowManagerService wms) {
         mSnapshotPersistQueue = new SnapshotPersistQueue();
         mTaskSnapshotController = new TaskSnapshotController(wms, mSnapshotPersistQueue);
         mActivitySnapshotController = new ActivitySnapshotController(wms, mSnapshotPersistQueue);
     }
 
-    void registerTransitionStateConsumer(@TransitionStateType int type,
-            Consumer<TransitionState> consumer) {
-        ArrayList<Consumer<TransitionState>> consumers = mTransitionStateConsumer.get(type);
-        if (consumers == null) {
-            consumers = new ArrayList<>();
-            mTransitionStateConsumer.set(type, consumers);
-        }
-        if (!consumers.contains(consumer)) {
-            consumers.add(consumer);
-        }
-        mActivatedType |= type;
-    }
-
-    void unregisterTransitionStateConsumer(int type, Consumer<TransitionState> consumer) {
-        final ArrayList<Consumer<TransitionState>> consumers = mTransitionStateConsumer.get(type);
-        if (consumers == null) {
-            return;
-        }
-        consumers.remove(consumer);
-        if (consumers.size() == 0) {
-            mActivatedType &= ~type;
-        }
-    }
-
-    private boolean hasTransitionStateConsumer(@TransitionStateType int type) {
-        return (mActivatedType & type) != 0;
-    }
-
     void systemReady() {
         mSnapshotPersistQueue.systemReady();
-        mTaskSnapshotController.systemReady();
-        mActivitySnapshotController.systemReady();
     }
 
     void setPause(boolean paused) {
@@ -212,47 +63,69 @@
     }
 
     void notifyAppVisibilityChanged(ActivityRecord appWindowToken, boolean visible) {
-        if (!visible && hasTransitionStateConsumer(TASK_CLOSE)) {
-            final Task task = appWindowToken.getTask();
-            if (task == null || task.isVisibleRequested()) {
-                return;
+        mActivitySnapshotController.notifyAppVisibilityChanged(appWindowToken, visible);
+    }
+
+    // For legacy transition, which won't support activity snapshot
+    void onTransitionStarting(DisplayContent displayContent) {
+        mTaskSnapshotController.handleClosingApps(displayContent.mClosingApps);
+    }
+
+    // For shell transition, record snapshots before transaction start.
+    void onTransactionReady(@WindowManager.TransitionType int type,
+            ArrayList<Transition.ChangeInfo> changeInfos) {
+        final boolean isTransitionOpen = isTransitionOpen(type);
+        final boolean isTransitionClose = isTransitionClose(type);
+        if (!isTransitionOpen && !isTransitionClose && type < TRANSIT_FIRST_CUSTOM) {
+            return;
+        }
+        for (int i = changeInfos.size() - 1; i >= 0; --i) {
+            Transition.ChangeInfo info = changeInfos.get(i);
+            // Intentionally skip record snapshot for changes originated from PiP.
+            if (info.mWindowingMode == WINDOWING_MODE_PINNED) continue;
+            if (info.mContainer.asTask() != null && !info.mContainer.isVisibleRequested()) {
+                mTaskSnapshotController.recordSnapshot(info.mContainer.asTask(),
+                        false /* allowSnapshotHome */);
             }
-            // close task transition
-            addTransitionRecord(TASK_CLOSE, false /*open*/, task);
-            mActivitySnapshotController.preTransitionStart();
-            notifyTransition(TASK_CLOSE);
-            mActivitySnapshotController.postTransitionStart();
-            clearRecord();
+            // Won't need to capture activity snapshot in close transition.
+            if (isTransitionClose) {
+                continue;
+            }
+            if (info.mContainer.asActivityRecord() != null
+                    || info.mContainer.asTaskFragment() != null) {
+                final TaskFragment tf = info.mContainer.asTaskFragment();
+                final ActivityRecord ar = tf != null ? tf.getTopMostActivity()
+                        : info.mContainer.asActivityRecord();
+                final boolean taskVis = ar != null && ar.getTask().isVisibleRequested();
+                if (ar != null && !ar.isVisibleRequested() && taskVis) {
+                    mActivitySnapshotController.recordSnapshot(ar);
+                }
+            }
         }
     }
 
-    // For legacy transition
-    void onTransitionStarting(DisplayContent displayContent) {
-        handleAppTransition(displayContent.mClosingApps, displayContent.mOpeningApps);
-    }
-
-    // For shell transition, adapt to legacy transition.
-    void onTransitionReady(@WindowManager.TransitionType int type,
-            ArraySet<WindowContainer> participants) {
+    void onTransitionFinish(@WindowManager.TransitionType int type,
+            ArrayList<Transition.ChangeInfo> changeInfos) {
         final boolean isTransitionOpen = isTransitionOpen(type);
         final boolean isTransitionClose = isTransitionClose(type);
         if (!isTransitionOpen && !isTransitionClose && type < TRANSIT_FIRST_CUSTOM
-                || (mActivatedType == 0)) {
+                || (changeInfos.isEmpty())) {
             return;
         }
-        final ArraySet<ActivityRecord> openingApps = new ArraySet<>();
-        final ArraySet<ActivityRecord> closingApps = new ArraySet<>();
-
-        for (int i = participants.size() - 1; i >= 0; --i) {
-            final ActivityRecord ar = participants.valueAt(i).asActivityRecord();
-            if (ar == null || ar.getTask() == null) continue;
-            if (ar.isVisibleRequested()) {
-                openingApps.add(ar);
-            } else {
-                closingApps.add(ar);
+        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "SnapshotController_analysis");
+        mActivitySnapshotController.beginSnapshotProcess();
+        final ArrayList<WindowContainer> windows = new ArrayList<>();
+        for (int i = changeInfos.size() - 1; i >= 0; --i) {
+            final WindowContainer wc = changeInfos.get(i).mContainer;
+            if (wc.asTask() == null && wc.asTaskFragment() == null
+                    && wc.asActivityRecord() == null) {
+                continue;
             }
+            windows.add(wc);
         }
-        handleAppTransition(closingApps, openingApps);
+        mActivitySnapshotController.handleTransitionFinish(windows);
+        mActivitySnapshotController.endSnapshotProcess();
+        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
     private static boolean isTransitionOpen(int type) {
@@ -262,78 +135,6 @@
         return type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK;
     }
 
-    @VisibleForTesting
-    void handleAppTransition(ArraySet<ActivityRecord> closingApps,
-            ArraySet<ActivityRecord> openApps) {
-        if (mActivatedType == 0) {
-            return;
-        }
-        analysisTransition(closingApps, openApps);
-        mActivitySnapshotController.preTransitionStart();
-        for (Integer transitionType : mTmpAnalysisRecord) {
-            notifyTransition(transitionType);
-        }
-        mActivitySnapshotController.postTransitionStart();
-        clearRecord();
-    }
-
-    private void notifyTransition(int transitionType) {
-        final TransitionState record = mTmpOpenCloseRecord.get(transitionType);
-        final ArrayList<Consumer<TransitionState>> consumers =
-                mTransitionStateConsumer.get(transitionType);
-        for (Consumer<TransitionState> consumer : consumers) {
-            consumer.accept(record);
-        }
-    }
-
-    private void analysisTransition(ArraySet<ActivityRecord> closingApps,
-            ArraySet<ActivityRecord> openingApps) {
-        getParticipantTasks(closingApps, mTmpCloseTasks, false /* isOpen */);
-        getParticipantTasks(openingApps, mTmpOpenTasks, true /* isOpen */);
-        if (DEBUG) {
-            Slog.d(TAG, "AppSnapshotController#analysisTransition participants"
-                    + " mTmpCloseTasks " + mTmpCloseTasks
-                    + " mTmpOpenTasks " + mTmpOpenTasks);
-        }
-        for (int i = mTmpCloseTasks.size() - 1; i >= 0; i--) {
-            final Task closeTask = mTmpCloseTasks.valueAt(i);
-            if (mTmpOpenTasks.contains(closeTask)) {
-                if (hasTransitionStateConsumer(ACTIVITY_OPEN)
-                        || hasTransitionStateConsumer(ACTIVITY_CLOSE)) {
-                    mActivityOrderCheck.analysisOrder(closingApps, openingApps, closeTask,
-                            mResultHandler);
-                }
-            } else if (hasTransitionStateConsumer(TASK_CLOSE)) {
-                // close task transition
-                addTransitionRecord(TASK_CLOSE, false /*open*/, closeTask);
-            }
-        }
-        if (hasTransitionStateConsumer(TASK_OPEN)) {
-            for (int i = mTmpOpenTasks.size() - 1; i >= 0; i--) {
-                final Task openTask = mTmpOpenTasks.valueAt(i);
-                if (!mTmpCloseTasks.contains(openTask)) {
-                    // this is open task transition
-                    addTransitionRecord(TASK_OPEN, true /*open*/, openTask);
-                }
-            }
-        }
-        mTmpCloseTasks.clear();
-        mTmpOpenTasks.clear();
-    }
-
-    private void getParticipantTasks(ArraySet<ActivityRecord> activityRecords, ArraySet<Task> tasks,
-            boolean isOpen) {
-        for (int i = activityRecords.size() - 1; i >= 0; i--) {
-            final ActivityRecord activity = activityRecords.valueAt(i);
-            final Task task = activity.getTask();
-            if (task == null) continue;
-
-            if (isOpen == activity.isVisibleRequested()) {
-                tasks.add(task);
-            }
-        }
-    }
-
     void dump(PrintWriter pw, String prefix) {
         mTaskSnapshotController.dump(pw, prefix);
         mActivitySnapshotController.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index 58e1c54..f4f641f 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -17,6 +17,7 @@
 package com.android.server.wm;
 
 import static android.graphics.Bitmap.CompressFormat.JPEG;
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -26,6 +27,7 @@
 import android.graphics.Bitmap;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.window.TaskSnapshot;
@@ -249,6 +251,7 @@
 
         @Override
         void write() {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "StoreWriteQueueItem");
             if (!mPersistInfoProvider.createDirectory(mUserId)) {
                 Slog.e(TAG, "Unable to create snapshot directory for user dir="
                         + mPersistInfoProvider.getDirectory(mUserId));
@@ -263,6 +266,7 @@
             if (failed) {
                 deleteSnapshot(mId, mUserId, mPersistInfoProvider);
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         boolean writeProto() {
@@ -373,7 +377,9 @@
 
         @Override
         void write() {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "DeleteWriteQueueItem");
             deleteSnapshot(mId, mUserId, mPersistInfoProvider);
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
     }
 }
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index 2b22d75..34806bd 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -65,6 +65,9 @@
     /** Whether to prepare the removal animation. */
     boolean mPrepareRemoveAnimation;
 
+    /** Non-zero if this starting window is added in a collecting transition. */
+    int mTransitionId;
+
     protected StartingData(WindowManagerService service, int typeParams) {
         mService = service;
         mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 4e95c84..69eddb9 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -2878,8 +2878,8 @@
             // No need to check if allowed if it's leaving dragResize
             if (dragResizing
                     && !(getRootTask().getWindowingMode() == WINDOWING_MODE_FREEFORM)) {
-                throw new IllegalArgumentException("Drag resize not allow for root task id="
-                        + getRootTaskId());
+                Slog.e(TAG, "Drag resize isn't allowed for root task id=" + getRootTaskId());
+                return;
             }
             mDragResizing = dragResizing;
             resetDragResizingChangeReported();
@@ -5737,7 +5737,7 @@
             }
         }
         ActivityRecord topActivity = getDisplayArea().topRunningActivity();
-        Task topRootTask = topActivity.getRootTask();
+        Task topRootTask = topActivity == null ? null : topActivity.getRootTask();
         if (topRootTask != null && topRootTask != this && topActivity.isState(RESUMED)) {
             // 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.
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 83949cc..d8cc8d3 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -86,7 +86,6 @@
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
 import android.os.IBinder;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.DisplayMetrics;
 import android.util.Slog;
@@ -1325,14 +1324,12 @@
             }
         }
 
-        // Launching this app's activity, make sure the app is no longer
-        // considered stopped.
         try {
             mTaskSupervisor.getActivityMetricsLogger()
                     .notifyBeforePackageUnstopped(next.packageName);
-            mAtmService.getPackageManager().setPackageStoppedState(
-                    next.packageName, false, next.mUserId); /* TODO: Verify if correct userid */
-        } catch (RemoteException e1) {
+            mAtmService.getPackageManagerInternalLocked().notifyComponentUsed(
+                    next.packageName, next.mUserId,
+                    next.packageName); /* TODO: Verify if correct userid */
         } catch (IllegalArgumentException e) {
             Slog.w(TAG, "Failed trying to unstop package "
                     + next.packageName + ": " + e);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index c747c09..4eb4290 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
@@ -77,13 +76,6 @@
         setSnapshotEnabled(snapshotEnabled);
     }
 
-    void systemReady() {
-        if (!shouldDisableSnapshots()) {
-            mService.mSnapshotController.registerTransitionStateConsumer(TASK_CLOSE,
-                    this::handleTaskClose);
-        }
-    }
-
     static PersistInfoProvider createPersistInfoProvider(WindowManagerService service,
             BaseAppSnapshotPersister.DirectoryResolver resolver) {
         final float highResTaskSnapshotScale = service.mContext.getResources().getFloat(
@@ -116,20 +108,23 @@
                 enableLowResSnapshots, lowResScaleFactor, use16BitFormat);
     }
 
-    void handleTaskClose(SnapshotController.TransitionState<Task> closeTaskTransitionRecord) {
+    // Still needed for legacy transition.(AppTransitionControllerTest)
+    void handleClosingApps(ArraySet<ActivityRecord> closingApps) {
         if (shouldDisableSnapshots()) {
             return;
         }
+        // We need to take a snapshot of the task if and only if all activities of the task are
+        // either closing or hidden.
         mTmpTasks.clear();
-        final ArraySet<Task> tasks = closeTaskTransitionRecord.getParticipant(false /* open */);
-        if (mService.mAtmService.getTransitionController().isShellTransitionsEnabled()) {
-            mTmpTasks.addAll(tasks);
-        } else {
-            for (Task task : tasks) {
-                getClosingTasksInner(task, mTmpTasks);
-            }
+        for (int i = closingApps.size() - 1; i >= 0; i--) {
+            final ActivityRecord activity = closingApps.valueAt(i);
+            final Task task = activity.getTask();
+            if (task == null) continue;
+
+            getClosingTasksInner(task, mTmpTasks);
         }
         snapshotTasks(mTmpTasks);
+        mTmpTasks.clear();
         mSkipClosingAppSnapshotTasks.clear();
     }
 
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index cd15119..3e8c017 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+
+import android.os.Trace;
 import android.util.ArraySet;
 import android.window.TaskSnapshot;
 
@@ -102,6 +105,7 @@
 
         @Override
         void write() {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RemoveObsoleteFilesQueueItem");
             final ArraySet<Integer> newPersistedTaskIds;
             synchronized (mLock) {
                 newPersistedTaskIds = new ArraySet<>(mPersistedTaskIdsSinceLastRemoveObsolete);
@@ -120,6 +124,7 @@
                     }
                 }
             }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
 
         @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index aad17aa..1566bb2c 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1191,8 +1191,6 @@
                                         "  Skipping post-transition snapshot for task %d",
                                         task.mTaskId);
                             }
-                            snapController.mActivitySnapshotController
-                                    .notifyAppVisibilityChanged(ar, false /* visible */);
                         }
                         ar.commitVisibility(false /* visible */, false /* performLayout */,
                                 true /* fromTransition */);
@@ -1389,6 +1387,7 @@
         // Handle back animation if it's already started.
         mController.mAtm.mBackNavigationController.onTransitionFinish(mTargets, this);
         mController.mFinishingTransition = null;
+        mController.mSnapshotController.onTransitionFinish(mType, mTargets);
     }
 
     void abort() {
@@ -1593,16 +1592,7 @@
         // transferred. If transition is transient, IME won't be moved during the transition and
         // the tasks are still live, so we take the snapshot at the end of the transition instead.
         if (mTransientLaunches == null) {
-            for (int i = mParticipants.size() - 1; i >= 0; --i) {
-                final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
-                if (ar == null || ar.getTask() == null
-                        || ar.getTask().isVisibleRequested()) continue;
-                final ChangeInfo change = mChanges.get(ar);
-                // Intentionally skip record snapshot for changes originated from PiP.
-                if (change != null && change.mWindowingMode == WINDOWING_MODE_PINNED) continue;
-                mController.mSnapshotController.mTaskSnapshotController.recordSnapshot(
-                        ar.getTask(), false /* allowSnapshotHome */);
-            }
+            mController.mSnapshotController.onTransactionReady(mType, mTargets);
         }
 
         // This is non-null only if display has changes. It handles the visible windows that don't
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
index af8fb02..c59d2d3 100644
--- a/services/core/java/com/android/server/wm/TransitionTracer.java
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -145,6 +145,27 @@
         }
     }
 
+    void logRemovingStartingWindow(@NonNull StartingData startingData) {
+        if (startingData.mTransitionId == 0) {
+            return;
+        }
+        try {
+            final ProtoOutputStream outputStream = new ProtoOutputStream(CHUNK_SIZE);
+            final long protoToken = outputStream
+                    .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITIONS);
+            outputStream.write(com.android.server.wm.shell.Transition.ID,
+                    startingData.mTransitionId);
+            outputStream.write(
+                    com.android.server.wm.shell.Transition.STARTING_WINDOW_REMOVE_TIME_NS,
+                    SystemClock.elapsedRealtimeNanos());
+            outputStream.end(protoToken);
+
+            mTraceBuffer.add(outputStream);
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Unexpected exception thrown while logging transitions", e);
+        }
+    }
+
     private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream,
             Transition transition, ArrayList<ChangeInfo> targets) {
         Trace.beginSection("TransitionTracer#dumpTransitionTargetsToProto");
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 6630e20..c7fd147 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -156,7 +156,7 @@
                 linkFixedRotationTransform(wallpaperTarget.mToken);
             }
         }
-        if (mTransitionController.isShellTransitionsEnabled()) {
+        if (mTransitionController.inTransition(this)) {
             // If wallpaper is in transition, setVisible() will be called from commitVisibility()
             // when finishing transition. Otherwise commitVisibility() is already called from above
             // setVisibility().
diff --git a/services/core/java/com/android/server/wm/WindowContextListenerController.java b/services/core/java/com/android/server/wm/WindowContextListenerController.java
index 626ad15..21f251f 100644
--- a/services/core/java/com/android/server/wm/WindowContextListenerController.java
+++ b/services/core/java/com/android/server/wm/WindowContextListenerController.java
@@ -26,7 +26,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.IWindowToken;
 import android.app.servertransaction.WindowContextInfoChangeItem;
 import android.app.servertransaction.WindowContextWindowRemovalItem;
 import android.content.Context;
@@ -142,7 +141,7 @@
             final WindowContextListenerImpl listener = mListeners.valueAt(i);
             if (listener.getWindowContainer().getDisplayContent().getDisplayId() == displayId
                     && listener.mHasPendingConfiguration) {
-                listener.reportConfigToWindowTokenClient();
+                listener.dispatchWindowContextInfoChange();
             }
         }
     }
@@ -208,7 +207,7 @@
         @NonNull
         private final WindowProcessController mWpc;
         @NonNull
-        private final IWindowToken mClientToken;
+        private final IBinder mClientToken;
         @NonNull
         private WindowContainer<?> mContainer;
         /**
@@ -230,7 +229,7 @@
                 @NonNull IBinder clientToken, @NonNull WindowContainer<?> container,
                 @WindowType int type, @Nullable Bundle options) {
             mWpc = Objects.requireNonNull(wpc);
-            mClientToken = IWindowToken.Stub.asInterface(clientToken);
+            mClientToken = clientToken;
             mContainer = Objects.requireNonNull(container);
             mType = type;
             mOptions = options;
@@ -272,7 +271,7 @@
         }
 
         private void register(boolean shouldDispatchConfig) {
-            final IBinder token = mClientToken.asBinder();
+            final IBinder token = mClientToken;
             if (mDeathRecipient == null) {
                 throw new IllegalStateException("Invalid client token: " + token);
             }
@@ -282,7 +281,7 @@
 
         private void unregister() {
             mContainer.unregisterWindowContainerListener(this);
-            mListeners.remove(mClientToken.asBinder());
+            mListeners.remove(mClientToken);
         }
 
         private void clear() {
@@ -292,17 +291,17 @@
 
         @Override
         public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) {
-            reportConfigToWindowTokenClient();
+            dispatchWindowContextInfoChange();
         }
 
         @Override
         public void onDisplayChanged(DisplayContent dc) {
-            reportConfigToWindowTokenClient();
+            dispatchWindowContextInfoChange();
         }
 
-        private void reportConfigToWindowTokenClient() {
+        private void dispatchWindowContextInfoChange() {
             if (mDeathRecipient == null) {
-                throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder());
+                throw new IllegalStateException("Invalid client token: " + mClientToken);
             }
             final DisplayContent dc = mContainer.getDisplayContent();
             if (!dc.isReady()) {
@@ -332,14 +331,14 @@
             mLastReportedDisplay = displayId;
 
             mWpc.scheduleClientTransactionItem(WindowContextInfoChangeItem.obtain(
-                    mClientToken.asBinder(), config, displayId));
+                    mClientToken, config, displayId));
             mHasPendingConfiguration = false;
         }
 
         @Override
         public void onRemoved() {
             if (mDeathRecipient == null) {
-                throw new IllegalStateException("Invalid client token: " + mClientToken.asBinder());
+                throw new IllegalStateException("Invalid client token: " + mClientToken);
             }
             final WindowToken windowToken = mContainer.asWindowToken();
             if (windowToken != null && windowToken.isFromClient()) {
@@ -357,14 +356,13 @@
                 }
             }
             mDeathRecipient.unlinkToDeath();
-            mWpc.scheduleClientTransactionItem(WindowContextWindowRemovalItem.obtain(
-                    mClientToken.asBinder()));
+            mWpc.scheduleClientTransactionItem(WindowContextWindowRemovalItem.obtain(mClientToken));
             unregister();
         }
 
         @Override
         public String toString() {
-            return "WindowContextListenerImpl{clientToken=" + mClientToken.asBinder() + ", "
+            return "WindowContextListenerImpl{clientToken=" + mClientToken + ", "
                     + "container=" + mContainer + "}";
         }
 
@@ -378,11 +376,11 @@
             }
 
             void linkToDeath() throws RemoteException {
-                mClientToken.asBinder().linkToDeath(this, 0);
+                mClientToken.linkToDeath(this, 0);
             }
 
             void unlinkToDeath() {
-                mClientToken.asBinder().unlinkToDeath(this, 0);
+                mClientToken.unlinkToDeath(this, 0);
             }
         }
     }
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a84749a..6d7e297 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -309,6 +309,14 @@
                                 applyTransaction(wct, -1 /* syncId */, nextTransition, caller,
                                         deferred);
                                 if (needsSetReady) {
+                                    // TODO(b/294925498): Remove this once we have accurate ready
+                                    //                    tracking.
+                                    if (hasActivityLaunch(wct) && !mService.mRootWindowContainer
+                                            .allPausedActivitiesComplete()) {
+                                        // WCT is launching an activity, so we need to wait for its
+                                        // lifecycle events.
+                                        return;
+                                    }
                                     nextTransition.setAllReady();
                                 }
                             });
@@ -344,6 +352,15 @@
         }
     }
 
+    private static boolean hasActivityLaunch(WindowContainerTransaction wct) {
+        for (int i = 0; i < wct.getHierarchyOps().size(); ++i) {
+            if (wct.getHierarchyOps().get(i).getType() == HIERARCHY_OP_TYPE_LAUNCH_TASK) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @Override
     public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
             @NonNull IWindowContainerTransactionCallback callback,
@@ -382,18 +399,13 @@
     }
 
     @Override
-    public int finishTransition(@NonNull IBinder transitionToken,
-            @Nullable WindowContainerTransaction t,
-            @Nullable IWindowContainerTransactionCallback callback) {
+    public void finishTransition(@NonNull IBinder transitionToken,
+            @Nullable WindowContainerTransaction t) {
         enforceTaskPermission("finishTransition()");
         final CallerInfo caller = new CallerInfo();
         final long ident = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                int syncId = -1;
-                if (t != null && callback != null) {
-                    syncId = startSyncWithOrganizer(callback);
-                }
                 final Transition transition = Transition.fromBinder(transitionToken);
                 // apply the incoming transaction before finish in case it alters the visibility
                 // of the participants.
@@ -402,14 +414,10 @@
                     // changes of the transition participants will only set visible-requested
                     // and still let finishTransition handle the participants.
                     mTransitionController.mFinishingTransition = transition;
-                    applyTransaction(t, syncId, null /*transition*/, caller, transition);
+                    applyTransaction(t, -1 /* syncId */, null /*transition*/, caller, transition);
                 }
                 mTransitionController.finishTransition(transition);
                 mTransitionController.mFinishingTransition = null;
-                if (syncId >= 0) {
-                    setSyncReady(syncId);
-                }
-                return syncId;
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -1649,9 +1657,18 @@
     }
 
     private int setAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
-        final TaskFragment root1 = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment();
-        final TaskFragment root2 =
-                WindowContainer.fromBinder(hop.getAdjacentRoot()).asTaskFragment();
+        final WindowContainer wc1 = WindowContainer.fromBinder(hop.getContainer());
+        if (wc1 == null || !wc1.isAttached()) {
+            Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc1);
+            return TRANSACT_EFFECTS_NONE;
+        }
+        final TaskFragment root1 = wc1.asTaskFragment();
+        final WindowContainer wc2 = WindowContainer.fromBinder(hop.getAdjacentRoot());
+        if (wc2 == null || !wc2.isAttached()) {
+            Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc2);
+            return TRANSACT_EFFECTS_NONE;
+        }
+        final TaskFragment root2 = wc2.asTaskFragment();
         if (!root1.mCreatedByOrganizer || !root2.mCreatedByOrganizer) {
             throw new IllegalArgumentException("setAdjacentRootsHierarchyOp: Not created by"
                     + " organizer root1=" + root1 + " root2=" + root2);
@@ -1664,7 +1681,12 @@
     }
 
     private int clearAdjacentRootsHierarchyOp(WindowContainerTransaction.HierarchyOp hop) {
-        final TaskFragment root = WindowContainer.fromBinder(hop.getContainer()).asTaskFragment();
+        final WindowContainer wc = WindowContainer.fromBinder(hop.getContainer());
+        if (wc == null || !wc.isAttached()) {
+            Slog.e(TAG, "Attempt to operate on unknown or detached container: " + wc);
+            return TRANSACT_EFFECTS_NONE;
+        }
+        final TaskFragment root = wc.asTaskFragment();
         if (!root.mCreatedByOrganizer) {
             throw new IllegalArgumentException("clearAdjacentRootsHierarchyOp: Not created by"
                     + " organizer root=" + root);
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 83e8646..e769a27 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -48,6 +48,7 @@
 import static java.util.Objects.requireNonNull;
 
 import android.Manifest;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -76,7 +77,6 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
-import android.view.IRemoteAnimationRunner;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -88,6 +88,8 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -250,11 +252,30 @@
     @Nullable
     private ArrayMap<ActivityRecord, int[]> mRemoteActivities;
 
-    /** Whether our process is currently running a {@link RecentsAnimation} */
-    private boolean mRunningRecentsAnimation;
+    /**
+     * It can be set for a running transition player ({@link android.window.ITransitionPlayer}) or
+     * remote animators (running {@link android.window.IRemoteTransition}).
+     */
+    static final int ANIMATING_REASON_REMOTE_ANIMATION = 1;
+    /** It is set for wakefulness transition. */
+    static final int ANIMATING_REASON_WAKEFULNESS_CHANGE = 1 << 1;
+    /** Whether the legacy {@link RecentsAnimation} is running. */
+    static final int ANIMATING_REASON_LEGACY_RECENT_ANIMATION = 1 << 2;
 
-    /** Whether our process is currently running a {@link IRemoteAnimationRunner} */
-    private boolean mRunningRemoteAnimation;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            ANIMATING_REASON_REMOTE_ANIMATION,
+            ANIMATING_REASON_WAKEFULNESS_CHANGE,
+            ANIMATING_REASON_LEGACY_RECENT_ANIMATION,
+    })
+    @interface AnimatingReason {}
+
+    /**
+     * Non-zero if this process is currently running an important animation. This should be never
+     * set for system server.
+     */
+    @AnimatingReason
+    private int mAnimatingReasons;
 
     // The bits used for mActivityStateFlags.
     private static final int ACTIVITY_STATE_FLAG_IS_VISIBLE = 1 << 16;
@@ -1869,30 +1890,45 @@
     }
 
     void setRunningRecentsAnimation(boolean running) {
-        if (mRunningRecentsAnimation == running) {
-            return;
+        if (running) {
+            addAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
+        } else {
+            removeAnimatingReason(ANIMATING_REASON_LEGACY_RECENT_ANIMATION);
         }
-        mRunningRecentsAnimation = running;
-        updateRunningRemoteOrRecentsAnimation();
     }
 
     void setRunningRemoteAnimation(boolean running) {
-        if (mRunningRemoteAnimation == running) {
-            return;
+        if (running) {
+            addAnimatingReason(ANIMATING_REASON_REMOTE_ANIMATION);
+        } else {
+            removeAnimatingReason(ANIMATING_REASON_REMOTE_ANIMATION);
         }
-        mRunningRemoteAnimation = running;
-        updateRunningRemoteOrRecentsAnimation();
     }
 
-    void updateRunningRemoteOrRecentsAnimation() {
+    void addAnimatingReason(@AnimatingReason int reason) {
+        final int prevReasons = mAnimatingReasons;
+        mAnimatingReasons |= reason;
+        if (prevReasons == 0) {
+            setAnimating(true);
+        }
+    }
+
+    void removeAnimatingReason(@AnimatingReason int reason) {
+        final int prevReasons = mAnimatingReasons;
+        mAnimatingReasons &= ~reason;
+        if (prevReasons != 0 && mAnimatingReasons == 0) {
+            setAnimating(false);
+        }
+    }
+
+    /** Applies the animating state to activity manager for updating process priority. */
+    private void setAnimating(boolean animating) {
         // Posting on handler so WM lock isn't held when we call into AM.
-        mAtm.mH.sendMessage(PooledLambda.obtainMessage(
-                WindowProcessListener::setRunningRemoteAnimation, mListener,
-                isRunningRemoteTransition()));
+        mAtm.mH.post(() -> mListener.setRunningRemoteAnimation(animating));
     }
 
     boolean isRunningRemoteTransition() {
-        return mRunningRecentsAnimation || mRunningRemoteAnimation;
+        return (mAnimatingReasons & ANIMATING_REASON_REMOTE_ANIMATION) != 0;
     }
 
     /** Adjusts scheduling group for animation. This method MUST NOT be called inside WM lock. */
@@ -1946,6 +1982,21 @@
         pw.println(prefix + " mLastReportedConfiguration=" + (mHasCachedConfiguration
                 ? ("(cached) " + mLastReportedConfiguration) : mLastReportedConfiguration));
 
+        final int animatingReasons = mAnimatingReasons;
+        if (animatingReasons != 0) {
+            pw.print(prefix + " mAnimatingReasons=");
+            if ((animatingReasons & ANIMATING_REASON_REMOTE_ANIMATION) != 0) {
+                pw.print("remote-animation|");
+            }
+            if ((animatingReasons & ANIMATING_REASON_WAKEFULNESS_CHANGE) != 0) {
+                pw.print("wakefulness|");
+            }
+            if ((animatingReasons & ANIMATING_REASON_LEGACY_RECENT_ANIMATION) != 0) {
+                pw.print("legacy-recents");
+            }
+            pw.println();
+        }
+
         final int stateFlags = mActivityStateFlags;
         if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
             pw.print(prefix + " mActivityStateFlags=");
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 029f46f..5a45fe1 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2334,6 +2334,8 @@
             mDisplayContent.updateImeControlTarget(isImeLayeringTarget() /* updateImeParent */);
             // Fix the starting window to task when Activity has changed.
             if (mStartingData != null && mStartingData.mAssociatedTask == null
+                    && mTempConfiguration.windowConfiguration.getRotation()
+                            == selfConfiguration.windowConfiguration.getRotation()
                     && !mTempConfiguration.windowConfiguration.getBounds().equals(getBounds())) {
                 mStartingData.mResizedFromTransfer = true;
                 // Lock the starting window to task, so it won't resize from transfer anymore.
@@ -2410,7 +2412,7 @@
         ProtoLog.v(WM_DEBUG_ADD_REMOVE,
                 "removeIfPossible: %s callers=%s", this, Debug.getCallers(5));
 
-        final boolean startingWindow = mAttrs.type == TYPE_APPLICATION_STARTING;
+        final boolean startingWindow = mStartingData != null;
         if (startingWindow) {
             ProtoLog.d(WM_DEBUG_STARTING_WINDOW, "Starting window removed %s", this);
             // Cancel the remove starting window animation on shell. The main window might changed
@@ -2424,6 +2426,7 @@
                     return false;
                 }, true);
             }
+            mTransitionController.mTransitionTracer.logRemovingStartingWindow(mStartingData);
         } else if (mAttrs.type == TYPE_BASE_APPLICATION
                 && isSelfAnimating(0, ANIMATION_TYPE_STARTING_REVEAL)) {
             // Cancel the remove starting window animation in case the binder dead before remove
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 101af4d..405b133 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -94,7 +94,10 @@
 
 cc_defaults {
     name: "libservices.core-libs",
-    defaults: ["android.hardware.graphics.common-ndk_shared"],
+    defaults: [
+        "android.hardware.graphics.common-ndk_shared",
+        "android.hardware.power-ndk_shared",
+    ],
     shared_libs: [
         "libadb_pairing_server",
         "libadb_pairing_connection",
@@ -177,7 +180,6 @@
         "android.hardware.power@1.1",
         "android.hardware.power@1.2",
         "android.hardware.power@1.3",
-        "android.hardware.power-V4-ndk",
         "android.hardware.power.stats@1.0",
         "android.hardware.power.stats-V1-ndk",
         "android.hardware.thermal@1.0",
diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index 4343edd..cfc63f0 100644
--- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -233,6 +233,7 @@
 // process_madvise on failure
 int madviseVmasFromBatch(unique_fd& pidfd, VmaBatch& batch, int madviseType,
                          uint64_t* outBytesProcessed) {
+    static const size_t kPageSize = getpagesize();
     if (batch.totalVmas == 0 || batch.totalBytes == 0) {
         // No VMAs in Batch, skip.
         *outBytesProcessed = 0;
@@ -258,7 +259,7 @@
     } else if (bytesProcessedInSend < batch.totalBytes) {
         // Partially processed the bytes requested
         // skip last page which is where it failed.
-        bytesProcessedInSend += PAGE_SIZE;
+        bytesProcessedInSend += kPageSize;
     }
     bytesProcessedInSend = consumeBytes(batch, bytesProcessedInSend);
 
diff --git a/services/core/jni/com_android_server_companion_virtual_InputController.cpp b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
index ad098b7..4cd018b 100644
--- a/services/core/jni/com_android_server_companion_virtual_InputController.cpp
+++ b/services/core/jni/com_android_server_companion_virtual_InputController.cpp
@@ -144,12 +144,20 @@
             }
             uinput_abs_setup slotAbsSetup;
             slotAbsSetup.code = ABS_MT_SLOT;
-            slotAbsSetup.absinfo.maximum = MAX_POINTERS;
+            slotAbsSetup.absinfo.maximum = MAX_POINTERS - 1;
             slotAbsSetup.absinfo.minimum = 0;
             if (ioctl(fd, UI_ABS_SETUP, &slotAbsSetup) != 0) {
                 ALOGE("Error creating touchscreen uinput slots: %s", strerror(errno));
                 return invalidFd();
             }
+            uinput_abs_setup trackingIdAbsSetup;
+            trackingIdAbsSetup.code = ABS_MT_TRACKING_ID;
+            trackingIdAbsSetup.absinfo.maximum = MAX_POINTERS - 1;
+            trackingIdAbsSetup.absinfo.minimum = 0;
+            if (ioctl(fd, UI_ABS_SETUP, &trackingIdAbsSetup) != 0) {
+                ALOGE("Error creating touchscreen uinput tracking ids: %s", strerror(errno));
+                return invalidFd();
+            }
         }
         if (ioctl(fd, UI_DEV_SETUP, &setup) != 0) {
             ALOGE("Error creating uinput device: %s", strerror(errno));
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index e148b94..7edf445 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -31,6 +31,7 @@
 
 using aidl::android::hardware::power::IPowerHintSession;
 using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::SessionMode;
 using aidl::android::hardware::power::WorkDuration;
 
 using android::base::StringPrintf;
@@ -41,6 +42,15 @@
 static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
 static std::mutex gSessionMapLock;
 
+static int64_t getHintSessionPreferredRate() {
+    int64_t rate = -1;
+    auto result = gPowerHalController.getHintSessionPreferredRate();
+    if (result.isOk()) {
+        rate = result.value();
+    }
+    return rate;
+}
+
 static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid,
                                std::vector<int32_t> threadIds, int64_t durationNanos) {
     auto result = gPowerHalController.createHintSession(tgid, uid, threadIds, durationNanos);
@@ -93,13 +103,9 @@
     appSession->setThreads(threadIds);
 }
 
-static int64_t getHintSessionPreferredRate() {
-    int64_t rate = -1;
-    auto result = gPowerHalController.getHintSessionPreferredRate();
-    if (result.isOk()) {
-        rate = result.value();
-    }
-    return rate;
+static void setMode(int64_t session_ptr, SessionMode mode, bool enabled) {
+    auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+    appSession->setMode(mode, enabled);
 }
 
 // ----------------------------------------------------------------------------
@@ -107,6 +113,10 @@
     gPowerHalController.init();
 }
 
+static jlong nativeGetHintSessionPreferredRate(JNIEnv* /* env */, jclass /* clazz */) {
+    return static_cast<jlong>(getHintSessionPreferredRate());
+}
+
 static jlong nativeCreateHintSession(JNIEnv* env, jclass /* clazz */, jint tgid, jint uid,
                                      jintArray tids, jlong durationNanos) {
     ScopedIntArrayRO tidArray(env, tids);
@@ -165,14 +175,16 @@
     setThreads(session_ptr, threadIds);
 }
 
-static jlong nativeGetHintSessionPreferredRate(JNIEnv* /* env */, jclass /* clazz */) {
-    return static_cast<jlong>(getHintSessionPreferredRate());
+static void nativeSetMode(JNIEnv* env, jclass /* clazz */, jlong session_ptr, jint mode,
+                          jboolean enabled) {
+    setMode(session_ptr, static_cast<SessionMode>(mode), enabled);
 }
 
 // ----------------------------------------------------------------------------
 static const JNINativeMethod sHintManagerServiceMethods[] = {
         /* name, signature, funcPtr */
         {"nativeInit", "()V", (void*)nativeInit},
+        {"nativeGetHintSessionPreferredRate", "()J", (void*)nativeGetHintSessionPreferredRate},
         {"nativeCreateHintSession", "(II[IJ)J", (void*)nativeCreateHintSession},
         {"nativePauseHintSession", "(J)V", (void*)nativePauseHintSession},
         {"nativeResumeHintSession", "(J)V", (void*)nativeResumeHintSession},
@@ -181,7 +193,7 @@
         {"nativeReportActualWorkDuration", "(J[J[J)V", (void*)nativeReportActualWorkDuration},
         {"nativeSendHint", "(JI)V", (void*)nativeSendHint},
         {"nativeSetThreads", "(J[I)V", (void*)nativeSetThreads},
-        {"nativeGetHintSessionPreferredRate", "()J", (void*)nativeGetHintSessionPreferredRate},
+        {"nativeSetMode", "(JIZ)V", (void*)nativeSetMode},
 };
 
 int register_android_server_HintManagerService(JNIEnv* env) {
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index e1de05c..11c40d7 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -69,7 +69,6 @@
 
 static jclass class_gnssPowerStats;
 
-static jmethodID method_reportNiNotification;
 static jmethodID method_reportGnssPowerStats;
 static jmethodID method_reportNfwNotification;
 static jmethodID method_isInEmergencySession;
@@ -92,8 +91,6 @@
 using android::hardware::gnss::V1_0::GnssLocationFlags;
 using android::hardware::gnss::V1_0::IGnssNavigationMessage;
 using android::hardware::gnss::V1_0::IGnssNavigationMessageCallback;
-using android::hardware::gnss::V1_0::IGnssNi;
-using android::hardware::gnss::V1_0::IGnssNiCallback;
 using android::hardware::gnss::V1_0::IGnssXtra;
 using android::hardware::gnss::V1_0::IGnssXtraCallback;
 using android::hardware::gnss::V2_0::ElapsedRealtimeFlags;
@@ -127,7 +124,6 @@
 using GnssLocationAidl = android::hardware::gnss::GnssLocation;
 using IGnssAntennaInfoAidl = android::hardware::gnss::IGnssAntennaInfo;
 
-sp<IGnssNi> gnssNiIface = nullptr;
 sp<IGnssPowerIndication> gnssPowerIndicationIface = nullptr;
 
 std::unique_ptr<android::gnss::GnssHal> gnssHal = nullptr;
@@ -195,42 +191,6 @@
     return Status::ok();
 }
 
-/*
- * GnssNiCallback implements callback methods required by the IGnssNi interface.
- */
-struct GnssNiCallback : public IGnssNiCallback {
-    Return<void> niNotifyCb(const IGnssNiCallback::GnssNiNotification& notification)
-            override;
-};
-
-Return<void> GnssNiCallback::niNotifyCb(
-        const IGnssNiCallback::GnssNiNotification& notification) {
-    JNIEnv* env = getJniEnv();
-    jstring requestorId = env->NewStringUTF(notification.requestorId.c_str());
-    jstring text = env->NewStringUTF(notification.notificationMessage.c_str());
-
-    if (requestorId && text) {
-        env->CallVoidMethod(mCallbacksObj, method_reportNiNotification,
-                            notification.notificationId, notification.niType,
-                            notification.notifyFlags, notification.timeoutSec,
-                            notification.defaultResponse, requestorId, text,
-                            notification.requestorIdEncoding,
-                            notification.notificationIdEncoding);
-    } else {
-        ALOGE("%s: OOM Error\n", __func__);
-    }
-
-    if (requestorId) {
-        env->DeleteLocalRef(requestorId);
-    }
-
-    if (text) {
-        env->DeleteLocalRef(text);
-    }
-    checkAndClearExceptionFromCallback(env, __FUNCTION__);
-    return Void();
-}
-
 /* Initializes the GNSS service handle. */
 static void android_location_gnss_hal_GnssNative_set_gps_service_handle() {
     gnssHal = std::make_unique<gnss::GnssHal>();
@@ -242,10 +202,6 @@
     android_location_gnss_hal_GnssNative_set_gps_service_handle();
 
     // Cache methodIDs and class IDs.
-
-    method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification",
-            "(IIIIILjava/lang/String;Ljava/lang/String;II)V");
-
     method_reportNfwNotification = env->GetMethodID(clazz, "reportNfwNotification",
             "(Ljava/lang/String;BLjava/lang/String;BLjava/lang/String;BZZ)V");
     method_reportGnssPowerStats =
@@ -305,7 +261,6 @@
     gnssAntennaInfoIface = gnssHal->getGnssAntennaInfoInterface();
     gnssMeasurementCorrectionsIface = gnssHal->getMeasurementCorrectionsInterface();
     gnssDebugIface = gnssHal->getGnssDebugInterface();
-    gnssNiIface = gnssHal->getGnssNiInterface();
     gnssConfigurationIface = gnssHal->getGnssConfigurationInterface();
     gnssGeofencingIface = gnssHal->getGnssGeofenceInterface();
     gnssBatchingIface = gnssHal->getGnssBatchingInterface();
@@ -376,15 +331,6 @@
         ALOGI("Unable to initialize IGnssGeofencing interface.");
     }
 
-    // Set IGnssNi.hal callback.
-    sp<IGnssNiCallback> gnssNiCbIface = new GnssNiCallback();
-    if (gnssNiIface != nullptr) {
-        auto status = gnssNiIface->setCallback(gnssNiCbIface);
-        checkHidlReturn(status, "IGnssNi setCallback() failed.");
-    } else {
-        ALOGI("Unable to initialize IGnssNi interface.");
-    }
-
     // Set IAGnssRil callback.
     if (agnssRilIface == nullptr ||
         !agnssRilIface->setCallback(std::make_unique<gnss::AGnssRilCallback>())) {
@@ -592,18 +538,6 @@
     }
 }
 
-static void android_location_gnss_hal_GnssNative_send_ni_response(JNIEnv* /* env */, jclass,
-                                                                  jint notifId, jint response) {
-    if (gnssNiIface == nullptr) {
-        ALOGE("%s: IGnssNi interface not available.", __func__);
-        return;
-    }
-
-    auto result = gnssNiIface->respond(notifId,
-            static_cast<IGnssNiCallback::GnssUserResponseType>(response));
-    checkHidlReturn(result, "IGnssNi respond() failed.");
-}
-
 static jstring android_location_gnss_hal_GnssNative_get_internal_state(JNIEnv* env, jclass) {
     /*
      * TODO: Create a jobject to represent GnssDebug.
@@ -987,8 +921,6 @@
          reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_set_agps_server)},
         {"native_inject_ni_supl_message_data", "([BII)V",
          reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_inject_ni_supl_message_data)},
-        {"native_send_ni_response", "(II)V",
-         reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_send_ni_response)},
         {"native_get_internal_state", "()Ljava/lang/String;",
          reinterpret_cast<void*>(android_location_gnss_hal_GnssNative_get_internal_state)},
         {"native_is_gnss_visibility_control_supported", "()Z",
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index caa4343..57fa12d 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -354,8 +354,6 @@
             "com.android.server.contentcapture.ContentCaptureManagerService";
     private static final String TRANSLATION_MANAGER_SERVICE_CLASS =
             "com.android.server.translation.TranslationManagerService";
-    private static final String SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS =
-            "com.android.server.selectiontoolbar.SelectionToolbarManagerService";
     private static final String MUSIC_RECOGNITION_MANAGER_SERVICE_CLASS =
             "com.android.server.musicrecognition.MusicRecognitionManagerService";
     private static final String AMBIENT_CONTEXT_MANAGER_SERVICE_CLASS =
@@ -2738,13 +2736,6 @@
             Slog.d(TAG, "TranslationService not defined by OEM");
         }
 
-        if (!isTv) {
-            // Selection toolbar service
-            t.traceBegin("StartSelectionToolbarManagerService");
-            mSystemServiceManager.startService(SELECTION_TOOLBAR_MANAGER_SERVICE_CLASS);
-            t.traceEnd();
-        }
-
         // NOTE: ClipboardService depends on ContentCapture and Autofill
         t.traceBegin("StartClipboardService");
         mSystemServiceManager.startService(ClipboardService.class);
diff --git a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
index b620407..f5360eb 100644
--- a/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
+++ b/services/people/java/com/android/server/people/prediction/ShareTargetPredictor.java
@@ -30,6 +30,7 @@
 import android.app.prediction.AppTarget;
 import android.app.prediction.AppTargetEvent;
 import android.app.prediction.AppTargetId;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentFilter;
 import android.content.pm.ShortcutInfo;
@@ -39,6 +40,7 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ChooserActivity;
 import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
@@ -63,6 +65,7 @@
     private static final String REMOTE_APP_PREDICTOR_KEY = "remote_app_predictor";
     private final IntentFilter mIntentFilter;
     private final AppPredictor mRemoteAppPredictor;
+    @Nullable private final String mChooserActivity;
 
     ShareTargetPredictor(@NonNull AppPredictionContext predictionContext,
             @NonNull Consumer<List<AppTarget>> updatePredictionsMethod,
@@ -81,6 +84,9 @@
         } else {
             mRemoteAppPredictor = null;
         }
+        ComponentName component = ComponentName.unflattenFromString(
+                context.getResources().getString(R.string.config_chooserActivity));
+        mChooserActivity = (component == null) ? null : component.getShortClassName();
     }
 
     /** Reports chosen history of direct/app share targets. */
@@ -138,7 +144,7 @@
         SharesheetModelScorer.computeScoreForAppShare(shareTargets,
                 getShareEventType(mIntentFilter), getPredictionContext().getPredictedTargetCount(),
                 System.currentTimeMillis(), getDataManager(),
-                mCallingUserId);
+                mCallingUserId, mChooserActivity);
         Collections.sort(shareTargets, (t1, t2) -> -Float.compare(t1.getScore(), t2.getScore()));
         List<AppTarget> appTargetList = new ArrayList<>();
         for (ShareTarget shareTarget : shareTargets) {
diff --git a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
index c77843c..b2f1e21 100644
--- a/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
+++ b/services/people/java/com/android/server/people/prediction/SharesheetModelScorer.java
@@ -26,7 +26,6 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.app.ChooserActivity;
 import com.android.server.people.data.AppUsageStatsData;
 import com.android.server.people.data.DataManager;
 import com.android.server.people.data.Event;
@@ -55,8 +54,6 @@
     private static final float FREQUENTLY_USED_APP_SCORE_INITIAL_DECAY = 0.3F;
     @VisibleForTesting
     static final float FOREGROUND_APP_WEIGHT = 0F;
-    @VisibleForTesting
-    static final String CHOOSER_ACTIVITY = ChooserActivity.class.getSimpleName();
 
     // Keep constructor private to avoid class being instantiated.
     private SharesheetModelScorer() {
@@ -169,13 +166,14 @@
      */
     static void computeScoreForAppShare(List<ShareTargetPredictor.ShareTarget> shareTargets,
             int shareEventType, int targetsLimit, long now, @NonNull DataManager dataManager,
-            @UserIdInt int callingUserId) {
+            @UserIdInt int callingUserId, @Nullable String chooserActivity) {
         computeScore(shareTargets, shareEventType, now);
-        postProcess(shareTargets, targetsLimit, dataManager, callingUserId);
+        postProcess(shareTargets, targetsLimit, dataManager, callingUserId, chooserActivity);
     }
 
     private static void postProcess(List<ShareTargetPredictor.ShareTarget> shareTargets,
-            int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+            int targetsLimit, @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+            @Nullable String chooserActivity) {
         // Populates a map which key is package name and value is list of shareTargets descended
         // on total score.
         Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap = new ArrayMap<>();
@@ -192,7 +190,7 @@
             }
             targetsList.add(index, shareTarget);
         }
-        promoteForegroundApp(shareTargetMap, dataManager, callingUserId);
+        promoteForegroundApp(shareTargetMap, dataManager, callingUserId, chooserActivity);
         promoteMostChosenAndFrequentlyUsedApps(shareTargetMap, targetsLimit, dataManager,
                 callingUserId);
     }
@@ -272,9 +270,10 @@
      */
     private static void promoteForegroundApp(
             Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
-            @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+            @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+            @Nullable String chooserActivity) {
         String sharingForegroundApp = findSharingForegroundApp(shareTargetMap, dataManager,
-                callingUserId);
+                callingUserId, chooserActivity);
         if (sharingForegroundApp != null) {
             ShareTargetPredictor.ShareTarget target = shareTargetMap.get(sharingForegroundApp).get(
                     0);
@@ -297,7 +296,8 @@
     @Nullable
     private static String findSharingForegroundApp(
             Map<String, List<ShareTargetPredictor.ShareTarget>> shareTargetMap,
-            @NonNull DataManager dataManager, @UserIdInt int callingUserId) {
+            @NonNull DataManager dataManager, @UserIdInt int callingUserId,
+            @Nullable String chooserActivity) {
         String sharingForegroundApp = null;
         long now = System.currentTimeMillis();
         List<UsageEvents.Event> events = dataManager.queryAppMovingToForegroundEvents(
@@ -306,8 +306,8 @@
         for (int i = events.size() - 1; i >= 0; i--) {
             String className = events.get(i).getClassName();
             String packageName = events.get(i).getPackageName();
-            if (packageName == null || (className != null && className.contains(CHOOSER_ACTIVITY))
-                    || packageName.contains(CHOOSER_ACTIVITY)) {
+            if (packageName == null || (className != null && chooserActivity != null
+                    && className.contains(chooserActivity))) {
                 continue;
             }
             if (sourceApp == null) {
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index 6a349e2..17474fb 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -26,7 +26,6 @@
 import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
 import com.android.server.permission.access.immutable.IndexedMap
 import com.android.server.permission.access.permission.AppIdPermissionPolicy
-import com.android.server.permission.access.permission.DevicePermissionPolicy
 import com.android.server.permission.access.util.attributeInt
 import com.android.server.permission.access.util.attributeInterned
 import com.android.server.permission.access.util.forEachTag
@@ -47,7 +46,6 @@
                 getOrPut(policy.subjectScheme) { MutableIndexedMap() }[policy.objectScheme] = policy
             }
             addPolicy(AppIdPermissionPolicy())
-            addPolicy(DevicePermissionPolicy())
             addPolicy(AppIdAppOpPolicy())
             addPolicy(PackageAppOpPolicy())
         } as IndexedMap<String, IndexedMap<String, SchemePolicy>>
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index 94c878a..4ec32ea 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -329,18 +329,6 @@
 private typealias AppIdPermissionFlagsReference =
     MutableReference<AppIdPermissionFlags, MutableAppIdPermissionFlags>
 
-
-typealias DevicePermissionFlags =
-    IndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
-typealias MutableDevicePermissionFlags =
-    MutableIndexedReferenceMap<String, IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
-typealias AppIdDevicePermissionFlags =
-    IntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags>
-typealias MutableAppIdDevicePermissionFlags =
-    MutableIntReferenceMap<DevicePermissionFlags, MutableDevicePermissionFlags>
-private typealias AppIdDevicePermissionFlagsReference =
-    MutableReference<AppIdDevicePermissionFlags, MutableAppIdDevicePermissionFlags>
-
 typealias AppIdAppOpModes =
     IntReferenceMap<IndexedMap<String, Int>, MutableIndexedMap<String, Int>>
 typealias MutableAppIdAppOpModes =
@@ -358,7 +346,6 @@
 sealed class UserState(
     internal val packageVersionsReference: PackageVersionsReference,
     internal val appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
-    internal val appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference,
     internal val appIdAppOpModesReference: AppIdAppOpModesReference,
     internal val packageAppOpModesReference: PackageAppOpModesReference,
     defaultPermissionGrantFingerprint: String?,
@@ -370,9 +357,6 @@
     val appIdPermissionFlags: AppIdPermissionFlags
         get() = appIdPermissionFlagsReference.get()
 
-    val appIdDevicePermissionFlags: AppIdDevicePermissionFlags
-        get() = appIdDevicePermissionFlagsReference.get()
-
     val appIdAppOpModes: AppIdAppOpModes
         get() = appIdAppOpModesReference.get()
 
@@ -391,7 +375,6 @@
 class MutableUserState private constructor(
     packageVersionsReference: PackageVersionsReference,
     appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
-    appIdDevicePermissionFlagsReference: AppIdDevicePermissionFlagsReference,
     appIdAppOpModesReference: AppIdAppOpModesReference,
     packageAppOpModesReference: PackageAppOpModesReference,
     defaultPermissionGrantFingerprint: String?,
@@ -399,7 +382,6 @@
 ) : UserState(
     packageVersionsReference,
     appIdPermissionFlagsReference,
-    appIdDevicePermissionFlagsReference,
     appIdAppOpModesReference,
     packageAppOpModesReference,
     defaultPermissionGrantFingerprint,
@@ -408,7 +390,6 @@
     constructor() : this(
         PackageVersionsReference(MutableIndexedMap<String, Int>()),
         AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()),
-        AppIdDevicePermissionFlagsReference(MutableAppIdDevicePermissionFlags()),
         AppIdAppOpModesReference(MutableAppIdAppOpModes()),
         PackageAppOpModesReference(MutablePackageAppOpModes()),
         null,
@@ -418,7 +399,6 @@
     internal constructor(userState: UserState) : this(
         userState.packageVersionsReference.toImmutable(),
         userState.appIdPermissionFlagsReference.toImmutable(),
-        userState.appIdDevicePermissionFlagsReference.toImmutable(),
         userState.appIdAppOpModesReference.toImmutable(),
         userState.packageAppOpModesReference.toImmutable(),
         userState.defaultPermissionGrantFingerprint,
@@ -430,9 +410,6 @@
     fun mutateAppIdPermissionFlags(): MutableAppIdPermissionFlags =
         appIdPermissionFlagsReference.mutate()
 
-    fun mutateAppIdDevicePermissionFlags(): MutableAppIdDevicePermissionFlags =
-        appIdDevicePermissionFlagsReference.mutate()
-
     fun mutateAppIdAppOpModes(): MutableAppIdAppOpModes = appIdAppOpModesReference.mutate()
 
     fun mutatePackageAppOpModes(): MutablePackageAppOpModes = packageAppOpModesReference.mutate()
diff --git a/services/permission/java/com/android/server/permission/access/AccessUri.kt b/services/permission/java/com/android/server/permission/access/AccessUri.kt
index 1d46ca7..d1abc04 100644
--- a/services/permission/java/com/android/server/permission/access/AccessUri.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessUri.kt
@@ -65,17 +65,6 @@
     }
 }
 
-data class DevicePermissionUri(
-    val permissionName: String,
-    val deviceId: Int
-) : AccessUri(SCHEME) {
-    override fun toString(): String = "$scheme:///$permissionName/$deviceId"
-
-    companion object {
-        const val SCHEME = "device-permission"
-    }
-}
-
 data class UidUri(
     val uid: Int
 ) : AccessUri(SCHEME) {
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt
deleted file mode 100644
index 37a4a90..0000000
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPersistence.kt
+++ /dev/null
@@ -1,169 +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.permission.access.permission
-
-import android.util.Slog
-import com.android.modules.utils.BinaryXmlPullParser
-import com.android.modules.utils.BinaryXmlSerializer
-import com.android.server.permission.access.AccessState
-import com.android.server.permission.access.DevicePermissionFlags
-import com.android.server.permission.access.MutableAccessState
-import com.android.server.permission.access.MutableAppIdDevicePermissionFlags
-import com.android.server.permission.access.MutableDevicePermissionFlags
-import com.android.server.permission.access.WriteMode
-import com.android.server.permission.access.immutable.IndexedMap
-import com.android.server.permission.access.immutable.MutableIndexedMap
-import com.android.server.permission.access.immutable.forEachIndexed
-import com.android.server.permission.access.immutable.forEachReversedIndexed
-import com.android.server.permission.access.immutable.set
-import com.android.server.permission.access.util.andInv
-import com.android.server.permission.access.util.attributeInt
-import com.android.server.permission.access.util.attributeInterned
-import com.android.server.permission.access.util.forEachTag
-import com.android.server.permission.access.util.getAttributeIntOrThrow
-import com.android.server.permission.access.util.getAttributeValueOrThrow
-import com.android.server.permission.access.util.hasBits
-import com.android.server.permission.access.util.tag
-import com.android.server.permission.access.util.tagName
-
-class DevicePermissionPersistence {
-    fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
-        when (tagName) {
-            TAG_APP_ID_DEVICE_PERMISSIONS -> parseAppIdDevicePermissions(state, userId)
-            else -> {}
-        }
-    }
-
-    private fun BinaryXmlPullParser.parseAppIdDevicePermissions(
-        state: MutableAccessState,
-        userId: Int
-    ) {
-        val userState = state.mutateUserState(userId, WriteMode.NONE)!!
-        val appIdDevicePermissionFlags = userState.mutateAppIdDevicePermissionFlags()
-        forEachTag {
-            when (tagName) {
-                TAG_APP_ID -> parseAppId(appIdDevicePermissionFlags)
-                else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
-            }
-        }
-
-        appIdDevicePermissionFlags.forEachReversedIndexed { appIdIndex, appId, _ ->
-            if (appId !in state.externalState.appIdPackageNames) {
-                Slog.w(LOG_TAG, "Dropping unknown app ID $appId when parsing permission state")
-                appIdDevicePermissionFlags.removeAt(appIdIndex)
-                userState.requestWriteMode(WriteMode.ASYNCHRONOUS)
-            }
-        }
-    }
-
-    private fun BinaryXmlPullParser.parseAppId(
-        appIdPermissionFlags: MutableAppIdDevicePermissionFlags
-    ) {
-        val appId = getAttributeIntOrThrow(ATTR_ID)
-        val devicePermissionFlags = MutableDevicePermissionFlags()
-        appIdPermissionFlags[appId] = devicePermissionFlags
-        forEachTag {
-            when (tagName) {
-                TAG_DEVICE -> parseDevice(devicePermissionFlags)
-                else -> {
-                    Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
-                }
-            }
-        }
-    }
-
-    private fun BinaryXmlPullParser.parseDevice(
-        deviceIdPermissionFlags: MutableDevicePermissionFlags
-    ) {
-        val deviceId = getAttributeValueOrThrow(ATTR_ID)
-        val permissionFlags = MutableIndexedMap<String, Int>()
-        deviceIdPermissionFlags.put(deviceId, permissionFlags)
-        forEachTag {
-            when (tagName) {
-                TAG_PERMISSION -> parsePermission(permissionFlags)
-                else -> Slog.w(LOG_TAG, "Ignoring unknown tag $name when parsing permission state")
-            }
-        }
-    }
-
-    private fun BinaryXmlPullParser.parsePermission(
-        permissionFlags: MutableIndexedMap<String, Int>
-    ) {
-        val name = getAttributeValueOrThrow(ATTR_NAME).intern()
-        val flags = getAttributeIntOrThrow(ATTR_FLAGS)
-        permissionFlags[name] = flags
-    }
-
-    fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
-        val appIdDevicePermissionFlags = state.userStates[userId]!!.appIdDevicePermissionFlags
-        tag(TAG_APP_ID_DEVICE_PERMISSIONS) {
-            appIdDevicePermissionFlags.forEachIndexed { _, appId, devicePermissionFlags ->
-                serializeAppId(appId, devicePermissionFlags)
-            }
-        }
-    }
-
-    private fun BinaryXmlSerializer.serializeAppId(
-        appId: Int,
-        devicePermissionFlags: DevicePermissionFlags
-    ) {
-        tag(TAG_APP_ID) {
-            attributeInt(ATTR_ID, appId)
-            devicePermissionFlags.forEachIndexed { _, deviceId, permissionFlags ->
-                serializeDevice(deviceId, permissionFlags)
-            }
-        }
-    }
-
-    private fun BinaryXmlSerializer.serializeDevice(
-        deviceId: String,
-        permissionFlags: IndexedMap<String, Int>
-    ) {
-        tag(TAG_DEVICE) {
-            attributeInterned(ATTR_ID, deviceId)
-            permissionFlags.forEachIndexed { _, name, flags ->
-                serializePermission(name, flags)
-            }
-        }
-    }
-
-    private fun BinaryXmlSerializer.serializePermission(name: String, flags: Int) {
-        tag(TAG_PERMISSION) {
-            attributeInterned(ATTR_NAME, name)
-            // Never serialize one-time permissions as granted.
-            val serializedFlags = if (flags.hasBits(PermissionFlags.ONE_TIME)) {
-                flags andInv PermissionFlags.RUNTIME_GRANTED
-            } else {
-                flags
-            }
-            attributeInt(ATTR_FLAGS, serializedFlags)
-        }
-    }
-
-    companion object {
-        private val LOG_TAG = DevicePermissionPersistence::class.java.simpleName
-
-        private const val TAG_APP_ID_DEVICE_PERMISSIONS = "app-id-device-permissions"
-        private const val TAG_APP_ID = "app-id"
-        private const val TAG_DEVICE = "device"
-        private const val TAG_PERMISSION = "permission"
-
-        private const val ATTR_ID = "id"
-        private const val ATTR_NAME = "name"
-        private const val ATTR_FLAGS = "flags"
-    }
-}
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
deleted file mode 100644
index c0d7546..0000000
--- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt
+++ /dev/null
@@ -1,276 +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.permission.access.permission
-
-import android.util.Slog
-import com.android.modules.utils.BinaryXmlPullParser
-import com.android.modules.utils.BinaryXmlSerializer
-import com.android.server.permission.access.AccessState
-import com.android.server.permission.access.DevicePermissionUri
-import com.android.server.permission.access.GetStateScope
-import com.android.server.permission.access.MutableAccessState
-import com.android.server.permission.access.MutateStateScope
-import com.android.server.permission.access.SchemePolicy
-import com.android.server.permission.access.UidUri
-import com.android.server.permission.access.collection.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.immutable.* // ktlint-disable no-wildcard-imports
-import com.android.server.permission.access.util.andInv
-import com.android.server.pm.pkg.PackageState
-
-class DevicePermissionPolicy : SchemePolicy() {
-    private val persistence = DevicePermissionPersistence()
-
-    @Volatile
-    private var listeners: IndexedListSet<OnDevicePermissionFlagsChangedListener> =
-        MutableIndexedListSet()
-    private val listenersLock = Any()
-
-    override val subjectScheme: String
-        get() = UidUri.SCHEME
-
-    override val objectScheme: String
-        get() = DevicePermissionUri.SCHEME
-
-    override fun GetStateScope.onStateMutated() {
-        listeners.forEachIndexed { _, it -> it.onStateMutated() }
-    }
-
-    override fun MutateStateScope.onAppIdRemoved(appId: Int) {
-        newState.userStates.forEachIndexed { userStateIndex, _, userState ->
-            if (appId in userState.appIdDevicePermissionFlags) {
-                newState.mutateUserStateAt(userStateIndex)
-                    .mutateAppIdDevicePermissionFlags() -= appId
-            }
-        }
-    }
-
-    override fun MutateStateScope.onStorageVolumeMounted(
-        volumeUuid: String?,
-        packageNames: List<String>,
-        isSystemUpdated: Boolean
-    ) {
-        packageNames.forEachIndexed { _, packageName ->
-            val packageState = newState.externalState.packageStates[packageName]!!
-            trimPermissionStates(packageState.appId)
-        }
-    }
-
-    override fun MutateStateScope.onPackageAdded(packageState: PackageState) {
-        trimPermissionStates(packageState.appId)
-    }
-
-    override fun MutateStateScope.onPackageRemoved(packageName: String, appId: Int) {
-        if (appId in newState.externalState.appIdPackageNames) {
-            trimPermissionStates(appId)
-        }
-    }
-
-    override fun MutateStateScope.onPackageUninstalled(
-        packageName: String,
-        appId: Int,
-        userId: Int
-    ) {
-        resetPermissionStates(packageName, userId)
-    }
-
-    private fun MutateStateScope.resetPermissionStates(packageName: String, userId: Int) {
-        // It's okay to skip resetting permissions for packages that are removed,
-        // because their states will be trimmed in onPackageRemoved()/onAppIdRemoved()
-        val packageState = newState.externalState.packageStates[packageName] ?: return
-        val androidPackage = packageState.androidPackage ?: return
-        val appId = packageState.appId
-        val appIdPermissionFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags
-        androidPackage.requestedPermissions.forEach { permissionName ->
-            val isRequestedByOtherPackages = anyPackageInAppId(appId) {
-                it.packageName != packageName &&
-                    permissionName in it.androidPackage!!.requestedPermissions
-            }
-            if (isRequestedByOtherPackages) {
-                return@forEach
-            }
-            appIdPermissionFlags[appId]?.forEachIndexed { _, deviceId, _ ->
-                setPermissionFlags(appId, deviceId, userId, permissionName, 0)
-            }
-        }
-    }
-
-    private fun MutateStateScope.trimPermissionStates(appId: Int) {
-        val requestedPermissions = MutableIndexedSet<String>()
-        forEachPackageInAppId(appId) {
-            requestedPermissions += it.androidPackage!!.requestedPermissions
-        }
-        newState.userStates.forEachIndexed { _, userId, userState ->
-            userState.appIdDevicePermissionFlags[appId]?.forEachReversedIndexed {
-                    _, deviceId, permissionFlags ->
-                permissionFlags.forEachReversedIndexed { _, permissionName, _ ->
-                    if (permissionName !in requestedPermissions) {
-                        setPermissionFlags(appId, deviceId, userId, permissionName, 0)
-                    }
-                }
-            }
-        }
-    }
-
-    private inline fun MutateStateScope.anyPackageInAppId(
-        appId: Int,
-        state: AccessState = newState,
-        predicate: (PackageState) -> Boolean
-    ): Boolean {
-        val packageNames = state.externalState.appIdPackageNames[appId]!!
-        return packageNames.anyIndexed { _, packageName ->
-            val packageState = state.externalState.packageStates[packageName]!!
-            packageState.androidPackage != null && predicate(packageState)
-        }
-    }
-
-    private inline fun MutateStateScope.forEachPackageInAppId(
-        appId: Int,
-        state: AccessState = newState,
-        action: (PackageState) -> Unit
-    ) {
-        val packageNames = state.externalState.appIdPackageNames[appId]!!
-        packageNames.forEachIndexed { _, packageName ->
-            val packageState = state.externalState.packageStates[packageName]!!
-            if (packageState.androidPackage != null) {
-                action(packageState)
-            }
-        }
-    }
-
-    override fun BinaryXmlPullParser.parseUserState(state: MutableAccessState, userId: Int) {
-        with(persistence) { this@parseUserState.parseUserState(state, userId) }
-    }
-
-    override fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
-        with(persistence) { this@serializeUserState.serializeUserState(state, userId) }
-    }
-
-    fun GetStateScope.getPermissionFlags(
-        appId: Int,
-        deviceId: String,
-        userId: Int,
-        permissionName: String
-    ): Int =
-        state.userStates[userId]?.appIdDevicePermissionFlags?.get(appId)?.get(deviceId)
-            ?.getWithDefault(permissionName, 0) ?: 0
-
-    fun MutateStateScope.setPermissionFlags(
-        appId: Int,
-        deviceId: String,
-        userId: Int,
-        permissionName: String,
-        flags: Int
-    ): Boolean =
-        updatePermissionFlags(
-            appId, deviceId, userId, permissionName, PermissionFlags.MASK_ALL, flags
-        )
-
-    private fun MutateStateScope.updatePermissionFlags(
-        appId: Int,
-        deviceId: String,
-        userId: Int,
-        permissionName: String,
-        flagMask: Int,
-        flagValues: Int
-    ): Boolean {
-        if (!isDeviceAwarePermission(permissionName)) {
-            Slog.w(LOG_TAG, "$permissionName is not a device aware permission.")
-            return false
-        }
-        val oldFlags = newState.userStates[userId]!!.appIdDevicePermissionFlags[appId]
-            ?.get(deviceId).getWithDefault(permissionName, 0)
-        val newFlags = (oldFlags andInv flagMask) or (flagValues and flagMask)
-        if (oldFlags == newFlags) {
-            return false
-        }
-        val appIdDevicePermissionFlags =
-            newState.mutateUserState(userId)!!.mutateAppIdDevicePermissionFlags()
-        val devicePermissionFlags = appIdDevicePermissionFlags.mutateOrPut(appId) {
-            MutableIndexedReferenceMap()
-        }
-        val permissionFlags = devicePermissionFlags.mutateOrPut(deviceId) { MutableIndexedMap() }
-        permissionFlags.putWithDefault(permissionName, newFlags, 0)
-        if (permissionFlags.isEmpty()) {
-            devicePermissionFlags -= deviceId
-            if (devicePermissionFlags.isEmpty()) {
-                appIdDevicePermissionFlags -= appId
-            }
-        }
-        listeners.forEachIndexed { _, it ->
-            it.onDevicePermissionFlagsChanged(
-                appId, userId, deviceId, permissionName, oldFlags, newFlags
-            )
-        }
-        return true
-    }
-
-    fun addOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) {
-        synchronized(listenersLock) {
-            listeners = listeners + listener
-        }
-    }
-
-    fun removeOnPermissionFlagsChangedListener(listener: OnDevicePermissionFlagsChangedListener) {
-        synchronized(listenersLock) {
-            listeners = listeners - listener
-        }
-    }
-
-    private fun isDeviceAwarePermission(permissionName: String): Boolean =
-        DEVICE_SUPPORTED_PERMISSIONS.contains(permissionName)
-
-    companion object {
-        private val LOG_TAG = DevicePermissionPolicy::class.java.simpleName
-
-        /**
-         * These permissions are supported for virtual devices.
-         */
-        private val DEVICE_SUPPORTED_PERMISSIONS = indexedSetOf(
-            android.Manifest.permission.CAMERA,
-            android.Manifest.permission.RECORD_AUDIO
-        )
-    }
-
-    /**
-     * TODO: b/289355341 - implement listener for permission changes
-     * Listener for permission flags changes.
-     */
-    abstract class OnDevicePermissionFlagsChangedListener {
-        /**
-         * Called when a permission flags change has been made to the upcoming new state.
-         *
-         * Implementations should keep this method fast to avoid stalling the locked state mutation,
-         * and only call external code after [onStateMutated] when the new state has actually become
-         * the current state visible to external code.
-         */
-        abstract fun onDevicePermissionFlagsChanged(
-            appId: Int,
-            userId: Int,
-            deviceId: String,
-            permissionName: String,
-            oldFlags: Int,
-            newFlags: Int
-        )
-
-        /**
-         * Called when the upcoming new state has become the current state.
-         *
-         * Implementations should keep this method fast to avoid stalling the locked state mutation.
-         */
-        abstract fun onStateMutated()
-    }
-}
\ No newline at end of file
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index d9f179a..edacf188 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -64,11 +64,9 @@
 import com.android.server.PermissionThread
 import com.android.server.ServiceThread
 import com.android.server.SystemConfig
-import com.android.server.companion.virtual.VirtualDeviceManagerInternal
 import com.android.server.permission.access.AccessCheckingService
 import com.android.server.permission.access.AccessState
 import com.android.server.permission.access.AppOpUri
-import com.android.server.permission.access.DevicePermissionUri
 import com.android.server.permission.access.GetStateScope
 import com.android.server.permission.access.MutateStateScope
 import com.android.server.permission.access.PermissionUri
@@ -112,9 +110,6 @@
     private val policy =
         service.getSchemePolicy(UidUri.SCHEME, PermissionUri.SCHEME) as AppIdPermissionPolicy
 
-    private val devicePolicy =
-        service.getSchemePolicy(UidUri.SCHEME, DevicePermissionUri.SCHEME) as DevicePermissionPolicy
-
     private val context = service.context
     private lateinit var metricsLogger: MetricsLogger
     private lateinit var packageManagerInternal: PackageManagerInternal
@@ -137,8 +132,6 @@
 
     private lateinit var permissionControllerManager: PermissionControllerManager
 
-    private lateinit var virtualDeviceManagerInternal: VirtualDeviceManagerInternal
-
     /**
      * A permission backup might contain apps that are not installed. In this case we delay the
      * restoration until the app is installed.
@@ -159,8 +152,6 @@
         systemConfig = SystemConfig.getInstance()
         userManagerInternal = LocalServices.getService(UserManagerInternal::class.java)
         userManagerService = UserManagerService.getInstance()
-        virtualDeviceManagerInternal =
-            LocalServices.getService(VirtualDeviceManagerInternal::class.java)
 
         // The package info cache is the cache for package and permission information.
         // Disable the package info and package permission caches locally but leave the
@@ -469,7 +460,7 @@
         return size
     }
 
-    override fun checkUidPermission(uid: Int, permissionName: String, deviceId: Int): Int {
+    override fun checkUidPermission(uid: Int, permissionName: String): Int {
         val userId = UserHandle.getUserId(uid)
         if (!userManagerInternal.exists(userId)) {
             return PackageManager.PERMISSION_DENIED
@@ -491,7 +482,7 @@
                 return PackageManager.PERMISSION_DENIED
             }
             val isPermissionGranted = service.getState {
-                isPermissionGranted(packageState, userId, permissionName, deviceId)
+                isPermissionGranted(packageState, userId, permissionName)
             }
             return if (isPermissionGranted) {
                 PackageManager.PERMISSION_GRANTED
@@ -524,12 +515,7 @@
         return false
     }
 
-    override fun checkPermission(
-        packageName: String,
-        permissionName: String,
-        deviceId: Int,
-        userId: Int
-    ): Int {
+    override fun checkPermission(packageName: String, permissionName: String, userId: Int): Int {
         if (!userManagerInternal.exists(userId)) {
             return PackageManager.PERMISSION_DENIED
         }
@@ -538,7 +524,7 @@
             .use { it.getPackageState(packageName) } ?: return PackageManager.PERMISSION_DENIED
 
         val isPermissionGranted = service.getState {
-            isPermissionGranted(packageState, userId, permissionName, deviceId)
+            isPermissionGranted(packageState, userId, permissionName)
         }
         return if (isPermissionGranted) {
             PackageManager.PERMISSION_GRANTED
@@ -556,21 +542,19 @@
     private fun GetStateScope.isPermissionGranted(
         packageState: PackageState,
         userId: Int,
-        permissionName: String,
-        deviceId: Int
+        permissionName: String
     ): Boolean {
         val appId = packageState.appId
         // Note that instant apps can't have shared UIDs, so we only need to check the current
         // package state.
         val isInstantApp = packageState.getUserStateOrDefault(userId).isInstantApp
-        if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName, deviceId)) {
+        if (isSinglePermissionGranted(appId, userId, isInstantApp, permissionName)) {
             return true
         }
 
         val fullerPermissionName = FULLER_PERMISSIONS[permissionName]
         if (fullerPermissionName != null &&
-            isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName, deviceId)
-        ) {
+            isSinglePermissionGranted(appId, userId, isInstantApp, fullerPermissionName)) {
             return true
         }
 
@@ -584,10 +568,9 @@
         appId: Int,
         userId: Int,
         isInstantApp: Boolean,
-        permissionName: String,
-        deviceId: Int,
+        permissionName: String
     ): Boolean {
-        val flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
+        val flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
         if (!PermissionFlags.isPermissionGranted(flags)) {
             return false
         }
@@ -618,8 +601,7 @@
                 ?: return emptySet()
 
             return permissionFlags.mapNotNullIndexedTo(ArraySet()) { _, permissionName, _ ->
-                if (isPermissionGranted(
-                        packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT)) {
+                if (isPermissionGranted(packageState, userId, permissionName)) {
                     permissionName
                 } else {
                     null
@@ -658,26 +640,18 @@
         }
     }
 
-    override fun grantRuntimePermission(
-        packageName: String,
-        permissionName: String,
-        deviceId: Int,
-        userId: Int
-    ) {
-        setRuntimePermissionGranted(
-            packageName, userId, permissionName, deviceId, isGranted = true
-        )
+    override fun grantRuntimePermission(packageName: String, permissionName: String, userId: Int) {
+        setRuntimePermissionGranted(packageName, userId, permissionName, isGranted = true)
     }
 
     override fun revokeRuntimePermission(
         packageName: String,
         permissionName: String,
-        deviceId: Int,
         userId: Int,
         reason: String?
     ) {
         setRuntimePermissionGranted(
-            packageName, userId, permissionName, deviceId, isGranted = false, revokeReason = reason
+            packageName, userId, permissionName, isGranted = false, revokeReason = reason
         )
     }
 
@@ -686,8 +660,8 @@
         userId: Int
     ) {
         setRuntimePermissionGranted(
-            packageName, userId, Manifest.permission.POST_NOTIFICATIONS, Context.DEVICE_ID_DEFAULT,
-            isGranted = false, skipKillUid = true
+            packageName, userId, Manifest.permission.POST_NOTIFICATIONS, isGranted = false,
+            skipKillUid = true
         )
     }
 
@@ -699,7 +673,6 @@
         packageName: String,
         userId: Int,
         permissionName: String,
-        deviceId: Int,
         isGranted: Boolean,
         skipKillUid: Boolean = false,
         revokeReason: String? = null
@@ -775,7 +748,7 @@
             }
 
             setRuntimePermissionGranted(
-                packageState, userId, permissionName, deviceId, isGranted, canManageRolePermission,
+                packageState, userId, permissionName, isGranted, canManageRolePermission,
                 overridePolicyFixed, reportError = true, methodName
             )
         }
@@ -809,16 +782,14 @@
                         if (permissionState ==
                             PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED) {
                             setRuntimePermissionGranted(
-                                packageState, userId, permissionName, Context.DEVICE_ID_DEFAULT,
-                                isGranted = true, canManageRolePermission = false,
-                                overridePolicyFixed = false, reportError = false,
-                                "setRequestedPermissionStates"
+                                packageState, userId, permissionName, isGranted = true,
+                                canManageRolePermission = false, overridePolicyFixed = false,
+                                reportError = false, "setRequestedPermissionStates"
                             )
                             updatePermissionFlags(
                                 packageState.appId, userId, permissionName,
-                                Context.DEVICE_ID_DEFAULT,
                                 PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED or
-                                PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0,
+                                    PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0,
                                 canUpdateSystemFlags = false,
                                 reportErrorForUnknownPermission = false,
                                 isPermissionRequested = true, "setRequestedPermissionStates",
@@ -845,7 +816,6 @@
         packageState: PackageState,
         userId: Int,
         permissionName: String,
-        deviceId: Int,
         isGranted: Boolean,
         canManageRolePermission: Boolean,
         overridePolicyFixed: Boolean,
@@ -901,7 +871,7 @@
         }
 
         val appId = packageState.appId
-        val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
+        val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
 
         if (permissionName !in androidPackage.requestedPermissions && oldFlags == 0) {
             if (reportError) {
@@ -964,7 +934,7 @@
             return
         }
 
-        setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags)
+        with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) }
 
         if (permission.isRuntime) {
             val action = if (isGranted) {
@@ -993,12 +963,7 @@
         with(appOpPolicy) { setAppOpMode(packageState.appId, userId, appOpName, mode) }
     }
 
-    override fun getPermissionFlags(
-        packageName: String,
-        permissionName: String,
-        deviceId: Int,
-        userId: Int,
-    ): Int {
+    override fun getPermissionFlags(packageName: String, permissionName: String, userId: Int): Int {
         if (!userManagerInternal.exists(userId)) {
             Slog.w(LOG_TAG, "getPermissionFlags: Unknown user $userId")
             return 0
@@ -1029,8 +994,7 @@
             }
 
             val flags =
-                getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId)
-
+                with(policy) { getPermissionFlags(packageState.appId, userId, permissionName) }
             return PermissionFlags.toApiFlags(flags)
         }
     }
@@ -1038,7 +1002,6 @@
     override fun isPermissionRevokedByPolicy(
         packageName: String,
         permissionName: String,
-        deviceId: Int,
         userId: Int
     ): Boolean {
         if (!userManagerInternal.exists(userId)) {
@@ -1055,13 +1018,13 @@
             .use { it.getPackageState(packageName) } ?: return false
 
         service.getState {
-            if (isPermissionGranted(packageState, userId, permissionName, deviceId)) {
+            if (isPermissionGranted(packageState, userId, permissionName)) {
                 return false
             }
 
-            val flags =
-                getPermissionFlagsWithPolicy(packageState.appId, userId, permissionName, deviceId)
-
+            val flags = with(policy) {
+                getPermissionFlags(packageState.appId, userId, permissionName)
+            }
             return flags.hasBits(PermissionFlags.POLICY_FIXED)
         }
     }
@@ -1083,8 +1046,7 @@
     override fun shouldShowRequestPermissionRationale(
         packageName: String,
         permissionName: String,
-        deviceId: Int,
-        userId: Int,
+        userId: Int
     ): Boolean {
         if (!userManagerInternal.exists(userId)) {
             Slog.w(LOG_TAG, "shouldShowRequestPermissionRationale: Unknown user $userId")
@@ -1106,11 +1068,11 @@
 
         val flags: Int
         service.getState {
-            if (isPermissionGranted(packageState, userId, permissionName, deviceId)) {
+            if (isPermissionGranted(packageState, userId, permissionName)) {
                 return false
             }
 
-            flags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
+            flags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
         }
         if (flags.hasAnyBit(UNREQUESTABLE_MASK)) {
             return false
@@ -1142,7 +1104,6 @@
         flagMask: Int,
         flagValues: Int,
         enforceAdjustPolicyPermission: Boolean,
-        deviceId: Int,
         userId: Int
     ) {
         val callingUid = Binder.getCallingUid()
@@ -1238,7 +1199,7 @@
         val appId = packageState.appId
         service.mutateState {
             updatePermissionFlags(
-                appId, userId, permissionName, deviceId, flagMask, flagValues, canUpdateSystemFlags,
+                appId, userId, permissionName, flagMask, flagValues, canUpdateSystemFlags,
                 reportErrorForUnknownPermission = true, isPermissionRequested,
                 "updatePermissionFlags", packageName
             )
@@ -1287,9 +1248,8 @@
                 val androidPackage = packageState.androidPackage ?: return@forEach
                 androidPackage.requestedPermissions.forEach { permissionName ->
                     updatePermissionFlags(
-                        packageState.appId, userId, permissionName, Context.DEVICE_ID_DEFAULT,
-                        flagMask, flagValues, canUpdateSystemFlags,
-                        reportErrorForUnknownPermission = false,
+                        packageState.appId, userId, permissionName, flagMask, flagValues,
+                        canUpdateSystemFlags, reportErrorForUnknownPermission = false,
                         isPermissionRequested = true, "updatePermissionFlagsForAllApps", packageName
                     )
                 }
@@ -1304,7 +1264,6 @@
         appId: Int,
         userId: Int,
         permissionName: String,
-        deviceId: Int,
         flagMask: Int,
         flagValues: Int,
         canUpdateSystemFlags: Boolean,
@@ -1339,7 +1298,7 @@
             return
         }
 
-        val oldFlags = getPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId)
+        val oldFlags = with(policy) { getPermissionFlags(appId, userId, permissionName) }
         if (!isPermissionRequested && oldFlags == 0) {
             Slog.w(
                 LOG_TAG, "$methodName: Permission $permissionName isn't requested by package" +
@@ -1349,7 +1308,7 @@
         }
 
         val newFlags = PermissionFlags.updateFlags(permission, oldFlags, flagMask, flagValues)
-        setPermissionFlagsWithPolicy(appId, userId, permissionName, deviceId, newFlags)
+        with(policy) { setPermissionFlags(appId, userId, permissionName, newFlags) }
     }
 
     override fun getAllowlistedRestrictedPermissions(
@@ -1406,49 +1365,6 @@
         )
     }
 
-    private fun GetStateScope.getPermissionFlagsWithPolicy(
-        appId: Int,
-        userId: Int,
-        permissionName: String,
-        deviceId: Int,
-    ): Int =
-        if (deviceId == Context.DEVICE_ID_DEFAULT) {
-            with(policy) { getPermissionFlags(appId, userId, permissionName) }
-        } else {
-            val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
-            if (persistentDeviceId != null) {
-                with(devicePolicy) {
-                    getPermissionFlags(appId, persistentDeviceId, userId, permissionName)
-                }
-            } else {
-                Slog.e(LOG_TAG, "Invalid deviceId $deviceId, no persistent device ID found.")
-                0
-            }
-        }
-
-    private fun MutateStateScope.setPermissionFlagsWithPolicy(
-        appId: Int,
-        userId: Int,
-        permissionName: String,
-        deviceId: Int,
-        flags: Int
-    ): Boolean =
-        if (deviceId == Context.DEVICE_ID_DEFAULT) {
-            with(policy) {
-                setPermissionFlags(appId, userId, permissionName, flags)
-            }
-        } else {
-            val persistentDeviceId = virtualDeviceManagerInternal.getPersistentIdForDevice(deviceId)
-            if (persistentDeviceId != null) {
-                with(devicePolicy) {
-                    setPermissionFlags(appId, persistentDeviceId, userId, permissionName, flags)
-                }
-            } else {
-                Slog.e(LOG_TAG, "Invalid deviceId $deviceId, no cdm association found.")
-                false
-            }
-        }
-
     /**
      * This method does not enforce checks on the caller, should only be called after
      * required checks.
@@ -1623,7 +1539,8 @@
     ) {
         service.mutateState {
             with(policy) {
-                val permissionsFlags = getUidPermissionFlags(appId, userId) ?: return@mutateState
+                val permissionsFlags =
+                    getUidPermissionFlags(appId, userId) ?: return@mutateState
 
                 val permissions = getPermissions()
                 androidPackage.requestedPermissions.forEachIndexed { _, requestedPermission ->
@@ -1744,6 +1661,8 @@
         )
     }
 
+
+
     override fun getAppOpPermissionPackages(permissionName: String): Array<String> {
         requireNotNull(permissionName) { "permissionName cannot be null" }
         val packageNames = ArraySet<String>()
@@ -1960,7 +1879,7 @@
                     println("Permissions:")
                     withIndent {
                         userState.appIdPermissionFlags[appId]?.forEachIndexed {
-                            _, permissionName, flags ->
+                                _, permissionName, flags ->
                             val isGranted = PermissionFlags.isPermissionGranted(flags)
                             println(
                                 "$permissionName: granted=$isGranted, flags=" +
@@ -1969,20 +1888,6 @@
                         }
                     }
 
-                    userState.appIdDevicePermissionFlags[appId]?.forEachIndexed {
-                            _, deviceId, devicePermissionFlags ->
-                        println("Permissions (Device $deviceId):")
-                        withIndent {
-                            devicePermissionFlags.forEachIndexed { _, permissionName, flags ->
-                                val isGranted = PermissionFlags.isPermissionGranted(flags)
-                                println(
-                                    "$permissionName: granted=$isGranted, flags=" +
-                                        PermissionFlags.toString(flags)
-                                )
-                            }
-                        }
-                    }
-
                     println("App ops:")
                     withIndent {
                         userState.appIdAppOpModes[appId]?.forEachIndexed {_, appOpName, appOpMode ->
@@ -2507,7 +2412,7 @@
         }
 
         private fun isAppBackupAndRestoreRunning(uid: Int): Boolean {
-            if (checkUidPermission(uid, Manifest.permission.BACKUP, Context.DEVICE_ID_DEFAULT) !=
+            if (checkUidPermission(uid, Manifest.permission.BACKUP) !=
                 PackageManager.PERMISSION_GRANTED) {
                 return false
             }
diff --git a/services/selectiontoolbar/Android.bp b/services/selectiontoolbar/Android.bp
deleted file mode 100644
index cc6405f..0000000
--- a/services/selectiontoolbar/Android.bp
+++ /dev/null
@@ -1,22 +0,0 @@
-package {
-    // See: http://go/android-license-faq
-    // A large-scale-change added 'default_applicable_licenses' to import
-    // all of the 'license_kinds' from "frameworks_base_license"
-    // to get the below license kinds:
-    //   SPDX-license-identifier-Apache-2.0
-    default_applicable_licenses: ["frameworks_base_license"],
-}
-
-filegroup {
-    name: "services.selectiontoolbar-sources",
-    srcs: ["java/**/*.java"],
-    path: "java",
-    visibility: ["//frameworks/base/services"],
-}
-
-java_library_static {
-    name: "services.selectiontoolbar",
-    defaults: ["platform_service_defaults"],
-    srcs: [":services.selectiontoolbar-sources"],
-    libs: ["services.core"],
-}
diff --git a/services/selectiontoolbar/OWNERS b/services/selectiontoolbar/OWNERS
deleted file mode 100644
index ed9425c..0000000
--- a/services/selectiontoolbar/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /core/java/android/view/selectiontoolbar/OWNERS
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java
deleted file mode 100644
index ae4227b..0000000
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/RemoteSelectionToolbarRenderService.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.selectiontoolbar;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.IBinder;
-import android.service.selectiontoolbar.ISelectionToolbarRenderService;
-import android.service.selectiontoolbar.SelectionToolbarRenderService;
-import android.util.Slog;
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ShowInfo;
-
-import com.android.internal.infra.AbstractRemoteService;
-import com.android.internal.infra.ServiceConnector;
-
-final class RemoteSelectionToolbarRenderService extends
-        ServiceConnector.Impl<ISelectionToolbarRenderService> {
-    private static final String TAG = "RemoteSelectionToolbarRenderService";
-
-    private static final long TIMEOUT_IDLE_UNBIND_MS =
-            AbstractRemoteService.PERMANENT_BOUND_TIMEOUT_MS;
-
-    private final ComponentName mComponentName;
-    private final IBinder mRemoteCallback;
-
-    RemoteSelectionToolbarRenderService(Context context, ComponentName serviceName, int userId,
-            IBinder callback) {
-        super(context, new Intent(SelectionToolbarRenderService.SERVICE_INTERFACE).setComponent(
-                serviceName), 0, userId, ISelectionToolbarRenderService.Stub::asInterface);
-        mComponentName = serviceName;
-        mRemoteCallback = callback;
-        // Bind right away.
-        connect();
-    }
-
-    @Override // from AbstractRemoteService
-    protected long getAutoDisconnectTimeoutMs() {
-        return TIMEOUT_IDLE_UNBIND_MS;
-    }
-
-    @Override // from ServiceConnector.Impl
-    protected void onServiceConnectionStatusChanged(ISelectionToolbarRenderService service,
-            boolean connected) {
-        try {
-            if (connected) {
-                service.onConnected(mRemoteCallback);
-            }
-        } catch (Exception e) {
-            Slog.w(TAG, "Exception calling onConnected().", e);
-        }
-    }
-
-    public ComponentName getComponentName() {
-        return mComponentName;
-    }
-
-    public void onShow(int callingUid, ShowInfo showInfo, ISelectionToolbarCallback callback) {
-        run((s) -> s.onShow(callingUid, showInfo, callback));
-    }
-
-    public void onHide(long widgetToken) {
-        run((s) -> s.onHide(widgetToken));
-    }
-
-    public void onDismiss(int callingUid, long widgetToken) {
-        run((s) -> s.onDismiss(callingUid, widgetToken));
-    }
-}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerService.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerService.java
deleted file mode 100644
index 3bdf55c..0000000
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerService.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.selectiontoolbar;
-
-import android.content.Context;
-import android.util.Slog;
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ISelectionToolbarManager;
-import android.view.selectiontoolbar.ShowInfo;
-
-import com.android.internal.util.DumpUtils;
-import com.android.server.infra.AbstractMasterSystemService;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-/**
- * Entry point service for selection toolbar management.
- */
-public final class SelectionToolbarManagerService extends
-        AbstractMasterSystemService<SelectionToolbarManagerService,
-                SelectionToolbarManagerServiceImpl> {
-
-    private static final String TAG = "SelectionToolbarManagerService";
-
-    @Override
-    public void onStart() {
-        publishBinderService(Context.SELECTION_TOOLBAR_SERVICE,
-                new SelectionToolbarManagerService.SelectionToolbarManagerServiceStub());
-    }
-
-    public SelectionToolbarManagerService(Context context) {
-        super(context, new SelectionToolbarServiceNameResolver(), /* disallowProperty= */
-                null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
-    }
-
-    @Override
-    protected SelectionToolbarManagerServiceImpl newServiceLocked(int resolvedUserId,
-            boolean disabled) {
-        return new SelectionToolbarManagerServiceImpl(this, mLock, resolvedUserId);
-    }
-
-    final class SelectionToolbarManagerServiceStub extends ISelectionToolbarManager.Stub {
-
-        @Override
-        public void showToolbar(ShowInfo showInfo, ISelectionToolbarCallback callback, int userId) {
-            synchronized (mLock) {
-                SelectionToolbarManagerServiceImpl service = getServiceForUserLocked(userId);
-                if (service != null) {
-                    service.showToolbar(showInfo, callback);
-                } else {
-                    Slog.v(TAG, "showToolbar(): no service for " + userId);
-                }
-            }
-        }
-
-        @Override
-        public void hideToolbar(long widgetToken, int userId) {
-            synchronized (mLock) {
-                SelectionToolbarManagerServiceImpl service = getServiceForUserLocked(userId);
-                if (service != null) {
-                    service.hideToolbar(widgetToken);
-                } else {
-                    Slog.v(TAG, "hideToolbar(): no service for " + userId);
-                }
-            }
-        }
-
-        @Override
-        public void dismissToolbar(long widgetToken, int userId) {
-            synchronized (mLock) {
-                SelectionToolbarManagerServiceImpl service = getServiceForUserLocked(userId);
-                if (service != null) {
-                    service.dismissToolbar(widgetToken);
-                } else {
-                    Slog.v(TAG, "dismissToolbar(): no service for " + userId);
-                }
-            }
-        }
-
-        @Override
-        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (!DumpUtils.checkDumpPermission(getContext(), TAG, pw)) return;
-
-            synchronized (mLock) {
-                dumpLocked("", pw);
-            }
-        }
-    }
-}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
deleted file mode 100644
index c8d153a..0000000
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarManagerServiceImpl.java
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.selectiontoolbar;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.content.ComponentName;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.os.Binder;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.service.selectiontoolbar.ISelectionToolbarRenderServiceCallback;
-import android.util.Slog;
-import android.view.selectiontoolbar.ISelectionToolbarCallback;
-import android.view.selectiontoolbar.ShowInfo;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.server.LocalServices;
-import com.android.server.infra.AbstractPerUserSystemService;
-import com.android.server.input.InputManagerInternal;
-
-final class SelectionToolbarManagerServiceImpl extends
-        AbstractPerUserSystemService<SelectionToolbarManagerServiceImpl,
-                SelectionToolbarManagerService> {
-
-    private static final String TAG = "SelectionToolbarManagerServiceImpl";
-
-    @GuardedBy("mLock")
-    @Nullable
-    private RemoteSelectionToolbarRenderService mRemoteService;
-
-    InputManagerInternal mInputManagerInternal;
-    private final SelectionToolbarRenderServiceRemoteCallback mRemoteServiceCallback =
-            new SelectionToolbarRenderServiceRemoteCallback();
-
-    protected SelectionToolbarManagerServiceImpl(@NonNull SelectionToolbarManagerService master,
-            @NonNull Object lock, int userId) {
-        super(master, lock, userId);
-        mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
-        updateRemoteServiceLocked();
-    }
-
-    @GuardedBy("mLock")
-    @Override // from PerUserSystemService
-    protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
-            throws PackageManager.NameNotFoundException {
-        return getServiceInfoOrThrow(serviceComponent, mUserId);
-    }
-
-    @GuardedBy("mLock")
-    @Override // from PerUserSystemService
-    protected boolean updateLocked(boolean disabled) {
-        final boolean enabledChanged = super.updateLocked(disabled);
-        updateRemoteServiceLocked();
-        return enabledChanged;
-    }
-
-    /**
-     * Updates the reference to the remote service.
-     */
-    @GuardedBy("mLock")
-    private void updateRemoteServiceLocked() {
-        if (mRemoteService != null) {
-            Slog.d(TAG, "updateRemoteService(): destroying old remote service");
-            mRemoteService.unbind();
-            mRemoteService = null;
-        }
-    }
-
-    @GuardedBy("mLock")
-    void showToolbar(ShowInfo showInfo, ISelectionToolbarCallback callback) {
-        final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
-        if (remoteService != null) {
-            remoteService.onShow(Binder.getCallingUid(), showInfo, callback);
-        }
-    }
-
-    @GuardedBy("mLock")
-    void hideToolbar(long widgetToken) {
-        final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
-        if (remoteService != null) {
-            remoteService.onHide(widgetToken);
-        }
-    }
-
-    @GuardedBy("mLock")
-    void dismissToolbar(long widgetToken) {
-        final RemoteSelectionToolbarRenderService remoteService = ensureRemoteServiceLocked();
-        if (remoteService != null) {
-            remoteService.onDismiss(Binder.getCallingUid(), widgetToken);
-        }
-    }
-
-    @GuardedBy("mLock")
-    @Nullable
-    private RemoteSelectionToolbarRenderService ensureRemoteServiceLocked() {
-        if (mRemoteService == null) {
-            final String serviceName = getComponentNameLocked();
-            final ComponentName serviceComponent = ComponentName.unflattenFromString(serviceName);
-            mRemoteService = new RemoteSelectionToolbarRenderService(getContext(), serviceComponent,
-                    mUserId, mRemoteServiceCallback);
-        }
-        return mRemoteService;
-    }
-
-    private static ServiceInfo getServiceInfoOrThrow(ComponentName comp, @UserIdInt int userId)
-            throws PackageManager.NameNotFoundException {
-        int flags = PackageManager.GET_META_DATA;
-
-        ServiceInfo si = null;
-        try {
-            si = AppGlobals.getPackageManager().getServiceInfo(comp, flags, userId);
-        } catch (RemoteException e) {
-        }
-        if (si == null) {
-            throw new PackageManager.NameNotFoundException("Could not get serviceInfo for "
-                    + comp.flattenToShortString());
-        }
-        return si;
-    }
-
-    private void transferTouchFocus(IBinder source, IBinder target) {
-        mInputManagerInternal.transferTouchFocus(source, target);
-    }
-
-    private final class SelectionToolbarRenderServiceRemoteCallback extends
-            ISelectionToolbarRenderServiceCallback.Stub {
-
-        @Override
-        public void transferTouch(IBinder source, IBinder target) {
-            transferTouchFocus(source, target);
-        }
-    }
-}
diff --git a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java b/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
deleted file mode 100644
index 99b0f25..0000000
--- a/services/selectiontoolbar/java/com/android/server/selectiontoolbar/SelectionToolbarServiceNameResolver.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.selectiontoolbar;
-
-import android.service.selectiontoolbar.DefaultSelectionToolbarRenderService;
-
-import com.android.server.infra.ServiceNameResolver;
-
-import java.io.PrintWriter;
-
-final class SelectionToolbarServiceNameResolver implements ServiceNameResolver {
-
-    // TODO: move to SysUi or ExtServices
-    private static final String SELECTION_TOOLBAR_SERVICE_NAME =
-            "android/" + DefaultSelectionToolbarRenderService.class.getName();
-
-    @Override
-    public String getDefaultServiceName(int userId) {
-        return SELECTION_TOOLBAR_SERVICE_NAME;
-    }
-
-    @Override
-    public void dumpShort(PrintWriter pw) {
-        pw.print("service="); pw.print(SELECTION_TOOLBAR_SERVICE_NAME);
-    }
-
-    @Override
-    public void dumpShort(PrintWriter pw, int userId) {
-        pw.print("defaultService="); pw.print(getDefaultServiceName(userId));
-    }
-}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index dc92376..74dc853 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -966,9 +966,11 @@
     }
 
     private static void assertUninstalled(ApplicationInfo info) throws Exception {
-        File nativeLibraryFile = new File(info.nativeLibraryDir);
-        assertFalse("Native library directory " + info.nativeLibraryDir
-                + " should be erased", nativeLibraryFile.exists());
+        if (info.nativeLibraryDir != null) {
+            File nativeLibraryFile = new File(info.nativeLibraryDir);
+            assertFalse("Native library directory " + info.nativeLibraryDir
+                    + " should be erased", nativeLibraryFile.exists());
+        }
     }
 
     public void deleteFromRawResource(int iFlags, int dFlags) throws Exception {
@@ -2883,14 +2885,15 @@
                     break;
                 }
             }
-            assertNotNull("activities should not be null", packageInfo.activities);
-            assertNotNull("configPreferences should not be null", packageInfo.configPreferences);
-            assertNotNull("instrumentation should not be null", packageInfo.instrumentation);
-            assertNotNull("permissions should not be null", packageInfo.permissions);
-            assertNotNull("providers should not be null", packageInfo.providers);
-            assertNotNull("receivers should not be null", packageInfo.receivers);
-            assertNotNull("services should not be null", packageInfo.services);
-            assertNotNull("signatures should not be null", packageInfo.signatures);
+            assertNotNull("applicationInfo should not be null", packageInfo.applicationInfo);
+            assertNull("activities should be null", packageInfo.activities);
+            assertNull("configPreferences should be null", packageInfo.configPreferences);
+            assertNull("instrumentation should be null", packageInfo.instrumentation);
+            assertNull("permissions should be null", packageInfo.permissions);
+            assertNull("providers should be null", packageInfo.providers);
+            assertNull("receivers should be null", packageInfo.receivers);
+            assertNull("services should be null", packageInfo.services);
+            assertNotNull("signingInfo should not be null", packageInfo.signingInfo);
         } finally {
             cleanUpInstall(ip);
         }
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
index 5a733c7..d217d63 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
@@ -29,7 +29,6 @@
 import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageState
-import com.android.server.pm.pkg.PackageStateImpl
 import com.android.server.pm.pkg.PackageUserState
 import com.android.server.pm.pkg.PackageUserStateImpl
 import com.android.server.pm.pkg.component.ParsedActivity
@@ -125,7 +124,7 @@
 
         fillMissingData(packageState, pkg as PackageImpl)
 
-        visitType(seenTypes, emptyList(), PackageStateImpl.copy(packageState),
+        visitType(seenTypes, emptyList(), PackageSetting(packageState, true),
             PackageState::class.starProjectedType)
         visitType(seenTypes, emptyList(), pkg, AndroidPackage::class.starProjectedType)
         visitType(seenTypes, emptyList(), packageState.getUserStateOrDefault(0),
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index da7a6a1..d9338a9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -22,8 +22,10 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
@@ -32,6 +34,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.hardware.display.DisplayManagerInternal;
 import android.os.Temperature;
 import android.util.SparseArray;
 import android.util.Spline;
@@ -74,8 +77,7 @@
     private static final int[] HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE = new int[]{-1, 30000};
     private static final float[] NITS = {2, 500, 800};
     private static final float[] BRIGHTNESS = {0, 0.62f, 1};
-    private static final Spline NITS_TO_BRIGHTNESS_SPLINE =
-            Spline.createSpline(NITS, BRIGHTNESS);
+    private static final Spline NITS_TO_BRIGHTNESS_SPLINE = Spline.createSpline(NITS, BRIGHTNESS);
 
     private DisplayDeviceConfig mDisplayDeviceConfig;
     private static final float ZERO_DELTA = 0.0f;
@@ -178,40 +180,174 @@
         assertEquals(82, mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr());
         assertEquals(83, mDisplayDeviceConfig.getDefaultRefreshRateInHbmSunlight());
 
-        assertEquals("sensor_12345",
-                mDisplayDeviceConfig.getScreenOffBrightnessSensor().type);
-        assertEquals("Sensor 12345",
-                mDisplayDeviceConfig.getScreenOffBrightnessSensor().name);
+        assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
+        assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
+        assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0);
+    }
 
-        assertArrayEquals(new int[]{-1, 10, 20, 30, 40},
-                mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux());
+    @Test
+    public void testConfigValuesFromConfigResource() {
+        setupDisplayDeviceConfigFromConfigResourceFile();
+        verifyConfigValuesFromConfigResource();
+    }
+
+    @Test
+    public void testThermalRefreshRateThrottlingFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        SparseArray<SurfaceControl.RefreshRateRange> defaultMap =
+                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData(null);
+        assertNotNull(defaultMap);
+        assertEquals(2, defaultMap.size());
+        assertEquals(30, defaultMap.get(Temperature.THROTTLING_CRITICAL).min, SMALL_DELTA);
+        assertEquals(60, defaultMap.get(Temperature.THROTTLING_CRITICAL).max, SMALL_DELTA);
+        assertEquals(0, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).min, SMALL_DELTA);
+        assertEquals(30, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).max, SMALL_DELTA);
+
+        SparseArray<SurfaceControl.RefreshRateRange> testMap =
+                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData("test");
+        assertNotNull(testMap);
+        assertEquals(1, testMap.size());
+        assertEquals(60, testMap.get(Temperature.THROTTLING_EMERGENCY).min, SMALL_DELTA);
+        assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA);
+    }
+
+    @Test
+    public void testValidLuxThrottling() throws Exception {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
+                mDisplayDeviceConfig.getLuxThrottlingData();
+        assertEquals(2, luxThrottlingData.size());
+
+        Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
+                DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
+        assertEquals(2, adaptiveOnBrightnessPoints.size());
+        assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
+        assertEquals(0.5f, adaptiveOnBrightnessPoints.get(5000f), SMALL_DELTA);
+
+        Map<Float, Float> adaptiveOffBrightnessPoints = luxThrottlingData.get(
+                DisplayDeviceConfig.BrightnessLimitMapType.DEFAULT);
+        assertEquals(2, adaptiveOffBrightnessPoints.size());
+        assertEquals(0.35f, adaptiveOffBrightnessPoints.get(1500f), SMALL_DELTA);
+        assertEquals(0.55f, adaptiveOffBrightnessPoints.get(5500f), SMALL_DELTA);
+    }
+
+    @Test
+    public void testInvalidLuxThrottling() throws Exception {
+        setupDisplayDeviceConfigFromDisplayConfigFile(
+                getContent(getInvalidLuxThrottling(), getValidProxSensor()));
+
+        Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
+                mDisplayDeviceConfig.getLuxThrottlingData();
+        assertEquals(1, luxThrottlingData.size());
+
+        Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
+                DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
+        assertEquals(1, adaptiveOnBrightnessPoints.size());
+        assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
+    }
+
+    @Test
+    public void testFallbackToConfigResource() throws IOException {
+        setupDisplayDeviceConfigFromConfigResourceFile();
+
+        // Empty display config file
+        setupDisplayDeviceConfigFromDisplayConfigFile(
+                "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<displayConfiguration />\n");
+
+        // We should fall back to the config resource
+        verifyConfigValuesFromConfigResource();
+    }
+
+    @Test
+    public void testDensityMappingFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        assertEquals(120, mDisplayDeviceConfig.getDensityMapping()
+                .getDensityForResolution(720, 480));
+        assertEquals(213, mDisplayDeviceConfig.getDensityMapping()
+                .getDensityForResolution(1280, 720));
+        assertEquals(320, mDisplayDeviceConfig.getDensityMapping()
+                .getDensityForResolution(1920, 1080));
+        assertEquals(640, mDisplayDeviceConfig.getDensityMapping()
+                .getDensityForResolution(3840, 2160));
+    }
+
+    @Test
+    public void testHighBrightnessModeDataFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        DisplayDeviceConfig.HighBrightnessModeData hbmData =
+                mDisplayDeviceConfig.getHighBrightnessModeData();
+        assertNotNull(hbmData);
+        assertEquals(BRIGHTNESS[1], hbmData.transitionPoint, ZERO_DELTA);
+        assertEquals(10000, hbmData.minimumLux, ZERO_DELTA);
+        assertEquals(1800 * 1000, hbmData.timeWindowMillis);
+        assertEquals(300 * 1000, hbmData.timeMaxMillis);
+        assertEquals(60 * 1000, hbmData.timeMinMillis);
+        assertFalse(hbmData.allowInLowPowerMode);
+        assertEquals(0.6f, hbmData.minimumHdrPercentOfScreen, ZERO_DELTA);
+
+        List<DisplayManagerInternal.RefreshRateLimitation> refreshRateLimitations =
+                mDisplayDeviceConfig.getRefreshRateLimitations();
+        assertEquals(1, refreshRateLimitations.size());
+        assertEquals(DisplayManagerInternal.REFRESH_RATE_LIMIT_HIGH_BRIGHTNESS_MODE,
+                refreshRateLimitations.get(0).type);
+        assertEquals(120, refreshRateLimitations.get(0).range.min, ZERO_DELTA);
+        assertEquals(120, refreshRateLimitations.get(0).range.max, ZERO_DELTA);
+
+        // Max desired Hdr/SDR ratio upper-bounds the HDR brightness.
+        assertTrue(mDisplayDeviceConfig.hasSdrToHdrRatioSpline());
+        assertEquals(NITS_TO_BRIGHTNESS_SPLINE.interpolate(500 * 1.6f),
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(500), Float.POSITIVE_INFINITY),
+                ZERO_DELTA);
+        assertEquals(NITS_TO_BRIGHTNESS_SPLINE.interpolate(500),
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(500), 1.0f),
+                ZERO_DELTA);
+        assertEquals(NITS_TO_BRIGHTNESS_SPLINE.interpolate(500 * 1.25f),
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(500), 1.25f),
+                SMALL_DELTA);
+        assertEquals(NITS_TO_BRIGHTNESS_SPLINE.interpolate(2 * 4),
+                mDisplayDeviceConfig.getHdrBrightnessFromSdr(
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(2), Float.POSITIVE_INFINITY),
+                SMALL_DELTA);
+    }
+
+    @Test
+    public void testThermalBrightnessThrottlingDataFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
 
         List<DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel>
                 defaultThrottlingLevels = new ArrayList<>();
         defaultThrottlingLevels.add(
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.4f
-        ));
+                        DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.4f
+                ));
         defaultThrottlingLevels.add(
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.3f
-        ));
+                        DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.3f
+                ));
         defaultThrottlingLevels.add(
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.2f
-        ));
+                        DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.2f
+                ));
         defaultThrottlingLevels.add(
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.1f
-        ));
+                        DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.1f
+                ));
         defaultThrottlingLevels.add(
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.05f
-        ));
+                        DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.05f
+                ));
         defaultThrottlingLevels.add(
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f
-        ));
+                        DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.025f
+                ));
 
         DisplayDeviceConfig.ThermalBrightnessThrottlingData defaultThrottlingData =
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData(defaultThrottlingLevels);
@@ -220,28 +356,28 @@
                 concurrentThrottlingLevels = new ArrayList<>();
         concurrentThrottlingLevels.add(
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.2f
-        ));
+                        DisplayDeviceConfig.convertThermalStatus(ThermalStatus.light), 0.2f
+                ));
         concurrentThrottlingLevels.add(
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.15f
-        ));
+                        DisplayDeviceConfig.convertThermalStatus(ThermalStatus.moderate), 0.15f
+                ));
         concurrentThrottlingLevels.add(
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.1f
-        ));
+                        DisplayDeviceConfig.convertThermalStatus(ThermalStatus.severe), 0.1f
+                ));
         concurrentThrottlingLevels.add(
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.05f
-        ));
+                        DisplayDeviceConfig.convertThermalStatus(ThermalStatus.critical), 0.05f
+                ));
         concurrentThrottlingLevels.add(
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.025f
-        ));
+                        DisplayDeviceConfig.convertThermalStatus(ThermalStatus.emergency), 0.025f
+                ));
         concurrentThrottlingLevels.add(
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel(
-                DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f
-        ));
+                        DisplayDeviceConfig.convertThermalStatus(ThermalStatus.shutdown), 0.0125f
+                ));
         DisplayDeviceConfig.ThermalBrightnessThrottlingData concurrentThrottlingData =
                 new DisplayDeviceConfig.ThermalBrightnessThrottlingData(concurrentThrottlingLevels);
 
@@ -252,29 +388,87 @@
 
         assertEquals(throttlingDataMap,
                 mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId());
-
-        assertNotNull(mDisplayDeviceConfig.getHostUsiVersion());
-        assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMajorVersion(), 2);
-        assertEquals(mDisplayDeviceConfig.getHostUsiVersion().getMinorVersion(), 0);
-
-        // Max desired Hdr/SDR ratio upper-bounds the HDR brightness.
-        assertEquals(1.0f,
-                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, Float.POSITIVE_INFINITY),
-                ZERO_DELTA);
-        assertEquals(0.62f,
-                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.0f),
-                ZERO_DELTA);
-        assertEquals(0.77787f,
-                mDisplayDeviceConfig.getHdrBrightnessFromSdr(0.62f, 1.25f),
-                SMALL_DELTA);
-
-        // Todo: Add asserts for DensityMapping,
-        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
     }
 
     @Test
-    public void testConfigValuesFromConfigResource() {
+    public void testAmbientLightSensorFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        assertEquals("test_light_sensor",
+                mDisplayDeviceConfig.getAmbientLightSensor().type);
+        assertEquals("Test Ambient Light Sensor",
+                mDisplayDeviceConfig.getAmbientLightSensor().name);
+        assertEquals(60, mDisplayDeviceConfig.getAmbientLightSensor().minRefreshRate, ZERO_DELTA);
+        assertEquals(120, mDisplayDeviceConfig.getAmbientLightSensor().maxRefreshRate, ZERO_DELTA);
+    }
+
+    @Test
+    public void testScreenOffBrightnessSensorFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        assertEquals("test_binned_brightness_sensor",
+                mDisplayDeviceConfig.getScreenOffBrightnessSensor().type);
+        assertEquals("Test Binned Brightness Sensor",
+                mDisplayDeviceConfig.getScreenOffBrightnessSensor().name);
+
+        assertArrayEquals(new int[]{ -1, 10, 20, 30, 40 },
+                mDisplayDeviceConfig.getScreenOffBrightnessSensorValueToLux());
+    }
+
+    @Test
+    public void testProximitySensorFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        assertEquals("test_proximity_sensor",
+                mDisplayDeviceConfig.getProximitySensor().type);
+        assertEquals("Test Proximity Sensor",
+                mDisplayDeviceConfig.getProximitySensor().name);
+    }
+
+    @Test
+    public void testProximitySensorWithEmptyValuesFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile(
+                getContent(getValidLuxThrottling(), getProxSensorWithEmptyValues()));
+        assertNull(mDisplayDeviceConfig.getProximitySensor());
+    }
+
+    @Test
+    public void testBlockingZoneThresholdsFromDisplayConfig() throws IOException {
+        setupDisplayDeviceConfigFromDisplayConfigFile();
+
+        assertArrayEquals(new float[]{ NITS_TO_BRIGHTNESS_SPLINE.interpolate(50),
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(300),
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(300), -1},
+                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), SMALL_DELTA);
+        assertArrayEquals(new float[]{50, 60, -1, 60},
+                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), ZERO_DELTA);
+        assertArrayEquals(new float[]{ NITS_TO_BRIGHTNESS_SPLINE.interpolate(80),
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(100),
+                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(100), -1},
+                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), SMALL_DELTA);
+        assertArrayEquals(new float[]{70, 80, -1, 80},
+                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA);
+    }
+
+    @Test
+    public void testBlockingZoneThresholdsFromConfigResource() {
         setupDisplayDeviceConfigFromConfigResourceFile();
+
+        assertArrayEquals(displayBrightnessThresholdsIntToFloat(
+                        LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE),
+                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), SMALL_DELTA);
+        assertArrayEquals(ambientBrightnessThresholdsIntToFloat(
+                        LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE),
+                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), ZERO_DELTA);
+        assertArrayEquals(displayBrightnessThresholdsIntToFloat(
+                        HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE),
+                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), SMALL_DELTA);
+        assertArrayEquals(ambientBrightnessThresholdsIntToFloat(
+                        HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE),
+                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA);
+    }
+
+    private void verifyConfigValuesFromConfigResource() {
         assertNull(mDisplayDeviceConfig.getName());
         assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
                 float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
@@ -342,100 +536,8 @@
         assertEquals(mDisplayDeviceConfig.getDefaultRefreshRateInHbmHdr(),
                 DEFAULT_REFRESH_RATE_IN_HBM_HDR);
 
-        // Todo: Add asserts for ThermalBrightnessThrottlingData, DensityMapping,
-        // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
-    }
-
-    @Test
-    public void testThermalRefreshRateThrottlingFromDisplayConfig() throws IOException {
-        setupDisplayDeviceConfigFromDisplayConfigFile();
-
-        SparseArray<SurfaceControl.RefreshRateRange> defaultMap =
-                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData(null);
-        assertNotNull(defaultMap);
-        assertEquals(2, defaultMap.size());
-        assertEquals(30, defaultMap.get(Temperature.THROTTLING_CRITICAL).min, SMALL_DELTA);
-        assertEquals(60, defaultMap.get(Temperature.THROTTLING_CRITICAL).max, SMALL_DELTA);
-        assertEquals(0, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).min, SMALL_DELTA);
-        assertEquals(30, defaultMap.get(Temperature.THROTTLING_SHUTDOWN).max, SMALL_DELTA);
-
-        SparseArray<SurfaceControl.RefreshRateRange> testMap =
-                mDisplayDeviceConfig.getThermalRefreshRateThrottlingData("test");
-        assertNotNull(testMap);
-        assertEquals(1, testMap.size());
-        assertEquals(60, testMap.get(Temperature.THROTTLING_EMERGENCY).min, SMALL_DELTA);
-        assertEquals(90, testMap.get(Temperature.THROTTLING_EMERGENCY).max, SMALL_DELTA);
-    }
-
-    @Test
-    public void testValidLuxThrottling() throws Exception {
-        setupDisplayDeviceConfigFromDisplayConfigFile();
-
-        Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
-                mDisplayDeviceConfig.getLuxThrottlingData();
-        assertEquals(2, luxThrottlingData.size());
-
-        Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
-                DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
-        assertEquals(2, adaptiveOnBrightnessPoints.size());
-        assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
-        assertEquals(0.5f, adaptiveOnBrightnessPoints.get(5000f), SMALL_DELTA);
-
-        Map<Float, Float> adaptiveOffBrightnessPoints = luxThrottlingData.get(
-                DisplayDeviceConfig.BrightnessLimitMapType.DEFAULT);
-        assertEquals(2, adaptiveOffBrightnessPoints.size());
-        assertEquals(0.35f, adaptiveOffBrightnessPoints.get(1500f), SMALL_DELTA);
-        assertEquals(0.55f, adaptiveOffBrightnessPoints.get(5500f), SMALL_DELTA);
-    }
-
-    @Test
-    public void testInvalidLuxThrottling() throws Exception {
-        setupDisplayDeviceConfigFromDisplayConfigFile(getContent(getInvalidLuxThrottling()));
-
-        Map<DisplayDeviceConfig.BrightnessLimitMapType, Map<Float, Float>> luxThrottlingData =
-                mDisplayDeviceConfig.getLuxThrottlingData();
-        assertEquals(1, luxThrottlingData.size());
-
-        Map<Float, Float> adaptiveOnBrightnessPoints = luxThrottlingData.get(
-                DisplayDeviceConfig.BrightnessLimitMapType.ADAPTIVE);
-        assertEquals(1, adaptiveOnBrightnessPoints.size());
-        assertEquals(0.3f, adaptiveOnBrightnessPoints.get(1000f), SMALL_DELTA);
-    }
-
-    @Test
-    public void testBlockingZoneThresholdsFromDisplayConfig() throws IOException {
-        setupDisplayDeviceConfigFromDisplayConfigFile();
-
-        assertArrayEquals(new float[]{ NITS_TO_BRIGHTNESS_SPLINE.interpolate(50),
-                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(300),
-                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(300), -1},
-                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), SMALL_DELTA);
-        assertArrayEquals(new float[]{50, 60, -1, 60},
-                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), ZERO_DELTA);
-        assertArrayEquals(new float[]{ NITS_TO_BRIGHTNESS_SPLINE.interpolate(80),
-                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(100),
-                        NITS_TO_BRIGHTNESS_SPLINE.interpolate(100), -1},
-                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), SMALL_DELTA);
-        assertArrayEquals(new float[]{70, 80, -1, 80},
-                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA);
-    }
-
-    @Test
-    public void testBlockingZoneThresholdsFromConfigResource() {
-        setupDisplayDeviceConfigFromConfigResourceFile();
-
-        assertArrayEquals(displayBrightnessThresholdsIntToFloat(
-                LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE),
-                mDisplayDeviceConfig.getLowDisplayBrightnessThresholds(), SMALL_DELTA);
-        assertArrayEquals(ambientBrightnessThresholdsIntToFloat(
-                LOW_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE),
-                mDisplayDeviceConfig.getLowAmbientBrightnessThresholds(), ZERO_DELTA);
-        assertArrayEquals(displayBrightnessThresholdsIntToFloat(
-                HIGH_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE),
-                mDisplayDeviceConfig.getHighDisplayBrightnessThresholds(), SMALL_DELTA);
-        assertArrayEquals(ambientBrightnessThresholdsIntToFloat(
-                HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE),
-                mDisplayDeviceConfig.getHighAmbientBrightnessThresholds(), ZERO_DELTA);
+        assertEquals("test_light_sensor", mDisplayDeviceConfig.getAmbientLightSensor().type);
+        assertEquals("", mDisplayDeviceConfig.getAmbientLightSensor().name);
     }
 
     private String getValidLuxThrottling() {
@@ -541,14 +643,50 @@
                + "</refreshRateThrottlingMap>\n";
     }
 
-    private String getContent() {
-        return getContent(getValidLuxThrottling());
+    private String getValidProxSensor() {
+        return "<proxSensor>\n"
+                +   "<type>test_proximity_sensor</type>\n"
+                +   "<name>Test Proximity Sensor</name>\n"
+                + "</proxSensor>\n";
     }
 
-    private String getContent(String brightnessCapConfig) {
+    private String getProxSensorWithEmptyValues() {
+        return "<proxSensor>\n"
+                +   "<type></type>\n"
+                +   "<name></name>\n"
+                + "</proxSensor>\n";
+    }
+
+    private String getContent() {
+        return getContent(getValidLuxThrottling(), getValidProxSensor());
+    }
+
+    private String getContent(String brightnessCapConfig, String proxSensor) {
         return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                 + "<displayConfiguration>\n"
-                +   "<name>Example Display</name>"
+                +   "<name>Example Display</name>\n"
+                +   "<densityMapping>\n"
+                +       "<density>\n"
+                +           "<height>480</height>\n"
+                +           "<width>720</width>\n"
+                +           "<density>120</density>\n"
+                +       "</density>\n"
+                +       "<density>\n"
+                +           "<height>720</height>\n"
+                +           "<width>1280</width>\n"
+                +           "<density>213</density>\n"
+                +       "</density>\n"
+                +       "<density>\n"
+                +           "<height>1080</height>\n"
+                +           "<width>1920</width>\n"
+                +           "<density>320</density>\n"
+                +       "</density>\n"
+                +       "<density>\n"
+                +           "<height>2160</height>\n"
+                +           "<width>3840</width>\n"
+                +           "<density>640</density>\n"
+                +       "</density>\n"
+                +   "</densityMapping>\n"
                 +   "<screenBrightnessMap>\n"
                 +       "<point>\n"
                 +           "<value>" + BRIGHTNESS[0] + "</value>\n"
@@ -578,7 +716,7 @@
                 +       "</displayBrightnessMapping>\n"
                 +   "</autoBrightness>\n"
                 +   "<highBrightnessMode enabled=\"true\">\n"
-                +       "<transitionPoint>0.62</transitionPoint>\n"
+                +       "<transitionPoint>" + BRIGHTNESS[1] + "</transitionPoint>\n"
                 +       "<minimumLux>10000</minimumLux>\n"
                 +       "<timing>\n"
                 +           "<!-- allow for 5 minutes out of every 30 minutes -->\n"
@@ -590,8 +728,8 @@
                 +           "<minimum>120</minimum>\n"
                 +           "<maximum>120</maximum>\n"
                 +       "</refreshRate>\n"
-                +       "<thermalStatusLimit>light</thermalStatusLimit>\n"
                 +       "<allowInLowPowerMode>false</allowInLowPowerMode>\n"
+                +       "<minimumHdrPercentOfScreen>0.6</minimumHdrPercentOfScreen>\n"
                 +       "<sdrHdrRatioMap>\n"
                 +            "<point>\n"
                 +                "<sdrNits>2.000</sdrNits>\n"
@@ -604,10 +742,19 @@
                 +       "</sdrHdrRatioMap>\n"
                 +   "</highBrightnessMode>\n"
                 + brightnessCapConfig
+                +   "<lightSensor>\n"
+                +       "<type>test_light_sensor</type>\n"
+                +       "<name>Test Ambient Light Sensor</name>\n"
+                +       "<refreshRate>\n"
+                +           "<minimum>60</minimum>\n"
+                +           "<maximum>120</maximum>\n"
+                +       "</refreshRate>\n"
+                +   "</lightSensor>\n"
                 +   "<screenOffBrightnessSensor>\n"
-                +       "<type>sensor_12345</type>\n"
-                +       "<name>Sensor 12345</name>\n"
+                +       "<type>test_binned_brightness_sensor</type>\n"
+                +       "<name>Test Binned Brightness Sensor</name>\n"
                 +   "</screenOffBrightnessSensor>\n"
+                + proxSensor
                 +   "<ambientBrightnessChangeThresholds>\n"
                 +       "<brighteningThresholds>\n"
                 +           "<minimum>10</minimum>\n"
@@ -946,9 +1093,9 @@
         when(mResources.getInteger(R.integer.config_defaultRefreshRate))
                 .thenReturn(DEFAULT_REFRESH_RATE);
         when(mResources.getInteger(R.integer.config_fixedRefreshRateInHighZone))
-            .thenReturn(DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
+                .thenReturn(DEFAULT_HIGH_BLOCKING_ZONE_REFRESH_RATE);
         when(mResources.getInteger(R.integer.config_defaultRefreshRateInZone))
-            .thenReturn(DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
+                .thenReturn(DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE);
         when(mResources.getIntArray(R.array.config_brightnessThresholdsOfPeakRefreshRate))
                 .thenReturn(LOW_BRIGHTNESS_THRESHOLD_OF_PEAK_REFRESH_RATE);
         when(mResources.getIntArray(R.array.config_ambientThresholdsOfPeakRefreshRate))
@@ -960,11 +1107,14 @@
                 R.array.config_highAmbientBrightnessThresholdsOfFixedRefreshRate))
                 .thenReturn(HIGH_AMBIENT_THRESHOLD_OF_PEAK_REFRESH_RATE);
         when(mResources.getInteger(
-            R.integer.config_defaultRefreshRateInHbmHdr))
-            .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_HDR);
+                R.integer.config_defaultRefreshRateInHbmHdr))
+                .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_HDR);
         when(mResources.getInteger(
-            R.integer.config_defaultRefreshRateInHbmSunlight))
-            .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
+                R.integer.config_defaultRefreshRateInHbmSunlight))
+                .thenReturn(DEFAULT_REFRESH_RATE_IN_HBM_SUNLIGHT);
+
+        when(mResources.getString(com.android.internal.R.string.config_displayLightSensorType))
+                .thenReturn("test_light_sensor");
 
         mDisplayDeviceConfig = DisplayDeviceConfig.create(mContext, true);
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index bf23117..979676e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -479,6 +479,41 @@
     }
 
     @Test
+    public void testCreateVirtualRotatesWithContent() throws RemoteException {
+        DisplayManagerService displayManager =
+                new DisplayManagerService(mContext, mBasicInjector);
+        registerDefaultDisplays(displayManager);
+
+        // This is effectively the DisplayManager service published to ServiceManager.
+        DisplayManagerService.BinderService bs = displayManager.new BinderService();
+
+        String uniqueId = "uniqueId --- Rotates with Content Test";
+        int width = 600;
+        int height = 800;
+        int dpi = 320;
+        int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+
+        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
+        final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(
+                VIRTUAL_DISPLAY_NAME, width, height, dpi);
+        builder.setFlags(flags);
+        builder.setUniqueId(uniqueId);
+        int displayId = bs.createVirtualDisplay(builder.build(), /* callback= */ mMockAppToken,
+                /* projection= */ null, PACKAGE_NAME);
+        verify(mMockProjectionService, never()).setContentRecordingSession(any(),
+                nullable(IMediaProjection.class));
+
+        displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
+
+        // flush the handler
+        displayManager.getDisplayHandler().runWithScissors(() -> {}, /* now= */ 0);
+
+        DisplayDeviceInfo ddi = displayManager.getDisplayDeviceInfoInternal(displayId);
+        assertNotNull(ddi);
+        assertTrue((ddi.flags & DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT) != 0);
+    }
+
+    @Test
     public void testCreateVirtualDisplayOwnFocus() throws RemoteException {
         DisplayManagerService displayManager =
                 new DisplayManagerService(mContext, mBasicInjector);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
index 2065479..c0128ae 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayTest.java
@@ -69,7 +69,6 @@
         mDisplayDeviceInfo.copyFrom(new DisplayDeviceInfo());
         mDisplayDeviceInfo.width = DISPLAY_WIDTH;
         mDisplayDeviceInfo.height = DISPLAY_HEIGHT;
-        mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
         mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
         mDisplayDeviceInfo.modeId = MODE_ID;
         mDisplayDeviceInfo.supportedModes = new Display.Mode[] {new Display.Mode(MODE_ID,
@@ -112,8 +111,18 @@
         mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
         assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
 
-        expectedPosition.set(40, -20);
         DisplayInfo displayInfo = new DisplayInfo();
+        displayInfo.logicalWidth = DISPLAY_WIDTH;
+        displayInfo.logicalHeight = DISPLAY_HEIGHT;
+        // Rotation doesn't matter when the FLAG_ROTATES_WITH_CONTENT is absent.
+        displayInfo.rotation = Surface.ROTATION_90;
+        mLogicalDisplay.setDisplayInfoOverrideFromWindowManagerLocked(displayInfo);
+        mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+        assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
+
+        expectedPosition.set(40, -20);
+        mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+        mLogicalDisplay.updateLocked(mDeviceRepo);
         displayInfo.logicalWidth = DISPLAY_HEIGHT;
         displayInfo.logicalHeight = DISPLAY_WIDTH;
         displayInfo.rotation = Surface.ROTATION_90;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java
new file mode 100644
index 0000000..0ff4724
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessLowPowerModeModifierTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.brightness.clamper;
+
+import static android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@SmallTest
+public class BrightnessLowPowerModeModifierTest {
+    private static final float FLOAT_TOLERANCE = 0.001f;
+    private static final float DEFAULT_BRIGHTNESS = 0.5f;
+    private static final float LOW_POWER_BRIGHTNESS_FACTOR = 0.8f;
+    private static final float EXPECTED_LOW_POWER_BRIGHTNESS =
+            DEFAULT_BRIGHTNESS * LOW_POWER_BRIGHTNESS_FACTOR;
+    private final DisplayPowerRequest mRequest = new DisplayPowerRequest();
+    private final DisplayBrightnessState.Builder mBuilder = prepareBuilder();
+    private BrightnessLowPowerModeModifier mModifier;
+
+    @Before
+    public void setUp() {
+        mModifier = new BrightnessLowPowerModeModifier();
+        mRequest.screenLowPowerBrightnessFactor = LOW_POWER_BRIGHTNESS_FACTOR;
+        mRequest.lowPowerMode = true;
+    }
+
+    @Test
+    public void testApply_lowPowerModeOff() {
+        mRequest.lowPowerMode = false;
+
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+        assertTrue(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOn() {
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(EXPECTED_LOW_POWER_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+                mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOnAndLowPowerBrightnessFactorHigh() {
+        mRequest.screenLowPowerBrightnessFactor = 1.1f;
+
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+                mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOnAndMinBrightness() {
+        mBuilder.setBrightness(0.0f);
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(0.0f, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOnAndLowPowerAlreadyApplied() {
+        mModifier.apply(mRequest, mBuilder);
+        DisplayBrightnessState.Builder builder = prepareBuilder();
+
+        mModifier.apply(mRequest, builder);
+
+        assertEquals(EXPECTED_LOW_POWER_BRIGHTNESS, builder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_LOW_POWER,
+                builder.getBrightnessReason().getModifier());
+        assertTrue(builder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_lowPowerModeOffAfterLowPowerOn() {
+        mModifier.apply(mRequest, mBuilder);
+        mRequest.lowPowerMode = false;
+        DisplayBrightnessState.Builder builder = prepareBuilder();
+
+        mModifier.apply(mRequest, builder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, builder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, builder.getBrightnessReason().getModifier());
+        assertFalse(builder.isSlowChange());
+    }
+
+    private DisplayBrightnessState.Builder prepareBuilder() {
+        DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+        builder.setBrightness(DEFAULT_BRIGHTNESS);
+        builder.setIsSlowChange(true);
+        return builder;
+    }
+}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.java
new file mode 100644
index 0000000..be4e7c7
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/DisplayDimModifierTest.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.display.brightness.clamper;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.R;
+import com.android.server.display.DisplayBrightnessState;
+import com.android.server.display.brightness.BrightnessReason;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+public class DisplayDimModifierTest {
+    private static final float FLOAT_TOLERANCE = 0.001f;
+    private static final float DEFAULT_BRIGHTNESS = 0.5f;
+    private static final float MIN_DIM_AMOUNT = 0.05f;
+    private static final float DIM_CONFIG = 0.4f;
+
+    @Mock
+    private Context mMockContext;
+
+    @Mock
+    private PowerManager mMockPowerManager;
+
+    @Mock
+    private Resources mMockResources;
+
+    private final DisplayManagerInternal.DisplayPowerRequest
+            mRequest = new DisplayManagerInternal.DisplayPowerRequest();
+    private final DisplayBrightnessState.Builder mBuilder = prepareBuilder();
+    private DisplayDimModifier mModifier;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mMockContext.getResources()).thenReturn(mMockResources);
+        when(mMockResources.getFloat(
+                R.dimen.config_screenBrightnessMinimumDimAmountFloat)).thenReturn(MIN_DIM_AMOUNT);
+        when(mMockContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
+        when(mMockPowerManager.getBrightnessConstraint(
+                PowerManager.BRIGHTNESS_CONSTRAINT_TYPE_DIM)).thenReturn(DIM_CONFIG);
+
+        mModifier = new DisplayDimModifier(mMockContext);
+        mRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_DIM;
+    }
+
+    @Test
+    public void testApply_noDimPolicy() {
+        mRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+        assertTrue(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_dimPolicyFromResources() {
+        mBuilder.setBrightness(0.4f);
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(0.4f - MIN_DIM_AMOUNT, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_DIMMED,
+                mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_dimPolicyFromConfig() {
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(DIM_CONFIG, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_DIMMED,
+                mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_dimPolicyAndDimPolicyAlreadyApplied() {
+        mModifier.apply(mRequest, mBuilder);
+        DisplayBrightnessState.Builder builder = prepareBuilder();
+
+        mModifier.apply(mRequest, builder);
+
+        assertEquals(DIM_CONFIG, builder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(BrightnessReason.MODIFIER_DIMMED,
+                builder.getBrightnessReason().getModifier());
+        assertTrue(builder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_dimPolicyAndMinBrightness() {
+        mBuilder.setBrightness(0.0f);
+        mModifier.apply(mRequest, mBuilder);
+
+        assertEquals(0.0f, mBuilder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, mBuilder.getBrightnessReason().getModifier());
+        assertFalse(mBuilder.isSlowChange());
+    }
+
+    @Test
+    public void testApply_dimPolicyOffAfterDimPolicyOn() {
+        mModifier.apply(mRequest, mBuilder);
+        mRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_OFF;
+        DisplayBrightnessState.Builder builder = prepareBuilder();
+
+        mModifier.apply(mRequest, builder);
+
+        assertEquals(DEFAULT_BRIGHTNESS, builder.getBrightness(), FLOAT_TOLERANCE);
+        assertEquals(0, builder.getBrightnessReason().getModifier());
+        assertFalse(builder.isSlowChange());
+    }
+
+    private DisplayBrightnessState.Builder prepareBuilder() {
+        DisplayBrightnessState.Builder builder = DisplayBrightnessState.builder();
+        builder.setBrightness(DEFAULT_BRIGHTNESS);
+        builder.setIsSlowChange(true);
+        return builder;
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index 88f3b2e..525bfd7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -48,7 +48,6 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
@@ -83,6 +82,7 @@
 import android.os.PowerManagerInternal;
 import android.os.PowerSaveState;
 import android.os.SystemClock;
+import android.os.WearModeManagerInternal;
 import android.provider.DeviceConfig;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyManager;
@@ -147,6 +147,8 @@
     private TelephonyManager mTelephonyManager;
     @Mock
     private Sensor mOffBodySensor;
+    @Mock
+    private WearModeManagerInternal mWearModeManagerInternal;
 
     class InjectorForTest extends DeviceIdleController.Injector {
         ConnectivityManager connectivityManager;
@@ -348,6 +350,9 @@
         mAnyMotionDetector = new AnyMotionDetectorForTest();
         mInjector = new InjectorForTest(getContext());
 
+        doReturn(mWearModeManagerInternal)
+                .when(() -> LocalServices.getService(WearModeManagerInternal.class));
+
         setupDeviceIdleController();
     }
 
@@ -2413,22 +2418,20 @@
     }
 
     @Test
-    public void testLowLatencyBodyDetection_NoBodySensor() {
-        mConstants.USE_BODY_SENSOR = true;
-        doReturn(null).when(mSensorManager).getDefaultSensor(
-                eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean());
+    public void testModeManager_NoModeManagerLocalService_AddListenerNotCalled() {
+        mConstants.USE_MODE_MANAGER = true;
+        doReturn(null)
+                .when(() -> LocalServices.getService(WearModeManagerInternal.class));
         cleanupDeviceIdleController();
         setupDeviceIdleController();
-        verify(mSensorManager, never())
-                .registerListener(any(), any(), anyInt());
+        verify(mWearModeManagerInternal, never()).addActiveStateChangeListener(
+                eq(WearModeManagerInternal.QUICK_DOZE_REQUEST_IDENTIFIER), any(),
+                eq(mDeviceIdleController.mModeManagerQuickDozeRequestConsumer));
     }
 
     @Test
-    public void testLowLatencyBodyDetection_NoBatterySaver_QuickDoze() {
-        mConstants.USE_BODY_SENSOR = true;
-        doReturn(mOffBodySensor)
-                .when(mSensorManager)
-                .getDefaultSensor(eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean());
+    public void testModeManager_NoBatterySaver_QuickDoze() {
+        mConstants.USE_MODE_MANAGER = true;
         PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
                 false).build();
         when(mPowerManagerInternal.getLowPowerState(anyInt()))
@@ -2436,32 +2439,19 @@
         cleanupDeviceIdleController();
         setupDeviceIdleController();
 
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager)
-                .registerListener(listenerCaptor.capture(), eq(mOffBodySensor),
-                        eq(SensorManager.SENSOR_DELAY_NORMAL));
-        final SensorEventListener listener = listenerCaptor.getValue();
-        // Set the device as off body
-        float[] valsZero = {0.0f};
-        SensorEvent offbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsZero);
-        listener.onSensorChanged(offbodyEvent);
+        // Mode manager quick doze request: true.
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(true);
         assertTrue(mDeviceIdleController.isQuickDozeEnabled());
 
-        // Set the device as on body
-        float[] valsNonZero = {1.0f};
-        SensorEvent onbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsNonZero);
-        listener.onSensorChanged(onbodyEvent);
+        // Mode manager quick doze request: false.
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(false);
         assertFalse(mDeviceIdleController.isQuickDozeEnabled());
         verifyStateConditions(STATE_ACTIVE);
     }
 
     @Test
-    public void testLowLatencyBodyDetection_WithBatterySaver_QuickDoze() {
-        mConstants.USE_BODY_SENSOR = true;
-        doReturn(mOffBodySensor)
-                .when(mSensorManager)
-                .getDefaultSensor(eq(Sensor.TYPE_LOW_LATENCY_OFFBODY_DETECT), anyBoolean());
+    public void testModeManager_WithBatterySaver_QuickDoze() {
+        mConstants.USE_MODE_MANAGER = true;
         PowerSaveState powerSaveState = new PowerSaveState.Builder().setBatterySaverEnabled(
                 true).build();
         when(mPowerManagerInternal.getLowPowerState(anyInt()))
@@ -2469,22 +2459,13 @@
         cleanupDeviceIdleController();
         setupDeviceIdleController();
 
-        ArgumentCaptor<SensorEventListener> listenerCaptor =
-                ArgumentCaptor.forClass(SensorEventListener.class);
-        verify(mSensorManager)
-                .registerListener(listenerCaptor.capture(), eq(mOffBodySensor),
-                        eq(SensorManager.SENSOR_DELAY_NORMAL));
-        final SensorEventListener listener = listenerCaptor.getValue();
-        // Set the device as off body
-        float[] valsZero = {0.0f};
-        SensorEvent offbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsZero);
-        listener.onSensorChanged(offbodyEvent);
+        // Mode manager quick doze request: true.
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(true);
         assertTrue(mDeviceIdleController.isQuickDozeEnabled());
 
-        // Set the device as on body. Quick doze should remain enabled because battery saver is on.
-        float[] valsNonZero = {1.0f};
-        SensorEvent onbodyEvent = new SensorEvent(mOffBodySensor, 1, 1L, valsNonZero);
-        listener.onSensorChanged(onbodyEvent);
+        // Mode manager quick doze request: false.
+        // Quick doze should remain enabled because battery saver is on.
+        mDeviceIdleController.mModeManagerQuickDozeRequestConsumer.accept(false);
         assertTrue(mDeviceIdleController.isQuickDozeEnabled());
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
index c6d8848..4095be7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/backup/SystemBackupAgentTest.java
@@ -83,7 +83,8 @@
                         "slices",
                         "people",
                         "app_locales",
-                        "app_gender");
+                        "app_gender",
+                        "companion");
     }
 
     @Test
@@ -106,7 +107,8 @@
                         "account_manager",
                         "people",
                         "app_locales",
-                        "app_gender");
+                        "app_gender",
+                        "companion");
     }
 
     @Test
@@ -121,7 +123,8 @@
                         "account_sync_settings",
                         "notifications",
                         "permissions",
-                        "app_locales");
+                        "app_locales",
+                        "companion");
     }
 
     @Test
@@ -140,7 +143,8 @@
                         "app_locales",
                         "account_manager",
                         "usage_stats",
-                        "shortcut_manager");
+                        "shortcut_manager",
+                        "companion");
     }
 
     private class TestableSystemBackupAgent extends SystemBackupAgent {
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 2d962ac..1a8b12a 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
@@ -686,9 +686,6 @@
     }
 
     @Override
-    protected void sendNiResponse(int notificationId, int userResponse) {}
-
-    @Override
     protected void requestPowerStats() {
         Objects.requireNonNull(mGnssNative).reportGnssPowerStats(mState.mPowerStats);
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 0664ab8..d32b6be 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -15,7 +15,10 @@
  */
 package com.android.server.pm;
 
+import static android.os.UserManager.DISALLOW_OUTGOING_CALLS;
+import static android.os.UserManager.DISALLOW_SMS;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
+import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -490,6 +493,17 @@
         assertThat(mUms.isUserSwitcherEnabled(USER_ID)).isTrue();
     }
 
+    @Test
+    public void testMainUser_hasNoCallsOrSMSRestrictionsByDefault() {
+        UserInfo mainUser = mUms.createUserWithThrow("main user", USER_TYPE_FULL_SECONDARY,
+                UserInfo.FLAG_FULL | UserInfo.FLAG_MAIN);
+
+        assertThat(mUms.hasUserRestriction(DISALLOW_OUTGOING_CALLS, mainUser.id))
+                .isFalse();
+        assertThat(mUms.hasUserRestriction(DISALLOW_SMS, mainUser.id))
+                .isFalse();
+    }
+
     private void resetUserSwitcherEnabled() {
         mUms.putUserInfo(new UserInfo(USER_ID, "Test User", 0));
         mUms.setUserRestriction(DISALLOW_USER_SWITCH, false, USER_ID);
diff --git a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
index d021562..d6d5264 100644
--- a/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/powerservicetests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -240,6 +240,10 @@
         cr.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
         when(mContextSpy.getContentResolver()).thenReturn(cr);
 
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_dreamsSupported))
+                .thenReturn(true);
+        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_dreamsEnabledByDefault))
+                .thenReturn(true);
         Settings.Global.putInt(mContextSpy.getContentResolver(),
                 Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
         Settings.Secure.putInt(mContextSpy.getContentResolver(),
@@ -1084,13 +1088,9 @@
     @SuppressWarnings("GuardedBy")
     @Test
     public void testScreensaverActivateOnSleepEnabled_powered_afterTimeout_goesToDreaming() {
-        when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_dreamsSupported))
-                .thenReturn(true);
         when(mBatteryManagerInternalMock.isPowered(anyInt())).thenReturn(true);
         Settings.Secure.putInt(mContextSpy.getContentResolver(),
                 Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ENABLED, 1);
 
         doAnswer(inv -> {
             when(mDreamManagerInternalMock.isDreaming()).thenReturn(true);
@@ -1112,8 +1112,6 @@
     public void testAmbientSuppression_disablesDreamingAndWakesDevice() {
         Settings.Secure.putInt(mContextSpy.getContentResolver(),
                 Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ENABLED, 1);
 
         setDreamsDisabledByAmbientModeSuppressionConfig(true);
         setMinimumScreenOffTimeoutConfig(10000);
@@ -1141,8 +1139,6 @@
     public void testAmbientSuppressionDisabled_shouldNotWakeDevice() {
         Settings.Secure.putInt(mContextSpy.getContentResolver(),
                 Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ENABLED, 1);
 
         setDreamsDisabledByAmbientModeSuppressionConfig(false);
         setMinimumScreenOffTimeoutConfig(10000);
@@ -1169,8 +1165,6 @@
     public void testAmbientSuppression_doesNotAffectDreamForcing() {
         Settings.Secure.putInt(mContextSpy.getContentResolver(),
                 Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ENABLED, 1);
 
         setDreamsDisabledByAmbientModeSuppressionConfig(true);
         setMinimumScreenOffTimeoutConfig(10000);
@@ -1196,8 +1190,6 @@
     public void testBatteryDrainDuringDream() {
         Settings.Secure.putInt(mContextSpy.getContentResolver(),
                 Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1);
-        Settings.Secure.putInt(mContextSpy.getContentResolver(),
-                Settings.Secure.SCREENSAVER_ENABLED, 1);
 
         setMinimumScreenOffTimeoutConfig(100);
         setDreamsBatteryLevelDrainConfig(5);
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
new file mode 100644
index 0000000..05acd9b
--- /dev/null
+++ b/services/tests/powerstatstests/Android.bp
@@ -0,0 +1,52 @@
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "PowerStatsTests",
+
+    // Include all test java files.
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    static_libs: [
+        "services.core",
+        "coretests-aidl",
+        "platformprotosnano",
+        "junit",
+        "truth-prebuilt",
+        "androidx.test.runner",
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
+        "androidx.test.uiautomator_uiautomator",
+        "mockito-target-minus-junit4",
+        "servicestests-utils",
+    ],
+
+    libs: [
+        "android.test.base",
+    ],
+
+    resource_dirs: ["res/"],
+
+    data: [
+        ":BstatsTestApp",
+    ],
+
+    test_suites: [
+        "automotive-tests",
+        "device-tests",
+    ],
+
+    platform_apis: true,
+
+    certificate: "platform",
+
+    dxflags: ["--multi-dex"],
+
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/powerstatstests/AndroidManifest.xml b/services/tests/powerstatstests/AndroidManifest.xml
new file mode 100644
index 0000000..d3a88d2
--- /dev/null
+++ b/services/tests/powerstatstests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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"
+     package="com.android.frameworks.powerstatstests">
+
+    <uses-permission android:name="android.permission.BATTERY_STATS" />
+    <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
+    <uses-permission android:name="android.permission.MANAGE_USERS"/>
+
+    <queries>
+        <package android:name="com.android.coretests.apps.bstatstestapp" />
+    </queries>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.frameworks.powerstatstests"
+         android:label="BatteryStats and PowerStats Services Tests"/>
+</manifest>
diff --git a/services/tests/powerstatstests/AndroidTest.xml b/services/tests/powerstatstests/AndroidTest.xml
new file mode 100644
index 0000000..79b07e8
--- /dev/null
+++ b/services/tests/powerstatstests/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<configuration description="Runs Power Stats Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <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="PowerStatsTests.apk" />
+        <option name="test-file-name" value="BstatsTestApp.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="PowerStatsTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.frameworks.powerstatstests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+        <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
+    </test>
+</configuration>
diff --git a/core/tests/coretests/BstatsTestApp/Android.bp b/services/tests/powerstatstests/BstatsTestApp/Android.bp
similarity index 100%
rename from core/tests/coretests/BstatsTestApp/Android.bp
rename to services/tests/powerstatstests/BstatsTestApp/Android.bp
diff --git a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml b/services/tests/powerstatstests/BstatsTestApp/AndroidManifest.xml
similarity index 100%
rename from core/tests/coretests/BstatsTestApp/AndroidManifest.xml
rename to services/tests/powerstatstests/BstatsTestApp/AndroidManifest.xml
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java
similarity index 100%
rename from core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java
rename to services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java
similarity index 99%
rename from core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java
rename to services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java
index d192fbd..c731e53 100644
--- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java
+++ b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java
@@ -15,8 +15,6 @@
  */
 package com.android.coretests.apps.bstatstestapp;
 
-import com.android.frameworks.coretests.aidl.ICmdCallback;
-
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -24,6 +22,8 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import com.android.frameworks.coretests.aidl.ICmdCallback;
+
 public class Common {
     private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
 
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
similarity index 100%
rename from core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
rename to services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
similarity index 100%
rename from core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
rename to services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java b/services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
similarity index 100%
rename from core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
rename to services/tests/powerstatstests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
diff --git a/services/tests/powerstatstests/OWNERS b/services/tests/powerstatstests/OWNERS
new file mode 100644
index 0000000..9ed0e05
--- /dev/null
+++ b/services/tests/powerstatstests/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 987260
+
+include /BATTERY_STATS_OWNERS
+include /services/core/java/com/android/server/powerstats/OWNERS
diff --git a/services/tests/powerstatstests/TEST_MAPPING b/services/tests/powerstatstests/TEST_MAPPING
new file mode 100644
index 0000000..e1eb1e4
--- /dev/null
+++ b/services/tests/powerstatstests/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+  "presubmit": [
+    {
+      "name": "PowerStatsTests",
+      "options": [
+        {"include-filter": "com.android.server.power.stats"},
+        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
+      "name": "PowerStatsTests",
+      "options": [
+        {"include-filter": "com.android.server.power.stats"},
+        {"exclude-annotation": "org.junit.Ignore"}
+      ]
+    }
+  ]
+}
diff --git a/services/tests/servicestests/res/xml/irq_device_map_1.xml b/services/tests/powerstatstests/res/xml/irq_device_map_1.xml
similarity index 100%
rename from services/tests/servicestests/res/xml/irq_device_map_1.xml
rename to services/tests/powerstatstests/res/xml/irq_device_map_1.xml
diff --git a/services/tests/servicestests/res/xml/irq_device_map_2.xml b/services/tests/powerstatstests/res/xml/irq_device_map_2.xml
similarity index 100%
rename from services/tests/servicestests/res/xml/irq_device_map_2.xml
rename to services/tests/powerstatstests/res/xml/irq_device_map_2.xml
diff --git a/services/tests/servicestests/res/xml/irq_device_map_3.xml b/services/tests/powerstatstests/res/xml/irq_device_map_3.xml
similarity index 100%
rename from services/tests/servicestests/res/xml/irq_device_map_3.xml
rename to services/tests/powerstatstests/res/xml/irq_device_map_3.xml
diff --git a/services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml b/services/tests/powerstatstests/res/xml/power_profile_test_legacy_modem.xml
similarity index 100%
rename from services/tests/servicestests/res/xml/power_profile_test_legacy_modem.xml
rename to services/tests/powerstatstests/res/xml/power_profile_test_legacy_modem.xml
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml b/services/tests/powerstatstests/res/xml/power_profile_test_modem_calculator.xml
similarity index 100%
rename from services/tests/servicestests/res/xml/power_profile_test_modem_calculator.xml
rename to services/tests/powerstatstests/res/xml/power_profile_test_modem_calculator.xml
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml b/services/tests/powerstatstests/res/xml/power_profile_test_modem_calculator_multiactive.xml
similarity index 100%
rename from services/tests/servicestests/res/xml/power_profile_test_modem_calculator_multiactive.xml
rename to services/tests/powerstatstests/res/xml/power_profile_test_modem_calculator_multiactive.xml
diff --git a/services/tests/servicestests/res/xml/power_profile_test_modem_default.xml b/services/tests/powerstatstests/res/xml/power_profile_test_modem_default.xml
similarity index 100%
rename from services/tests/servicestests/res/xml/power_profile_test_modem_default.xml
rename to services/tests/powerstatstests/res/xml/power_profile_test_modem_default.xml
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/AmbientDisplayPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/AudioPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryChargeCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryExternalStatsWorkerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBackgroundStatsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsBinderCallStatsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCounterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCounterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCounterTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCounterTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsCpuTimesTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsDualTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsDualTimerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsDualTimerTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsDualTimerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsDurationTimerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryIteratorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsHistoryTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
index f20f061..5ebc6ca 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsImplTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsImplTest.java
@@ -267,7 +267,7 @@
         final long[][] delta3 = {
                 {98545, 95768795, 76586, 548945, 57846},
                 {788876, 586, 578459, 8776984, 9578923},
-                {3049509483598l, 4597834, 377654, 94589035, 7854},
+                {3049509483598L, 4597834, 377654, 94589035, 7854},
                 {9493, 784, 99895, 8974893, 9879843}
         };
 
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsManagerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsNoteTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsResetTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsResetTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsResetTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java
index 784d673..ee68bf8 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSamplingTimerTest.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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.
+ * 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.power.stats;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSensorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSensorTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSensorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSensorTest.java
index b8f0ce3..9c70f37 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsSensorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsSensorTest.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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.
+ * 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.power.stats;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsServTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsServTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsServTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsServTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java
similarity index 94%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java
index fcae42a..18a366c 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsStopwatchTimerTest.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2017 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
+ * 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.
+ * 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.power.stats;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsTimeBaseTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTimerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsTimerTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTimerTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsTimerTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryStatsUserLifecycleTests.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsProviderTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsRule.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsStoreTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BatteryUsageStatsTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BluetoothPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/BstatsCpuTimesValidationTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/CameraPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/CpuPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/CustomEnergyConsumerPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/EnergyConsumerSnapshotTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/FlashlightPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/GnssPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/IdlePowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
similarity index 97%
rename from services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
index c0f3c77..2edfc8e 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/KernelWakelockReaderTest.java
@@ -1,17 +1,17 @@
 /*
  * Copyright (C) 2016 The Android Open Source Project
  *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
+ * 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.
+ * 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.power.stats;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterArrayTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/LongSamplingCounterTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/LongSamplingCounterTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/LongSamplingCounterTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/MemoryPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
index d3ec0d7..888a168 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerCalculatorTest.java
@@ -46,7 +46,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.frameworks.servicestests.R;
+import com.android.frameworks.powerstatstests.R;
 
 import com.google.common.collect.Range;
 
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/MockBatteryStatsImpl.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/MockClock.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MockClock.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/MockClock.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/MockClock.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/OWNERS b/services/tests/powerstatstests/src/com/android/server/power/stats/OWNERS
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/OWNERS
rename to services/tests/powerstatstests/src/com/android/server/power/stats/OWNERS
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/ScreenPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/SensorPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/SystemServerCpuThreadReaderTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/SystemServicePowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/UserPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/UserPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/UserPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/UserPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/VideoPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/WakelockPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerCalculatorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
similarity index 99%
rename from services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
index b81b776..0dc836b 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java
@@ -34,7 +34,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.frameworks.servicestests.R;
+import com.android.frameworks.powerstatstests.R;
 import com.android.server.power.stats.wakeups.CpuWakeupStats.Wakeup;
 
 import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java
similarity index 98%
rename from services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java
index 47a8f49..9af2884 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java
@@ -23,7 +23,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.frameworks.servicestests.R;
+import com.android.frameworks.powerstatstests.R;
 import com.android.internal.util.CollectionUtils;
 
 import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/wakeups/WakingActivityHistoryTest.java
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java
rename to services/tests/powerstatstests/src/com/android/server/powerstats/IntervalRandomNoiseGeneratorTest.java
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java b/services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
similarity index 100%
rename from services/tests/servicestests/src/com/android/server/powerstats/PowerStatsServiceTest.java
rename to services/tests/powerstatstests/src/com/android/server/powerstats/PowerStatsServiceTest.java
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 3272ce6..92ff7ab 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -112,7 +112,6 @@
     },
 
     data: [
-        ":BstatsTestApp",
         ":JobTestApp",
         ":SimpleServiceTestApp1",
         ":SimpleServiceTestApp2",
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index fbb0ca1..b1d5039 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -28,7 +28,6 @@
     <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="BstatsTestApp.apk" />
         <option name="test-file-name" value="FrameworksServicesTests.apk" />
         <option name="test-file-name" value="JobTestApp.apk" />
         <option name="test-file-name" value="SuspendTestApp.apk" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
index 19fb2c9..0a8c570 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java
@@ -51,7 +51,6 @@
 import android.os.UserHandle;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
-import android.platform.test.annotations.FlakyTest;
 import android.provider.Settings;
 import android.testing.TestableContext;
 import android.util.DebugUtils;
@@ -59,6 +58,7 @@
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.util.ConcurrentUtils;
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 0cfddd3..769be17 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java
@@ -351,6 +351,8 @@
         assertEquals(startFingerprintNow ? BiometricSensor.STATE_AUTHENTICATING
                         : BiometricSensor.STATE_COOKIE_RETURNED,
                 session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());
+        verify(mBiometricContext).updateContext((OperationContextExt) anyObject(),
+                eq(session.isCrypto()));
 
         // start fingerprint sensor if it was delayed
         if (!startFingerprintNow) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
new file mode 100644
index 0000000..99d66c5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsCollectorTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.biometrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+
+import com.android.internal.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class AuthenticationStatsCollectorTest {
+
+    private AuthenticationStatsCollector mAuthenticationStatsCollector;
+    private static final float FRR_THRESHOLD = 0.2f;
+    private static final int USER_ID_1 = 1;
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private Resources mResources;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+                .thenReturn(FRR_THRESHOLD);
+
+        mAuthenticationStatsCollector = new AuthenticationStatsCollector(mContext,
+                0 /* modality */);
+    }
+
+
+    @Test
+    public void authenticate_authenticationSucceeded_mapShouldBeUpdated() {
+        // Assert that the user doesn't exist in the map initially.
+        assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, true /* authenticated*/);
+
+        AuthenticationStats authenticationStats =
+                mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+        assertEquals(USER_ID_1, authenticationStats.getUserId());
+        assertEquals(1, authenticationStats.getTotalAttempts());
+        assertEquals(0, authenticationStats.getRejectedAttempts());
+        assertEquals(0, authenticationStats.getEnrollmentNotifications());
+    }
+
+    @Test
+    public void authenticate_authenticationFailed_mapShouldBeUpdated() {
+        // Assert that the user doesn't exist in the map initially.
+        assertNull(mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1));
+
+        mAuthenticationStatsCollector.authenticate(USER_ID_1, false /* authenticated*/);
+
+        AuthenticationStats authenticationStats =
+                mAuthenticationStatsCollector.getAuthenticationStatsForUser(USER_ID_1);
+        assertEquals(USER_ID_1, authenticationStats.getUserId());
+        assertEquals(1, authenticationStats.getTotalAttempts());
+        assertEquals(1, authenticationStats.getRejectedAttempts());
+        assertEquals(0, authenticationStats.getEnrollmentNotifications());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java
new file mode 100644
index 0000000..e8e72cb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthenticationStatsTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.biometrics;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+public class AuthenticationStatsTest {
+
+    @Test
+    public void authenticate_statsShouldBeUpdated() {
+        AuthenticationStats authenticationStats =
+                new AuthenticationStats(1 /* userId */ , 0 /* totalAttempts */,
+                        0 /* rejectedAttempts */, 0 /* enrollmentNotifications */,
+                        0 /* modality */);
+
+        authenticationStats.authenticate(true /* authenticated */);
+
+        assertEquals(authenticationStats.getTotalAttempts(), 1);
+        assertEquals(authenticationStats.getRejectedAttempts(), 0);
+
+        authenticationStats.authenticate(false /* authenticated */);
+
+        assertEquals(authenticationStats.getTotalAttempts(), 2);
+        assertEquals(authenticationStats.getRejectedAttempts(), 1);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index fc62e75..e79ac09 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.biometrics;
 
+import static android.hardware.biometrics.BiometricAuthenticator.TYPE_CREDENTIAL;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricManager.Authenticators;
@@ -116,9 +117,9 @@
     private static final String ERROR_LOCKOUT = "error_lockout";
     private static final String FACE_SUBTITLE = "face_subtitle";
     private static final String FINGERPRINT_SUBTITLE = "fingerprint_subtitle";
+    private static final String CREDENTIAL_SUBTITLE = "credential_subtitle";
     private static final String DEFAULT_SUBTITLE = "default_subtitle";
 
-
     private static final String FINGERPRINT_ACQUIRED_SENSOR_DIRTY = "sensor_dirty";
 
     private static final int SENSOR_ID_FINGERPRINT = 0;
@@ -143,6 +144,8 @@
     @Mock
     IBiometricAuthenticator mFaceAuthenticator;
     @Mock
+    IBiometricAuthenticator mCredentialAuthenticator;
+    @Mock
     ITrustManager mTrustManager;
     @Mock
     DevicePolicyManager mDevicePolicyManager;
@@ -196,10 +199,12 @@
                 .thenReturn(ERROR_NOT_RECOGNIZED);
         when(mResources.getString(R.string.biometric_error_user_canceled))
                 .thenReturn(ERROR_USER_CANCELED);
-        when(mContext.getString(R.string.biometric_dialog_face_subtitle))
+        when(mContext.getString(R.string.face_dialog_default_subtitle))
                 .thenReturn(FACE_SUBTITLE);
-        when(mContext.getString(R.string.biometric_dialog_fingerprint_subtitle))
+        when(mContext.getString(R.string.fingerprint_dialog_default_subtitle))
                 .thenReturn(FINGERPRINT_SUBTITLE);
+        when(mContext.getString(R.string.screen_lock_dialog_default_subtitle))
+                .thenReturn(CREDENTIAL_SUBTITLE);
         when(mContext.getString(R.string.biometric_dialog_default_subtitle))
                 .thenReturn(DEFAULT_SUBTITLE);
 
@@ -292,7 +297,8 @@
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                Authenticators.DEVICE_CREDENTIAL);
+                Authenticators.DEVICE_CREDENTIAL, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_CREDENTIAL),
@@ -312,7 +318,8 @@
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                Authenticators.DEVICE_CREDENTIAL);
+                Authenticators.DEVICE_CREDENTIAL, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         assertNotNull(mBiometricService.mAuthSession);
@@ -338,7 +345,8 @@
         mBiometricService.onStart();
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_NONE),
@@ -357,7 +365,8 @@
                 mFingerprintAuthenticator);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(TYPE_FINGERPRINT),
@@ -370,7 +379,8 @@
         setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                Authenticators.BIOMETRIC_STRONG);
+                Authenticators.BIOMETRIC_STRONG, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_NONE),
@@ -429,7 +439,8 @@
                 mFingerprintAuthenticator);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(TYPE_FINGERPRINT),
@@ -441,9 +452,9 @@
     public void testAuthenticateFace_shouldShowSubtitleForFace() throws Exception {
         setupAuthForOnly(TYPE_FACE, Authenticators.BIOMETRIC_STRONG);
 
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */,
-                null);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, true /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         assertEquals(FACE_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
@@ -453,9 +464,9 @@
     public void testAuthenticateFingerprint_shouldShowSubtitleForFingerprint() throws Exception {
         setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
 
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */,
-                null);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, true /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         assertEquals(FINGERPRINT_SUBTITLE,
@@ -463,6 +474,19 @@
     }
 
     @Test
+    public void testAuthenticateFingerprint_shouldShowSubtitleForCredential() throws Exception {
+        setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL);
+
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, true /* useDefaultSubtitle */,
+                true /* deviceCredentialAllowed */);
+        waitForIdle();
+
+        assertEquals(CREDENTIAL_SUBTITLE,
+                mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
+    }
+
+    @Test
     public void testAuthenticateBothFpAndFace_shouldShowDefaultSubtitle() throws Exception {
         final int[] modalities = new int[] {
                 TYPE_FINGERPRINT,
@@ -476,9 +500,9 @@
 
         setupAuthForMultiple(modalities, strengths);
 
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */,
-                null);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, true /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         assertEquals(DEFAULT_SUBTITLE, mBiometricService.mAuthSession.mPromptInfo.getSubtitle());
@@ -492,7 +516,8 @@
         // Disabled in user settings receives onError
         when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(false);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(BiometricAuthenticator.TYPE_NONE),
@@ -506,7 +531,8 @@
                 anyInt() /* modality */, anyInt() /* userId */))
                 .thenReturn(true);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
         final byte[] HAT = generateRandomHAT();
@@ -524,7 +550,8 @@
                 anyInt() /* modality */, anyInt() /* userId */))
                 .thenReturn(false);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded(
                 SENSOR_ID_FACE,
@@ -552,7 +579,8 @@
             throws Exception {
         // Start testing the happy path
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         // Creates a pending auth session with the correct initial states
@@ -632,7 +660,8 @@
                 .thenReturn(true);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 true /* requireConfirmation */,
-                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK,
+                false /* useDefaultSubtitle*/, false /* deviceCredentialAllowed */);
         waitForIdle();
 
         assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL,
@@ -702,7 +731,8 @@
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 true /* requireConfirmation */,
-                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG);
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG,
+                false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */);
         waitForIdle();
 
         verify(mReceiver1).onError(anyInt() /* modality */,
@@ -754,7 +784,8 @@
                 false /* requireConfirmation */, null /* authenticators */);
 
         invokeAuthenticate(mBiometricService.mImpl, mReceiver2, false /* requireConfirmation */,
-                null /* authenticators */);
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         verify(mReceiver1).onError(
@@ -887,7 +918,8 @@
         setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */,
-                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK);
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK,
+                false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */);
         waitForIdle();
 
         assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState());
@@ -920,8 +952,9 @@
     public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed()
             throws Exception {
         setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, null /* authenticators */);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         mBiometricService.mAuthSession.mSensorReceiver.onError(
@@ -957,8 +990,9 @@
         setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
         when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
                 .thenReturn(lockoutMode);
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, null /* authenticators */);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         // Modality and error are sent
@@ -996,8 +1030,9 @@
         when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
                 .thenReturn(lockoutMode);
         when(mFaceAuthenticator.hasEnrolledTemplates(anyInt(), any())).thenReturn(false);
-        invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, null /* authenticators */);
+        invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */,
+                null /* authenticators */, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
 
         // The lockout error should be sent, instead of ERROR_NONE_ENROLLED. See b/286923477.
@@ -1014,7 +1049,8 @@
                 .thenReturn(LockoutTracker.LOCKOUT_PERMANENT);
         invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */,
-                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG);
+                Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_STRONG,
+                false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */);
         waitForIdle();
 
         verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt());
@@ -1503,7 +1539,8 @@
         assertEquals(BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
                 invokeCanAuthenticate(mBiometricService, authenticators));
         long requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
-                false /* requireConfirmation */, authenticators);
+                false /* requireConfirmation */, authenticators, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         verify(mReceiver1).onError(
                 eq(TYPE_FINGERPRINT),
@@ -1539,7 +1576,8 @@
                 invokeCanAuthenticate(mBiometricService, authenticators));
         requestId = invokeAuthenticate(mBiometricService.mImpl, mReceiver1,
                 false /* requireConfirmation */,
-                authenticators);
+                authenticators, false /* useDefaultSubtitle */,
+                false /* deviceCredentialAllowed */);
         waitForIdle();
         assertTrue(Utils.isCredentialRequested(mBiometricService.mAuthSession.mPromptInfo));
         verify(mBiometricService.mStatusBarService).showAuthenticationDialog(
@@ -1749,6 +1787,11 @@
             mBiometricService.mImpl.registerAuthenticator(SENSOR_ID_FACE, modality, strength,
                     mFaceAuthenticator);
         }
+
+        if ((modality & TYPE_CREDENTIAL) != 0) {
+            when(mTrustManager.isDeviceSecure(anyInt(), anyInt()))
+                    .thenReturn(true);
+        }
     }
 
     // TODO: Reduce duplicated code, currently we cannot start the BiometricService in setUp() for
@@ -1799,7 +1842,8 @@
             Integer authenticators) throws Exception {
         // Request auth, creates a pending session
         final long requestId = invokeAuthenticate(
-                service, receiver, requireConfirmation, authenticators);
+                service, receiver, requireConfirmation, authenticators,
+                false /* useDefaultSubtitle */, false /* deviceCredentialAllowed */);
         waitForIdle();
 
         startPendingAuthSession(mBiometricService);
@@ -1827,7 +1871,8 @@
 
     private static long invokeAuthenticate(IBiometricService.Stub service,
             IBiometricServiceReceiver receiver, boolean requireConfirmation,
-            Integer authenticators) throws Exception {
+            Integer authenticators, boolean useDefaultSubtitle,
+            boolean deviceCredentialAllowed) throws Exception {
         return service.authenticate(
                 new Binder() /* token */,
                 0 /* operationId */,
@@ -1835,7 +1880,8 @@
                 receiver,
                 TEST_PACKAGE_NAME /* packageName */,
                 createTestPromptInfo(requireConfirmation, authenticators,
-                        false /* checkDevicePolicy */));
+                        false /* checkDevicePolicy */, useDefaultSubtitle,
+                        deviceCredentialAllowed));
     }
 
     private static long invokeAuthenticateForWorkApp(IBiometricService.Stub service,
@@ -1847,16 +1893,19 @@
                 receiver,
                 TEST_PACKAGE_NAME /* packageName */,
                 createTestPromptInfo(false /* requireConfirmation */, authenticators,
-                        true /* checkDevicePolicy */));
+                        true /* checkDevicePolicy */, false /* useDefaultSubtitle */,
+                        false /* deviceCredentialAllowed */));
     }
 
     private static PromptInfo createTestPromptInfo(
             boolean requireConfirmation,
             Integer authenticators,
-            boolean checkDevicePolicy) {
+            boolean checkDevicePolicy,
+            boolean useDefaultSubtitle,
+            boolean deviceCredentialAllowed) {
         final PromptInfo promptInfo = new PromptInfo();
         promptInfo.setConfirmationRequested(requireConfirmation);
-        promptInfo.setUseDefaultSubtitle(true);
+        promptInfo.setUseDefaultSubtitle(useDefaultSubtitle);
 
         if (authenticators != null) {
             promptInfo.setAuthenticators(authenticators);
@@ -1864,6 +1913,7 @@
         if (checkDevicePolicy) {
             promptInfo.setDisallowBiometricsIfPolicyExists(checkDevicePolicy);
         }
+        promptInfo.setDeviceCredentialAllowed(deviceCredentialAllowed);
         return promptInfo;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 612f717..a508718 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -39,6 +39,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.server.biometrics.AuthenticationStatsCollector;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 
 import org.junit.Before;
@@ -65,6 +66,8 @@
     @Mock
     private BiometricFrameworkStatsLogger mSink;
     @Mock
+    private AuthenticationStatsCollector mAuthenticationStatsCollector;
+    @Mock
     private SensorManager mSensorManager;
     @Mock
     private BaseClientMonitor mClient;
@@ -87,7 +90,8 @@
     }
 
     private BiometricLogger createLogger(int statsModality, int statsAction, int statsClient) {
-        return new BiometricLogger(statsModality, statsAction, statsClient, mSink, mSensorManager);
+        return new BiometricLogger(statsModality, statsAction, statsClient, mSink,
+                mAuthenticationStatsCollector, mSensorManager);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
index d1d6e9d..f43120d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceProviderTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.biometrics.common.CommonProps;
 import android.hardware.biometrics.face.IFace;
 import android.hardware.biometrics.face.ISession;
@@ -39,6 +40,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
@@ -59,11 +61,15 @@
 
     private static final String TAG = "FaceProviderTest";
 
+    private static final float FRR_THRESHOLD = 0.2f;
+
     @Mock
     private Context mContext;
     @Mock
     private UserManager mUserManager;
     @Mock
+    private Resources mResources;
+    @Mock
     private IFace mDaemon;
     @Mock
     private BiometricContext mBiometricContext;
@@ -86,6 +92,10 @@
         when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
         when(mDaemon.createSession(anyInt(), anyInt(), any())).thenReturn(mock(ISession.class));
 
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+                .thenReturn(FRR_THRESHOLD);
+
         final SensorProps sensor1 = new SensorProps();
         sensor1.commonProps = new CommonProps();
         sensor1.commonProps.sensorId = 0;
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
index d174533..e558c4d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/hidl/Face10Test.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.face.FaceSensorProperties;
@@ -41,6 +42,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -65,12 +67,15 @@
     private static final String TAG = "Face10Test";
     private static final int SENSOR_ID = 1;
     private static final int USER_ID = 20;
+    private static final float FRR_THRESHOLD = 0.2f;
 
     @Mock
     private Context mContext;
     @Mock
     private UserManager mUserManager;
     @Mock
+    private Resources mResources;
+    @Mock
     private BiometricScheduler mScheduler;
     @Mock
     private BiometricContext mBiometricContext;
@@ -93,6 +98,10 @@
         when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
         when(mUserManager.getAliveUsers()).thenReturn(new ArrayList<>());
 
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mResources.getFraction(R.fraction.config_biometricNotificationFrrThreshold, 1, 1))
+                .thenReturn(FRR_THRESHOLD);
+
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
         final int maxEnrollmentsPerUser = 1;
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 4512cc02..bcbbcd4 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -363,7 +363,8 @@
                 new CameraAccessController(mContext, mLocalService, mCameraAccessBlockedCallback);
 
         mAssociationInfo = new AssociationInfo(/* associationId= */ 1, 0, null,
-                MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false, 0, 0, -1);
+                null, MacAddress.BROADCAST_ADDRESS, "", null, null, true, false, false,
+                0, 0, -1);
 
         mVdms = new VirtualDeviceManagerService(mContext);
         mLocalService = mVdms.getLocalServiceInstance();
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
index ffe088c..a621055 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecAtomLoggingTest.java
@@ -19,14 +19,17 @@
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
 import static com.android.server.hdmi.Constants.PATH_RELATIONSHIP_ANCESTOR;
+import static com.android.server.hdmi.HdmiControlService.WAKE_UP_SCREEN_ON;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -307,4 +310,60 @@
                         HdmiCecAtomWriter.FEATURE_ABORT_OPCODE_UNKNOWN,
                         HdmiStatsEnums.FEATURE_ABORT_REASON_UNKNOWN);
     }
+
+    @Test
+    public void testDsmStatusChanged_toggleDsmStatus_ArcSupported_writesAtom() {
+        doReturn(true).when(mHdmiControlServiceSpy).isArcSupported();
+        mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .dsmStatusChanged(eq(true), eq(true),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+    }
+
+    @Test
+    public void testDsmStatusChanged_toggleDsmStatus_ArcNotSupported_writesAtom() {
+        doReturn(false).when(mHdmiControlServiceSpy).isArcSupported();
+        mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_ENABLED);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .dsmStatusChanged(eq(false), eq(true),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+    }
+
+    @Test
+    public void testDsmStatusChanged_onWakeUp_ArcSupported_writesAtom_logReasonWake() {
+        mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_DISABLED);
+        Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+        doReturn(true).when(mHdmiControlServiceSpy).isArcSupported();
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .dsmStatusChanged(eq(true), eq(false),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_WAKE));
+        verify(mHdmiCecAtomWriterSpy, never())
+                .dsmStatusChanged(anyBoolean(), anyBoolean(),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+    }
+
+    @Test
+    public void testDsmStatusChanged_onWakeUp_ArcNotSupported_writesAtom_logReasonWake() {
+        mHdmiControlServiceSpy.setSoundbarMode(HdmiControlManager.SOUNDBAR_MODE_DISABLED);
+        Mockito.clearInvocations(mHdmiCecAtomWriterSpy);
+
+        doReturn(false).when(mHdmiControlServiceSpy).isArcSupported();
+        mHdmiControlServiceSpy.onWakeUp(WAKE_UP_SCREEN_ON);
+        mTestLooper.dispatchAll();
+
+        verify(mHdmiCecAtomWriterSpy, times(1))
+                .dsmStatusChanged(eq(false), eq(false),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_WAKE));
+        verify(mHdmiCecAtomWriterSpy, never())
+                .dsmStatusChanged(anyBoolean(), anyBoolean(),
+                        eq(HdmiStatsEnums.LOG_REASON_DSM_SETTING_TOGGLED));
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index 55e5dbd..c632727f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -174,6 +174,20 @@
                     protected boolean earcBlocksArcConnection() {
                         return mEarcBlocksArc;
                     }
+
+                    /**
+                     * Override displayOsd to prevent it from broadcasting an intent, which
+                     * can trigger a SecurityException.
+                    */
+                    @Override
+                    void displayOsd(int messageId) {
+                        // do nothing
+                    }
+
+                    @Override
+                    void displayOsd(int messageId, int extra) {
+                        // do nothing
+                    }
                 };
 
         mHdmiControlService.setIoLooper(mMyLooper);
diff --git a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
index 45fff48..2cd9198 100644
--- a/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/prediction/SharesheetModelScorerTest.java
@@ -57,6 +57,7 @@
     private static final String PACKAGE_3 = "pkg3";
     private static final String CLASS_1 = "cls1";
     private static final String CLASS_2 = "cls2";
+    private static final String CHOOSER_ACTIVITY = "ChooserActivity";
     private static final double DELTA = 1e-6;
     private static final long NOW = System.currentTimeMillis();
     private static final Range<Long> WITHIN_ONE_DAY = new Range(
@@ -246,7 +247,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         // Verification
         assertEquals(0.514f, mShareTarget1.getScore(), DELTA);
@@ -278,7 +279,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, times(1)).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
                 anySet());
@@ -311,7 +312,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, times(1)).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
                 anySet());
@@ -349,7 +350,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 4, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, never()).queryAppUsageStats(anyInt(), anyLong(), anyLong(),
                 anySet());
@@ -377,7 +378,7 @@
                 anyLong())).thenReturn(
                 List.of(createUsageEvent(PACKAGE_2),
                         createUsageEvent(PACKAGE_3),
-                        createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
+                        createUsageEvent(CHOOSER_ACTIVITY),
                         createUsageEvent(PACKAGE_3),
                         createUsageEvent(PACKAGE_3))
         );
@@ -385,7 +386,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
                 anyLong());
@@ -413,7 +414,7 @@
                 anyLong())).thenReturn(
                 List.of(createUsageEvent(PACKAGE_3),
                         createUsageEvent(PACKAGE_3),
-                        createUsageEvent(SharesheetModelScorer.CHOOSER_ACTIVITY),
+                        createUsageEvent(CHOOSER_ACTIVITY),
                         createUsageEvent(PACKAGE_3),
                         createUsageEvent(PACKAGE_3))
         );
@@ -421,7 +422,7 @@
         SharesheetModelScorer.computeScoreForAppShare(
                 List.of(mShareTarget1, mShareTarget2, mShareTarget3, mShareTarget4, mShareTarget5,
                         mShareTarget6),
-                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID);
+                Event.TYPE_SHARE_TEXT, 20, NOW, mDataManager, USER_ID, CHOOSER_ACTIVITY);
 
         verify(mDataManager, times(1)).queryAppMovingToForegroundEvents(anyInt(), anyLong(),
                 anyLong());
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index f834cb2..560a919 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -138,6 +138,17 @@
             R.styleable.AndroidManifestApplication_taskAffinity,
             1024
         )
+        validateTagAttr(
+            tag,
+            "zygotePreloadName",
+            R.styleable.AndroidManifestApplication_zygotePreloadName,
+            1024
+        )
+        validateTagAttrComponentName(
+            tag,
+            "zygotePreloadName",
+            R.styleable.AndroidManifestApplication_zygotePreloadName
+        )
         validateTagCount("profileable", 100, tag)
         validateTagCount("uses-native-library", 100, tag)
         validateTagCount("receiver", 1000, tag)
@@ -507,8 +518,26 @@
             }
         }
 
-        val failNames = arrayOf("com.android.TestClass:", "-TestClass", "TestClass.", ".", "..")
-        for (name in failNames) {
+        val badNames = arrayOf(
+            ";",
+            ",",
+            "[",
+            "]",
+            "(",
+            ")",
+            "{",
+            "}",
+            ":",
+            "?",
+            "-",
+            "%",
+            "^",
+            "*",
+            "|",
+            "/",
+            "\\"
+        )
+        for (name in badNames) {
             val xml = "<$tag $attr=\"$name\" />"
             pullParser.setInput(ByteArrayInputStream(xml.toByteArray()), null)
             val validator = Validator()
diff --git a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
index 726a4e2..9fca513 100644
--- a/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/hint/HintManagerServiceTest.java
@@ -27,10 +27,11 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
@@ -73,6 +74,7 @@
     private static final int TGID = Process.getThreadGroupLeader(TID);
     private static final int[] SESSION_TIDS_A = new int[] {TID};
     private static final int[] SESSION_TIDS_B = new int[] {TID};
+    private static final int[] SESSION_TIDS_C = new int[] {TID};
     private static final long[] DURATIONS_THREE = new long[] {1L, 100L, 1000L};
     private static final long[] TIMESTAMPS_THREE = new long[] {1L, 2L, 3L};
     private static final long[] DURATIONS_ZERO = new long[] {};
@@ -94,6 +96,8 @@
               eq(DEFAULT_TARGET_DURATION))).thenReturn(1L);
         when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_B),
               eq(DEFAULT_TARGET_DURATION))).thenReturn(2L);
+        when(mNativeWrapperMock.halCreateHintSession(eq(TGID), eq(UID), eq(SESSION_TIDS_C),
+              eq(0L))).thenReturn(1L);
         when(mAmInternalMock.getIsolatedProcesses(anyInt())).thenReturn(null);
         LocalServices.removeServiceForTest(ActivityManagerInternal.class);
         LocalServices.addService(ActivityManagerInternal.class, mAmInternalMock);
@@ -138,6 +142,10 @@
         IHintSession b = service.getBinderServiceInstance().createHintSession(token,
                 SESSION_TIDS_B, DEFAULT_TARGET_DURATION);
         assertNotEquals(a, b);
+
+        IHintSession c = service.getBinderServiceInstance().createHintSession(token,
+                SESSION_TIDS_C, 0L);
+        assertNotNull(c);
     }
 
     @Test
@@ -338,4 +346,35 @@
         a.setThreads(SESSION_TIDS_A);
         verify(mNativeWrapperMock, never()).halSetThreads(anyLong(), any());
     }
+
+    @Test
+    public void testSetMode() throws Exception {
+        HintManagerService service = createService();
+        IBinder token = new Binder();
+
+        AppHintSession a = (AppHintSession) service.getBinderServiceInstance()
+                .createHintSession(token, SESSION_TIDS_A, DEFAULT_TARGET_DURATION);
+
+        a.setMode(0, true);
+        verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(),
+                eq(0), eq(true));
+
+        a.setMode(0, false);
+        verify(mNativeWrapperMock, times(1)).halSetMode(anyLong(),
+                eq(0), eq(false));
+
+        assertThrows(IllegalArgumentException.class, () -> {
+            a.setMode(-1, true);
+        });
+
+        reset(mNativeWrapperMock);
+        // Set session to background, then the duration would not be updated.
+        service.mUidObserver.onUidStateChanged(
+                a.mUid, ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND, 0, 0);
+        FgThread.getHandler().runWithScissors(() -> { }, 500);
+        assertFalse(a.updateHintAllowed());
+        a.setMode(0, true);
+        verify(mNativeWrapperMock, never()).halSetMode(anyLong(), anyInt(), anyBoolean());
+    }
+
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java b/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
deleted file mode 100644
index 48290e5..0000000
--- a/services/tests/servicestests/src/com/android/server/power/stats/BatteryStatsTests.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.power.stats;
-
-import org.junit.runner.RunWith;
-import org.junit.runners.Suite;
-
-@RunWith(Suite.class)
-@Suite.SuiteClasses({
-        AmbientDisplayPowerCalculatorTest.class,
-        AudioPowerCalculatorTest.class,
-        BatteryChargeCalculatorTest.class,
-        BatteryExternalStatsWorkerTest.class,
-        BatteryStatsCpuTimesTest.class,
-        BatteryStatsBackgroundStatsTest.class,
-        BatteryStatsBinderCallStatsTest.class,
-        BatteryStatsCounterTest.class,
-        BatteryStatsDualTimerTest.class,
-        BatteryStatsDurationTimerTest.class,
-        BatteryStatsHistoryIteratorTest.class,
-        BatteryStatsHistoryTest.class,
-        BatteryStatsImplTest.class,
-        BatteryStatsManagerTest.class,
-        BatteryStatsNoteTest.class,
-        BatteryStatsSamplingTimerTest.class,
-        BatteryStatsSensorTest.class,
-        BatteryStatsServTest.class,
-        BatteryStatsStopwatchTimerTest.class,
-        BatteryStatsTimeBaseTest.class,
-        BatteryStatsTimerTest.class,
-        BatteryUsageStatsProviderTest.class,
-        BatteryUsageStatsTest.class,
-        BatteryUsageStatsStoreTest.class,
-        BatteryStatsUserLifecycleTests.class,
-        BluetoothPowerCalculatorTest.class,
-        BstatsCpuTimesValidationTest.class,
-        CameraPowerCalculatorTest.class,
-        CpuPowerCalculatorTest.class,
-        CustomEnergyConsumerPowerCalculatorTest.class,
-        FlashlightPowerCalculatorTest.class,
-        GnssPowerCalculatorTest.class,
-        IdlePowerCalculatorTest.class,
-        KernelWakelockReaderTest.class,
-        LongSamplingCounterTest.class,
-        LongSamplingCounterArrayTest.class,
-        EnergyConsumerSnapshotTest.class,
-        MobileRadioPowerCalculatorTest.class,
-        ScreenPowerCalculatorTest.class,
-        SensorPowerCalculatorTest.class,
-        SystemServerCpuThreadReaderTest.class,
-        SystemServicePowerCalculatorTest.class,
-        UserPowerCalculatorTest.class,
-        VideoPowerCalculatorTest.class,
-        WakelockPowerCalculatorTest.class,
-        WifiPowerCalculatorTest.class,
-})
-public class BatteryStatsTests {
-}
diff --git a/services/tests/servicestests/src/com/android/server/powerstats/OWNERS b/services/tests/servicestests/src/com/android/server/powerstats/OWNERS
deleted file mode 100644
index 12f13ea..0000000
--- a/services/tests/servicestests/src/com/android/server/powerstats/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/powerstats/OWNERS
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
index b94ed01..d04c518 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ConditionProvidersTest.java
@@ -18,10 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
 import android.content.ServiceConnection;
@@ -51,6 +55,7 @@
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
+        mContext.ensureTestableResources();
 
         mProviders = new ConditionProviders(mContext, mUserProfiles, mIpm);
         mProviders.setCallback(mCallback);
@@ -131,4 +136,21 @@
         verify(mCallback).onConditionChanged(eq(Uri.parse("b")), eq(conditionsToNotify[3]));
         verifyNoMoreInteractions(mCallback);
     }
+
+    @Test
+    public void testRemoveDefaultFromConfig() {
+        final int userId = 0;
+        ComponentName oldDefaultComponent = ComponentName.unflattenFromString("package/Component1");
+
+        when(mContext.getResources().getString(
+                com.android.internal.R.string.config_defaultDndDeniedPackages))
+                .thenReturn("package");
+        mProviders.setPackageOrComponentEnabled(oldDefaultComponent.flattenToString(),
+                userId, true, true /*enabled*/, false /*userSet*/);
+        assertEquals("package", mProviders.getApproved(userId, true));
+
+        mProviders.removeDefaultFromConfig(userId);
+
+        assertTrue(mProviders.getApproved(userId, true).isEmpty());
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
index 95fae07..7b16500 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java
@@ -15,17 +15,20 @@
  */
 package com.android.server.notification;
 
-import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.NO_SORT_BY_INTERRUPTIVENESS;
+import static android.telecom.TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
@@ -33,8 +36,10 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Person;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -47,6 +52,8 @@
 import android.telecom.TelecomManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import androidx.test.runner.AndroidJUnit4;
+
 import com.android.internal.config.sysui.SystemUiSystemPropertiesFlags;
 import com.android.server.UiServiceTestCase;
 
@@ -54,16 +61,17 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 
 @SmallTest
-@RunWith(Parameterized.class)
+@RunWith(AndroidJUnit4.class)
 public class NotificationComparatorTest extends UiServiceTestCase {
     @Mock Context mMockContext;
     @Mock TelecomManager mTm;
@@ -97,24 +105,9 @@
     private NotificationRecord mRecordColorized;
     private NotificationRecord mRecordColorizedCall;
 
-    @Parameterized.Parameters(name = "sortByInterruptiveness={0}")
-    public static Boolean[] getSortByInterruptiveness() {
-        return new Boolean[] { true, false };
-    }
-
-    @Parameterized.Parameter
-    public boolean mSortByInterruptiveness;
-
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
-        SystemUiSystemPropertiesFlags.TEST_RESOLVER = flag -> {
-            if (flag.mSysPropKey.equals(NO_SORT_BY_INTERRUPTIVENESS.mSysPropKey)) {
-                return !mSortByInterruptiveness;
-            }
-            return new SystemUiSystemPropertiesFlags.DebugResolver().isEnabled(flag);
-        };
-
         int userId = UserHandle.myUserId();
 
         final Resources res = mContext.getResources();
@@ -309,13 +302,8 @@
         expected.add(mNoMediaSessionMedia);
         expected.add(mRecordCheater);
         expected.add(mRecordCheaterColorized);
-        if (mSortByInterruptiveness) {
-            expected.add(mRecordMinCall);
-            expected.add(mRecordMinCallNonInterruptive);
-        } else {
-            expected.add(mRecordMinCallNonInterruptive);
-            expected.add(mRecordMinCall);
-        }
+        expected.add(mRecordMinCallNonInterruptive);
+        expected.add(mRecordMinCall);
 
         List<NotificationRecord> actual = new ArrayList<>();
         actual.addAll(expected);
@@ -330,11 +318,7 @@
     public void testRankingScoreOverrides() {
         NotificationComparator comp = new NotificationComparator(mMockContext);
         NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive);
-        if (mSortByInterruptiveness) {
-            assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0);
-        } else {
-            assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
-        }
+        assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
 
         when(recordMinCallNonInterruptive.getRankingScore()).thenReturn(1f);
         assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) > 0);
@@ -360,6 +344,73 @@
         assertTrue(comp.isImportantPeople(mRecordContact));
     }
 
+    @Test
+    public void testChangeDialerPackageWhileSorting() throws InterruptedException {
+        final int halfList = 100;
+        int userId = UserHandle.myUserId();
+        when(mTm.getDefaultDialerPackage()).thenReturn("B");
+
+        ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
+                BroadcastReceiver.class);
+        NotificationComparator comparator = new NotificationComparator(mMockContext);
+        verify(mMockContext).registerReceiver(broadcastReceiverCaptor.capture(), any());
+        BroadcastReceiver dialerChangedBroadcastReceiver = broadcastReceiverCaptor.getValue();
+
+        ArrayList<NotificationRecord> records = new ArrayList<>();
+        for (int i = 0; i < halfList; i++) {
+            Notification notifCallFromPkgA = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
+                    .setCategory(Notification.CATEGORY_CALL)
+                    .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                    .build();
+            records.add(new NotificationRecord(mMockContext,
+                    new StatusBarNotification("A", "A", 2 * i, "callA", callUid, callUid,
+                            notifCallFromPkgA, new UserHandle(userId), "", 0),
+                    getDefaultChannel()));
+
+            Notification notifCallFromPkgB = new Notification.Builder(mMockContext, TEST_CHANNEL_ID)
+                    .setCategory(Notification.CATEGORY_CALL)
+                    .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+                    .build();
+            records.add(new NotificationRecord(mMockContext,
+                    new StatusBarNotification("B", "B", 2 * i + 1, "callB", callUid, callUid,
+                            notifCallFromPkgB, new UserHandle(userId), "", 0),
+                    getDefaultChannel()));
+        }
+
+        CountDownLatch allDone = new CountDownLatch(2);
+        new Thread(() -> {
+            // The lock prevents the other thread from changing the dialer package mid-sort, so:
+            // 1) Results should be "all B before all A" (asserted below).
+            // 2) No "IllegalArgumentException: Comparison method violates its general contract!"
+            synchronized (comparator.mStateLock) {
+                records.sort(comparator);
+                allDone.countDown();
+            }
+        }).start();
+
+        new Thread(() -> {
+            String nextDialer = "A";
+            while (allDone.getCount() == 2) {
+                Intent dialerChangedIntent = new Intent();
+                dialerChangedIntent.putExtra(EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, nextDialer);
+                dialerChangedBroadcastReceiver.onReceive(mMockContext, dialerChangedIntent);
+                nextDialer = nextDialer.equals("A") ? "B" : "A";
+            }
+            allDone.countDown();
+        }).start();
+
+        allDone.await();
+
+        for (int i = 0; i < halfList; i++) {
+            assertWithMessage("Wrong element in position #" + i)
+                    .that(records.get(i).getSbn().getPackageName()).isEqualTo("B");
+        }
+        for (int i = halfList; i < 2 * halfList; i++) {
+            assertWithMessage("Wrong element in position #" + i)
+                    .that(records.get(i).getSbn().getPackageName()).isEqualTo("A");
+        }
+    }
+
     private NotificationChannel getDefaultChannel() {
         return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
                 NotificationManager.IMPORTANCE_LOW);
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 bdee99b..e1f3c2b 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -117,6 +117,7 @@
 import static org.mockito.Mockito.clearInvocations;
 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;
@@ -12431,6 +12432,21 @@
         verify(mDevicePolicyManager).isActiveDeviceOwner(uid);
     }
 
+    @Test
+    public void testResetDefaultDnd() {
+        TestableNotificationManagerService service = spy(mService);
+        UserInfo user = new UserInfo(0, "owner", 0);
+        when(mUm.getAliveUsers()).thenReturn(List.of(user));
+        doReturn(false).when(service).isDNDMigrationDone(anyInt());
+
+        service.resetDefaultDndIfNecessary();
+
+        verify(mConditionProviders, times(1)).removeDefaultFromConfig(user.id);
+        verify(mConditionProviders, times(1)).resetDefaultFromConfig();
+        verify(service, times(1)).allowDndPackages(user.id);
+        verify(service, times(1)).setDNDMigrationDone(user.id);
+    }
+
     private static <T extends Parcelable> T parcelAndUnparcel(T source,
             Parcelable.Creator<T> creator) {
         Parcel parcel = Parcel.obtain();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index c242554..81d939f 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -600,7 +600,7 @@
         NotificationChannel channel2 =
                 new NotificationChannel("id2", "name2", IMPORTANCE_LOW);
         channel2.setDescription("descriptions for all");
-        channel2.setSound(SOUND_URI, mAudioAttributes);
+        channel2.setSound(CANONICAL_SOUND_URI, mAudioAttributes);
         channel2.enableLights(true);
         channel2.setBypassDnd(true);
         channel2.setLockscreenVisibility(VISIBILITY_SECRET);
@@ -1374,6 +1374,8 @@
                 .when(mTestIContentProvider).uncanonicalize(any(), eq(CANONICAL_SOUND_URI));
         doReturn(localUri)
                 .when(mTestIContentProvider).uncanonicalize(any(), eq(canonicalBasedOnLocal));
+        doReturn(canonicalBasedOnLocal)
+                .when(mTestIContentProvider).canonicalize(any(), eq(localUri));
 
         NotificationChannel channel =
                 new NotificationChannel("id", "name", IMPORTANCE_LOW);
@@ -1387,7 +1389,7 @@
 
         NotificationChannel actualChannel = mHelper.getNotificationChannel(
                 PKG_N_MR1, UID_N_MR1, channel.getId(), false);
-        assertEquals(localUri, actualChannel.getSound());
+        assertEquals(canonicalBasedOnLocal, actualChannel.getSound());
     }
 
     @Test
diff --git a/services/tests/vibrator/TEST_MAPPING b/services/tests/vibrator/TEST_MAPPING
index f0a7e47..39bd238 100644
--- a/services/tests/vibrator/TEST_MAPPING
+++ b/services/tests/vibrator/TEST_MAPPING
@@ -3,9 +3,8 @@
     {
       "name": "FrameworksVibratorServicesTests",
       "options": [
-        {"exclude-annotation": "android.platform.test.annotations.LargeTest"},
-        {"exclude-annotation": "android.platform.test.annotations.FlakyTest"},
         {"exclude-annotation": "androidx.test.filters.FlakyTest"},
+        {"exclude-annotation": "androidx.test.filters.LargeTest"},
         {"exclude-annotation": "org.junit.Ignore"}
       ]
     }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index edc5df2..44cf333 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -60,11 +60,11 @@
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
-import android.platform.test.annotations.FlakyTest;
-import android.platform.test.annotations.LargeTest;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.LargeTest;
 
 import com.android.server.LocalServices;
 
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 3db53eb..3eed0b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2939,6 +2939,9 @@
         // transform to activity1.
         int rotation = (mDisplayContent.getRotation() + 1) % 4;
         mDisplayContent.setFixedRotationLaunchingApp(activity, rotation);
+        // The configuration with rotation change should not trigger task-association.
+        assertNotNull(activity.mStartingData);
+        assertNull(activity.mStartingData.mAssociatedTask);
         doReturn(rotation).when(mDisplayContent)
                 .rotationForActivityInDifferentOrientation(topActivity);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
index 0eca8c9..98f1843 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivitySnapshotControllerTests.java
@@ -18,6 +18,9 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
 import static org.junit.Assert.assertEquals;
 
 import android.platform.test.annotations.Presubmit;
@@ -28,6 +31,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+
 /**
  * Test class for {@link ActivitySnapshotController}.
  *
@@ -42,13 +47,13 @@
     private ActivitySnapshotController mActivitySnapshotController;
     @Before
     public void setUp() throws Exception {
+        spyOn(mWm.mSnapshotController.mActivitySnapshotController);
         mActivitySnapshotController = mWm.mSnapshotController.mActivitySnapshotController;
+        doReturn(false).when(mActivitySnapshotController).shouldDisableSnapshots();
         mActivitySnapshotController.resetTmpFields();
     }
     @Test
     public void testOpenActivityTransition() {
-        final SnapshotController.TransitionState transitionState =
-                new SnapshotController.TransitionState();
         final Task task = createTask(mDisplayContent);
         // note for createAppWindow: the new child is added at index 0
         final WindowState openingWindow = createAppWindow(task,
@@ -59,14 +64,12 @@
                 "closingWindow");
         closingWindow.mActivityRecord.commitVisibility(
                 false /* visible */, true /* performLayout */);
-        transitionState.addParticipant(closingWindow.mActivityRecord, false);
-        transitionState.addParticipant(openingWindow.mActivityRecord, true);
-        mActivitySnapshotController.handleOpenActivityTransition(transitionState);
+        final ArrayList<WindowContainer> windows = new ArrayList<>();
+        windows.add(openingWindow.mActivityRecord);
+        windows.add(closingWindow.mActivityRecord);
+        mActivitySnapshotController.handleTransitionFinish(windows);
 
-        assertEquals(1, mActivitySnapshotController.mPendingCaptureActivity.size());
         assertEquals(0, mActivitySnapshotController.mPendingRemoveActivity.size());
-        assertEquals(closingWindow.mActivityRecord,
-                mActivitySnapshotController.mPendingCaptureActivity.valueAt(0));
         mActivitySnapshotController.resetTmpFields();
 
         // simulate three activity
@@ -74,19 +77,15 @@
                 "belowClose");
         belowClose.mActivityRecord.commitVisibility(
                 false /* visible */, true /* performLayout */);
-        mActivitySnapshotController.handleOpenActivityTransition(transitionState);
-        assertEquals(1, mActivitySnapshotController.mPendingCaptureActivity.size());
+        windows.add(belowClose.mActivityRecord);
+        mActivitySnapshotController.handleTransitionFinish(windows);
         assertEquals(1, mActivitySnapshotController.mPendingRemoveActivity.size());
-        assertEquals(closingWindow.mActivityRecord,
-                mActivitySnapshotController.mPendingCaptureActivity.valueAt(0));
         assertEquals(belowClose.mActivityRecord,
                 mActivitySnapshotController.mPendingRemoveActivity.valueAt(0));
     }
 
     @Test
     public void testCloseActivityTransition() {
-        final SnapshotController.TransitionState transitionState =
-                new SnapshotController.TransitionState();
         final Task task = createTask(mDisplayContent);
         // note for createAppWindow: the new child is added at index 0
         final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
@@ -97,10 +96,10 @@
                 ACTIVITY_TYPE_STANDARD, "openingWindow");
         openingWindow.mActivityRecord.commitVisibility(
                 true /* visible */, true /* performLayout */);
-        transitionState.addParticipant(closingWindow.mActivityRecord, false);
-        transitionState.addParticipant(openingWindow.mActivityRecord, true);
-        mActivitySnapshotController.handleCloseActivityTransition(transitionState);
-        assertEquals(0, mActivitySnapshotController.mPendingCaptureActivity.size());
+        final ArrayList<WindowContainer> windows = new ArrayList<>();
+        windows.add(openingWindow.mActivityRecord);
+        windows.add(closingWindow.mActivityRecord);
+        mActivitySnapshotController.handleTransitionFinish(windows);
         assertEquals(1, mActivitySnapshotController.mPendingDeleteActivity.size());
         assertEquals(openingWindow.mActivityRecord,
                 mActivitySnapshotController.mPendingDeleteActivity.valueAt(0));
@@ -111,8 +110,8 @@
                 "belowOpen");
         belowOpen.mActivityRecord.commitVisibility(
                 false /* visible */, true /* performLayout */);
-        mActivitySnapshotController.handleCloseActivityTransition(transitionState);
-        assertEquals(0, mActivitySnapshotController.mPendingCaptureActivity.size());
+        windows.add(belowOpen.mActivityRecord);
+        mActivitySnapshotController.handleTransitionFinish(windows);
         assertEquals(1, mActivitySnapshotController.mPendingDeleteActivity.size());
         assertEquals(1, mActivitySnapshotController.mPendingLoadActivity.size());
         assertEquals(openingWindow.mActivityRecord,
@@ -123,10 +122,6 @@
 
     @Test
     public void testTaskTransition() {
-        final SnapshotController.TransitionState taskCloseTransition =
-                new SnapshotController.TransitionState();
-        final SnapshotController.TransitionState taskOpenTransition =
-                new SnapshotController.TransitionState();
         final Task closeTask = createTask(mDisplayContent);
         // note for createAppWindow: the new child is added at index 0
         final WindowState closingWindow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD,
@@ -147,10 +142,10 @@
                 "openingWindowBelow");
         openingWindowBelow.mActivityRecord.commitVisibility(
                 false /* visible */, true /* performLayout */);
-        taskCloseTransition.addParticipant(closeTask, false);
-        taskOpenTransition.addParticipant(openTask, true);
-        mActivitySnapshotController.handleCloseTaskTransition(taskCloseTransition);
-        mActivitySnapshotController.handleOpenTaskTransition(taskOpenTransition);
+        final ArrayList<WindowContainer> windows = new ArrayList<>();
+        windows.add(closeTask);
+        windows.add(openTask);
+        mActivitySnapshotController.handleTransitionFinish(windows);
 
         assertEquals(1, mActivitySnapshotController.mPendingRemoveActivity.size());
         assertEquals(closingWindowBelow.mActivityRecord,
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java
deleted file mode 100644
index 83af1814..0000000
--- a/services/tests/wmtests/src/com/android/server/wm/AppSnapshotControllerTests.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wm;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-
-import static com.android.server.wm.SnapshotController.ACTIVITY_CLOSE;
-import static com.android.server.wm.SnapshotController.ACTIVITY_OPEN;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
-import static com.android.server.wm.SnapshotController.TASK_OPEN;
-
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.util.ArraySet;
-
-import androidx.test.filters.SmallTest;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-
-/**
- * Test class for {@link SnapshotController}.
- *
- * Build/Install/Run:
- *  *  atest WmTests:AppSnapshotControllerTests
- */
-@SmallTest
-@Presubmit
-@RunWith(WindowTestRunner.class)
-public class AppSnapshotControllerTests extends WindowTestsBase {
-    final ArraySet<ActivityRecord> mClosingApps = new ArraySet<>();
-    final ArraySet<ActivityRecord> mOpeningApps = new ArraySet<>();
-
-    final TransitionMonitor mOpenActivityMonitor = new TransitionMonitor();
-    final TransitionMonitor mCloseActivityMonitor = new TransitionMonitor();
-    final TransitionMonitor mOpenTaskMonitor = new TransitionMonitor();
-    final TransitionMonitor mCloseTaskMonitor = new TransitionMonitor();
-
-    @Before
-    public void setUp() throws Exception {
-        resetStatus();
-        mWm.mSnapshotController.registerTransitionStateConsumer(
-                ACTIVITY_CLOSE, mCloseActivityMonitor::handleTransition);
-        mWm.mSnapshotController.registerTransitionStateConsumer(
-                ACTIVITY_OPEN, mOpenActivityMonitor::handleTransition);
-        mWm.mSnapshotController.registerTransitionStateConsumer(
-                TASK_CLOSE, mCloseTaskMonitor::handleTransition);
-        mWm.mSnapshotController.registerTransitionStateConsumer(
-                TASK_OPEN, mOpenTaskMonitor::handleTransition);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        mWm.mSnapshotController.unregisterTransitionStateConsumer(
-                ACTIVITY_CLOSE, mCloseActivityMonitor::handleTransition);
-        mWm.mSnapshotController.unregisterTransitionStateConsumer(
-                ACTIVITY_OPEN, mOpenActivityMonitor::handleTransition);
-        mWm.mSnapshotController.unregisterTransitionStateConsumer(
-                TASK_CLOSE, mCloseTaskMonitor::handleTransition);
-        mWm.mSnapshotController.unregisterTransitionStateConsumer(
-                TASK_OPEN, mOpenTaskMonitor::handleTransition);
-    }
-
-    private static class TransitionMonitor {
-        private final ArraySet<WindowContainer> mOpenParticipant = new ArraySet<>();
-        private final ArraySet<WindowContainer> mCloseParticipant = new ArraySet<>();
-        void handleTransition(SnapshotController.TransitionState<ActivityRecord> state) {
-            mOpenParticipant.addAll(state.getParticipant(true /* open */));
-            mCloseParticipant.addAll(state.getParticipant(false /* close */));
-        }
-        void reset() {
-            mOpenParticipant.clear();
-            mCloseParticipant.clear();
-        }
-    }
-
-    private void resetStatus() {
-        mClosingApps.clear();
-        mOpeningApps.clear();
-        mOpenActivityMonitor.reset();
-        mCloseActivityMonitor.reset();
-        mOpenTaskMonitor.reset();
-        mCloseTaskMonitor.reset();
-    }
-
-    @Test
-    public void testHandleAppTransition_openActivityTransition() {
-        final Task task = createTask(mDisplayContent);
-        // note for createAppWindow: the new child is added at index 0
-        final WindowState openingWindow = createAppWindow(task,
-                ACTIVITY_TYPE_STANDARD, "openingWindow");
-        openingWindow.mActivityRecord.commitVisibility(
-                true /* visible */, true /* performLayout */);
-        final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
-                "closingWindow");
-        closingWindow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-        mClosingApps.add(closingWindow.mActivityRecord);
-        mOpeningApps.add(openingWindow.mActivityRecord);
-        mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps);
-        assertTrue(mOpenActivityMonitor.mCloseParticipant.contains(closingWindow.mActivityRecord));
-        assertTrue(mOpenActivityMonitor.mOpenParticipant.contains(openingWindow.mActivityRecord));
-    }
-
-    @Test
-    public void testHandleAppTransition_closeActivityTransition() {
-        final Task task = createTask(mDisplayContent);
-        // note for createAppWindow: the new child is added at index 0
-        final WindowState closingWindow = createAppWindow(task, ACTIVITY_TYPE_STANDARD,
-                "closingWindow");
-        closingWindow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-        final WindowState openingWindow = createAppWindow(task,
-                ACTIVITY_TYPE_STANDARD, "openingWindow");
-        openingWindow.mActivityRecord.commitVisibility(
-                true /* visible */, true /* performLayout */);
-        mClosingApps.add(closingWindow.mActivityRecord);
-        mOpeningApps.add(openingWindow.mActivityRecord);
-        mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps);
-        assertTrue(mCloseActivityMonitor.mCloseParticipant.contains(closingWindow.mActivityRecord));
-        assertTrue(mCloseActivityMonitor.mOpenParticipant.contains(openingWindow.mActivityRecord));
-    }
-
-    @Test
-    public void testHandleAppTransition_TaskTransition() {
-        final Task closeTask = createTask(mDisplayContent);
-        // note for createAppWindow: the new child is added at index 0
-        final WindowState closingWindow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD,
-                "closingWindow");
-        closingWindow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-        final WindowState closingWindowBelow = createAppWindow(closeTask, ACTIVITY_TYPE_STANDARD,
-                "closingWindowBelow");
-        closingWindowBelow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-
-        final Task openTask = createTask(mDisplayContent);
-        final WindowState openingWindow = createAppWindow(openTask, ACTIVITY_TYPE_STANDARD,
-                "openingWindow");
-        openingWindow.mActivityRecord.commitVisibility(
-                true /* visible */, true /* performLayout */);
-        final WindowState openingWindowBelow = createAppWindow(openTask, ACTIVITY_TYPE_STANDARD,
-                "openingWindowBelow");
-        openingWindowBelow.mActivityRecord.commitVisibility(
-                false /* visible */, true /* performLayout */);
-
-        mClosingApps.add(closingWindow.mActivityRecord);
-        mOpeningApps.add(openingWindow.mActivityRecord);
-        mWm.mSnapshotController.handleAppTransition(mClosingApps, mOpeningApps);
-        assertTrue(mCloseTaskMonitor.mCloseParticipant.contains(closeTask));
-        assertTrue(mOpenTaskMonitor.mOpenParticipant.contains(openTask));
-    }
-}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
index 52226c2..4473a31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecordingControllerTests.java
@@ -123,6 +123,7 @@
         controller.setContentRecordingSessionLocked(mWaitingDisplaySession, mWm);
         verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(
                 mWaitingDisplaySession);
+        verify(mVirtualDisplayContent).updateRecording();
 
         // WHEN updating the session on the same display, so no longer waiting to record.
         ContentRecordingSession sessionUpdate = ContentRecordingSession.createTaskSession(
@@ -135,7 +136,7 @@
         // THEN the session was accepted.
         assertThat(resultingSession).isEqualTo(sessionUpdate);
         verify(mVirtualDisplayContent, atLeastOnce()).setContentRecordingSession(sessionUpdate);
-        verify(mVirtualDisplayContent).updateRecording();
+        verify(mVirtualDisplayContent, atLeastOnce()).updateRecording();
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index d015ca3..1c0fd4f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -2049,6 +2049,17 @@
         assertNotEquals(testPlayer.mLastReady.getChange(dcToken).getEndRotation(),
                 testPlayer.mLastReady.getChange(dcToken).getStartRotation());
         testPlayer.finish();
+
+        // The AsyncRotationController should only exist if there is an ongoing rotation change.
+        dc.finishAsyncRotationIfPossible();
+        dc.setLastHasContent();
+        doReturn(dr.getRotation() + 1).when(dr).rotationForOrientation(anyInt(), anyInt());
+        dr.updateRotationUnchecked(true /* forceUpdate */);
+        assertNotNull(dc.getAsyncRotationController());
+        doReturn(dr.getRotation() - 1).when(dr).rotationForOrientation(anyInt(), anyInt());
+        dr.updateRotationUnchecked(true /* forceUpdate */);
+        assertNull("Cancel AsyncRotationController for the intermediate rotation changes 0->1->0",
+                dc.getAsyncRotationController());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 939ff97..c4302db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -1194,6 +1194,15 @@
     }
 
     @Test
+    public void testIsFixedToUserRotation_displayContentOrientationFixed() throws Exception {
+        mBuilder.build();
+        when(mMockDisplayContent.isDisplayOrientationFixed()).thenReturn(true);
+
+        assertFalse("Display rotation should respect app requested orientation if"
+                + " the display has fixed orientation.", mTarget.isFixedToUserRotation());
+    }
+
+    @Test
     public void testIsFixedToUserRotation_FixedToUserRotationIfNoAutoRotation() throws Exception {
         mBuilder.build();
         mTarget.setFixedToUserRotation(FIXED_TO_USER_ROTATION_IF_NO_AUTO_ROTATION);
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 72ab18d..2ad9fa0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -37,6 +37,8 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
@@ -48,6 +50,8 @@
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE;
 import static android.view.WindowManager.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS;
 import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION;
 
@@ -807,6 +811,108 @@
                 /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT);
     }
 
+    // shouldApplyUser...Override
+    @Test
+    public void testShouldApplyUserFullscreenOverride_trueProperty_returnsFalse() throws Exception {
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+                /* value */ true);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+        doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+
+        assertFalse(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_falseFullscreenProperty_returnsFalse()
+            throws Exception {
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+                /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_falseSettingsProperty_returnsFalse()
+            throws Exception {
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_disabledIgnoreOrientationRequest() {
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+        mDisplayContent.setIgnoreOrientationRequest(false);
+
+        assertFalse(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserFullscreenOverride_returnsTrue() {
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+
+        assertTrue(mController.shouldApplyUserFullscreenOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_falseProperty_returnsFalse()
+            throws Exception {
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ false);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_trueProperty_returnsFalse()
+            throws Exception {
+        doReturn(false).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_OVERRIDE, /* value */ true);
+
+        mController = new LetterboxUiController(mWm, mActivity);
+
+        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_disabledIgnoreOrientationRequest() {
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+        mDisplayContent.setIgnoreOrientationRequest(false);
+
+        assertFalse(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    @Test
+    public void testShouldApplyUserMinAspectRatioOverride_returnsTrue() {
+        prepareActivityThatShouldApplyUserMinAspectRatioOverride();
+
+        assertTrue(mController.shouldApplyUserMinAspectRatioOverride());
+    }
+
+    private void prepareActivityThatShouldApplyUserMinAspectRatioOverride() {
+        spyOn(mController);
+        doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioSettingsEnabled();
+        mDisplayContent.setIgnoreOrientationRequest(true);
+        doReturn(USER_MIN_ASPECT_RATIO_3_2).when(mController).getUserMinAspectRatioOverrideCode();
+    }
+
+    private void prepareActivityThatShouldApplyUserFullscreenOverride() {
+        spyOn(mController);
+        doReturn(true).when(mLetterboxConfiguration).isUserAppAspectRatioFullscreenEnabled();
+        mDisplayContent.setIgnoreOrientationRequest(true);
+        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN).when(mController)
+                .getUserMinAspectRatioOverrideCode();
+    }
+
     // shouldUseDisplayLandscapeNaturalOrientation
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
index 9a93746..e3f8e8c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlTests.java
@@ -125,6 +125,7 @@
 
     @Test
     public void testSurfaceChangedOnRotation() {
+        CommonUtils.dismissKeyguard();
         final Instrumentation instrumentation = getInstrumentation();
         final Context context = instrumentation.getContext();
         final Intent intent = new Intent().setComponent(
@@ -137,6 +138,8 @@
         instrumentation.runOnMainSync(() -> activity.setContentView(sv));
         sv.getHolder().addCallback(new SurfaceHolder.Callback() {
             int mInitialTransformHint = -1;
+            int mInitialW;
+            int mInitialH;
 
             @Override
             public void surfaceCreated(@NonNull SurfaceHolder holder) {
@@ -148,7 +151,10 @@
                         sv.getViewRootImpl().getSurfaceControl().getTransformHint();
                 if (mInitialTransformHint == -1) {
                     mInitialTransformHint = transformHint;
-                } else if (mInitialTransformHint == transformHint) {
+                    mInitialW = width;
+                    mInitialH = height;
+                } else if (mInitialTransformHint == transformHint
+                        && (width > height) != (mInitialW > mInitialH)) {
                     // For example, the initial hint is from portrait, so the later changes from
                     // landscape should not receive the same hint.
                     unexpectedTransformHint[0] = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index be436bf..7634d9f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -384,7 +384,16 @@
     }
 
     private void tearDown() {
-        mWmService.mRoot.forAllDisplayPolicies(DisplayPolicy::release);
+        for (int i = mWmService.mRoot.getChildCount() - 1; i >= 0; i--) {
+            final DisplayContent dc = mWmService.mRoot.getChildAt(i);
+            // Unregister SettingsObserver.
+            dc.getDisplayPolicy().release();
+            // Unregister SensorEventListener (foldable device may register for hinge angle).
+            dc.getDisplayRotation().onDisplayRemoved();
+            if (dc.mDisplayRotationCompatPolicy != null) {
+                dc.mDisplayRotationCompatPolicy.dispose();
+            }
+        }
 
         // Unregister display listener from root to avoid issues with subsequent tests.
         mContext.getSystemService(DisplayManager.class)
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 40b1521..07cdfaf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -46,7 +46,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.server.wm.SnapshotController.TASK_CLOSE;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
@@ -1387,8 +1386,6 @@
     @Test
     public void testTransientLaunch() {
         spyOn(mWm.mSnapshotController.mTaskSnapshotController);
-        mWm.mSnapshotController.registerTransitionStateConsumer(TASK_CLOSE,
-                mWm.mSnapshotController.mTaskSnapshotController::handleTaskClose);
         final ArrayList<ActivityRecord> enteringAnimReports = new ArrayList<>();
         final TransitionController controller = new TestTransitionController(mAtm) {
             @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
index 55dc375..d85d9b5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContextListenerControllerTests.java
@@ -41,19 +41,16 @@
 import static org.mockito.MockitoAnnotations.initMocks;
 
 import android.app.ClientTransactionHandler;
-import android.app.IWindowToken;
 import android.app.servertransaction.ClientTransactionItem;
 import android.app.servertransaction.WindowContextInfoChangeItem;
 import android.content.res.Configuration;
 import android.graphics.Rect;
-import android.os.Binder;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.platform.test.annotations.Presubmit;
-import android.util.ArrayMap;
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.window.WindowContextInfo;
+import android.window.WindowTokenClient;
 
 import androidx.test.filters.SmallTest;
 
@@ -78,11 +75,11 @@
 
     @Mock
     private ClientTransactionHandler mHandler;
+    @Mock
+    private WindowTokenClient mClientToken;
 
     private WindowProcessController mWpc;
-    private final IBinder mClientToken = new Binder();
     private WindowContainer<?> mContainer;
-    private ArrayMap<IBinder, TestWindowTokenClient> mWindowTokenClientMap;
 
     @Before
     public void setUp() {
@@ -92,7 +89,6 @@
         // Make display on to verify configuration propagation.
         mDefaultDisplay.getDisplayInfo().state = STATE_ON;
         mDisplayContent.getDisplayInfo().state = STATE_ON;
-        mWindowTokenClientMap = new ArrayMap<>();
 
         mWpc = mSystemServicesTestRule.addProcess(
                 DEFAULT_COMPONENT_PACKAGE_NAME, DEFAULT_COMPONENT_PACKAGE_NAME, 0 /* pid */,
@@ -102,13 +98,9 @@
         doAnswer(invocation -> {
             // Mock ActivityThread
             final Object[] args = invocation.getArguments();
-            final IBinder clientToken = (IBinder) args[0];
+            final WindowTokenClient clientToken = (WindowTokenClient) args[0];
             final WindowContextInfo info = (WindowContextInfo) args[1];
-            final TestWindowTokenClient client = mWindowTokenClientMap.get(clientToken);
-            if (client != null) {
-                // Can be null for mock client.
-                client.onConfigurationChanged(info.getConfiguration(), info.getDisplayId());
-            }
+            clientToken.onConfigurationChanged(info.getConfiguration(), info.getDisplayId());
             return null;
         }).when(mHandler).handleWindowContextInfoChanged(any(), any());
         doAnswer(invocation -> {
@@ -131,7 +123,7 @@
 
         assertEquals(1, mController.mListeners.size());
 
-        final IBinder clientToken = mock(IBinder.class);
+        final WindowTokenClient clientToken = mock(WindowTokenClient.class);
         mController.registerWindowContainerListener(mWpc, clientToken, mContainer,
                 TYPE_APPLICATION_OVERLAY, null /* options */);
 
@@ -344,15 +336,11 @@
         assertThat(clientToken.mDisplayId).isEqualTo(mDisplayContent.mDisplayId);
     }
 
-    private class TestWindowTokenClient extends IWindowToken.Stub {
+    private static class TestWindowTokenClient extends WindowTokenClient {
         private Configuration mConfiguration;
         private int mDisplayId;
         private boolean mRemoved;
 
-        TestWindowTokenClient() {
-            mWindowTokenClientMap.put(asBinder(), this);
-        }
-
         @Override
         public void onConfigurationChanged(Configuration configuration, int displayId) {
             mConfiguration = configuration;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index cf83981..ebe40b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -170,9 +170,13 @@
     }
 
     @Test
-    public void testSetRunningRecentsAnimation() {
-        mWpc.setRunningRecentsAnimation(true);
-        mWpc.setRunningRecentsAnimation(false);
+    public void testSetAnimatingReason() {
+        mWpc.addAnimatingReason(WindowProcessController.ANIMATING_REASON_REMOTE_ANIMATION);
+        assertTrue(mWpc.isRunningRemoteTransition());
+        mWpc.addAnimatingReason(WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
+        mWpc.removeAnimatingReason(WindowProcessController.ANIMATING_REASON_REMOTE_ANIMATION);
+        assertFalse(mWpc.isRunningRemoteTransition());
+        mWpc.removeAnimatingReason(WindowProcessController.ANIMATING_REASON_WAKEFULNESS_CHANGE);
         waitHandlerIdle(mAtm.mH);
 
         InOrder orderVerifier = Mockito.inOrder(mMockListener);
@@ -201,7 +205,7 @@
         waitHandlerIdle(mAtm.mH);
 
         InOrder orderVerifier = Mockito.inOrder(mMockListener);
-        orderVerifier.verify(mMockListener, times(3)).setRunningRemoteAnimation(eq(true));
+        orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(true));
         orderVerifier.verify(mMockListener, times(1)).setRunningRemoteAnimation(eq(false));
         orderVerifier.verifyNoMoreInteractions();
     }
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 0d88a0d..befc4a0 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -364,8 +364,10 @@
             if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) {
                 // We don't count code baked into system image
             } else {
-                codePaths = ArrayUtils.appendElement(String.class, codePaths,
+                if (appInfo.getCodePath() != null) {
+                    codePaths = ArrayUtils.appendElement(String.class, codePaths,
                         appInfo.getCodePath());
+                }
             }
 
             final PackageStats stats = new PackageStats(TAG);
@@ -418,8 +420,10 @@
                 if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) {
                     // We don't count code baked into system image
                 } else {
-                    codePaths = ArrayUtils.appendElement(String.class, codePaths,
-                            appInfo.getCodePath());
+                    if (appInfo.getCodePath() != null) {
+                        codePaths = ArrayUtils.appendElement(String.class, codePaths,
+                                appInfo.getCodePath());
+                    }
                 }
             } catch (NameNotFoundException e) {
                 throw new ParcelableException(e);
diff --git a/services/usage/java/com/android/server/usage/TEST_MAPPING b/services/usage/java/com/android/server/usage/TEST_MAPPING
index a3fe6f2..6e84543 100644
--- a/services/usage/java/com/android/server/usage/TEST_MAPPING
+++ b/services/usage/java/com/android/server/usage/TEST_MAPPING
@@ -20,22 +20,13 @@
       ]
     },
     {
-      "name": "CtsUsageStatsTestCases",
+      "name": "CtsBRSTestCases",
       "options": [
         {
-          "include-filter": "android.app.usage.cts.BroadcastResponseStatsTest"
-        },
-        {
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         },
         {
-          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.MediumTest"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.LargeTest"
+          "exclude-annotation": "org.junit.Ignore"
         }
       ]
     }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index ccc4ac2..58da4b43 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -368,29 +368,29 @@
     /**
      * This method is only used by VisualQueryDetector.
      */
-    void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+    boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
         }
         final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
         if (session == null) {
-            return;
+            return false;
         }
-        session.startPerceivingLocked(callback);
+        return session.startPerceivingLocked(callback);
     }
 
     /**
      * This method is only used by VisaulQueryDetector.
      */
-    void stopPerceivingLocked() {
+    boolean stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
         }
         final VisualQueryDetectorSession session = getVisualQueryDetectorSessionLocked();
         if (session == null) {
-            return;
+            return false;
         }
-        session.stopPerceivingLocked();
+        return session.stopPerceivingLocked();
     }
 
     public void startListeningFromExternalSourceLocked(
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
index 2e05e20..4720d27 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VisualQueryDetectorSession.java
@@ -95,7 +95,7 @@
     }
 
     @SuppressWarnings("GuardedBy")
-    void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+    boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
         }
@@ -198,15 +198,16 @@
                 mQueryStreaming = false;
             }
         };
-        mRemoteDetectionService.run(service -> service.detectWithVisualSignals(internalCallback));
+        return mRemoteDetectionService.run(
+                service -> service.detectWithVisualSignals(internalCallback));
     }
 
     @SuppressWarnings("GuardedBy")
-    void stopPerceivingLocked() {
+    boolean stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
         }
-        mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
+        return mRemoteDetectionService.run(ISandboxedDetectionService::stopDetection);
     }
 
     @Override
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 3502a3f..98cc1da 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -23,7 +23,6 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
-import android.app.ActivityThread;
 import android.app.AppGlobals;
 import android.app.role.OnRoleHoldersChangedListener;
 import android.app.role.RoleManager;
@@ -51,7 +50,6 @@
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.media.AudioFormat;
 import android.media.permission.Identity;
-import android.media.permission.IdentityContext;
 import android.media.permission.PermissionUtil;
 import android.media.permission.SafeCloseable;
 import android.os.Binder;
@@ -61,7 +59,6 @@
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
-import android.os.Process;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -88,6 +85,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IHotwordRecognitionStatusCallback;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
+import com.android.internal.app.IVisualQueryRecognitionStatusListener;
 import com.android.internal.app.IVoiceActionCheckCallback;
 import com.android.internal.app.IVoiceInteractionManagerService;
 import com.android.internal.app.IVoiceInteractionSessionListener;
@@ -139,6 +137,7 @@
 
     private final RemoteCallbackList<IVoiceInteractionSessionListener>
             mVoiceInteractionSessionListeners = new RemoteCallbackList<>();
+    private IVisualQueryRecognitionStatusListener mVisualQueryRecognitionStatusListener;
 
     public VoiceInteractionManagerService(Context context) {
         super(context);
@@ -1346,6 +1345,17 @@
         @android.annotation.EnforcePermission(
                 android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
         @Override
+        public void subscribeVisualQueryRecognitionStatus(IVisualQueryRecognitionStatusListener
+                listener) {
+            super.subscribeVisualQueryRecognitionStatus_enforcePermission();
+            synchronized (this) {
+                mVisualQueryRecognitionStatusListener = listener;
+            }
+        }
+
+        @android.annotation.EnforcePermission(
+                android.Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE)
+        @Override
         public void enableVisualQueryDetection(
                 IVisualQueryDetectionAttentionListener listener) {
             super.enableVisualQueryDetection_enforcePermission();
@@ -1391,7 +1401,10 @@
                 }
                 final long caller = Binder.clearCallingIdentity();
                 try {
-                    mImpl.startPerceivingLocked(callback);
+                    boolean success = mImpl.startPerceivingLocked(callback);
+                    if (success && mVisualQueryRecognitionStatusListener != null) {
+                        mVisualQueryRecognitionStatusListener.onStartPerceiving();
+                    }
                 } finally {
                     Binder.restoreCallingIdentity(caller);
                 }
@@ -1409,7 +1422,10 @@
                 }
                 final long caller = Binder.clearCallingIdentity();
                 try {
-                    mImpl.stopPerceivingLocked();
+                    boolean success = mImpl.stopPerceivingLocked();
+                    if (success && mVisualQueryRecognitionStatusListener != null) {
+                        mVisualQueryRecognitionStatusListener.onStopPerceiving();
+                    }
                 } 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 5d88a65..471acc1 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -784,30 +784,30 @@
         mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener);
     }
 
-    public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
+    public boolean startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) {
         if (DEBUG) {
             Slog.d(TAG, "startPerceivingLocked");
         }
 
         if (mHotwordDetectionConnection == null) {
             // TODO: callback.onError();
-            return;
+            return false;
         }
 
-        mHotwordDetectionConnection.startPerceivingLocked(callback);
+        return mHotwordDetectionConnection.startPerceivingLocked(callback);
     }
 
-    public void stopPerceivingLocked() {
+    public boolean stopPerceivingLocked() {
         if (DEBUG) {
             Slog.d(TAG, "stopPerceivingLocked");
         }
 
         if (mHotwordDetectionConnection == null) {
             Slog.w(TAG, "stopPerceivingLocked() called but connection isn't established");
-            return;
+            return false;
         }
 
-        mHotwordDetectionConnection.stopPerceivingLocked();
+        return mHotwordDetectionConnection.stopPerceivingLocked();
     }
 
     public void startListeningFromMicLocked(
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 78b86d3..5bdcdf4 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -2661,7 +2661,9 @@
         // remove ourselves from the Phone. Note that we do this after completing all state updates
         // so a client can cleanly transition all their UI to the state appropriate for a
         // DISCONNECTED Call while still relying on the existence of that Call in the Phone's list.
-        if (mState == STATE_DISCONNECTED) {
+        // Check if the original state is already disconnected, otherwise onCallRemoved will be
+        // triggered before onCallAdded.
+        if (mState == STATE_DISCONNECTED && stateChanged) {
             fireCallDestroyed();
         }
     }
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 95a8e16..61e829e7 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -174,6 +174,9 @@
             checkCallTree(parcelableCall);
             call.internalUpdate(parcelableCall, mCallByTelecomCallId);
             fireCallAdded(call);
+            if (call.getState() == Call.STATE_DISCONNECTED) {
+                internalRemoveCall(call);
+            }
         } else {
             Log.w(this, "Call %s added, but it was already present", call.internalGetCallId());
             checkCallTree(parcelableCall);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 314150b..4907134 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -9418,6 +9418,7 @@
      * <li>3 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_SMS}</li>
      * <li>4 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_VIDEO}</li>
      * <li>5 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_EMERGENCY}</li>
+     * <li>6 = {@link android.telephony.NetworkRegistrationInfo#SERVICE_TYPE_MMS}</li>
      * </ul>
      * <p>
      * An example config for two PLMNs "123411" and "123412":
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index 6258b9c..631013f 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -170,7 +170,7 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = "SERVICE_TYPE_",
             value = {SERVICE_TYPE_UNKNOWN, SERVICE_TYPE_VOICE, SERVICE_TYPE_DATA, SERVICE_TYPE_SMS,
-                    SERVICE_TYPE_VIDEO, SERVICE_TYPE_EMERGENCY})
+                    SERVICE_TYPE_VIDEO, SERVICE_TYPE_EMERGENCY, SERVICE_TYPE_MMS})
     public @interface ServiceType {}
 
     /**
@@ -203,11 +203,16 @@
      */
     public static final int SERVICE_TYPE_EMERGENCY  = 5;
 
+    /**
+     * MMS service
+     */
+    public static final int SERVICE_TYPE_MMS = 6;
+
     /** @hide  */
     public static final int FIRST_SERVICE_TYPE = SERVICE_TYPE_VOICE;
 
     /** @hide  */
-    public static final int LAST_SERVICE_TYPE = SERVICE_TYPE_EMERGENCY;
+    public static final int LAST_SERVICE_TYPE = SERVICE_TYPE_MMS;
 
     @Domain
     private final int mDomain;
@@ -739,6 +744,7 @@
             case SERVICE_TYPE_SMS: return "SMS";
             case SERVICE_TYPE_VIDEO: return "VIDEO";
             case SERVICE_TYPE_EMERGENCY: return "EMERGENCY";
+            case SERVICE_TYPE_MMS: return "MMS";
         }
         return "Unknown service type " + serviceType;
     }
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 040c5b0..64c2a4c 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1107,6 +1107,16 @@
      */
     public static final String SATELLITE_ENABLED = SimInfo.COLUMN_SATELLITE_ENABLED;
 
+    /**
+     * TelephonyProvider column name for satellite attach enabled for carrier. The value of this
+     * column is set based on user settings.
+     * By default, it's disabled.
+     * <P>Type: INTEGER (int)</P>
+     * @hide
+     */
+    public static final String SATELLITE_ATTACH_ENABLED_FOR_CARRIER =
+            SimInfo.COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"USAGE_SETTING_"},
diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt
index 32c2230..ad95fbc 100644
--- a/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt
+++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/PeerDeviceSystemAttestationVerificationTest.kt
@@ -39,7 +39,7 @@
     @Before
     fun setup() {
         rule.getScenario().onActivity {
-            avm = it.getSystemService(AttestationVerificationManager::class.java)
+            avm = it.getSystemService(AttestationVerificationManager::class.java)!!
             activity = it
         }
         invalidAttestationByteArray = TEST_ATTESTATION_CERT_FILENAME.fromPEMFileToByteArray()
diff --git a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
index 169effa..8f06b4a2 100644
--- a/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
+++ b/tests/AttestationVerificationTest/src/android/security/attestationverification/SystemAttestationVerificationTest.kt
@@ -43,7 +43,7 @@
     @Before
     fun setup() {
         rule.getScenario().onActivity {
-            avm = it.getSystemService(AttestationVerificationManager::class.java)
+            avm = it.getSystemService(AttestationVerificationManager::class.java)!!
             activity = it
             androidKeystore = KeyStore.getInstance(ANDROID_KEYSTORE).apply { load(null) }
         }
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
index 1167a3e..03335c7 100644
--- a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
+++ b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp
@@ -37,7 +37,6 @@
     },
     data: [
         ":cdm_snippet",
-        "requirements.txt",
     ],
     version: {
         py2: {
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py b/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py
index 9cb2d10..5516c0f 100644
--- a/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py
+++ b/tests/CompanionDeviceMultiDeviceTests/host/cdm_transport_test.py
@@ -25,12 +25,4 @@
 
 
 if __name__ == '__main__':
-    try:
-        # Take test args and remove standalone '--' from the list
-        index = sys.argv.index('--')
-        sys.argv = sys.argv[:1] + sys.argv[index + 1:]
-    except ValueError:
-        # Ignore if '--' is not in args
-        pass
-
     test_runner.main()
\ No newline at end of file
diff --git a/tests/CompanionDeviceMultiDeviceTests/host/requirements.txt b/tests/CompanionDeviceMultiDeviceTests/host/requirements.txt
deleted file mode 100644
index 86a11aa..0000000
--- a/tests/CompanionDeviceMultiDeviceTests/host/requirements.txt
+++ /dev/null
@@ -1 +0,0 @@
-mobly==1.12.1
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
index c3529ba..baf109b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/layoutchange/HorizontalSplitChangeRatioTest.kt
@@ -16,13 +16,13 @@
 
 package com.android.server.wm.flicker.activityembedding
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.datatypes.Rect
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
 import org.junit.FixMethodOrder
@@ -148,6 +148,7 @@
     companion object {
         /** {@inheritDoc} */
         private var startDisplayBounds = Rect.EMPTY
+
         /**
          * Creates the test configurations.
          *
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
index 244c5dc..d97027e 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -16,13 +16,13 @@
 
 package com.android.server.wm.flicker.activityembedding.open
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.datatypes.Rect
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
@@ -133,6 +133,7 @@
     companion object {
         /** {@inheritDoc} */
         private var startDisplayBounds = Rect.EMPTY
+
         /**
          * Creates the test configurations.
          *
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
index f409c4e..0fdf63c 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenTrampolineActivityTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.activityembedding.open
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.datatypes.Rect
 import android.tools.common.flicker.subject.region.RegionSubject
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
index 71db76e..288558ae 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppBackButtonTest.kt
@@ -16,12 +16,12 @@
 
 package com.android.server.wm.flicker.close
 
-import android.platform.test.annotations.FlakyTest
 import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
index 8dd7e65..32305c6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppHomeButtonTest.kt
@@ -16,12 +16,12 @@
 
 package com.android.server.wm.flicker.close
 
-import android.platform.test.annotations.FlakyTest
 import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
index 8737edb..8d752cc 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/close/CloseAppTransition.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.close
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
 import android.tools.common.traces.component.ComponentNameMatcher
@@ -24,6 +23,7 @@
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TransferSplashscreenAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TransferSplashscreenAppHelper.kt
new file mode 100644
index 0000000..6311678
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/TransferSplashscreenAppHelper.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.server.wm.flicker.helpers
+
+import android.app.Instrumentation
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.apphelpers.StandardAppHelper
+import android.tools.device.traces.parsers.toFlickerComponent
+import com.android.server.wm.flicker.testapp.ActivityOptions
+
+class TransferSplashscreenAppHelper
+@JvmOverloads
+constructor(
+    instr: Instrumentation,
+    launcherName: String = ActivityOptions.TransferSplashscreenActivity.LABEL,
+    component: ComponentNameMatcher =
+        ActivityOptions.TransferSplashscreenActivity.COMPONENT.toFlickerComponent()
+) : StandardAppHelper(instr, launcherName, component)
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
index 99858ce..b44f1a6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeToHomeOnFinishActivityTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.ime
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.PlatinumTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
index 3a784ff..48d5041 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIconColdTest.kt
@@ -16,13 +16,13 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.tools.common.Rotation
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
+import androidx.test.filters.FlakyTest
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -53,7 +53,8 @@
 @RunWith(Parameterized::class)
 @Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
-class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) : OpenAppFromLauncherTransition(flicker) {
+open class OpenAppFromIconColdTest(flicker: LegacyFlickerTest) :
+    OpenAppFromLauncherTransition(flicker) {
     /** {@inheritDoc} */
     override val transition: FlickerBuilder.() -> Unit
         get() = {
@@ -87,6 +88,7 @@
     override fun appWindowReplacesLauncherAsTopWindow() {
         super.appWindowReplacesLauncherAsTopWindow()
     }
+
     @FlakyTest(bugId = 240916028)
     @Test
     override fun appWindowAsTopWindowAtEnd() {
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
index 0197e66..78b58f4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromIntentWarmTest.kt
@@ -16,13 +16,13 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.flicker.annotation.FlickerServiceCompatible
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.FixMethodOrder
 import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
index 36e66c7..cc501e6 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenTransition.kt
@@ -16,11 +16,11 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.navBarLayerPositionAtEnd
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
 import org.junit.Assume
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
index e0fb751..3f931c4 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockscreenViaIntentTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
@@ -25,6 +24,7 @@
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import org.junit.Assume
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
index f1cd69a..b85362a 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromOverviewTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.launch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
 import android.tools.common.flicker.annotation.FlickerServiceCompatible
@@ -24,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.helpers.setRotation
 import org.junit.FixMethodOrder
 import org.junit.Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
new file mode 100644
index 0000000..3d9c067
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenTransferSplashscreenAppFromLauncherTransition.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.launch
+
+import android.platform.test.annotations.Presubmit
+import android.tools.common.traces.component.ComponentNameMatcher
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.LegacyFlickerTest
+import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.TransferSplashscreenAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test cold launching an app from launcher
+ *
+ * To run this test: `atest FlickerTests:OpenTransferSplashscreenAppFromLauncherTransition`
+ *
+ * Actions:
+ * ```
+ *     Inherit from OpenAppFromIconColdTest, Launch an app [testApp] with an animated splash screen
+ *     by clicking it's icon on all apps, and wait for transfer splash screen complete
+ * ```
+ *
+ * Notes:
+ * ```
+ *     1. Some default assertions (e.g., nav bar, status bar and screen covered)
+ *        are inherited [OpenAppTransition]
+ *     2. Verify no flickering when transfer splash screen to app window.
+ * ```
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class OpenTransferSplashscreenAppFromLauncherTransition(flicker: LegacyFlickerTest) :
+    OpenAppFromIconColdTest(flicker) {
+    override val testApp = TransferSplashscreenAppHelper(instrumentation)
+
+    /**
+     * Checks that [ComponentNameMatcher.LAUNCHER] window is the top window at the start of the
+     * transition, and is replaced by [ComponentNameMatcher.SPLASH_SCREEN], then [testApp] remains
+     * visible until the end
+     */
+    @Presubmit
+    @Test
+    fun appWindowAfterSplash() {
+        flicker.assertWm {
+            this.isAppWindowOnTop(ComponentNameMatcher.LAUNCHER)
+                .then()
+                .isAppWindowOnTop(ComponentNameMatcher.SPLASH_SCREEN)
+                .then()
+                .isAppWindowOnTop(testApp)
+                .isAppWindowInvisible(ComponentNameMatcher.SPLASH_SCREEN)
+        }
+    }
+
+    companion object {
+        /**
+         * Creates the test configurations.
+         *
+         * See [LegacyFlickerTestFactory.nonRotationTests] for configuring screen orientation and
+         * navigation modes.
+         */
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+    }
+}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
index df9780e..b82a129 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/TaskTransitionTest.kt
@@ -19,7 +19,6 @@
 import android.app.Instrumentation
 import android.app.WallpaperManager
 import android.content.res.Resources
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.flicker.subject.layers.LayersTraceSubject.Companion.VISIBLE_FOR_MORE_THAN_ONE_ENTRY_IGNORE_LAYERS
 import android.tools.common.traces.component.ComponentNameMatcher
@@ -33,6 +32,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.WindowUtils
 import android.tools.device.traces.parsers.toFlickerComponent
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NewTasksAppHelper
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
index bf0f4ab..9722216 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWarmTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.notification
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.platform.test.rule.SettingOverrideRule
 import android.provider.Settings
@@ -25,6 +24,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.statusBarLayerPositionAtEnd
 import org.junit.ClassRule
@@ -174,7 +174,7 @@
         val disableUnseenNotifFilterRule =
             SettingOverrideRule(
                 Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
-                /* value= */ "0",
+                "0",
             )
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
index 3f3542d..ffd8171 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/notification/OpenAppFromLockscreenNotificationWithOverlayAppTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.notification
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.tools.common.traces.component.ComponentNameMatcher
@@ -25,6 +24,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import android.tools.device.helpers.wakeUpAndGoToHomeScreen
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.ShowWhenLockedAppHelper
 import org.junit.FixMethodOrder
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
index 50ff62b..13fcc2b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsBackTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.quickswitch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.datatypes.Rect
@@ -25,6 +24,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
index aee9163..c090415 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchBetweenTwoAppsForwardTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.quickswitch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.datatypes.Rect
@@ -25,6 +24,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.NonResizeableAppHelper
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
index d6a951d..f51be90 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/quickswitch/QuickSwitchFromLauncherTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.server.wm.flicker.quickswitch
 
-import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Presubmit
 import android.tools.common.NavBar
 import android.tools.common.Rotation
@@ -26,6 +25,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import com.android.server.wm.flicker.BaseTest
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import org.junit.FixMethodOrder
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt
index b34da72..4adcc8b 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape.kt
@@ -25,10 +25,12 @@
 import android.tools.common.flicker.config.FlickerServiceConfig
 import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
 import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(FlickerServiceJUnit4ClassRunner::class)
+@Ignore("b/294418322: no notification launch animation exists when keyguard is occluded")
 class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavLandscape :
     OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_90) {
     @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt
index b163897..f7211e7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait.kt
@@ -25,10 +25,12 @@
 import android.tools.common.flicker.config.FlickerServiceConfig
 import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
 import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(FlickerServiceJUnit4ClassRunner::class)
+@Ignore("b/294418322: no notification launch animation exists when keyguard is occluded")
 class OpenAppFromLockscreenNotificationWithOverlayApp3ButtonNavPortrait :
     OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_3BUTTON, Rotation.ROTATION_0) {
     @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt
index 19b533e..1ade956 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape.kt
@@ -25,10 +25,12 @@
 import android.tools.common.flicker.config.FlickerServiceConfig
 import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
 import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(FlickerServiceJUnit4ClassRunner::class)
+@Ignore("b/294418322: no notification launch animation exists when keyguard is occluded")
 class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavLandscape :
     OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_90) {
     @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt
index c9ed4f4..ea26f08 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/service/notification/flicker/OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait.kt
@@ -25,10 +25,12 @@
 import android.tools.common.flicker.config.FlickerServiceConfig
 import android.tools.device.flicker.junit.FlickerServiceJUnit4ClassRunner
 import com.android.server.wm.flicker.service.notification.scenarios.OpenAppFromLockscreenNotificationWithOverlayApp
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(FlickerServiceJUnit4ClassRunner::class)
+@Ignore("b/294418322: no notification launch animation exists when keyguard is occluded")
 class OpenAppFromLockscreenNotificationWithOverlayAppGesturalNavPortrait :
     OpenAppFromLockscreenNotificationWithOverlayApp(NavBar.MODE_GESTURAL, Rotation.ROTATION_0) {
     @ExpectedScenarios(["APP_LAUNCH_FROM_NOTIFICATION"])
diff --git a/tests/FlickerTests/test-apps/flickerapp/Android.bp b/tests/FlickerTests/test-apps/flickerapp/Android.bp
index 75e35ee..e3b23b9 100644
--- a/tests/FlickerTests/test-apps/flickerapp/Android.bp
+++ b/tests/FlickerTests/test-apps/flickerapp/Android.bp
@@ -24,6 +24,9 @@
 android_test {
     name: "FlickerTestApp",
     srcs: ["**/*.java"],
+    resource_dirs: [
+        "res",
+    ],
     sdk_version: "current",
     test_suites: ["device-tests"],
     static_libs: [
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index ff9799a..704798e 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -347,6 +347,17 @@
             android:exported="false"
             android:theme="@style/CutoutShortEdges"
             android:resizeableActivity="true"/>
+        <activity
+            android:name=".TransferSplashscreenActivity"
+            android:taskAffinity="com.android.server.wm.flicker.testapp.TransferSplashscreenActivity"
+            android:label="TransferSplashscreenActivity"
+            android:theme="@style/SplashscreenAppTheme"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
         <service
             android:name=".AssistantInteractionSessionService"
             android:exported="true"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/drawable/avd_anim.xml b/tests/FlickerTests/test-apps/flickerapp/res/drawable/avd_anim.xml
new file mode 100644
index 0000000..19205d4
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/drawable/avd_anim.xml
@@ -0,0 +1,94 @@
+<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="432dp" android:width="432dp" android:viewportHeight="432" android:viewportWidth="432">
+            <group android:name="_R_G">
+                <group android:name="_R_G_L_5_G" android:translateX="216" android:translateY="216" android:scaleX="1.5" android:scaleY="1.5">
+                    <path android:name="_R_G_L_5_G_D_0_P_0" android:fillColor="#555555" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M53.82 -56.7 C53.82,-56.7 43.64,-49.06 43.64,-49.06 C43.64,-49.06 0,-16.34 0,-16.34 C0,-16.34 -43.64,-49.06 -43.64,-49.06 C-43.64,-49.06 -53.82,-56.7 -53.82,-56.7 C-64.6,-64.79 -80,-57.09 -80,-43.61 C-80,-43.61 -80,-29.07 -80,-29.07 C-80,-29.07 -80,49.09 -80,49.09 C-80,55.12 -75.12,60 -69.09,60 C-69.09,60 -43.64,60 -43.64,60 C-43.64,60 -43.64,-1.8 -43.64,-1.8 C-43.64,-1.8 0,30.92 0,30.92 C0,30.92 43.64,-1.8 43.64,-1.8 C43.64,-1.8 43.64,60 43.64,60 C43.64,60 69.09,60 69.09,60 C75.12,60 80,55.12 80,49.09 C80,49.09 80,-29.07 80,-29.07 C80,-29.07 80,-43.61 80,-43.61 C80,-57.09 64.61,-64.79 53.82,-56.7c "/>
+                </group>
+                <group android:name="_R_G_L_4_G" android:translateX="216" android:translateY="216" android:scaleX="1.5" android:scaleY="1.5">
+                    <clip-path android:name="mask_x" android:pathData="M53.82 -56.7 C53.82,-56.7 43.64,-49.06 43.64,-49.06 C43.64,-49.06 0,-16.34 0,-16.34 C0,-16.34 -43.64,-49.06 -43.64,-49.06 C-43.64,-49.06 -53.82,-56.7 -53.82,-56.7 C-64.6,-64.79 -80,-57.09 -80,-43.61 C-80,-43.61 -80,-29.07 -80,-29.07 C-80,-29.07 -80,49.09 -80,49.09 C-80,55.12 -75.12,60 -69.09,60 C-69.09,60 -43.64,60 -43.64,60 C-43.64,60 -43.64,-1.8 -43.64,-1.8 C-43.64,-1.8 0,30.92 0,30.92 C0,30.92 43.64,-1.8 43.64,-1.8 C43.64,-1.8 43.64,60 43.64,60 C43.64,60 69.09,60 69.09,60 C75.12,60 80,55.12 80,49.09 C80,49.09 80,-29.07 80,-29.07 C80,-29.07 80,-43.61 80,-43.61 C80,-57.09 64.61,-64.79 53.82,-56.7c"/>
+                    <path android:name="_R_G_L_4_G_D_0_P_0" android:fillColor="#2684fc" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-80.39 60 C-80.39,60 -105.84,60 -105.84,60 C-111.87,60 -116.75,55.12 -116.75,49.09 C-116.75,49.09 -116.75,-60 -116.75,-60 C-116.75,-60 -80.39,-60 -80.39,-60 C-80.39,-60 -80.39,60 -80.39,60c "/>
+                </group>
+                <group android:name="_R_G_L_3_G" android:translateX="216" android:translateY="216" android:scaleX="1.5" android:scaleY="1.5">
+                    <clip-path android:name="mask_x" android:pathData="M53.82 -56.7 C53.82,-56.7 43.64,-49.06 43.64,-49.06 C43.64,-49.06 0,-16.34 0,-16.34 C0,-16.34 -43.64,-49.06 -43.64,-49.06 C-43.64,-49.06 -53.82,-56.7 -53.82,-56.7 C-64.6,-64.79 -80,-57.09 -80,-43.61 C-80,-43.61 -80,-29.07 -80,-29.07 C-80,-29.07 -80,49.09 -80,49.09 C-80,55.12 -75.12,60 -69.09,60 C-69.09,60 -43.64,60 -43.64,60 C-43.64,60 -43.64,-1.8 -43.64,-1.8 C-43.64,-1.8 0,30.92 0,30.92 C0,30.92 43.64,-1.8 43.64,-1.8 C43.64,-1.8 43.64,60 43.64,60 C43.64,60 69.09,60 69.09,60 C75.12,60 80,55.12 80,49.09 C80,49.09 80,-29.07 80,-29.07 C80,-29.07 80,-43.61 80,-43.61 C80,-57.09 64.61,-64.79 53.82,-56.7c"/>
+                    <path android:name="_R_G_L_3_G_D_0_P_0" android:fillColor="#00ac47" android:fillAlpha="1" android:fillType="nonZero" android:pathData="M80.64 60 C80.64,60 106.09,60 106.09,60 C112.12,60 117,55.12 117,49.09 C117,49.09 117,-60 117,-60 C117,-60 80.64,-60 80.64,-60 C80.64,-60 80.64,60 80.64,60c "/>
+                </group>
+                <group android:name="_R_G_L_2_G" android:translateX="216" android:translateY="216" android:scaleX="1.5" android:scaleY="1.5">
+                    <clip-path android:name="mask_x" android:pathData="M53.82 -56.7 C53.82,-56.7 43.64,-49.06 43.64,-49.06 C43.64,-49.06 0,-16.34 0,-16.34 C0,-16.34 -43.64,-49.06 -43.64,-49.06 C-43.64,-49.06 -53.82,-56.7 -53.82,-56.7 C-64.6,-64.79 -80,-57.09 -80,-43.61 C-80,-43.61 -80,-29.07 -80,-29.07 C-80,-29.07 -80,49.09 -80,49.09 C-80,55.12 -75.12,60 -69.09,60 C-69.09,60 -43.64,60 -43.64,60 C-43.64,60 -43.64,-1.8 -43.64,-1.8 C-43.64,-1.8 0,30.92 0,30.92 C0,30.92 43.64,-1.8 43.64,-1.8 C43.64,-1.8 43.64,60 43.64,60 C43.64,60 69.09,60 69.09,60 C75.12,60 80,55.12 80,49.09 C80,49.09 80,-29.07 80,-29.07 C80,-29.07 80,-43.61 80,-43.61 C80,-57.09 64.61,-64.79 53.82,-56.7c"/>
+                    <path android:name="_R_G_L_2_G_D_0_P_0" android:fillColor="#fe2c25" android:fillAlpha="1" android:fillType="nonZero" android:pathData="M53.82 -104.7 C53.82,-104.7 0,-64.34 0,-64.34 C0,-64.34 -53.82,-104.7 -53.82,-104.7 C-64.6,-112.79 -80,-105.09 -80,-91.61 C-80,-91.61 -80,-77.07 -80,-77.07 C-80,-77.07 0,-17.08 0,-17.08 C0,-17.08 80,-77.07 80,-77.07 C80,-77.07 80,-91.61 80,-91.61 C80,-105.09 64.61,-112.79 53.82,-104.7c "/>
+                </group>
+                <group android:name="_R_G_L_1_G" android:translateX="216" android:translateY="216" android:scaleX="1.5" android:scaleY="1.5">
+                    <clip-path android:name="mask_x" android:pathData="M43.64 -1.8 C43.64,-1.8 43.64,-49.06 43.64,-49.06 C43.64,-49.06 53.82,-56.7 53.82,-56.7 C64.61,-64.79 80,-57.09 80,-43.61 C80,-43.61 80,-1.8 80,-1.8 C80,-1.8 43.64,-1.8 43.64,-1.8c"/>
+                    <path android:name="_R_G_L_1_G_D_0_P_0" android:fillColor="#ffba00" android:fillAlpha="1" android:fillType="nonZero" android:pathData="M80.64 -135 C80.64,-135 117,-135 117,-135 C117,-135 117,-104.07 117,-104.07 C117,-104.07 80.64,-76.8 80.64,-76.8 C80.64,-76.8 80.64,-135 80.64,-135c "/>
+                </group>
+                <group android:name="_R_G_L_0_G" android:translateX="216" android:translateY="216" android:scaleX="1.5" android:scaleY="1.5">
+                    <clip-path android:name="mask_x" android:pathData="M-43.64 -1.8 C-43.64,-1.8 -80,-1.8 -80,-1.8 C-80,-1.8 -80,-43.61 -80,-43.61 C-80,-57.09 -64.6,-64.79 -53.82,-56.7 C-53.82,-56.7 -43.64,-49.06 -43.64,-49.06 C-43.64,-49.06 -43.64,-1.8 -43.64,-1.8c"/>
+                    <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#d70007" android:fillAlpha="1" android:fillType="nonZero" android:pathData=" M-117 -104.07 C-117,-104.07 -117,-135 -117,-135 C-117,-135 -80.64,-135 -80.64,-135 C-80.64,-135 -80.64,-76.8 -80.64,-76.8 C-80.64,-76.8 -117,-104.07 -117,-104.07c "/>
+                </group>
+            </group>
+            <group android:name="time_group"/>
+        </vector>
+    </aapt:attr>
+    <target android:name="_R_G_L_4_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="1567" android:startOffset="233" android:valueFrom="M-80.39 60 C-80.39,60 -105.84,60 -105.84,60 C-111.87,60 -116.75,55.12 -116.75,49.09 C-116.75,49.09 -116.75,-60 -116.75,-60 C-116.75,-60 -80.39,-60 -80.39,-60 C-80.39,-60 -80.39,60 -80.39,60c " android:valueTo=" M-43.64 60 C-43.64,60 -69.09,60 -69.09,60 C-75.12,60 -80,55.12 -80,49.09 C-80,49.09 -80,-60 -80,-60 C-80,-60 -43.64,-60 -43.64,-60 C-43.64,-60 -43.64,60 -43.64,60c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_3_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="1567" android:startOffset="233" android:valueFrom=" M80.64 60 C80.64,60 106.09,60 106.09,60 C112.12,60 117,55.12 117,49.09 C117,49.09 117,-60 117,-60 C117,-60 80.64,-60 80.64,-60 C80.64,-60 80.64,60 80.64,60c " android:valueTo=" M43.64 60 C43.64,60 69.09,60 69.09,60 C75.12,60 80,55.12 80,49.09 C80,49.09 80,-60 80,-60 C80,-60 43.64,-60 43.64,-60 C43.64,-60 43.64,60 43.64,60c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <target android:name="_R_G_L_2_G_D_0_P_0">
+        <aapt:attr name="android:animation">
+            <set android:ordering="together">
+                <objectAnimator android:propertyName="pathData" android:duration="1567" android:startOffset="233" android:valueFrom="M53.82 -104.7 C53.82,-104.7 0,-64.34 0,-64.34 C0,-64.34 -53.82,-104.7 -53.82,-104.7 C-64.6,-112.79 -80,-105.09 -80,-91.61 C-80,-91.61 -80,-77.07 -80,-77.07 C-80,-77.07 0,-17.08 0,-17.08 C0,-17.08 80,-77.07 80,-77.07 C80,-77.07 80,-91.61 80,-91.61 C80,-105.09 64.61,-112.79 53.82,-104.7c" android:valueTo="M53.82 -56.7 C53.82,-56.7 0,-16.34 0,-16.34 C0,-16.34 -53.82,-56.7 -53.82,-56.7 C-64.6,-64.79 -80,-57.09 -80,-43.61 C-80,-43.61 -80,-29.07 -80,-29.07 C-80,-29.07 0,30.92 0,30.92 C0,30.92 80,-29.07 80,-29.07 C80,-29.07 80,-43.61 80,-43.61 C80,-57.09 64.61,-64.79 53.82,-56.7c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0,1 1.0,1.0"/>
+                    </aapt:attr>
+                </objectAnimator>
+            </set>
+        </aapt:attr>
+    </target>
+    <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="1567" android:startOffset="233" android:valueFrom=" M80.64 -135 C80.64,-135 117,-135 117,-135 C117,-135 117,-104.07 117,-104.07 C117,-104.07 80.64,-76.8 80.64,-76.8 C80.64,-76.8 80.64,-135 80.64,-135c " android:valueTo=" M43.64 -60 C43.64,-60 80,-60 80,-60 C80,-60 80,-29.07 80,-29.07 C80,-29.07 43.64,-1.8 43.64,-1.8 C43.64,-1.8 43.64,-60 43.64,-60c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0,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="pathData" android:duration="1567" android:startOffset="233" android:valueFrom=" M-117 -104.07 C-117,-104.07 -117,-135 -117,-135 C-117,-135 -80.64,-135 -80.64,-135 C-80.64,-135 -80.64,-76.8 -80.64,-76.8 C-80.64,-76.8 -117,-104.07 -117,-104.07c " android:valueTo=" M-80 -29.07 C-80,-29.07 -80,-60 -80,-60 C-80,-60 -43.64,-60 -43.64,-60 C-43.64,-60 -43.64,-1.8 -43.64,-1.8 C-43.64,-1.8 -80,-29.07 -80,-29.07c " android:valueType="pathType">
+                    <aapt:attr name="android:interpolator">
+                        <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0,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="2017" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/>
+            </set>
+        </aapt:attr>
+    </target>
+</animated-vector>
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
index e51ed29..9b742d9 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml
@@ -53,4 +53,11 @@
     <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault">
         <item name="android:windowDisablePreview">true</item>
     </style>
+
+    <style name="SplashscreenAppTheme" parent="@android:style/Theme.DeviceDefault">
+        <!-- Splashscreen Attributes -->
+        <item name="android:windowSplashScreenAnimatedIcon">@drawable/avd_anim</item>
+        <!-- Here we want to match the duration of our AVD -->
+        <item name="android:windowSplashScreenAnimationDuration">900</item>
+    </style>
 </resources>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 2795a6c..7c5e9a3 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -178,6 +178,12 @@
                 FLICKER_APP_PACKAGE + ".LaunchNewActivity");
     }
 
+    public static class TransferSplashscreenActivity {
+        public static final String LABEL = "TransferSplashscreenActivity";
+        public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+                FLICKER_APP_PACKAGE + ".TransferSplashscreenActivity");
+    }
+
     public static class Pip {
         // Test App > Pip Activity
         public static final String LABEL = "PipActivity";
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransferSplashscreenActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransferSplashscreenActivity.java
new file mode 100644
index 0000000..0323286
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/TransferSplashscreenActivity.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.window.SplashScreen;
+import android.window.SplashScreenView;
+
+public class TransferSplashscreenActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final SplashScreen splashScreen = getSplashScreen();
+        // Register setOnExitAnimationListener to transfer the splash screen window to client.
+        splashScreen.setOnExitAnimationListener(this::onSplashScreenExit);
+        final View content = findViewById(android.R.id.content);
+        // By register preDrawListener to defer app window draw signal about 500ms, which to ensure
+        // the splash screen must show when cold launch.
+        content.getViewTreeObserver().addOnPreDrawListener(
+                new ViewTreeObserver.OnPreDrawListener() {
+                    final long mCreateTime = SystemClock.uptimeMillis();
+                    @Override
+                    public boolean onPreDraw() {
+                        return SystemClock.uptimeMillis() - mCreateTime > 500;
+                    }
+                }
+        );
+    }
+
+    private void onSplashScreenExit(SplashScreenView view) {
+        view.remove();
+    }
+}
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
index ef49c7f..cb16191 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/BitmapsAlphaActivity.java
@@ -48,15 +48,15 @@
         BitmapsView(Context c) {
             super(c);
 
-            Log.d("OpenGLRenderer", "Loading sunset1, default options");
+            Log.d("HWUI", "Loading sunset1, default options");
             mBitmap1 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset1);
-            Log.d("OpenGLRenderer", "Loading sunset2, default options");
+            Log.d("HWUI", "Loading sunset2, default options");
             mBitmap2 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset2);
-            Log.d("OpenGLRenderer", "Loading sunset3, forcing ARGB-8888");
+            Log.d("HWUI", "Loading sunset3, forcing ARGB-8888");
             BitmapFactory.Options opts = new BitmapFactory.Options();
             opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
             mBitmap3 = BitmapFactory.decodeResource(c.getResources(), R.drawable.sunset3, opts);
-            Log.d("OpenGLRenderer", "    has bitmap alpha? " + mBitmap3.hasAlpha());
+            Log.d("HWUI", "    has bitmap alpha? " + mBitmap3.hasAlpha());
 
             mBitmapPaint = new Paint();
         }
@@ -65,7 +65,7 @@
         protected void onDraw(Canvas canvas) {
             super.onDraw(canvas);
             
-            Log.d("OpenGLRenderer", "================= Draw");
+            Log.d("HWUI", "================= Draw");
 
             canvas.translate(120.0f, 50.0f);
             canvas.drawBitmap(mBitmap1, 0.0f, 0.0f, mBitmapPaint);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java
index 1c82e9b..dbfb4ca 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ClearActivity.java
@@ -77,7 +77,7 @@
                     canvas.drawPath(mPath, mClearPaint);
                 }
                 canvas.restore();
-                canvas.drawText("OpenGLRenderer", 50.0f, 50.0f, mClearPaint);
+                canvas.drawText("HWUI", 50.0f, 50.0f, mClearPaint);
                 mClearPaint.setColor(0xff000000);
                 canvas.drawRect(800.0f, 100.0f, 900.0f, 200.0f, mClearPaint);
                 mClearPaint.setColor(0x0000ff00);
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
index e2d17cd..1f4c6c5 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ColorBitmapActivity.java
@@ -17,6 +17,7 @@
 package com.android.test.hwui;
 
 import android.app.Activity;
+import android.content.pm.ActivityInfo;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.ColorSpace;
@@ -72,6 +73,10 @@
     private int[] mGradientEndColors = {0xFFFFFFFF, 0xFFFF0000, 0xFF00FF00, 0xFF0000FF};
     private String[] mGradientColorNames = {"Grayscale", "Red", "Green", "Blue"};
 
+    private int mColorMode = ActivityInfo.COLOR_MODE_DEFAULT;
+    private int[] mColorModes = {ActivityInfo.COLOR_MODE_DEFAULT, ActivityInfo.COLOR_MODE_HDR};
+    private String[] mColorModeNames = {"DEFAULT", "HDR"};
+
     private final ExecutorService mBufferFenceExecutor = Executors.newFixedThreadPool(1);
     private final ExecutorService mBufferExecutor = Executors.newFixedThreadPool(1);
 
@@ -139,6 +144,15 @@
             gradientColorSpinner
                     .setOnItemSelectedListener(new GradientColorOnItemSelectedListener());
 
+            ArrayAdapter<String> colorModeAdapter = new ArrayAdapter<>(
+                    this, android.R.layout.simple_spinner_item, mColorModeNames);
+
+            colorModeAdapter
+                    .setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+            Spinner colorModeSpinner = new Spinner(this);
+            colorModeSpinner.setAdapter(colorModeAdapter);
+            colorModeSpinner.setOnItemSelectedListener(new ColorModeOnItemSelectedListener());
+
             mGradientBuffer = getGradientBuffer().get();
 
             LinearLayout linearLayout = new LinearLayout(this);
@@ -169,6 +183,10 @@
                     LinearLayout.LayoutParams.WRAP_CONTENT,
                     LinearLayout.LayoutParams.WRAP_CONTENT));
 
+            spinnerLayout.addView(colorModeSpinner, new LinearLayout.LayoutParams(
+                    LinearLayout.LayoutParams.WRAP_CONTENT,
+                    LinearLayout.LayoutParams.WRAP_CONTENT));
+
             linearLayout.addView(spinnerLayout, new LinearLayout.LayoutParams(
                     LinearLayout.LayoutParams.WRAP_CONTENT,
                     LinearLayout.LayoutParams.WRAP_CONTENT));
@@ -187,6 +205,8 @@
             linearLayout.addView(mSurfaceView, new LinearLayout.LayoutParams(WIDTH, HEIGHT));
 
             setContentView(linearLayout);
+
+            getWindow().setColorMode(mColorMode);
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
@@ -312,4 +332,22 @@
 
         }
     }
+
+    private final class ColorModeOnItemSelectedListener
+            implements AdapterView.OnItemSelectedListener {
+
+        @Override
+        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+            ColorBitmapActivity.this.mColorMode = ColorBitmapActivity.this.mColorModes[position];
+            ColorBitmapActivity.this.getMainExecutor()
+                    .execute(() -> {
+                        ColorBitmapActivity.this.getWindow().setColorMode(mColorMode);
+                    });
+        }
+
+        @Override
+        public void onNothingSelected(AdapterView<?> parent) {
+
+        }
+    }
 }
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
index 5192bfe..11a2a41 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/QuickRejectActivity.java
@@ -53,30 +53,30 @@
             super.onDraw(canvas);
 
             int count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "count=" + count);
+            Log.d("HWUI", "count=" + count);
             count = canvas.save();
-            Log.d("OpenGLRenderer", "count after save=" + count);
+            Log.d("HWUI", "count after save=" + count);
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "getSaveCount after save=" + count);
+            Log.d("HWUI", "getSaveCount after save=" + count);
             canvas.restore();
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "count after restore=" + count);
+            Log.d("HWUI", "count after restore=" + count);
             canvas.save();
-            Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+            Log.d("HWUI", "count after save=" + canvas.getSaveCount());
             canvas.save();
-            Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+            Log.d("HWUI", "count after save=" + canvas.getSaveCount());
             canvas.save();
-            Log.d("OpenGLRenderer", "count after save=" + canvas.getSaveCount());
+            Log.d("HWUI", "count after save=" + canvas.getSaveCount());
             canvas.restoreToCount(count);
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "count after restoreToCount=" + count);
+            Log.d("HWUI", "count after restoreToCount=" + count);
             count = canvas.saveLayer(0, 0, 10, 10, mBitmapPaint, Canvas.ALL_SAVE_FLAG);
-            Log.d("OpenGLRenderer", "count after saveLayer=" + count);
+            Log.d("HWUI", "count after saveLayer=" + count);
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "getSaveCount after saveLayer=" + count);
+            Log.d("HWUI", "getSaveCount after saveLayer=" + count);
             canvas.restore();
             count = canvas.getSaveCount();
-            Log.d("OpenGLRenderer", "count after restore=" + count);
+            Log.d("HWUI", "count after restore=" + count);
 
             canvas.save();
             canvas.clipRect(0.0f, 0.0f, 40.0f, 40.0f);
diff --git a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
index 56b0b9a..b39c932 100644
--- a/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyboardMetricsCollectorTests.kt
@@ -123,10 +123,6 @@
             createImeSubtype(3, ULocale.forLanguageTag("en-US"), "qwerty"),
             KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
-        ).addLayoutSelection(
-            createImeSubtype(4, null, "qwerty"), // Default language tag
-            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
-            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
         ).setIsFirstTimeConfiguration(true).build()
 
         assertEquals(
@@ -142,8 +138,8 @@
         assertTrue(event.isFirstConfiguration)
 
         assertEquals(
-            "KeyboardConfigurationEvent should contain 4 configurations provided",
-            4,
+            "KeyboardConfigurationEvent should contain 3 configurations provided",
+            3,
             event.layoutConfigurations.size
         )
         assertExpectedLayoutConfiguration(
@@ -159,7 +155,7 @@
             event.layoutConfigurations[1],
             "de-CH",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
-            KeyboardMetricsCollector.DEFAULT_LAYOUT,
+            KeyboardMetricsCollector.DEFAULT_LAYOUT_NAME,
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_USER,
             "en-US",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
@@ -173,10 +169,29 @@
             "en-US",
             KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwerty"),
         )
+    }
+
+    @Test
+    fun testCreateKeyboardConfigurationEvent_withDefaultLanguageTag() {
+        val builder = KeyboardMetricsCollector.KeyboardConfigurationEvent.Builder(
+            createKeyboard(
+                DEVICE_ID,
+                DEFAULT_VENDOR_ID,
+                DEFAULT_PRODUCT_ID,
+                "und", // Undefined language tag
+                "azerty"
+            )
+        )
+        val event = builder.addLayoutSelection(
+            createImeSubtype(4, null, "qwerty"), // Default language tag
+            KeyboardLayout(null, "German", null, 0, null, 0, 0, 0),
+            KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE
+        ).build()
+
         assertExpectedLayoutConfiguration(
-            event.layoutConfigurations[3],
-            "de-CH",
-            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("qwertz"),
+            event.layoutConfigurations[0],
+            KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
+            KeyboardLayout.LayoutType.getLayoutTypeEnumValue("azerty"),
             "German",
             KeyboardMetricsCollector.LAYOUT_SELECTION_CRITERIA_DEVICE,
             KeyboardMetricsCollector.DEFAULT_LANGUAGE_TAG,
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 5719273..4893d14 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -134,7 +134,7 @@
     private fun getExitReasons(): List<ApplicationExitInfo> {
         lateinit var infos: List<ApplicationExitInfo>
         instrumentation.runOnMainSync {
-            val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)
+            val am = instrumentation.getContext().getSystemService(ActivityManager::class.java)!!
             infos = am.getHistoricalProcessExitReasons(PACKAGE_NAME, ALL_PIDS, NO_MAX)
         }
         return infos
diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
index 3a24406..e56ce81 100644
--- a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
+++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt
@@ -48,6 +48,6 @@
         val inputManager = getSystemService(InputManager::class.java)
         mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId)
         mInputEventReceiver = UnresponsiveReceiver(
-                mInputMonitor.getInputChannel(), Looper.myLooper())
+                mInputMonitor.getInputChannel(), Looper.myLooper()!!)
     }
 }
diff --git a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
index f656881..c0d7cb4 100644
--- a/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
+++ b/tests/Internal/src/com/android/internal/app/LocaleStoreTest.java
@@ -63,6 +63,25 @@
     }
 
     @Test
+    public void testTransformImeLanguageTagToLocaleInfo_duplicateTagFilter() {
+        List<InputMethodSubtype> list = List.of(
+                new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("en-US").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("zh-TW").build(),
+                new InputMethodSubtypeBuilder().setLanguageTag("ja-JP").build());
+
+        Set<LocaleInfo> localeSet = LocaleStore.transformImeLanguageTagToLocaleInfo(list);
+
+        Set<String> expectedLanguageTag = Set.of("en-US", "zh-TW", "ja-JP");
+        assertEquals(localeSet.size(), expectedLanguageTag.size());
+        for (LocaleInfo info : localeSet) {
+            assertEquals(info.mSuggestionFlags, LocaleInfo.SUGGESTION_TYPE_IME_LANGUAGE);
+            assertTrue(expectedLanguageTag.contains(info.getId()));
+        }
+    }
+
+    @Test
     public void convertExplicitLocales_noExplicitLcoales_returnEmptyHashMap() {
         Collection<LocaleInfo> supportedLocale = getFakeSupportedLocales();
 
diff --git a/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt b/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt
index 9d17d38..4d38660 100644
--- a/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt
+++ b/tests/SilkFX/src/com/android/test/silkfx/materials/BackgroundBlurActivity.kt
@@ -132,7 +132,7 @@
         mBlurForceDisabled = disabled
         Settings.Global.putInt(getContentResolver(), Settings.Global.DISABLE_WINDOW_BLURS,
                 if (mBlurForceDisabled) 1 else 0)
-        (findViewById(R.id.toggle_blur_enabled) as Button)
+        (requireViewById(R.id.toggle_blur_enabled) as Button)
                 .setText(if (mBlurForceDisabled) "Enable blurs" else "Disable blurs")
     }
 
@@ -142,13 +142,13 @@
 
     fun setBackgroundBlur(radius: Int) {
         mBackgroundBlurRadius = radius
-        (findViewById(R.id.background_blur_radius) as TextView).setText(radius.toString())
+        (requireViewById(R.id.background_blur_radius) as TextView).setText(radius.toString())
         window.setBackgroundBlurRadius(mBackgroundBlurRadius)
     }
 
     fun setBlurBehind(radius: Int) {
         mBlurBehindRadius = radius
-        (findViewById(R.id.blur_behind_radius) as TextView).setText(radius.toString())
+        (requireViewById(R.id.blur_behind_radius) as TextView).setText(radius.toString())
         window.getAttributes().setBlurBehindRadius(mBlurBehindRadius)
         window.setAttributes(window.getAttributes())
     }
@@ -159,7 +159,7 @@
         } else {
             mDimAmountNoBlur = amount
         }
-        (findViewById(R.id.dim_amount) as TextView).setText("%.2f".format(amount))
+        (requireViewById(R.id.dim_amount) as TextView).setText("%.2f".format(amount))
         window.getAttributes().dimAmount = amount
         window.setAttributes(window.getAttributes())
     }
@@ -168,7 +168,7 @@
         mBatterySavingModeOn = on
         Settings.Global.putInt(getContentResolver(),
             Settings.Global.LOW_POWER_MODE, if (on) 1 else 0)
-        (findViewById(R.id.toggle_battery_saving_mode) as Button).setText(
+        (requireViewById(R.id.toggle_battery_saving_mode) as Button).setText(
             if (on) "Exit low power mode" else "Enter low power mode")
     }
 
@@ -182,7 +182,7 @@
         } else {
             mAlphaNoBlur = alpha
         }
-        (findViewById(R.id.background_alpha) as TextView).setText("%.2f".format(alpha))
+        (requireViewById(R.id.background_alpha) as TextView).setText("%.2f".format(alpha))
         mBackgroundDrawable.setAlpha((alpha * 255f).toInt())
         getWindowManager().updateViewLayout(window.getDecorView(), window.getAttributes())
     }
diff --git a/tests/testables/src/android/testing/TestableResources.java b/tests/testables/src/android/testing/TestableResources.java
index 27d5b66..0ec106e 100644
--- a/tests/testables/src/android/testing/TestableResources.java
+++ b/tests/testables/src/android/testing/TestableResources.java
@@ -15,9 +15,11 @@
 package android.testing;
 
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
 import android.content.Context;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.util.Log;
 import android.util.SparseArray;
@@ -54,6 +56,16 @@
     }
 
     /**
+     * Sets a configuration for {@link #getResources()} to return to allow custom configs to
+     * be set and tested.
+     *
+     * @param configuration the configuration to return from resources.
+     */
+    public void overrideConfiguration(Configuration configuration) {
+        when(mResources.getConfiguration()).thenReturn(configuration);
+    }
+
+    /**
      * Sets the return value for the specified resource id.
      * <p>
      * Since resource ids are unique there is a single addOverride that will override the value