Merge "Gracefully failing Exceptions during logging" into udc-dev
diff --git a/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java b/apex/jobscheduler/service/java/com/android/server/AppSchedulingModuleThread.java
similarity index 69%
rename from apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
rename to apex/jobscheduler/service/java/com/android/server/AppSchedulingModuleThread.java
index a413f7b..d36f44f 100644
--- a/apex/jobscheduler/service/java/com/android/server/JobSchedulerBackgroundThread.java
+++ b/apex/jobscheduler/service/java/com/android/server/AppSchedulingModuleThread.java
@@ -20,29 +20,30 @@
 import android.os.HandlerExecutor;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.Process;
 import android.os.Trace;
 
 import java.util.concurrent.Executor;
 
 /**
- * Shared singleton background thread.
+ * Shared singleton default priority thread for the app scheduling module.
  *
  * @see com.android.internal.os.BackgroundThread
  */
-public final class JobSchedulerBackgroundThread extends HandlerThread {
+public final class AppSchedulingModuleThread extends HandlerThread {
     private static final long SLOW_DISPATCH_THRESHOLD_MS = 10_000;
     private static final long SLOW_DELIVERY_THRESHOLD_MS = 30_000;
-    private static JobSchedulerBackgroundThread sInstance;
+    private static AppSchedulingModuleThread sInstance;
     private static Handler sHandler;
     private static Executor sHandlerExecutor;
 
-    private JobSchedulerBackgroundThread() {
-        super("jobscheduler.bg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+    private AppSchedulingModuleThread() {
+        super("appscheduling.default", Process.THREAD_PRIORITY_DEFAULT);
     }
 
     private static void ensureThreadLocked() {
         if (sInstance == null) {
-            sInstance = new JobSchedulerBackgroundThread();
+            sInstance = new AppSchedulingModuleThread();
             sInstance.start();
             final Looper looper = sInstance.getLooper();
             looper.setTraceTag(Trace.TRACE_TAG_SYSTEM_SERVER);
@@ -53,25 +54,25 @@
         }
     }
 
-    /** Returns the JobSchedulerBackgroundThread singleton */
-    public static JobSchedulerBackgroundThread get() {
-        synchronized (JobSchedulerBackgroundThread.class) {
+    /** Returns the AppSchedulingModuleThread singleton */
+    public static AppSchedulingModuleThread get() {
+        synchronized (AppSchedulingModuleThread.class) {
             ensureThreadLocked();
             return sInstance;
         }
     }
 
-    /** Returns the singleton handler for JobSchedulerBackgroundThread */
+    /** Returns the singleton handler for AppSchedulingModuleThread */
     public static Handler getHandler() {
-        synchronized (JobSchedulerBackgroundThread.class) {
+        synchronized (AppSchedulingModuleThread.class) {
             ensureThreadLocked();
             return sHandler;
         }
     }
 
-    /** Returns the singleton handler executor for JobSchedulerBackgroundThread */
+    /** Returns the singleton handler executor for AppSchedulingModuleThread */
     public static Executor getExecutor() {
-        synchronized (JobSchedulerBackgroundThread.class) {
+        synchronized (AppSchedulingModuleThread.class) {
             ensureThreadLocked();
             return sHandlerExecutor;
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 5de1172..9e4321d 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -1327,7 +1327,7 @@
                 IDLE_AFTER_INACTIVE_TIMEOUT = DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT_SMALL_BATTERY;
             }
             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DEVICE_IDLE,
-                    JobSchedulerBackgroundThread.getExecutor(), this);
+                    AppSchedulingModuleThread.getExecutor(), this);
             // Load all the constants.
             onPropertiesChanged(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_DEVICE_IDLE));
         }
@@ -2416,7 +2416,7 @@
         }
 
         MyHandler getHandler(DeviceIdleController controller) {
-            return controller.new MyHandler(JobSchedulerBackgroundThread.getHandler().getLooper());
+            return controller.new MyHandler(AppSchedulingModuleThread.getHandler().getLooper());
         }
 
         Sensor getMotionSensor() {
@@ -2488,7 +2488,7 @@
         mConfigFile = new AtomicFile(new File(getSystemDir(), "deviceidle.xml"));
         mHandler = mInjector.getHandler(this);
         mAppStateTracker = mInjector.getAppStateTracker(context,
-                JobSchedulerBackgroundThread.get().getLooper());
+                AppSchedulingModuleThread.get().getLooper());
         LocalServices.addService(AppStateTracker.class, mAppStateTracker);
         mUseMotionSensor = mInjector.useMotionSensor();
     }
@@ -2681,7 +2681,7 @@
                 mLocalActivityTaskManager.registerScreenObserver(mScreenObserver);
 
                 mInjector.getTelephonyManager().registerTelephonyCallback(
-                        JobSchedulerBackgroundThread.getExecutor(), mEmergencyCallListener);
+                        AppSchedulingModuleThread.getExecutor(), mEmergencyCallListener);
 
                 passWhiteListsToForceAppStandbyTrackerLocked();
                 updateInteractivityLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index deb8a63..1151bb7 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -147,12 +147,12 @@
 import com.android.internal.util.RingBuffer;
 import com.android.internal.util.StatLogger;
 import com.android.server.AlarmManagerInternal;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.AppStateTracker;
 import com.android.server.AppStateTrackerImpl;
 import com.android.server.AppStateTrackerImpl.Listener;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.EventLogTags;
-import com.android.server.JobSchedulerBackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemClockTime;
 import com.android.server.SystemClockTime.TimeConfidence;
@@ -4691,7 +4691,7 @@
 
         void registerDeviceConfigListener(DeviceConfig.OnPropertiesChangedListener listener) {
             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ALARM_MANAGER,
-                    JobSchedulerBackgroundThread.getExecutor(), listener);
+                    AppSchedulingModuleThread.getExecutor(), listener);
         }
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 272a79e..89eb1a9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -62,7 +62,7 @@
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.util.MemInfoReader;
 import com.android.internal.util.StatLogger;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
 import com.android.server.job.controllers.JobStatus;
 import com.android.server.job.controllers.StateController;
@@ -499,7 +499,7 @@
         mInjector = injector;
         mNotificationCoordinator = new JobNotificationCoordinator();
 
-        mHandler = JobSchedulerBackgroundThread.getHandler();
+        mHandler = AppSchedulingModuleThread.getHandler();
 
         mGracePeriodObserver = new GracePeriodObserver(mContext);
         mShouldRestrictBgUser = mContext.getResources().getBoolean(
@@ -533,7 +533,8 @@
             mIdleContexts.add(
                     mInjector.createJobServiceContext(mService, this,
                             mNotificationCoordinator, batteryStats,
-                            mService.mJobPackageTracker, mContext.getMainLooper()));
+                            mService.mJobPackageTracker,
+                            AppSchedulingModuleThread.get().getLooper()));
         }
     }
 
@@ -1925,7 +1926,7 @@
         return mInjector.createJobServiceContext(mService, this, mNotificationCoordinator,
                 IBatteryStats.Stub.asInterface(
                         ServiceManager.getService(BatteryStats.SERVICE_NAME)),
-                mService.mJobPackageTracker, mContext.getMainLooper());
+                mService.mJobPackageTracker, AppSchedulingModuleThread.get().getLooper());
     }
 
     @GuardedBy("mLock")
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 0af191a..3cc67e7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -105,10 +105,10 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.AppStateTracker;
 import com.android.server.AppStateTrackerImpl;
 import com.android.server.DeviceIdleInternal;
-import com.android.server.JobSchedulerBackgroundThread;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
 import com.android.server.job.controllers.BackgroundJobsController;
@@ -411,7 +411,7 @@
             EconomyManagerInternal.TareStateChangeListener {
         public void start() {
             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                    JobSchedulerBackgroundThread.getExecutor(), this);
+                    AppSchedulingModuleThread.getExecutor(), this);
             final EconomyManagerInternal economyManagerInternal =
                     LocalServices.getService(EconomyManagerInternal.class);
             economyManagerInternal
@@ -2009,7 +2009,7 @@
         mActivityManagerInternal = Objects.requireNonNull(
                 LocalServices.getService(ActivityManagerInternal.class));
 
-        mHandler = new JobHandler(context.getMainLooper());
+        mHandler = new JobHandler(AppSchedulingModuleThread.get().getLooper());
         mConstants = new Constants();
         mConstantsObserver = new ConstantsObserver();
         mJobSchedulerStub = new JobSchedulerStub();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index 2ca3f8f..4c55dac 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -35,7 +35,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
@@ -151,7 +151,7 @@
     @GuardedBy("mLock")
     public void onBatteryStateChangedLocked() {
         // Update job bookkeeping out of band.
-        JobSchedulerBackgroundThread.getHandler().post(() -> {
+        AppSchedulingModuleThread.getHandler().post(() -> {
             synchronized (mLock) {
                 maybeReportNewChargingStateLocked();
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 2a9186a..e55bda7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -52,7 +52,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobSchedulerService.Constants;
@@ -256,7 +256,7 @@
     public ConnectivityController(JobSchedulerService service,
             @NonNull FlexibilityController flexibilityController) {
         super(service);
-        mHandler = new CcHandler(mContext.getMainLooper());
+        mHandler = new CcHandler(AppSchedulingModuleThread.get().getLooper());
 
         mConnManager = mContext.getSystemService(ConnectivityManager.class);
         mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);
@@ -1347,7 +1347,7 @@
                 TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId);
                 CellSignalStrengthCallback callback = new CellSignalStrengthCallback();
                 idTm.registerTelephonyCallback(
-                        JobSchedulerBackgroundThread.getExecutor(), callback);
+                        AppSchedulingModuleThread.getExecutor(), callback);
                 mSignalStrengths.put(subId, callback);
 
                 final SignalStrength signalStrength = idTm.getSignalStrength();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
index 847a1bf..122fe69 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
@@ -33,6 +33,7 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
 import com.android.server.job.StateControllerProto.ContentObserverController.Observer.TriggerContentData;
@@ -70,7 +71,7 @@
 
     public ContentObserverController(JobSchedulerService service) {
         super(service);
-        mHandler = new Handler(mContext.getMainLooper());
+        mHandler = new Handler(AppSchedulingModuleThread.get().getLooper());
     }
 
     @Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index bdf72b6..d5c9ae6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -36,6 +36,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.ArrayUtils;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
@@ -127,7 +128,7 @@
     public DeviceIdleJobsController(JobSchedulerService service) {
         super(service);
 
-        mHandler = new DeviceIdleJobsDelayHandler(mContext.getMainLooper());
+        mHandler = new DeviceIdleJobsDelayHandler(AppSchedulingModuleThread.get().getLooper());
         // Register for device idle mode changes
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mLocalDeviceIdleController =
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 4c17692..620c48d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -44,7 +44,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.utils.AlarmQueue;
 
@@ -183,7 +183,7 @@
         mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS);
         mFcConfig = new FcConfig();
         mFlexibilityAlarmQueue = new FlexibilityAlarmQueue(
-                mContext, JobSchedulerBackgroundThread.get().getLooper());
+                mContext, AppSchedulingModuleThread.get().getLooper());
         mPercentToDropConstraints =
                 mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS;
         mPrefetchController = prefetchController;
@@ -427,7 +427,7 @@
     @GuardedBy("mLock")
     public void onConstantsUpdatedLocked() {
         if (mFcConfig.mShouldReevaluateConstraints) {
-            JobSchedulerBackgroundThread.getHandler().post(() -> {
+            AppSchedulingModuleThread.getHandler().post(() -> {
                 final ArraySet<JobStatus> changedJobs = new ArraySet<>();
                 synchronized (mLock) {
                     final long nowElapsed = sElapsedRealtimeClock.millis();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index c46ffd7..2b7438c 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -46,7 +46,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.utils.AlarmQueue;
@@ -130,9 +130,9 @@
     public PrefetchController(JobSchedulerService service) {
         super(service);
         mPcConstants = new PcConstants();
-        mHandler = new PcHandler(mContext.getMainLooper());
+        mHandler = new PcHandler(AppSchedulingModuleThread.get().getLooper());
         mThresholdAlarmListener = new ThresholdAlarmListener(
-                mContext, JobSchedulerBackgroundThread.get().getLooper());
+                mContext, AppSchedulingModuleThread.get().getLooper());
         mUsageStatsManagerInternal = LocalServices.getService(UsageStatsManagerInternal.class);
 
         mUsageStatsManagerInternal
@@ -400,7 +400,7 @@
     public void onConstantsUpdatedLocked() {
         if (mPcConstants.mShouldReevaluateConstraints) {
             // Update job bookkeeping out of band.
-            JobSchedulerBackgroundThread.getHandler().post(() -> {
+            AppSchedulingModuleThread.getHandler().post(() -> {
                 final ArraySet<JobStatus> changedJobs = new ArraySet<>();
                 synchronized (mLock) {
                     final long nowElapsed = sElapsedRealtimeClock.millis();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index cbfad94..aca0a6e 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -65,7 +65,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
 import com.android.server.PowerAllowlistInternal;
 import com.android.server.job.ConstantsProto;
@@ -560,13 +560,14 @@
             @NonNull BackgroundJobsController backgroundJobsController,
             @NonNull ConnectivityController connectivityController) {
         super(service);
-        mHandler = new QcHandler(mContext.getMainLooper());
+        mHandler = new QcHandler(AppSchedulingModuleThread.get().getLooper());
         mAlarmManager = mContext.getSystemService(AlarmManager.class);
         mQcConstants = new QcConstants();
         mBackgroundJobsController = backgroundJobsController;
         mConnectivityController = connectivityController;
         mIsEnabled = !mConstants.USE_TARE_POLICY;
-        mInQuotaAlarmQueue = new InQuotaAlarmQueue(mContext, mContext.getMainLooper());
+        mInQuotaAlarmQueue =
+                new InQuotaAlarmQueue(mContext, AppSchedulingModuleThread.get().getLooper());
 
         // Set up the app standby bucketing tracker
         AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class);
@@ -1649,7 +1650,7 @@
         mEJPkgTimers.forEach(mTimerChargingUpdateFunctor);
         mPkgTimers.forEach(mTimerChargingUpdateFunctor);
         // Now update jobs out of band so broadcast processing can proceed.
-        JobSchedulerBackgroundThread.getHandler().post(() -> {
+        AppSchedulingModuleThread.getHandler().post(() -> {
             synchronized (mLock) {
                 maybeUpdateAllConstraintsLocked();
             }
@@ -2486,7 +2487,7 @@
         public void onAppIdleStateChanged(final String packageName, final @UserIdInt int userId,
                 boolean idle, int bucket, int reason) {
             // Update job bookkeeping out of band.
-            JobSchedulerBackgroundThread.getHandler().post(() -> {
+            AppSchedulingModuleThread.getHandler().post(() -> {
                 final int bucketIndex = JobSchedulerService.standbyBucketToBucketIndex(bucket);
                 updateStandbyBucket(userId, packageName, bucketIndex);
             });
@@ -2938,7 +2939,7 @@
         if (mQcConstants.mShouldReevaluateConstraints || mIsEnabled == mConstants.USE_TARE_POLICY) {
             mIsEnabled = !mConstants.USE_TARE_POLICY;
             // Update job bookkeeping out of band.
-            JobSchedulerBackgroundThread.getHandler().post(() -> {
+            AppSchedulingModuleThread.getHandler().post(() -> {
                 synchronized (mLock) {
                     invalidateAllExecutionStatsLocked();
                     maybeUpdateAllConstraintsLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
index 792155b..7408088 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
@@ -30,7 +30,7 @@
 import android.util.SparseArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.tare.EconomicPolicy;
@@ -424,7 +424,7 @@
         if (mIsEnabled != mConstants.USE_TARE_POLICY) {
             mIsEnabled = mConstants.USE_TARE_POLICY;
             // Update job bookkeeping out of band.
-            JobSchedulerBackgroundThread.getHandler().post(() -> {
+            AppSchedulingModuleThread.getHandler().post(() -> {
                 synchronized (mLock) {
                     final long nowElapsed = sElapsedRealtimeClock.millis();
                     mService.getJobStore().forEachJob((jobStatus) -> {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
index 47b7e13..c458cae 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/CarIdlenessTracker.java
@@ -24,7 +24,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
@@ -91,7 +91,7 @@
         filter.addAction(ACTION_UNFORCE_IDLE);
         filter.addAction(ActivityManagerService.ACTION_TRIGGER_IDLE);
 
-        context.registerReceiver(this, filter, null, JobSchedulerBackgroundThread.getHandler());
+        context.registerReceiver(this, filter, null, AppSchedulingModuleThread.getHandler());
     }
 
     @Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
index 15d6766..c943e73 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/idle/DeviceIdlenessTracker.java
@@ -31,7 +31,7 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.StateControllerProto;
@@ -102,10 +102,10 @@
         filter.addAction(Intent.ACTION_DOCK_IDLE);
         filter.addAction(Intent.ACTION_DOCK_ACTIVE);
 
-        context.registerReceiver(this, filter, null, JobSchedulerBackgroundThread.getHandler());
+        context.registerReceiver(this, filter, null, AppSchedulingModuleThread.getHandler());
 
         context.getSystemService(UiModeManager.class).addOnProjectionStateChangedListener(
-                UiModeManager.PROJECTION_TYPE_ALL, JobSchedulerBackgroundThread.getExecutor(),
+                UiModeManager.PROJECTION_TYPE_ALL, AppSchedulingModuleThread.getExecutor(),
                 mOnProjectionStateChangedListener);
     }
 
@@ -226,7 +226,7 @@
             }
             mAlarm.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                     when, mIdleWindowSlop, "JS idleness",
-                    JobSchedulerBackgroundThread.getExecutor(), mIdleAlarmListener);
+                    AppSchedulingModuleThread.getExecutor(), mIdleAlarmListener);
         }
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index a3d566b..c3118ff 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -125,7 +125,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.ConcurrentUtils;
 import com.android.server.AlarmManagerInternal;
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.usage.AppIdleHistory.AppUsageHistory;
@@ -592,7 +592,7 @@
     }
 
     public AppStandbyController(Context context) {
-        this(new Injector(context, JobSchedulerBackgroundThread.get().getLooper()));
+        this(new Injector(context, AppSchedulingModuleThread.get().getLooper()));
     }
 
     AppStandbyController(Injector injector) {
@@ -2761,7 +2761,7 @@
         void registerDeviceConfigPropertiesChangedListener(
                 @NonNull DeviceConfig.OnPropertiesChangedListener listener) {
             DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_STANDBY,
-                    JobSchedulerBackgroundThread.getExecutor(), listener);
+                    AppSchedulingModuleThread.getExecutor(), listener);
         }
 
         void dump(PrintWriter pw) {
diff --git a/core/api/current.txt b/core/api/current.txt
index 211f603..5b9970b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5279,10 +5279,28 @@
   }
 
   public class BroadcastOptions {
-    ctor public BroadcastOptions();
+    method public void clearDeferralPolicy();
+    method public void clearDeliveryGroupMatchingFilter();
+    method public void clearDeliveryGroupMatchingKey();
+    method public void clearDeliveryGroupPolicy();
+    method @NonNull public static android.app.BroadcastOptions fromBundle(@NonNull android.os.Bundle);
+    method public int getDeferralPolicy();
+    method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
+    method @Nullable public String getDeliveryGroupMatchingKey();
+    method public int getDeliveryGroupPolicy();
     method public boolean isShareIdentityEnabled();
+    method @NonNull public static android.app.BroadcastOptions makeBasic();
+    method @NonNull public android.app.BroadcastOptions setDeferralPolicy(int);
+    method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter);
+    method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
+    method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int);
     method @NonNull public android.app.BroadcastOptions setShareIdentityEnabled(boolean);
     method @NonNull public android.os.Bundle toBundle();
+    field public static final int DEFERRAL_POLICY_DEFAULT = 0; // 0x0
+    field public static final int DEFERRAL_POLICY_NONE = 1; // 0x1
+    field public static final int DEFERRAL_POLICY_UNTIL_ACTIVE = 2; // 0x2
+    field public static final int DELIVERY_GROUP_POLICY_ALL = 0; // 0x0
+    field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1
   }
 
   public class DatePickerDialog extends android.app.AlertDialog implements android.widget.DatePicker.OnDateChangedListener android.content.DialogInterface.OnClickListener {
@@ -7398,12 +7416,15 @@
   }
 
   public class UiModeManager {
+    method public void addContrastChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.UiModeManager.ContrastChangeListener);
     method public void disableCarMode(int);
     method public void enableCarMode(int);
+    method @FloatRange(from=-1.0F, to=1.0f) public float getContrast();
     method public int getCurrentModeType();
     method @NonNull public java.time.LocalTime getCustomNightModeEnd();
     method @NonNull public java.time.LocalTime getCustomNightModeStart();
     method public int getNightMode();
+    method public void removeContrastChangeListener(@NonNull android.app.UiModeManager.ContrastChangeListener);
     method public void setApplicationNightMode(int);
     method public void setCustomNightModeEnd(@NonNull java.time.LocalTime);
     method public void setCustomNightModeStart(@NonNull java.time.LocalTime);
@@ -7421,6 +7442,10 @@
     field public static final int MODE_NIGHT_YES = 2; // 0x2
   }
 
+  public static interface UiModeManager.ContrastChangeListener {
+    method public void onContrastChanged(@FloatRange(from=-1.0F, to=1.0f) float);
+  }
+
   public final class VoiceInteractor {
     method public android.app.VoiceInteractor.Request getActiveRequest(String);
     method public android.app.VoiceInteractor.Request[] getActiveRequests();
@@ -11157,6 +11182,7 @@
     method public final String getDataScheme(int);
     method public final android.os.PatternMatcher getDataSchemeSpecificPart(int);
     method public final String getDataType(int);
+    method @NonNull public final android.os.PersistableBundle getExtras();
     method public final int getPriority();
     method public final boolean hasAction(String);
     method public final boolean hasCategory(String);
@@ -11175,6 +11201,7 @@
     method public void readFromXml(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
     method public final java.util.Iterator<android.os.PatternMatcher> schemeSpecificPartsIterator();
     method public final java.util.Iterator<java.lang.String> schemesIterator();
+    method public final void setExtras(@NonNull android.os.PersistableBundle);
     method public final void setPriority(int);
     method public final java.util.Iterator<java.lang.String> typesIterator();
     method public final void writeToParcel(android.os.Parcel, int);
@@ -12731,6 +12758,7 @@
     field public static final String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute";
     field public static final String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level";
     field public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
+    field public static final String FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS = "android.software.wallet_location_based_suggestions";
     field public static final String FEATURE_WATCH = "android.hardware.type.watch";
     field public static final String FEATURE_WEBVIEW = "android.software.webview";
     field public static final String FEATURE_WIFI = "android.hardware.wifi";
@@ -26275,9 +26303,9 @@
 
   public final class MediaProjection {
     method public android.hardware.display.VirtualDisplay createVirtualDisplay(@NonNull String, int, int, int, int, @Nullable android.view.Surface, @Nullable android.hardware.display.VirtualDisplay.Callback, @Nullable android.os.Handler);
-    method public void registerCallback(android.media.projection.MediaProjection.Callback, android.os.Handler);
+    method public void registerCallback(@NonNull android.media.projection.MediaProjection.Callback, @Nullable android.os.Handler);
     method public void stop();
-    method public void unregisterCallback(android.media.projection.MediaProjection.Callback);
+    method public void unregisterCallback(@NonNull android.media.projection.MediaProjection.Callback);
   }
 
   public abstract static class MediaProjection.Callback {
@@ -41108,6 +41136,7 @@
     method @NonNull public String getCardId();
     method @NonNull public android.graphics.drawable.Icon getCardImage();
     method @Nullable public CharSequence getCardLabel();
+    method @NonNull public java.util.List<android.location.Location> getCardLocations();
     method @NonNull public int getCardType();
     method @NonNull public CharSequence getContentDescription();
     method @Nullable public android.graphics.drawable.Icon getNonPaymentCardSecondaryImage();
@@ -41125,6 +41154,7 @@
     method @NonNull public android.service.quickaccesswallet.WalletCard build();
     method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardIcon(@Nullable android.graphics.drawable.Icon);
     method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardLabel(@Nullable CharSequence);
+    method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setCardLocations(@NonNull java.util.List<android.location.Location>);
     method @NonNull public android.service.quickaccesswallet.WalletCard.Builder setNonPaymentCardSecondaryImage(@Nullable android.graphics.drawable.Icon);
   }
 
@@ -54159,14 +54189,12 @@
     method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
     method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
     method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
-    method public void addUiContrastChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener);
     method @ColorInt public int getAccessibilityFocusColor();
     method public int getAccessibilityFocusStrokeWidth();
     method @Deprecated public java.util.List<android.content.pm.ServiceInfo> getAccessibilityServiceList();
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(int);
     method public java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList();
     method public int getRecommendedTimeoutMillis(int, int);
-    method @FloatRange(from=-1.0F, to=1.0f) public float getUiContrast();
     method public void interrupt();
     method public static boolean isAccessibilityButtonSupported();
     method public boolean isAudioDescriptionRequested();
@@ -54178,7 +54206,6 @@
     method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
     method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
     method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
-    method public void removeUiContrastChangeListener(@NonNull android.view.accessibility.AccessibilityManager.UiContrastChangeListener);
     method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
     field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
     field public static final int FLAG_CONTENT_ICONS = 1; // 0x1
@@ -54201,10 +54228,6 @@
     method public void onTouchExplorationStateChanged(boolean);
   }
 
-  public static interface AccessibilityManager.UiContrastChangeListener {
-    method public void onUiContrastChanged(@FloatRange(from=-1.0F, to=1.0f) float);
-  }
-
   public class AccessibilityNodeInfo implements android.os.Parcelable {
     ctor public AccessibilityNodeInfo();
     ctor public AccessibilityNodeInfo(@NonNull android.view.View);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 11a0f0b..fb5ee8d 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -834,23 +834,13 @@
   }
 
   public class BroadcastOptions {
-    method public void clearDeliveryGroupMatchingFilter();
-    method public void clearDeliveryGroupMatchingKey();
-    method public void clearDeliveryGroupPolicy();
     method public void clearRequireCompatChange();
-    method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter();
-    method @Nullable public String getDeliveryGroupMatchingKey();
-    method public int getDeliveryGroupPolicy();
     method public int getPendingIntentBackgroundActivityStartMode();
-    method public boolean isDeferUntilActive();
+    method @Deprecated public boolean isDeferUntilActive();
     method @Deprecated public boolean isPendingIntentBackgroundActivityLaunchAllowed();
-    method @Deprecated @NonNull public static android.app.BroadcastOptions makeBasic();
     method @RequiresPermission(android.Manifest.permission.ACCESS_BROADCAST_RESPONSE_STATS) public void recordResponseEventWhileInBackground(@IntRange(from=0) long);
     method @RequiresPermission(android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND) public void setBackgroundActivityStartsAllowed(boolean);
-    method @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
-    method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter);
-    method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String);
-    method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int);
+    method @Deprecated @NonNull public android.app.BroadcastOptions setDeferUntilActive(boolean);
     method public void setDontSendToRestrictedApps(boolean);
     method @Deprecated public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
     method @NonNull public android.app.BroadcastOptions setPendingIntentBackgroundActivityStartMode(int);
@@ -859,8 +849,6 @@
     method public void setRequireNoneOfPermissions(@Nullable String[]);
     method @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppAllowlist(long, int, int, @Nullable String);
     method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST, android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, android.Manifest.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND}) public void setTemporaryAppWhitelistDuration(long);
-    field public static final int DELIVERY_GROUP_POLICY_ALL = 0; // 0x0
-    field public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1; // 0x1
   }
 
   public class DownloadManager {
@@ -3581,9 +3569,7 @@
   }
 
   public class IntentFilter implements android.os.Parcelable {
-    method @NonNull public final android.os.PersistableBundle getExtras();
     method public final int getOrder();
-    method public final void setExtras(@NonNull android.os.PersistableBundle);
     method public final void setOrder(int);
   }
 
@@ -10143,9 +10129,9 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public boolean disconnectHotspotNetwork(@NonNull android.net.wifi.sharedconnectivity.app.HotspotNetwork);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public boolean forgetKnownNetwork(@NonNull android.net.wifi.sharedconnectivity.app.KnownNetwork);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public android.net.wifi.sharedconnectivity.app.HotspotNetworkConnectionStatus getHotspotNetworkConnectionStatus();
-    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.sharedconnectivity.app.HotspotNetwork> getHotspotNetworks();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.sharedconnectivity.app.HotspotNetwork> getHotspotNetworks();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public android.net.wifi.sharedconnectivity.app.KnownNetworkConnectionStatus getKnownNetworkConnectionStatus();
-    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.sharedconnectivity.app.KnownNetwork> getKnownNetworks();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.sharedconnectivity.app.KnownNetwork> getKnownNetworks();
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public android.net.wifi.sharedconnectivity.app.SharedConnectivitySettingsState getSettingsState();
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback);
     method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public boolean unregisterCallback(@NonNull android.net.wifi.sharedconnectivity.app.SharedConnectivityClientCallback);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a3bc392..46651a9 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -269,6 +269,7 @@
   }
 
   public class BroadcastOptions extends android.app.ComponentOptions {
+    ctor public BroadcastOptions();
     ctor public BroadcastOptions(@NonNull android.os.Bundle);
     method @Deprecated public int getMaxManifestReceiverApiLevel();
     method public long getTemporaryAppAllowlistDuration();
@@ -1145,7 +1146,6 @@
 
   public final class Entry implements android.os.Parcelable {
     ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice);
-    ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, @NonNull android.app.PendingIntent, @NonNull android.content.Intent);
     ctor public Entry(@NonNull String, @NonNull String, @NonNull android.app.slice.Slice, @NonNull android.content.Intent);
     method public int describeContents();
     method @Nullable public android.content.Intent getFrameworkExtrasIntent();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 9bf9e0c..7c32c9c 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -7200,10 +7200,16 @@
         if (mContext != null) {
             final PackageManager pm = mContext.getPackageManager();
             try {
-                if (pm != null && pm.checkPermission(Manifest.permission.READ_DEVICE_CONFIG,
-                        mContext.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
-                    DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY,
-                            mContext.getMainExecutor(), properties -> {
+                if (Build.IS_ENG
+                        && pm != null
+                        && pm.checkPermission(
+                                        Manifest.permission.READ_DEVICE_CONFIG,
+                                        mContext.getPackageName())
+                                == PackageManager.PERMISSION_GRANTED) {
+                    DeviceConfig.addOnPropertiesChangedListener(
+                            DeviceConfig.NAMESPACE_PRIVACY,
+                            mContext.getMainExecutor(),
+                            properties -> {
                                 if (properties.getKeyset().contains(FULL_LOG)) {
                                     sFullLog = properties.getBoolean(FULL_LOG, false);
                                 }
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index f48181b..481f671 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -64,6 +64,7 @@
     private @Nullable String mDeliveryGroupMatchingKey;
     private @Nullable BundleMerger mDeliveryGroupExtrasMerger;
     private @Nullable IntentFilter mDeliveryGroupMatchingFilter;
+    private @DeferralPolicy int mDeferralPolicy;
 
     /** @hide */
     @IntDef(flag = true, prefix = { "FLAG_" }, value = {
@@ -71,8 +72,8 @@
             FLAG_ALLOW_BACKGROUND_ACTIVITY_STARTS,
             FLAG_REQUIRE_COMPAT_CHANGE_ENABLED,
             FLAG_IS_ALARM_BROADCAST,
-            FLAG_IS_DEFER_UNTIL_ACTIVE,
             FLAG_SHARE_IDENTITY,
+            FLAG_INTERACTIVE,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Flags {}
@@ -81,8 +82,8 @@
     private static final int FLAG_ALLOW_BACKGROUND_ACTIVITY_STARTS = 1 << 1;
     private static final int FLAG_REQUIRE_COMPAT_CHANGE_ENABLED = 1 << 2;
     private static final int FLAG_IS_ALARM_BROADCAST = 1 << 3;
-    private static final int FLAG_IS_DEFER_UNTIL_ACTIVE = 1 << 4;
-    private static final int FLAG_SHARE_IDENTITY = 1 << 5;
+    private static final int FLAG_SHARE_IDENTITY = 1 << 4;
+    private static final int FLAG_INTERACTIVE = 1 << 5;
 
     /**
      * Change ID which is invalid.
@@ -213,11 +214,17 @@
             "android:broadcast.deliveryGroupMatchingFilter";
 
     /**
+     * Corresponds to {@link #setDeferralPolicy(int)}
+     */
+    private static final String KEY_DEFERRAL_POLICY =
+            "android:broadcast.deferralPolicy";
+
+    /**
      * The list of delivery group policies which specify how multiple broadcasts belonging to
      * the same delivery group has to be handled.
      * @hide
      */
-    @IntDef(flag = true, prefix = { "DELIVERY_GROUP_POLICY_" }, value = {
+    @IntDef(prefix = { "DELIVERY_GROUP_POLICY_" }, value = {
             DELIVERY_GROUP_POLICY_ALL,
             DELIVERY_GROUP_POLICY_MOST_RECENT,
             DELIVERY_GROUP_POLICY_MERGED,
@@ -228,19 +235,13 @@
     /**
      * Delivery group policy that indicates that all the broadcasts in the delivery group
      * need to be delivered as is.
-     *
-     * @hide
      */
-    @SystemApi
     public static final int DELIVERY_GROUP_POLICY_ALL = 0;
 
     /**
      * Delivery group policy that indicates that only the most recent broadcast in the delivery
      * group need to be delivered and the rest can be dropped.
-     *
-     * @hide
      */
-    @SystemApi
     public static final int DELIVERY_GROUP_POLICY_MOST_RECENT = 1;
 
     /**
@@ -251,24 +252,62 @@
      */
     public static final int DELIVERY_GROUP_POLICY_MERGED = 2;
 
+    /** {@hide} */
+    @IntDef(prefix = { "DEFERRAL_POLICY_" }, value = {
+            DEFERRAL_POLICY_DEFAULT,
+            DEFERRAL_POLICY_NONE,
+            DEFERRAL_POLICY_UNTIL_ACTIVE,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DeferralPolicy {}
+
+    /**
+     * Deferral policy that indicates no desire has been expressed, and that the
+     * system should use a reasonable default behavior.
+     */
+    public static final int DEFERRAL_POLICY_DEFAULT = 0;
+
+    /**
+     * Deferral policy that indicates a strong desire that no receiver of this
+     * broadcast should be deferred.
+     */
+    public static final int DEFERRAL_POLICY_NONE = 1;
+
+    /**
+     * Deferral policy that indicates a strong desire that each receiver of this
+     * broadcast should be deferred until that receiver's process is in an
+     * active (non-cached) state. Whether an app's process state is considered
+     * active is independent of its standby bucket.
+     * <p>
+     * This policy only applies to runtime registered receivers of a broadcast,
+     * and does not apply to ordered broadcasts, alarm broadcasts, interactive
+     * broadcasts, or manifest broadcasts.
+     * <p>
+     * This policy means that a runtime registered receiver will not typically
+     * execute until that receiver's process is brought to an active state by
+     * some other action, such as a job, alarm, or service binding. As a result,
+     * the receiver may be delayed indefinitely.
+     * <p>
+     * When this policy is set on an unordered broadcast with a completion
+     * callback, the completion callback will run once all eligible processes
+     * have finished receiving the broadcast. Processes in inactive process
+     * state are not considered eligible and may not receive the broadcast prior
+     * to the completion callback.
+     */
+    public static final int DEFERRAL_POLICY_UNTIL_ACTIVE = 2;
+
     /**
      * Creates a basic {@link BroadcastOptions} with no options initially set.
      *
      * @return an instance of {@code BroadcastOptions} against which options can be set
-     *
-     * @deprecated Use {@link BroadcastOptions#BroadcastOptions()} instead.
-     * @hide
      */
-    @Deprecated
-    @SystemApi
     public static @NonNull BroadcastOptions makeBasic() {
         BroadcastOptions opts = new BroadcastOptions();
         return opts;
     }
 
-    /**
-     * Creates a new {@code BroadcastOptions} with no options initially set.
-     */
+    /** @hide */
+    @TestApi
     public BroadcastOptions() {
         super();
         resetTemporaryAppAllowlist();
@@ -303,6 +342,7 @@
                 BundleMerger.class);
         mDeliveryGroupMatchingFilter = opts.getParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER,
                 IntentFilter.class);
+        mDeferralPolicy = opts.getInt(KEY_DEFERRAL_POLICY, DEFERRAL_POLICY_DEFAULT);
     }
 
     /**
@@ -728,60 +768,56 @@
         return mIdForResponseEvent;
     }
 
-    /**
-     * Sets whether the broadcast should not run until the process is in an active process state
-     * (ie, a process exists for the app and the app is not in a cached process state).
-     *
-     * Whether an app's process state is considered active is independent of its standby bucket.
-     *
-     * A broadcast that is deferred until the process is active will not execute until the process
-     * is brought to an active state by some other action, like a job, alarm, or service binding. As
-     * a result, the broadcast may be delayed indefinitely. This deferral only applies to runtime
-     * registered receivers of a broadcast. Any manifest receivers will run immediately, similar to
-     * how a manifest receiver would start a new process in order to run a broadcast receiver.
-     *
-     * Ordered broadcasts, alarm broadcasts, interactive broadcasts, and manifest broadcasts are
-     * never deferred.
-     *
-     * Unordered broadcasts and unordered broadcasts with completion callbacks may be
-     * deferred. Completion callbacks for broadcasts deferred until active are
-     * best-effort. Completion callbacks will run when all eligible processes have finished
-     * executing the broadcast. Processes in inactive process states that defer the broadcast are
-     * not considered eligible and may not execute the broadcast prior to the completion callback.
-     *
-     * @hide
-     */
+    /** {@hide} */
     @SystemApi
+    @Deprecated
+    // STOPSHIP: remove entirely after this API change lands in AOSP
     public @NonNull BroadcastOptions setDeferUntilActive(boolean shouldDefer) {
         if (shouldDefer) {
-            mFlags |= FLAG_IS_DEFER_UNTIL_ACTIVE;
+            setDeferralPolicy(DEFERRAL_POLICY_UNTIL_ACTIVE);
         } else {
-            mFlags &= ~FLAG_IS_DEFER_UNTIL_ACTIVE;
+            setDeferralPolicy(DEFERRAL_POLICY_NONE);
         }
         return this;
     }
 
-    /**
-     * Returns if this broadcast should not run until the process is in an active process state.
-     *
-     * @return {@code true} if this broadcast should not run until the process is in an active
-     *                      process state. Otherwise, {@code false}.
-     * @see #setDeferUntilActive(boolean)
-     *
-     * @hide
-     */
+    /** {@hide} */
     @SystemApi
+    @Deprecated
+    // STOPSHIP: remove entirely after this API change lands in AOSP
     public boolean isDeferUntilActive() {
-        return (mFlags & FLAG_IS_DEFER_UNTIL_ACTIVE) != 0;
+        return (mDeferralPolicy == DEFERRAL_POLICY_UNTIL_ACTIVE);
+    }
+
+    /**
+     * Sets deferral policy for this broadcast that specifies how this broadcast
+     * can be deferred for delivery at some future point.
+     */
+    public @NonNull BroadcastOptions setDeferralPolicy(@DeferralPolicy int deferralPolicy) {
+        mDeferralPolicy = deferralPolicy;
+        return this;
+    }
+
+    /**
+     * Gets deferral policy for this broadcast that specifies how this broadcast
+     * can be deferred for delivery at some future point.
+     */
+    public @DeferralPolicy int getDeferralPolicy() {
+        return mDeferralPolicy;
+    }
+
+    /**
+     * Clears any deferral policy for this broadcast that specifies how this
+     * broadcast can be deferred for delivery at some future point.
+     */
+    public void clearDeferralPolicy() {
+        mDeferralPolicy = DEFERRAL_POLICY_DEFAULT;
     }
 
     /**
      * Set delivery group policy for this broadcast to specify how multiple broadcasts belonging to
      * the same delivery group has to be handled.
-     *
-     * @hide
      */
-    @SystemApi
     @NonNull
     public BroadcastOptions setDeliveryGroupPolicy(@DeliveryGroupPolicy int policy) {
         mDeliveryGroupPolicy = policy;
@@ -791,10 +827,7 @@
     /**
      * Get the delivery group policy for this broadcast that specifies how multiple broadcasts
      * belonging to the same delivery group has to be handled.
-     *
-     * @hide
      */
-    @SystemApi
     public @DeliveryGroupPolicy int getDeliveryGroupPolicy() {
         return mDeliveryGroupPolicy;
     }
@@ -803,10 +836,7 @@
      * Clears any previously set delivery group policies using
      * {@link #setDeliveryGroupMatchingKey(String, String)} and resets the delivery group policy to
      * the default value ({@link #DELIVERY_GROUP_POLICY_ALL}).
-     *
-     * @hide
      */
-    @SystemApi
     public void clearDeliveryGroupPolicy() {
         mDeliveryGroupPolicy = DELIVERY_GROUP_POLICY_ALL;
     }
@@ -821,10 +851,7 @@
      * <p> If neither matching key using this API nor matching filter using
      * {@link #setDeliveryGroupMatchingFilter(IntentFilter)} is specified, then by default
      * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
-     *
-     * @hide
      */
-    @SystemApi
     @NonNull
     public BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String namespace,
             @NonNull String key) {
@@ -842,9 +869,7 @@
      *
      * @return the delivery group namespace and key that was previously set using
      *         {@link #setDeliveryGroupMatchingKey(String, String)}, concatenated with a {@code :}.
-     * @hide
      */
-    @SystemApi
     @Nullable
     public String getDeliveryGroupMatchingKey() {
         return mDeliveryGroupMatchingKey;
@@ -853,10 +878,7 @@
     /**
      * Clears the namespace and key that was previously set using
      * {@link #setDeliveryGroupMatchingKey(String, String)}.
-     *
-     * @hide
      */
-    @SystemApi
     public void clearDeliveryGroupMatchingKey() {
         mDeliveryGroupMatchingKey = null;
     }
@@ -871,10 +893,7 @@
      * <p> If neither matching key using {@link #setDeliveryGroupMatchingKey(String, String)} nor
      * matching filter using this API is specified, then by default
      * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group.
-     *
-     * @hide
      */
-    @SystemApi
     @NonNull
     public BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull IntentFilter matchingFilter) {
         mDeliveryGroupMatchingFilter = Objects.requireNonNull(matchingFilter);
@@ -887,9 +906,7 @@
      *
      * @return the {@link IntentFilter} object that was previously set using
      *         {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
-     * @hide
      */
-    @SystemApi
     @Nullable
     public IntentFilter getDeliveryGroupMatchingFilter() {
         return mDeliveryGroupMatchingFilter;
@@ -898,10 +915,7 @@
     /**
      * Clears the {@link IntentFilter} object that was previously set using
      * {@link #setDeliveryGroupMatchingFilter(IntentFilter)}.
-     *
-     * @hide
      */
-    @SystemApi
     public void clearDeliveryGroupMatchingFilter() {
         mDeliveryGroupMatchingFilter = null;
     }
@@ -944,6 +958,36 @@
     }
 
     /**
+     * Sets whether the broadcast should be considered as having originated from
+     * some direct interaction by the user such as a notification tap or button
+     * press. This signal is used internally to ensure the broadcast is
+     * delivered quickly with low latency.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE)
+    public @NonNull BroadcastOptions setInteractive(boolean interactive) {
+        if (interactive) {
+            mFlags |= FLAG_INTERACTIVE;
+        } else {
+            mFlags &= ~FLAG_INTERACTIVE;
+        }
+        return this;
+    }
+
+    /**
+     * Returns whether the broadcast should be considered as having originated
+     * from some direct interaction by the user such as a notification tap or
+     * button press.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE)
+    public boolean isInteractive() {
+        return (mFlags & FLAG_INTERACTIVE) != 0;
+    }
+
+    /**
      * Set PendingIntent activity is allowed to be started in the background if the caller
      * can start background activities.
      *
@@ -1064,11 +1108,22 @@
         if (mDeliveryGroupMatchingFilter != null) {
             b.putParcelable(KEY_DELIVERY_GROUP_MATCHING_FILTER, mDeliveryGroupMatchingFilter);
         }
-        return b.isEmpty() ? null : b;
+        if (mDeferralPolicy != DEFERRAL_POLICY_DEFAULT) {
+            b.putInt(KEY_DEFERRAL_POLICY, mDeferralPolicy);
+        }
+        return b;
     }
 
-    /** @hide */
-    public static @Nullable BroadcastOptions fromBundle(@Nullable Bundle options) {
+    /**
+     * Returns a {@link BroadcastOptions} parsed from the given {@link Bundle},
+     * typically generated from {@link #toBundle()}.
+     */
+    public static @NonNull BroadcastOptions fromBundle(@NonNull Bundle options) {
+        return new BroadcastOptions(options);
+    }
+
+    /** {@hide} */
+    public static @Nullable BroadcastOptions fromBundleNullable(@Nullable Bundle options) {
         return (options != null) ? new BroadcastOptions(options) : null;
     }
 }
diff --git a/core/java/android/app/ComponentOptions.java b/core/java/android/app/ComponentOptions.java
index ceb9e90..e0e2855 100644
--- a/core/java/android/app/ComponentOptions.java
+++ b/core/java/android/app/ComponentOptions.java
@@ -19,7 +19,6 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.TestApi;
 import android.os.Bundle;
@@ -53,15 +52,8 @@
     public static final String KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION =
             "android.pendingIntent.backgroundActivityAllowedByPermission";
 
-    /**
-     * Corresponds to {@link #setInteractive(boolean)}
-     * @hide
-     */
-    private static final String KEY_INTERACTIVE = "android:component.isInteractive";
-
     private @Nullable Boolean mPendingIntentBalAllowed = null;
     private boolean mPendingIntentBalAllowedByPermission = false;
-    private boolean mIsInteractive = false;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -106,29 +98,6 @@
         setPendingIntentBackgroundActivityLaunchAllowedByPermission(
                 opts.getBoolean(
                         KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, false));
-        mIsInteractive = opts.getBoolean(KEY_INTERACTIVE, false);
-    }
-
-    /**
-     * When set, a broadcast will be understood as having originated from
-     * some direct interaction by the user such as a notification tap or button
-     * press.  Only the OS itself may use this option.
-     * @hide
-     * @param interactive
-     * @see #isInteractive()
-     */
-    @RequiresPermission(android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE)
-    public void setInteractive(boolean interactive) {
-        mIsInteractive = interactive;
-    }
-
-    /**
-     * Did this PendingIntent send originate with a direct user interaction?
-     * @return true if this is the result of an interaction, false otherwise
-     * @hide
-     */
-    public boolean isInteractive() {
-        return mIsInteractive;
     }
 
     /**
@@ -232,9 +201,6 @@
             b.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION,
                     mPendingIntentBalAllowedByPermission);
         }
-        if (mIsInteractive) {
-            b.putBoolean(KEY_INTERACTIVE, true);
-        }
         return b;
     }
 
diff --git a/core/java/android/app/IUiModeManager.aidl b/core/java/android/app/IUiModeManager.aidl
index 2242224b..2345c27 100644
--- a/core/java/android/app/IUiModeManager.aidl
+++ b/core/java/android/app/IUiModeManager.aidl
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.app.IOnProjectionStateChangedListener;
+import android.app.IUiModeManagerCallback;
 
 /**
  * Interface used to control special UI modes.
@@ -24,6 +25,11 @@
  */
 interface IUiModeManager {
     /**
+     * @hide
+     */
+    void addCallback(IUiModeManagerCallback callback);
+
+    /**
      * Enables the car mode. Only the system can do this.
      * @hide
      */
@@ -173,4 +179,9 @@
     * Returns currently set projection types.
     */
     int getActiveProjectionTypes();
+
+    /**
+    * Returns the contrast for the current user
+    */
+    float getContrast();
 }
diff --git a/core/java/android/app/IUiModeManagerCallback.aidl b/core/java/android/app/IUiModeManagerCallback.aidl
new file mode 100644
index 0000000..47c18a8
--- /dev/null
+++ b/core/java/android/app/IUiModeManagerCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+/**
+* Implemented by the UiModeManager client to receive information about changes from the service.
+* This is a oneway interface since the server should not block waiting for the client.
+*
+* @hide
+*/
+oneway interface IUiModeManagerCallback {
+  void notifyContrastChanged(float contrast);
+}
diff --git a/core/java/android/app/UiModeManager.java b/core/java/android/app/UiModeManager.java
index ecab37d..d90257a 100644
--- a/core/java/android/app/UiModeManager.java
+++ b/core/java/android/app/UiModeManager.java
@@ -17,6 +17,7 @@
 package android.app;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -34,6 +35,7 @@
 import android.os.ServiceManager.ServiceNotFoundException;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
@@ -43,10 +45,13 @@
 import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.time.LocalTime;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.stream.Stream;
 
 /**
  * This class provides access to the system uimode services.  These services
@@ -72,6 +77,10 @@
  */
 @SystemService(Context.UI_MODE_SERVICE)
 public class UiModeManager {
+
+    private static final String TAG = "UiModeManager";
+
+
     /**
      * A listener with a single method that is invoked whenever the packages projecting using the
      * {@link ProjectionType}s for which it is registered change.
@@ -91,7 +100,20 @@
                 @NonNull Set<String> packageNames);
     }
 
-    private static final String TAG = "UiModeManager";
+    /**
+     * Listener for the UI contrast. To listen for changes to
+     * the UI contrast on the device, implement this interface and
+     * register it with the system by calling {@link #addContrastChangeListener}.
+     */
+    public interface ContrastChangeListener {
+
+        /**
+         * Called when the color contrast enabled state changes.
+         *
+         * @param contrast The color contrast as in {@link #getContrast}
+         */
+        void onContrastChanged(@FloatRange(from = -1.0f, to = 1.0f) float contrast);
+    }
 
     /**
      * Broadcast sent when the device's UI has switched to car mode, either
@@ -319,6 +341,95 @@
             mOnProjectionStateChangedListenerResourceManager =
             new OnProjectionStateChangedListenerResourceManager();
 
+    /**
+     * Define constants and conversions between {@link ContrastLevel}s and contrast values.
+     * <p>
+     * Contrast values are floats defined in [-1, 1], as defined in {@link #getContrast}.
+     * This is the official data type for contrast;
+     * all methods from the public API return contrast values.
+     * </p>
+     * <p>
+     * {@code ContrastLevel}, on the other hand, is an internal-only enumeration of contrasts that
+     * can be set from the system ui. Each {@code ContrastLevel} has an associated contrast value.
+     * </p>
+     * <p>
+     * Currently, a user chan chose from three contrast levels:
+     * <ul>
+     *     <li>{@link #CONTRAST_LEVEL_STANDARD}, corresponding to the default contrast value 0f</li>
+     *     <li>{@link #CONTRAST_LEVEL_MEDIUM}, corresponding to the contrast value 0.5f</li>
+     *     <li>{@link #CONTRAST_LEVEL_HIGH}, corresponding to the maximum contrast value 1f</li>
+     * </ul>
+     * </p>
+     *
+     * @hide
+     */
+    public static class ContrastUtils {
+
+        private static final float CONTRAST_MIN_VALUE = -1f;
+        private static final float CONTRAST_MAX_VALUE = 1f;
+        public static final float CONTRAST_DEFAULT_VALUE = 0f;
+
+        @IntDef(flag = true, prefix = { "CONTRAST_LEVEL_" }, value = {
+                CONTRAST_LEVEL_STANDARD,
+                CONTRAST_LEVEL_MEDIUM,
+                CONTRAST_LEVEL_HIGH
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface ContrastLevel {}
+
+        public static final int CONTRAST_LEVEL_STANDARD = 0;
+        public static final int CONTRAST_LEVEL_MEDIUM = 1;
+        public static final int CONTRAST_LEVEL_HIGH = 2;
+
+        private static Stream<Integer> allContrastLevels() {
+            return Stream.of(CONTRAST_LEVEL_STANDARD, CONTRAST_LEVEL_MEDIUM, CONTRAST_LEVEL_HIGH);
+        }
+
+        /**
+         * Convert a contrast value in [-1, 1] to its associated {@link ContrastLevel}
+         */
+        public static @ContrastLevel int toContrastLevel(float contrast) {
+            if (contrast < CONTRAST_MIN_VALUE || contrast > CONTRAST_MAX_VALUE) {
+                throw new IllegalArgumentException("contrast values should be in [-1, 1]");
+            }
+            return allContrastLevels().min(Comparator.comparingDouble(contrastLevel ->
+                    Math.abs(contrastLevel - 2 * contrast))).orElseThrow();
+        }
+
+        /**
+         * Convert a {@link ContrastLevel} to its associated contrast value in [-1, 1]
+         */
+        public static float fromContrastLevel(@ContrastLevel int contrastLevel) {
+            if (allContrastLevels().noneMatch(level -> level == contrastLevel)) {
+                throw new IllegalArgumentException("unrecognized contrast level: " + contrastLevel);
+            }
+            return contrastLevel / 2f;
+        }
+    }
+
+    /**
+     * Map that stores user provided {@link ContrastChangeListener} callbacks,
+     * and the executors on which these callbacks should be called.
+     */
+    private final ArrayMap<ContrastChangeListener, Executor>
+            mContrastChangeListeners = new ArrayMap<>();
+    private float mContrast;
+
+    private final IUiModeManagerCallback.Stub mCallback = new IUiModeManagerCallback.Stub() {
+        @Override
+        public void notifyContrastChanged(float contrast) {
+            final ArrayMap<ContrastChangeListener, Executor> listeners;
+            synchronized (mLock) {
+                // if value changed in the settings, update the cached value and notify listeners
+                if (Math.abs(mContrast - contrast) < 1e-10) return;
+                mContrast = contrast;
+                listeners = new ArrayMap<>(mContrastChangeListeners);
+            }
+            listeners.forEach((listener, executor) -> executor.execute(
+                    () -> listener.onContrastChanged(mContrast)));
+        }
+    };
+
     @UnsupportedAppUsage
     /*package*/ UiModeManager() throws ServiceNotFoundException {
         this(null /* context */);
@@ -328,6 +439,12 @@
         mService = IUiModeManager.Stub.asInterface(
                 ServiceManager.getServiceOrThrow(Context.UI_MODE_SERVICE));
         mContext = context;
+        try {
+            mService.addCallback(mCallback);
+            mContrast = mService.getContrast();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Setup failed: UiModeManagerService is dead", e);
+        }
     }
 
     /**
@@ -1067,4 +1184,56 @@
             return mExecutorMap.get(innerListener);
         }
     }
+
+    /**
+     * Returns the color contrast for the user.
+     * <p>
+     * <strong>Note:</strong> You need to query this only if your application is
+     * doing its own rendering and does not rely on the material rendering pipeline.
+     * </p>
+     * @return The color contrast, float in [-1, 1] where
+     * <ul>
+     *     <li> &nbsp; 0 corresponds to the default contrast </li>
+     *     <li>       -1 corresponds to the minimum contrast </li>
+     *     <li> &nbsp; 1 corresponds to the maximum contrast </li>
+     * </ul>
+     *
+     *
+     *
+     */
+    @FloatRange(from = -1.0f, to = 1.0f)
+    public float getContrast() {
+        synchronized (mLock) {
+            return mContrast;
+        }
+    }
+
+    /**
+     * Registers a {@link ContrastChangeListener} for the current user.
+     *
+     * @param executor The executor on which the listener should be called back.
+     * @param listener The listener.
+     */
+    public void addContrastChangeListener(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull ContrastChangeListener listener) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(listener);
+        synchronized (mLock) {
+            mContrastChangeListeners.put(listener, executor);
+        }
+    }
+
+    /**
+     * Unregisters a {@link ContrastChangeListener} for the current user.
+     * If the listener was not registered, does nothing and returns.
+     *
+     * @param listener The listener to unregister.
+     */
+    public void removeContrastChangeListener(@NonNull ContrastChangeListener listener) {
+        Objects.requireNonNull(listener);
+        synchronized (mLock) {
+            mContrastChangeListeners.remove(listener);
+        }
+    }
 }
diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java
index afc2285..5928a50 100644
--- a/core/java/android/content/IntentFilter.java
+++ b/core/java/android/content/IntentFilter.java
@@ -2204,9 +2204,7 @@
      * <p> Subsequent calls to this method  will override any previously set extras.
      *
      * @param extras The intent extras to match against.
-     * @hide
      */
-    @SystemApi
     public final void setExtras(@NonNull PersistableBundle extras) {
         mExtras = extras;
     }
@@ -2216,11 +2214,8 @@
      *
      * @return the extras that were previously set using {@link #setExtras(PersistableBundle)} or
      *         an empty {@link PersistableBundle} object if no extras were set.
-     * @hide
      */
-    @SystemApi
-    @NonNull
-    public final PersistableBundle getExtras() {
+    public final @NonNull PersistableBundle getExtras() {
         return mExtras == null ? new PersistableBundle() : mExtras;
     }
 
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9388823..6486278 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -4353,19 +4353,26 @@
     public static final String FEATURE_CREDENTIALS = "android.software.credentials";
 
     /**
-     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
-     * The device supports locking (for example, by a financing provider in case of a missed
-     * payment).
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * supports locking (for example, by a financing provider in case of a missed payment).
      */
     @SdkConstant(SdkConstantType.FEATURE)
     public static final String FEATURE_DEVICE_LOCK = "android.software.device_lock";
 
+    /**
+     * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+     * supports showing location-based suggestions for wallet cards provided by the default payment
+     * app.
+     */
+    @SdkConstant(SdkConstantType.FEATURE)
+    public static final String FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS =
+            "android.software.wallet_location_based_suggestions";
+
     /** @hide */
     public static final boolean APP_ENUMERATION_ENABLED_BY_DEFAULT = true;
 
     /**
-     * Extra field name for the URI to a verification file. Passed to a package
-     * verifier.
+     * Extra field name for the URI to a verification file. Passed to a package verifier.
      *
      * @hide
      */
diff --git a/core/java/android/credentials/ui/Entry.java b/core/java/android/credentials/ui/Entry.java
index 12665ba..55f2a3e 100644
--- a/core/java/android/credentials/ui/Entry.java
+++ b/core/java/android/credentials/ui/Entry.java
@@ -70,17 +70,6 @@
     /** Constructor to be used for an entry that requires a pending intent to be invoked
      * when clicked.
      */
-    // TODO: Remove this constructor as it is no longer used
-    public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
-            @NonNull PendingIntent pendingIntent, @NonNull Intent intent) {
-        this(key, subkey, slice);
-        mPendingIntent = pendingIntent;
-        mFrameworkExtrasIntent = intent;
-    }
-
-    /** Constructor to be used for an entry that requires a pending intent to be invoked
-     * when clicked.
-     */
     public Entry(@NonNull String key, @NonNull String subkey, @NonNull Slice slice,
             @NonNull Intent intent) {
         this(key, subkey, slice);
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index 4b3eb3a..4532661 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -1392,6 +1392,10 @@
         return sql.replaceAll("[\\s]*\\n+[\\s]*", " ");
     }
 
+    void clearPreparedStatementCache() {
+        mPreparedStatementCache.evictAll();
+    }
+
     /**
      * Holder type for a prepared statement.
      *
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 069c264..6023d66 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -1126,6 +1126,16 @@
         mConnectionWaiterPool = waiter;
     }
 
+    void clearAcquiredConnectionsPreparedStatementCache() {
+        synchronized (mLock) {
+            if (!mAcquiredConnections.isEmpty()) {
+                for (SQLiteConnection connection : mAcquiredConnections.keySet()) {
+                    connection.clearPreparedStatementCache();
+                }
+            }
+        }
+    }
+
     /**
      * Dumps debugging information about this connection pool.
      *
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index c08294f..db898c3 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -2088,10 +2088,12 @@
             try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) {
                 return statement.executeUpdateDelete();
             } finally {
-                // If schema was updated, close non-primary connections, otherwise they might
-                // have outdated schema information
+                // If schema was updated, close non-primary connections and clear prepared
+                // statement caches of active connections, otherwise they might have outdated
+                // schema information.
                 if (statementType == DatabaseUtils.STATEMENT_DDL) {
                     mConnectionPoolLocked.closeAvailableNonPrimaryConnectionsAndLogExceptions();
+                    mConnectionPoolLocked.clearAcquiredConnectionsPreparedStatementCache();
                 }
             }
         } finally {
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 9640b0e..631df01 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -1132,6 +1132,12 @@
 
     private boolean setGpsLocation(Location l) {
         if (l == null) {
+            // If Location value being set is null, remove corresponding keys.
+            // This is safe because api1/client2/CameraParameters.cpp already erases
+            // the keys for JPEG_GPS_LOCATION for certain cases.
+            setBase(CaptureRequest.JPEG_GPS_TIMESTAMP, null);
+            setBase(CaptureRequest.JPEG_GPS_COORDINATES, null);
+            setBase(CaptureRequest.JPEG_GPS_PROCESSING_METHOD, null);
             return false;
         }
 
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index fb8f84a..a52e3d49 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -474,9 +474,8 @@
      *        target functionality.  The ANGLE broadcast receiver code will apply a "deferlist" at
      *        the first boot of a newly-flashed device.  However, there is a gap in time between
      *        when applications can start and when the deferlist is applied.  For now, assume that
-     *        if ANGLE is the system driver and Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS is
-     *        empty, that the deferlist has not yet been applied.  In this case, select the Legacy
-     *        driver.
+     *        if ANGLE is the system driver and Settings.Global.ANGLE_DEFERLIST is empty, that the
+     *        deferlist has not yet been applied.  In this case, select the Legacy driver.
      *    otherwise ...
      * 3) Use ANGLE if isAngleEnabledByGameMode() returns true; otherwise ...
      * 4) The global switch (i.e. use the system driver, whether ANGLE or legacy;
@@ -516,14 +515,17 @@
                 contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_PKGS);
         final List<String> optInValues = getGlobalSettingsString(
                 contentResolver, bundle, Settings.Global.ANGLE_GL_DRIVER_SELECTION_VALUES);
+        final List<String> angleDeferlist = getGlobalSettingsString(
+                contentResolver, bundle, Settings.Global.ANGLE_DEFERLIST);
         Log.v(TAG, "Currently set values for:");
         Log.v(TAG, "    angle_gl_driver_selection_pkgs =" + optInPackages);
         Log.v(TAG, "  angle_gl_driver_selection_values =" + optInValues);
 
         // If ANGLE is the system driver AND the deferlist has not yet been applied, select the
         // Legacy driver
-        if (mAngleIsSystemDriver && optInPackages.size() <= 1) {
-            Log.v(TAG, "Ignoring angle_gl_driver_selection_* until deferlist has been applied");
+        if (mAngleIsSystemDriver && angleDeferlist.size() == 0) {
+            Log.v(TAG, "ANGLE deferlist (" + Settings.Global.ANGLE_DEFERLIST + ") has not been "
+                           + "applied, defaulting to legacy driver");
             return ANGLE_GL_DRIVER_TO_USE_LEGACY;
         }
 
diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
index fd9360f..9cd5aa0 100644
--- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java
+++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java
@@ -41,6 +41,8 @@
 import android.util.Log;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -115,6 +117,26 @@
                 .build();
     }
 
+    /**
+     * Constructs an information instance of the credential provider for testing purposes. Does
+     * not run any verifications and passes parameters as is.
+     */
+    @VisibleForTesting
+    public static CredentialProviderInfo createForTests(
+            @NonNull ServiceInfo serviceInfo,
+            @NonNull CharSequence overrideLabel,
+            boolean isSystemProvider,
+            boolean isEnabled,
+            @NonNull List<String> capabilities) {
+        return new CredentialProviderInfo.Builder(serviceInfo)
+                .setEnabled(isEnabled)
+                .setOverrideLabel(overrideLabel)
+                .setSystemProvider(isSystemProvider)
+                .addCapabilities(capabilities)
+                .build();
+
+    }
+
     private static void verifyProviderPermission(ServiceInfo serviceInfo) throws SecurityException {
         final String permission = Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE;
         if (permission.equals(serviceInfo.permission)) {
@@ -349,6 +371,31 @@
     }
 
     /**
+     * Returns a valid credential provider that has the given package name. Returns null if no
+     * match is found.
+     */
+    @Nullable
+    public static CredentialProviderInfo getCredentialProviderFromPackageName(
+            @NonNull Context context,
+            int userId,
+            @NonNull String packageName,
+            int providerFilter,
+            @NonNull Set<ServiceInfo> enabledServices) {
+        requireNonNull(context, "context must not be null");
+        requireNonNull(packageName, "package name must not be null");
+        requireNonNull(enabledServices, "enabledServices must not be null");
+
+        for (CredentialProviderInfo credentialProviderInfo : getCredentialProviderServices(context,
+                userId, providerFilter, enabledServices)) {
+            if (credentialProviderInfo.getServiceInfo()
+                    .packageName.equals(packageName)) {
+                return credentialProviderInfo;
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns the valid credential provider services available for the user with the given {@code
      * userId}.
      */
diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java
index a389223..a2fa139 100644
--- a/core/java/android/service/dreams/DreamActivity.java
+++ b/core/java/android/service/dreams/DreamActivity.java
@@ -58,13 +58,11 @@
             setTitle(title);
         }
 
-        final Object callback = getIntent().getExtras().getBinder(EXTRA_CALLBACK);
-        if (callback instanceof DreamService.DreamActivityCallbacks) {
-            mCallback = (DreamService.DreamActivityCallbacks) callback;
+        final Bundle extras = getIntent().getExtras();
+        mCallback = (DreamService.DreamActivityCallbacks) extras.getBinder(EXTRA_CALLBACK);
+
+        if (mCallback != null) {
             mCallback.onActivityCreated(this);
-        } else {
-            mCallback = null;
-            finishAndRemoveTask();
         }
     }
 
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index c7099fd..ce8af83 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1409,6 +1409,10 @@
                             // Request the DreamOverlay be told to dream with dream's window
                             // parameters once the window has been attached.
                             mDreamStartOverlayConsumer = overlay -> {
+                                if (mWindow == null) {
+                                    Slog.d(TAG, "mWindow is null");
+                                    return;
+                                }
                                 try {
                                     overlay.startDream(mWindow.getAttributes(), mOverlayCallback,
                                             mDreamComponent.flattenToString(),
diff --git a/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java b/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
index ae67068..8409503 100644
--- a/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
+++ b/core/java/android/service/quickaccesswallet/GetWalletCardsCallbackImpl.java
@@ -17,6 +17,8 @@
 package android.service.quickaccesswallet;
 
 import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 import android.os.Handler;
@@ -36,13 +38,18 @@
     private final IQuickAccessWalletServiceCallbacks mCallback;
     private final GetWalletCardsRequest mRequest;
     private final Handler mHandler;
+    private final Context mContext;
     private boolean mCalled;
 
-    GetWalletCardsCallbackImpl(GetWalletCardsRequest request,
-            IQuickAccessWalletServiceCallbacks callback, Handler handler) {
+    GetWalletCardsCallbackImpl(
+            GetWalletCardsRequest request,
+            IQuickAccessWalletServiceCallbacks callback,
+            Handler handler,
+            Context context) {
         mRequest = request;
         mCallback = callback;
         mHandler = handler;
+        mContext = context;
     }
 
     /**
@@ -50,11 +57,17 @@
      * was successfully handled by the service.
      *
      * @param response The response contains the list of {@link WalletCard walletCards} to be shown
-     *                 to the user as well as the index of the card that should initially be
-     *                 presented as the selected card.
+     *     to the user as well as the index of the card that should initially be presented as the
+     *     selected card.
      */
     public void onSuccess(@NonNull GetWalletCardsResponse response) {
         if (isValidResponse(response)) {
+            // Strip location info from response if the feature is not enabled.
+            if (!mContext.getPackageManager()
+                    .hasSystemFeature(PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS)) {
+                removeLocationsFromResponse(response);
+            }
+
             mHandler.post(() -> onSuccessInternal(response));
         } else {
             Log.w(TAG, "Invalid GetWalletCards response");
@@ -152,4 +165,10 @@
         }
         return true;
     }
+
+    private void removeLocationsFromResponse(@NonNull GetWalletCardsResponse response) {
+        for (WalletCard card : response.getWalletCards()) {
+            card.removeCardLocations();
+        }
+    }
 }
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
index d004f34..36fa21c 100644
--- a/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletService.java
@@ -262,8 +262,8 @@
     private void onWalletCardsRequestedInternal(
             GetWalletCardsRequest request,
             IQuickAccessWalletServiceCallbacks callback) {
-        onWalletCardsRequested(request,
-                new GetWalletCardsCallbackImpl(request, callback, mHandler));
+        onWalletCardsRequested(
+                request, new GetWalletCardsCallbackImpl(request, callback, mHandler, this));
     }
 
     private void onTargetActivityIntentRequestedInternal(
diff --git a/core/java/android/service/quickaccesswallet/WalletCard.java b/core/java/android/service/quickaccesswallet/WalletCard.java
index e52adcc..4a4fd04 100644
--- a/core/java/android/service/quickaccesswallet/WalletCard.java
+++ b/core/java/android/service/quickaccesswallet/WalletCard.java
@@ -21,6 +21,7 @@
 import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.graphics.drawable.Icon;
+import android.location.Location;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
@@ -29,7 +30,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * A {@link WalletCard} can represent anything that a user might carry in their wallet -- a credit
@@ -67,6 +69,7 @@
     private final Icon mCardIcon;
     private final CharSequence mCardLabel;
     private final Icon mNonPaymentCardSecondaryImage;
+    private List<Location> mCardLocations;
 
     private WalletCard(Builder builder) {
         this.mCardId = builder.mCardId;
@@ -77,6 +80,7 @@
         this.mCardIcon = builder.mCardIcon;
         this.mCardLabel = builder.mCardLabel;
         this.mNonPaymentCardSecondaryImage = builder.mNonPaymentCardSecondaryImage;
+        this.mCardLocations = builder.mCardLocations;
     }
 
     /**
@@ -106,7 +110,7 @@
         writeIconIfNonNull(mCardIcon, dest, flags);
         TextUtils.writeToParcel(mCardLabel, dest, flags);
         writeIconIfNonNull(mNonPaymentCardSecondaryImage, dest, flags);
-
+        dest.writeTypedList(mCardLocations, flags);
     }
 
     /** Utility function called by writeToParcel
@@ -128,15 +132,20 @@
         PendingIntent pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(source);
         Icon cardIcon = source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
         CharSequence cardLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
-        Icon nonPaymentCardSecondaryImage = source.readByte() == 0 ? null :
-                Icon.CREATOR.createFromParcel(source);
-        Builder builder = new Builder(cardId, cardType, cardImage, contentDesc, pendingIntent)
-                .setCardIcon(cardIcon)
-                .setCardLabel(cardLabel);
+        Icon nonPaymentCardSecondaryImage =
+                source.readByte() == 0 ? null : Icon.CREATOR.createFromParcel(source);
+        Builder builder =
+                new Builder(cardId, cardType, cardImage, contentDesc, pendingIntent)
+                        .setCardIcon(cardIcon)
+                        .setCardLabel(cardLabel);
+        if (cardType == CARD_TYPE_NON_PAYMENT) {
+            builder.setNonPaymentCardSecondaryImage(nonPaymentCardSecondaryImage);
+        }
+        List<Location> cardLocations = new ArrayList<>();
+        source.readTypedList(cardLocations, Location.CREATOR);
+        builder.setCardLocations(cardLocations);
 
-        return cardType == CARD_TYPE_NON_PAYMENT
-                ? builder.setNonPaymentCardSecondaryImage(nonPaymentCardSecondaryImage).build() :
-                 builder.build();
+        return builder.build();
     }
 
     @NonNull
@@ -226,13 +235,29 @@
 
     /**
      * Visual representation of the card when it is tapped. May include additional information
-     *  unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT.
+     * unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT.
      */
     @Nullable
     public Icon getNonPaymentCardSecondaryImage() {
         return mNonPaymentCardSecondaryImage;
     }
 
+    /** List of locations that this card might be useful at. */
+    @NonNull
+    public List<Location> getCardLocations() {
+        return mCardLocations;
+    }
+
+    /**
+     * Removes locations from card. Should be called if {@link
+     * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is disabled.
+     *
+     * @hide
+     */
+    public void removeCardLocations() {
+        mCardLocations = new ArrayList<>();
+    }
+
     /**
      * Builder for {@link WalletCard} objects. You must provide cardId, cardImage,
      * contentDescription, and pendingIntent. If the card is opaque and should be shown with
@@ -247,6 +272,7 @@
         private Icon mCardIcon;
         private CharSequence mCardLabel;
         private Icon mNonPaymentCardSecondaryImage;
+        private List<Location> mCardLocations = new ArrayList<>();
 
         /**
          * @param cardId             The card id must be non-null and unique within the list of
@@ -333,18 +359,31 @@
 
         /**
          * Visual representation of the card when it is tapped. May include additional information
-         *  unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT.
+         * unique to the card, such as a barcode or number. Only valid for CARD_TYPE_NON_PAYMENT.
          */
         @NonNull
-        public Builder
-                setNonPaymentCardSecondaryImage(@Nullable Icon nonPaymentCardSecondaryImage) {
-            Preconditions.checkState(mCardType == CARD_TYPE_NON_PAYMENT,
+        public Builder setNonPaymentCardSecondaryImage(
+                @Nullable Icon nonPaymentCardSecondaryImage) {
+            Preconditions.checkState(
+                    mCardType == CARD_TYPE_NON_PAYMENT,
                     "This field can only be set on non-payment cards");
             mNonPaymentCardSecondaryImage = nonPaymentCardSecondaryImage;
             return this;
         }
 
         /**
+         * Set of locations this card might be useful at. If {@link
+         * PackageManager.FEATURE_WALLET_LOCATION_BASED_SUGGESTIONS} is enabled, the card might be
+         * shown to the user when a user is near one of these locations.
+         */
+        @NonNull
+        public Builder setCardLocations(@NonNull List<Location> cardLocations) {
+            Preconditions.checkCollectionElementsNotNull(cardLocations, "cardLocations");
+            mCardLocations = cardLocations;
+            return this;
+        }
+
+        /**
          * Builds a new {@link WalletCard} instance.
          *
          * @return A built response.
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 6201b3a..2ae882c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -220,9 +220,9 @@
         DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
         DEFAULT_FLAGS.put(SETTINGS_NEED_CONNECTED_BLE_DEVICE_FOR_BROADCAST, "true");
         DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
-        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "true");
-        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "true");
-        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "true");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false");
diff --git a/core/java/android/view/AccessibilityInteractionController.java b/core/java/android/view/AccessibilityInteractionController.java
index 6804c5c..6901b72 100644
--- a/core/java/android/view/AccessibilityInteractionController.java
+++ b/core/java/android/view/AccessibilityInteractionController.java
@@ -874,8 +874,11 @@
             return;
         }
         try {
+            // Clearing focus does not expose sensitive data, so set fetch flags to ensure that the
+            // root view is always returned if present.
             setAccessibilityFetchFlags(
-                    AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS);
+                    AccessibilityNodeInfo.FLAG_SERVICE_REQUESTS_INCLUDE_NOT_IMPORTANT_VIEWS
+                            | AccessibilityNodeInfo.FLAG_SERVICE_IS_ACCESSIBILITY_TOOL);
             final View root = getRootView();
             if (root != null && isShown(root)) {
                 final View host = mViewRootImpl.mAccessibilityFocusedHost;
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index 0a2b06c..867d2b4 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -18,10 +18,14 @@
 
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
 
+import android.annotation.IntRange;
+import android.annotation.NonNull;
 import android.graphics.Insets;
 import android.graphics.Rect;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.view.WindowInsets.Type.InsetsType;
 
 import java.util.Arrays;
 import java.util.Objects;
@@ -62,19 +66,18 @@
     private static final int HAS_INSETS_SIZE = 1;
     private static final int HAS_INSETS_SIZE_OVERRIDE = 2;
 
-    private static Rect sTmpRect = new Rect();
-    private static Rect sTmpRect2 = new Rect();
+    private static final Rect sTmpRect = new Rect();
+    private static final Rect sTmpRect2 = new Rect();
 
-    /**
-     * The type of insets to provide.
-     */
-    public @InsetsState.InternalInsetsType int type;
+    private final IBinder mOwner;
+    private final int mIndex;
+    private final @InsetsType int mType;
 
     /**
      * The source of frame. By default, all adjustment will be based on the window frame, it
      * can be set to window bounds or display bounds instead.
      */
-    public int source = SOURCE_FRAME;
+    private int mSource = SOURCE_FRAME;
 
     /**
      * The provided insets size based on the source frame. The result will be used as the insets
@@ -85,13 +88,13 @@
      * (0, 0, 0, 50) instead, the insets frame will be a frame starting from the bottom side of the
      * source frame with height of 50, i.e., (0, 150) - (100, 200).
      */
-    public Insets insetsSize = null;
+    private Insets mInsetsSize = null;
 
     /**
      * If null, the size set in insetsSize will be applied to all window types. If it contains
      * element of some types, the insets reported to the window with that types will be overridden.
      */
-    public InsetsSizeOverride[] insetsSizeOverrides = null;
+    private InsetsSizeOverride[] mInsetsSizeOverrides = null;
 
     /**
      * This field, if set, is indicating the insets needs to be at least the given size inside the
@@ -103,22 +106,80 @@
      *
      * Be cautious, this will not be in effect for the window types whose insets size is overridden.
      */
-    public Insets minimalInsetsSizeInDisplayCutoutSafe = null;
+    private Insets mMinimalInsetsSizeInDisplayCutoutSafe = null;
 
-    public InsetsFrameProvider(int type) {
-        this(type, SOURCE_FRAME, null, null);
+    /**
+     * Creates an InsetsFrameProvider which describes what frame an insets source should have.
+     *
+     * @param owner the owner of this provider. We might have multiple sources with the same type on
+     *              a display, this is used to identify them.
+     * @param index the index of this provider. An owner might provide multiple sources with the
+     *              same type, this is used to identify them.
+     *              The value must be in a range of [0, 2047].
+     * @param type the {@link InsetsType}.
+     * @see InsetsSource#createId(Object, int, int)
+     */
+    public InsetsFrameProvider(IBinder owner, @IntRange(from = 0, to = 2047) int index,
+            @InsetsType int type) {
+        if (index < 0 || index >= 2048) {
+            throw new IllegalArgumentException();
+        }
+
+        // This throws IllegalArgumentException if the type is not valid.
+        WindowInsets.Type.indexOf(type);
+
+        mOwner = owner;
+        mIndex = index;
+        mType = type;
     }
 
-    public InsetsFrameProvider(int type, Insets insetsSize) {
-        this(type, SOURCE_FRAME, insetsSize, null);
+    public IBinder getOwner() {
+        return mOwner;
     }
 
-    public InsetsFrameProvider(int type, int source, Insets insetsSize,
-            InsetsSizeOverride[] insetsSizeOverride) {
-        this.type = type;
-        this.source = source;
-        this.insetsSize = insetsSize;
-        this.insetsSizeOverrides = insetsSizeOverride;
+    public int getIndex() {
+        return mIndex;
+    }
+
+    public int getType() {
+        return mType;
+    }
+
+    public InsetsFrameProvider setSource(int source) {
+        mSource = source;
+        return this;
+    }
+
+    public int getSource() {
+        return mSource;
+    }
+
+    public InsetsFrameProvider setInsetsSize(Insets insetsSize) {
+        mInsetsSize = insetsSize;
+        return this;
+    }
+
+    public Insets getInsetsSize() {
+        return mInsetsSize;
+    }
+
+    public InsetsFrameProvider setInsetsSizeOverrides(InsetsSizeOverride[] insetsSizeOverrides) {
+        mInsetsSizeOverrides = insetsSizeOverrides;
+        return this;
+    }
+
+    public InsetsSizeOverride[] getInsetsSizeOverrides() {
+        return mInsetsSizeOverrides;
+    }
+
+    public InsetsFrameProvider setMinimalInsetsSizeInDisplayCutoutSafe(
+            Insets minimalInsetsSizeInDisplayCutoutSafe) {
+        mMinimalInsetsSizeInDisplayCutoutSafe = minimalInsetsSizeInDisplayCutoutSafe;
+        return this;
+    }
+
+    public Insets getMinimalInsetsSizeInDisplayCutoutSafe() {
+        return mMinimalInsetsSizeInDisplayCutoutSafe;
     }
 
     @Override
@@ -128,63 +189,73 @@
 
     @Override
     public String toString() {
-        StringBuilder sb = new StringBuilder(32);
-        sb.append("InsetsFrameProvider: {");
-        sb.append("type=").append(InsetsState.typeToString(type));
-        sb.append(", source=");
-        switch (source) {
-            case SOURCE_DISPLAY:
-                sb.append("SOURCE_DISPLAY");
-                break;
-            case SOURCE_CONTAINER_BOUNDS:
-                sb.append("SOURCE_CONTAINER_BOUNDS Bounds");
-                break;
-            case SOURCE_FRAME:
-                sb.append("SOURCE_FRAME");
-                break;
+        final StringBuilder sb = new StringBuilder("InsetsFrameProvider: {");
+        sb.append("owner=").append(mOwner);
+        sb.append(", index=").append(mIndex);
+        sb.append(", type=").append(WindowInsets.Type.toString(mType));
+        sb.append(", source=").append(sourceToString(mSource));
+        if (mInsetsSize != null) {
+            sb.append(", insetsSize=").append(mInsetsSize);
         }
-        if (insetsSize != null) {
-            sb.append(", insetsSize=").append(insetsSize);
-        }
-        if (insetsSizeOverrides != null) {
-            sb.append(", insetsSizeOverrides=").append(Arrays.toString(insetsSizeOverrides));
+        if (mInsetsSizeOverrides != null) {
+            sb.append(", insetsSizeOverrides=").append(Arrays.toString(mInsetsSizeOverrides));
         }
         sb.append("}");
         return sb.toString();
     }
 
+    private static String sourceToString(int source) {
+        switch (source) {
+            case SOURCE_DISPLAY:
+                return "DISPLAY";
+            case SOURCE_CONTAINER_BOUNDS:
+                return "CONTAINER_BOUNDS";
+            case SOURCE_FRAME:
+                return "FRAME";
+        }
+        return "UNDEFINED";
+    }
+
     public InsetsFrameProvider(Parcel in) {
+        mOwner = in.readStrongBinder();
+        mIndex = in.readInt();
+        mType = in.readInt();
         int insetsSizeModified = in.readInt();
-        type = in.readInt();
-        source = in.readInt();
+        mSource = in.readInt();
         if ((insetsSizeModified & HAS_INSETS_SIZE) != 0) {
-            insetsSize = Insets.CREATOR.createFromParcel(in);
+            mInsetsSize = Insets.CREATOR.createFromParcel(in);
         }
         if ((insetsSizeModified & HAS_INSETS_SIZE_OVERRIDE) != 0) {
-            insetsSizeOverrides = in.createTypedArray(InsetsSizeOverride.CREATOR);
+            mInsetsSizeOverrides = in.createTypedArray(InsetsSizeOverride.CREATOR);
         }
     }
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
+        out.writeStrongBinder(mOwner);
+        out.writeInt(mIndex);
+        out.writeInt(mType);
         int insetsSizeModified = 0;
-        if (insetsSize != null) {
+        if (mInsetsSize != null) {
             insetsSizeModified |= HAS_INSETS_SIZE;
         }
-        if (insetsSizeOverrides != null) {
+        if (mInsetsSizeOverrides != null) {
             insetsSizeModified |= HAS_INSETS_SIZE_OVERRIDE;
         }
         out.writeInt(insetsSizeModified);
-        out.writeInt(type);
-        out.writeInt(source);
-        if (insetsSize != null) {
-            insetsSize.writeToParcel(out, flags);
+        out.writeInt(mSource);
+        if (mInsetsSize != null) {
+            mInsetsSize.writeToParcel(out, flags);
         }
-        if (insetsSizeOverrides != null) {
-            out.writeTypedArray(insetsSizeOverrides, flags);
+        if (mInsetsSizeOverrides != null) {
+            out.writeTypedArray(mInsetsSizeOverrides, flags);
         }
     }
 
+    public boolean idEquals(InsetsFrameProvider o) {
+        return Objects.equals(mOwner, o.mOwner) && mIndex == o.mIndex && mType == o.mType;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -193,19 +264,21 @@
         if (o == null || getClass() != o.getClass()) {
             return false;
         }
-        InsetsFrameProvider other = (InsetsFrameProvider) o;
-        return type == other.type && source == other.source
-                && Objects.equals(insetsSize, other.insetsSize)
-                && Arrays.equals(insetsSizeOverrides, other.insetsSizeOverrides);
+        final InsetsFrameProvider other = (InsetsFrameProvider) o;
+        return Objects.equals(mOwner, other.mOwner) && mIndex == other.mIndex
+                && mType == other.mType && mSource == other.mSource
+                && Objects.equals(mInsetsSize, other.mInsetsSize)
+                && Arrays.equals(mInsetsSizeOverrides, other.mInsetsSizeOverrides);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(type, source, insetsSize, Arrays.hashCode(insetsSizeOverrides));
+        return Objects.hash(mOwner, mIndex, mType, mSource, mInsetsSize,
+                Arrays.hashCode(mInsetsSizeOverrides));
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<InsetsFrameProvider>
-            CREATOR = new Parcelable.Creator<InsetsFrameProvider>() {
+    public static final @NonNull Parcelable.Creator<InsetsFrameProvider> CREATOR =
+            new Parcelable.Creator<>() {
                 @Override
                 public InsetsFrameProvider createFromParcel(Parcel in) {
                     return new InsetsFrameProvider(in);
@@ -282,21 +355,28 @@
      * directly for that window type.
      */
     public static class InsetsSizeOverride implements Parcelable {
-        public final int windowType;
-        public Insets insetsSize;
+
+        private final int mWindowType;
+        private final Insets mInsetsSize;
 
         protected InsetsSizeOverride(Parcel in) {
-            windowType = in.readInt();
-            insetsSize = in.readParcelable(null, Insets.class);
+            mWindowType = in.readInt();
+            mInsetsSize = in.readParcelable(null, Insets.class);
         }
 
-        public InsetsSizeOverride(int type, Insets size) {
-            windowType = type;
-            insetsSize = size;
+        public InsetsSizeOverride(int windowType, Insets insetsSize) {
+            mWindowType = windowType;
+            mInsetsSize = insetsSize;
+        }
+        public int getWindowType() {
+            return mWindowType;
         }
 
-        public static final Creator<InsetsSizeOverride> CREATOR =
-                new Creator<InsetsSizeOverride>() {
+        public Insets getInsetsSize() {
+            return mInsetsSize;
+        }
+
+        public static final Creator<InsetsSizeOverride> CREATOR = new Creator<>() {
             @Override
             public InsetsSizeOverride createFromParcel(Parcel in) {
                 return new InsetsSizeOverride(in);
@@ -315,8 +395,8 @@
 
         @Override
         public void writeToParcel(Parcel out, int flags) {
-            out.writeInt(windowType);
-            out.writeParcelable(insetsSize, flags);
+            out.writeInt(mWindowType);
+            out.writeParcelable(mInsetsSize, flags);
         }
 
         @Override
@@ -324,15 +404,15 @@
             StringBuilder sb = new StringBuilder(32);
             sb.append("TypedInsetsSize: {");
             sb.append("windowType=").append(ViewDebug.intToString(
-                    WindowManager.LayoutParams.class, "type", windowType));
-            sb.append(", insetsSize=").append(insetsSize);
+                    WindowManager.LayoutParams.class, "type", mWindowType));
+            sb.append(", insetsSize=").append(mInsetsSize);
             sb.append("}");
             return sb.toString();
         }
 
         @Override
         public int hashCode() {
-            return Objects.hash(windowType, insetsSize);
+            return Objects.hash(mWindowType, mInsetsSize);
         }
     }
 }
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 997e4a5..761d504 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -42,7 +42,6 @@
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.ArraySet;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
@@ -707,40 +706,6 @@
                 && !WindowConfiguration.inMultiWindowMode(windowingMode);
     }
 
-    public static @InternalInsetsType ArraySet<Integer> toInternalType(@InsetsType int types) {
-        final ArraySet<Integer> result = new ArraySet<>();
-        if ((types & Type.STATUS_BARS) != 0) {
-            result.add(ITYPE_STATUS_BAR);
-            result.add(ITYPE_CLIMATE_BAR);
-        }
-        if ((types & Type.NAVIGATION_BARS) != 0) {
-            result.add(ITYPE_NAVIGATION_BAR);
-            result.add(ITYPE_EXTRA_NAVIGATION_BAR);
-        }
-        if ((types & Type.SYSTEM_OVERLAYS) != 0) {
-            result.add(ITYPE_LEFT_GENERIC_OVERLAY);
-            result.add(ITYPE_TOP_GENERIC_OVERLAY);
-            result.add(ITYPE_RIGHT_GENERIC_OVERLAY);
-            result.add(ITYPE_BOTTOM_GENERIC_OVERLAY);
-        }
-        if ((types & Type.CAPTION_BAR) != 0) {
-            result.add(ITYPE_CAPTION_BAR);
-        }
-        if ((types & Type.SYSTEM_GESTURES) != 0) {
-            result.add(ITYPE_LEFT_GESTURES);
-            result.add(ITYPE_TOP_GESTURES);
-            result.add(ITYPE_RIGHT_GESTURES);
-            result.add(ITYPE_BOTTOM_GESTURES);
-        }
-        if ((types & Type.MANDATORY_SYSTEM_GESTURES) != 0) {
-            result.add(ITYPE_LEFT_MANDATORY_GESTURES);
-            result.add(ITYPE_TOP_MANDATORY_GESTURES);
-            result.add(ITYPE_RIGHT_MANDATORY_GESTURES);
-            result.add(ITYPE_BOTTOM_MANDATORY_GESTURES);
-        }
-        return result;
-    }
-
     /**
      * Converting a internal type to the public type.
      * @param type internal insets type, {@code InternalInsetsType}.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 8bf3232..ef76ce3 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -142,7 +142,6 @@
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Transformation;
-import android.view.autofill.AutofillFeatureFlags;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
@@ -10363,15 +10362,21 @@
     private boolean isAutofillable() {
         if (getAutofillType() == AUTOFILL_TYPE_NONE) return false;
 
+        final AutofillManager afm = getAutofillManager();
+        if (afm == null) {
+            return false;
+        }
+
         // Disable triggering autofill if the view is integrated with CredentialManager.
-        if (AutofillFeatureFlags.shouldIgnoreCredentialViews()
-                && isCredential()) return false;
+        if (afm.shouldIgnoreCredentialViews() && isCredential()) {
+            return false;
+        }
 
         if (!isImportantForAutofill()) {
             // If view matches heuristics and is not denied, it will be treated same as view that's
             // important for autofill
-            if (isMatchingAutofillableHeuristics()
-                    && !isActivityDeniedForAutofillForUnimportantView()) {
+            if (afm.isMatchingAutofillableHeuristics(this)
+                    && !afm.isActivityDeniedForAutofillForUnimportantView()) {
                 return getAutofillViewId() > LAST_APP_AUTOFILL_ID;
             }
             // View is not important for "regular" autofill, so we must check if Augmented Autofill
@@ -10380,8 +10385,7 @@
             if (options == null || !options.isAugmentedAutofillEnabled(mContext)) {
                 return false;
             }
-            final AutofillManager afm = getAutofillManager();
-            if (afm == null) return false;
+
             afm.notifyViewEnteredForAugmentedAutofill(this);
         }
 
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 9504852..5ad2476 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -25,7 +25,6 @@
 import android.accessibilityservice.AccessibilityShortcutInfo;
 import android.annotation.CallbackExecutor;
 import android.annotation.ColorInt;
-import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -76,7 +75,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -140,21 +138,6 @@
     public static final int AUTOCLICK_DELAY_DEFAULT = 600;
 
     /**
-     * The contrast is defined as a float in [-1, 1], with a default value of 0.
-     * @hide
-     */
-    public static final float CONTRAST_MIN_VALUE = -1f;
-
-    /** @hide */
-    public static final float CONTRAST_MAX_VALUE = 1f;
-
-    /** @hide */
-    public static final float CONTRAST_DEFAULT_VALUE = 0f;
-
-    /** @hide */
-    public static final float CONTRAST_NOT_SET = Float.MIN_VALUE;
-
-    /**
      * Activity action: Launch UI to manage which accessibility service or feature is assigned
      * to the navigation bar Accessibility button.
      * <p>
@@ -288,8 +271,6 @@
     @UnsupportedAppUsage(trackingBug = 123768939L)
     boolean mIsHighTextContrastEnabled;
 
-    private float mUiContrast;
-
     boolean mIsAudioDescriptionByDefaultRequested;
 
     // accessibility tracing state
@@ -314,9 +295,6 @@
     private final ArrayMap<HighTextContrastChangeListener, Handler>
             mHighTextContrastStateChangeListeners = new ArrayMap<>();
 
-    private final ArrayMap<UiContrastChangeListener, Executor>
-            mUiContrastChangeListeners = new ArrayMap<>();
-
     private final ArrayMap<AccessibilityServicesStateChangeListener, Executor>
             mServicesStateChangeListeners = new ArrayMap<>();
 
@@ -390,7 +368,7 @@
          *
          * @param manager The manager that is calling back
          */
-        void onAccessibilityServicesStateChanged(@NonNull AccessibilityManager manager);
+        void onAccessibilityServicesStateChanged(@NonNull  AccessibilityManager manager);
     }
 
     /**
@@ -412,21 +390,6 @@
     }
 
     /**
-     * Listener for the UI contrast. To listen for changes to
-     * the UI contrast on the device, implement this interface and
-     * register it with the system by calling {@link #addUiContrastChangeListener}.
-     */
-    public interface UiContrastChangeListener {
-
-        /**
-         * Called when the color contrast enabled state changes.
-         *
-         * @param uiContrast The color contrast as in {@link #getUiContrast}
-         */
-        void onUiContrastChanged(@FloatRange(from = -1.0f, to = 1.0f) float uiContrast);
-    }
-
-    /**
      * Listener for the audio description by default state. To listen for
      * changes to the audio description by default state on the device,
      * implement this interface and register it with the system by calling
@@ -540,16 +503,6 @@
                 updateFocusAppearanceLocked(strokeWidth, color);
             }
         }
-
-        @Override
-        public void setUiContrast(float contrast) {
-            synchronized (mLock) {
-                // if value changed in the settings, update the cached value and notify listeners
-                if (Math.abs(mUiContrast - contrast) < 1e-10) return;
-                mUiContrast = contrast;
-            }
-            mHandler.obtainMessage(MyCallback.MSG_NOTIFY_CONTRAST_CHANGED).sendToTarget();
-        }
     };
 
     /**
@@ -720,7 +673,7 @@
     /**
      * Returns if the high text contrast in the system is enabled.
      * <p>
-     * <strong>Note:</strong> You need to query this only if your application is
+     * <strong>Note:</strong> You need to query this only if you application is
      * doing its own rendering and does not rely on the platform rendering pipeline.
      * </p>
      *
@@ -740,24 +693,6 @@
     }
 
     /**
-     * Returns the color contrast for the user.
-     * <p>
-     * <strong>Note:</strong> You need to query this only if your application is
-     * doing its own rendering and does not rely on the platform rendering pipeline.
-     * </p>
-     * @return The color contrast, float in [-1, 1] where
-     *          0 corresponds to the default contrast
-     *         -1 corresponds to the minimum contrast that the user can set
-     *          1 corresponds to the maximum contrast that the user can set
-     */
-    @FloatRange(from = -1.0f, to = 1.0f)
-    public float getUiContrast() {
-        synchronized (mLock) {
-            return mUiContrast;
-        }
-    }
-
-    /**
      * Sends an {@link AccessibilityEvent}.
      *
      * @param event The event to send.
@@ -1346,35 +1281,6 @@
     }
 
     /**
-     * Registers a {@link UiContrastChangeListener} for the current user.
-     *
-     * @param executor The executor on which the listener should be called back.
-     * @param listener The listener.
-     */
-    public void addUiContrastChangeListener(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull UiContrastChangeListener listener) {
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(listener);
-        synchronized (mLock) {
-            mUiContrastChangeListeners.put(listener, executor);
-        }
-    }
-
-    /**
-     * Unregisters a {@link UiContrastChangeListener} for the current user.
-     * If the listener was not registered, does nothing and returns.
-     *
-     * @param listener The listener to unregister.
-     */
-    public void removeUiContrastChangeListener(@NonNull UiContrastChangeListener listener) {
-        Objects.requireNonNull(listener);
-        synchronized (mLock) {
-            mUiContrastChangeListeners.remove(listener);
-        }
-    }
-
-    /**
      * Registers a {@link AudioDescriptionRequestedChangeListener}
      * for changes in the audio description by default state of the system.
      * The value could be read via {@link #isAudioDescriptionRequested}.
@@ -2232,7 +2138,6 @@
             mRelevantEventTypes = IntPair.second(userStateAndRelevantEvents);
             updateUiTimeout(service.getRecommendedTimeoutMillis());
             updateFocusAppearanceLocked(service.getFocusStrokeWidth(), service.getFocusColor());
-            mUiContrast = service.getUiContrast();
             mService = service;
         } catch (RemoteException re) {
             Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
@@ -2311,22 +2216,6 @@
     }
 
     /**
-     * Notifies the registered {@link UiContrastChangeListener}s if the value changed.
-     */
-    private void notifyUiContrastChanged() {
-        final ArrayMap<UiContrastChangeListener, Executor> listeners;
-        synchronized (mLock) {
-            listeners = new ArrayMap<>(mUiContrastChangeListeners);
-        }
-
-        listeners.entrySet().forEach(entry -> {
-            UiContrastChangeListener listener = entry.getKey();
-            Executor executor = entry.getValue();
-            executor.execute(() -> listener.onUiContrastChanged(mUiContrast));
-        });
-    }
-
-    /**
      * Notifies the registered {@link AudioDescriptionStateChangeListener}s.
      */
     private void notifyAudioDescriptionbyDefaultStateChanged() {
@@ -2416,7 +2305,6 @@
 
     private final class MyCallback implements Handler.Callback {
         public static final int MSG_SET_STATE = 1;
-        public static final int MSG_NOTIFY_CONTRAST_CHANGED = 2;
 
         @Override
         public boolean handleMessage(Message message) {
@@ -2428,9 +2316,6 @@
                         setStateLocked(state);
                     }
                 } break;
-                case MSG_NOTIFY_CONTRAST_CHANGED: {
-                    notifyUiContrastChanged();
-                }
             }
             return true;
         }
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 1302421..c828058 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -120,8 +120,6 @@
     // Used by UiAutomation for tests on the InputFilter
     void injectInputEventToInputFilter(in InputEvent event);
 
-    float getUiContrast();
-
     boolean startFlashNotificationSequence(String opPkg, int reason, IBinder token);
     boolean stopFlashNotificationSequence(String opPkg);
     boolean startFlashNotificationEvent(String opPkg, int reason, String reasonPkg);
diff --git a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
index 931f862..041399c 100644
--- a/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManagerClient.aidl
@@ -31,6 +31,4 @@
     void setRelevantEventTypes(int eventTypes);
 
     void setFocusAppearance(int strokeWidth, int color);
-
-    void setUiContrast(float contrast);
 }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index ab9f492..14c781b 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -691,6 +691,9 @@
     // Indicates whether called the showAutofillDialog() method.
     private boolean mShowAutofillDialogCalled = false;
 
+    // Cached autofill feature flag
+    private boolean mShouldIgnoreCredentialViews = false;
+
     private final String[] mFillDialogEnabledHints;
 
     // Tracked all views that have appeared, including views that there are no
@@ -838,6 +841,7 @@
 
         mIsFillDialogEnabled = AutofillFeatureFlags.isFillDialogEnabled();
         mFillDialogEnabledHints = AutofillFeatureFlags.getFillDialogEnabledHints();
+        mShouldIgnoreCredentialViews = AutofillFeatureFlags.shouldIgnoreCredentialViews();
         if (sDebug) {
             Log.d(TAG, "Fill dialog is enabled:" + mIsFillDialogEnabled
                     + ", hints=" + Arrays.toString(mFillDialogEnabledHints));
@@ -2081,6 +2085,11 @@
     }
 
     /** @hide */
+    public boolean shouldIgnoreCredentialViews() {
+        return mShouldIgnoreCredentialViews;
+    }
+
+    /** @hide */
     public void onAuthenticationResult(int authenticationId, Intent data, View focusView) {
         if (!hasAutofillFeature()) {
             return;
diff --git a/core/java/android/window/BackNavigationInfo.java b/core/java/android/window/BackNavigationInfo.java
index d7bca30..5140594 100644
--- a/core/java/android/window/BackNavigationInfo.java
+++ b/core/java/android/window/BackNavigationInfo.java
@@ -92,6 +92,8 @@
     @Nullable
     private final IOnBackInvokedCallback mOnBackInvokedCallback;
     private final boolean mPrepareRemoteAnimation;
+    @Nullable
+    private final CustomAnimationInfo mCustomAnimationInfo;
 
     /**
      * Create a new {@link BackNavigationInfo} instance.
@@ -104,11 +106,13 @@
     private BackNavigationInfo(@BackTargetType int type,
             @Nullable RemoteCallback onBackNavigationDone,
             @Nullable IOnBackInvokedCallback onBackInvokedCallback,
-            boolean isPrepareRemoteAnimation) {
+            boolean isPrepareRemoteAnimation,
+            @Nullable CustomAnimationInfo customAnimationInfo) {
         mType = type;
         mOnBackNavigationDone = onBackNavigationDone;
         mOnBackInvokedCallback = onBackInvokedCallback;
         mPrepareRemoteAnimation = isPrepareRemoteAnimation;
+        mCustomAnimationInfo = customAnimationInfo;
     }
 
     private BackNavigationInfo(@NonNull Parcel in) {
@@ -116,6 +120,7 @@
         mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR);
         mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
         mPrepareRemoteAnimation = in.readBoolean();
+        mCustomAnimationInfo = in.readTypedObject(CustomAnimationInfo.CREATOR);
     }
 
     /** @hide */
@@ -125,6 +130,7 @@
         dest.writeTypedObject(mOnBackNavigationDone, flags);
         dest.writeStrongInterface(mOnBackInvokedCallback);
         dest.writeBoolean(mPrepareRemoteAnimation);
+        dest.writeTypedObject(mCustomAnimationInfo, flags);
     }
 
     /**
@@ -172,6 +178,15 @@
         }
     }
 
+    /**
+     * Get customize animation info.
+     * @hide
+     */
+    @Nullable
+    public CustomAnimationInfo getCustomAnimationInfo() {
+        return mCustomAnimationInfo;
+    }
+
     /** @hide */
     @Override
     public int describeContents() {
@@ -197,6 +212,7 @@
                 + "mType=" + typeToString(mType) + " (" + mType + ")"
                 + ", mOnBackNavigationDone=" + mOnBackNavigationDone
                 + ", mOnBackInvokedCallback=" + mOnBackInvokedCallback
+                + ", mCustomizeAnimationInfo=" + mCustomAnimationInfo
                 + '}';
     }
 
@@ -223,6 +239,67 @@
     }
 
     /**
+     * Information for customize back animation.
+     * @hide
+     */
+    public static final class CustomAnimationInfo implements Parcelable {
+        private final String mPackageName;
+        private int mWindowAnimations;
+
+        /**
+         * The package name of the windowAnimations.
+         */
+        @NonNull
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        /**
+         * The resource Id of window animations.
+         */
+        public int getWindowAnimations() {
+            return mWindowAnimations;
+        }
+
+        public CustomAnimationInfo(@NonNull String packageName) {
+            this.mPackageName = packageName;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
+            dest.writeString8(mPackageName);
+            dest.writeInt(mWindowAnimations);
+        }
+
+        private CustomAnimationInfo(@NonNull Parcel in) {
+            mPackageName = in.readString8();
+            mWindowAnimations = in.readInt();
+        }
+
+        @Override
+        public String toString() {
+            return "CustomAnimationInfo, package name= " + mPackageName;
+        }
+
+        @NonNull
+        public static final Creator<CustomAnimationInfo> CREATOR = new Creator<>() {
+            @Override
+            public CustomAnimationInfo createFromParcel(Parcel in) {
+                return new CustomAnimationInfo(in);
+            }
+
+            @Override
+            public CustomAnimationInfo[] newArray(int size) {
+                return new CustomAnimationInfo[size];
+            }
+        };
+    }
+    /**
      * @hide
      */
     @SuppressWarnings("UnusedReturnValue") // Builder pattern
@@ -233,6 +310,7 @@
         @Nullable
         private IOnBackInvokedCallback mOnBackInvokedCallback = null;
         private boolean mPrepareRemoteAnimation;
+        private CustomAnimationInfo mCustomAnimationInfo;
 
         /**
          * @see BackNavigationInfo#getType()
@@ -268,12 +346,22 @@
         }
 
         /**
+         * Set windowAnimations for customize animation.
+         */
+        public Builder setWindowAnimations(String packageName, int windowAnimations) {
+            mCustomAnimationInfo = new CustomAnimationInfo(packageName);
+            mCustomAnimationInfo.mWindowAnimations = windowAnimations;
+            return this;
+        }
+
+        /**
          * Builds and returns an instance of {@link BackNavigationInfo}
          */
         public BackNavigationInfo build() {
             return new BackNavigationInfo(mType, mOnBackNavigationDone,
                     mOnBackInvokedCallback,
-                    mPrepareRemoteAnimation);
+                    mPrepareRemoteAnimation,
+                    mCustomAnimationInfo);
         }
     }
 }
diff --git a/core/java/android/window/IBackAnimationRunner.aidl b/core/java/android/window/IBackAnimationRunner.aidl
index 1c67789..b1d7582 100644
--- a/core/java/android/window/IBackAnimationRunner.aidl
+++ b/core/java/android/window/IBackAnimationRunner.aidl
@@ -37,14 +37,13 @@
 
     /**
      * Called when the system is ready for the handler to start animating all the visible tasks.
-     * @param type The back navigation type.
      * @param apps The list of departing (type=MODE_CLOSING) and entering (type=MODE_OPENING)
                    windows to animate,
      * @param wallpapers The list of wallpapers to animate.
      * @param nonApps The list of non-app windows such as Bubbles to animate.
      * @param finishedCallback The callback to invoke when the animation is finished.
      */
-    void onAnimationStart(in int type,
+    void onAnimationStart(
             in RemoteAnimationTarget[] apps,
             in RemoteAnimationTarget[] wallpapers,
             in RemoteAnimationTarget[] nonApps,
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index fd86769..bf9d3c2 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -34,9 +34,8 @@
      * has create a starting window for the Task.
      *
      * @param info The information about the Task that's available
-     * @param appToken Token of the application being started.
      */
-    void addStartingWindow(in StartingWindowInfo info, IBinder appToken);
+    void addStartingWindow(in StartingWindowInfo info);
 
     /**
      * Called when the Task want to remove the starting window.
diff --git a/core/java/android/window/IWindowlessStartingSurfaceCallback.aidl b/core/java/android/window/IWindowlessStartingSurfaceCallback.aidl
new file mode 100644
index 0000000..a081356
--- /dev/null
+++ b/core/java/android/window/IWindowlessStartingSurfaceCallback.aidl
@@ -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 android.window;
+
+import android.view.SurfaceControl;
+
+/**
+ * Interface to be invoked when a windowless starting surface added.
+ *
+ * @param addedSurface The starting surface.
+ * {@hide}
+ */
+interface IWindowlessStartingSurfaceCallback {
+    void onSurfaceAdded(in SurfaceControl addedSurface);
+}
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index 1a58fd5..071c20f 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -329,6 +329,21 @@
     }
 
     /**
+     * Get or create a TaskDescription from a RunningTaskInfo.
+     */
+    public static ActivityManager.TaskDescription getOrCreateTaskDescription(
+            ActivityManager.RunningTaskInfo runningTaskInfo) {
+        final ActivityManager.TaskDescription taskDescription;
+        if (runningTaskInfo.taskDescription != null) {
+            taskDescription = runningTaskInfo.taskDescription;
+        } else {
+            taskDescription = new ActivityManager.TaskDescription();
+            taskDescription.setBackgroundColor(WHITE);
+        }
+        return taskDescription;
+    }
+
+    /**
      * Help method to draw the snapshot on a surface.
      */
     public static void drawSnapshotOnSurface(StartingWindowInfo info, WindowManager.LayoutParams lp,
@@ -344,13 +359,8 @@
 
         final WindowManager.LayoutParams attrs = info.topOpaqueWindowLayoutParams;
         final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
-        final ActivityManager.TaskDescription taskDescription;
-        if (runningTaskInfo.taskDescription != null) {
-            taskDescription = runningTaskInfo.taskDescription;
-        } else {
-            taskDescription = new ActivityManager.TaskDescription();
-            taskDescription.setBackgroundColor(WHITE);
-        }
+        final ActivityManager.TaskDescription taskDescription =
+                getOrCreateTaskDescription(runningTaskInfo);
         drawSurface.initiateSystemBarPainter(lp.flags, lp.privateFlags,
                 attrs.insetsFlags.appearance, taskDescription, info.requestedVisibleTypes);
         final Rect systemBarInsets = getSystemBarInsets(windowBounds, topWindowInsetsState);
diff --git a/core/java/android/window/StartingWindowInfo.java b/core/java/android/window/StartingWindowInfo.java
index 1b64e61..451acbe 100644
--- a/core/java/android/window/StartingWindowInfo.java
+++ b/core/java/android/window/StartingWindowInfo.java
@@ -22,9 +22,12 @@
 import android.app.ActivityManager;
 import android.app.TaskInfo;
 import android.content.pm.ActivityInfo;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.RemoteException;
 import android.view.InsetsState;
+import android.view.SurfaceControl;
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowManager;
@@ -59,6 +62,8 @@
     /** @hide **/
     public static final int STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN = 4;
 
+    public static final int STARTING_WINDOW_TYPE_WINDOWLESS = 5;
+
     /**
      * @hide
      */
@@ -67,7 +72,8 @@
             STARTING_WINDOW_TYPE_SPLASH_SCREEN,
             STARTING_WINDOW_TYPE_SNAPSHOT,
             STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN,
-            STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+            STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN,
+            STARTING_WINDOW_TYPE_WINDOWLESS
     })
     public @interface StartingWindowType {}
 
@@ -118,6 +124,7 @@
             TYPE_PARAMETER_ACTIVITY_CREATED,
             TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN,
             TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN,
+            TYPE_PARAMETER_WINDOWLESS,
             TYPE_PARAMETER_LEGACY_SPLASH_SCREEN
     })
     public @interface StartingTypeParams {}
@@ -151,6 +158,12 @@
      * @hide
      */
     public static final int TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN = 0x00000080;
+
+    /**
+     * Windowless surface
+     */
+    public static final int TYPE_PARAMETER_WINDOWLESS = 0x00000100;
+
     /**
      * Application is allowed to use the legacy splash screen
      * @hide
@@ -182,7 +195,33 @@
      */
     public TaskSnapshot taskSnapshot;
 
-    public @InsetsType int requestedVisibleTypes = WindowInsets.Type.defaultVisible();
+    @InsetsType public int requestedVisibleTypes = WindowInsets.Type.defaultVisible();
+
+    /**
+     * App token where the starting window should add to.
+     */
+    public IBinder appToken;
+
+    public IWindowlessStartingSurfaceCallback windowlessStartingSurfaceCallback;
+
+    /**
+     * The root surface where windowless surface should attach on.
+     */
+    public SurfaceControl rootSurface;
+
+    /**
+     * Notify windowless surface is created.
+     * @param addedSurface Created surface.
+     */
+    public void notifyAddComplete(SurfaceControl addedSurface) {
+        if (windowlessStartingSurfaceCallback != null) {
+            try {
+                windowlessStartingSurfaceCallback.onSurfaceAdded(addedSurface);
+            } catch (RemoteException e) {
+                //
+            }
+        }
+    }
 
     public StartingWindowInfo() {
 
@@ -216,6 +255,9 @@
         dest.writeBoolean(isKeyguardOccluded);
         dest.writeTypedObject(taskSnapshot, flags);
         dest.writeInt(requestedVisibleTypes);
+        dest.writeStrongBinder(appToken);
+        dest.writeStrongInterface(windowlessStartingSurfaceCallback);
+        dest.writeTypedObject(rootSurface, flags);
     }
 
     void readFromParcel(@NonNull Parcel source) {
@@ -230,6 +272,10 @@
         isKeyguardOccluded = source.readBoolean();
         taskSnapshot = source.readTypedObject(TaskSnapshot.CREATOR);
         requestedVisibleTypes = source.readInt();
+        appToken = source.readStrongBinder();
+        windowlessStartingSurfaceCallback = IWindowlessStartingSurfaceCallback.Stub
+                .asInterface(source.readStrongBinder());
+        rootSurface = source.readTypedObject(SurfaceControl.CREATOR);
     }
 
     @Override
diff --git a/core/java/android/window/StartingWindowRemovalInfo.java b/core/java/android/window/StartingWindowRemovalInfo.java
index 384dacf..5181236 100644
--- a/core/java/android/window/StartingWindowRemovalInfo.java
+++ b/core/java/android/window/StartingWindowRemovalInfo.java
@@ -67,6 +67,16 @@
      */
     public float roundedCornerRadius;
 
+    /**
+     * Remove windowless surface.
+     */
+    public boolean windowlessSurface;
+
+    /**
+     * Remove immediately.
+     */
+    public boolean removeImmediately;
+
     public StartingWindowRemovalInfo() {
 
     }
@@ -87,6 +97,8 @@
         playRevealAnimation = source.readBoolean();
         deferRemoveForIme = source.readBoolean();
         roundedCornerRadius = source.readFloat();
+        windowlessSurface = source.readBoolean();
+        removeImmediately = source.readBoolean();
     }
 
     @Override
@@ -97,6 +109,8 @@
         dest.writeBoolean(playRevealAnimation);
         dest.writeBoolean(deferRemoveForIme);
         dest.writeFloat(roundedCornerRadius);
+        dest.writeBoolean(windowlessSurface);
+        dest.writeBoolean(removeImmediately);
     }
 
     @Override
@@ -105,7 +119,9 @@
                 + " frame=" + mainFrame
                 + " playRevealAnimation=" + playRevealAnimation
                 + " roundedCornerRadius=" + roundedCornerRadius
-                + " deferRemoveForIme=" + deferRemoveForIme + "}";
+                + " deferRemoveForIme=" + deferRemoveForIme
+                + " windowlessSurface=" + windowlessSurface
+                + " removeImmediately=" + removeImmediately + "}";
     }
 
     public static final @android.annotation.NonNull Creator<StartingWindowRemovalInfo> CREATOR =
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
index 02878f8..d4728c1 100644
--- a/core/java/android/window/TaskOrganizer.java
+++ b/core/java/android/window/TaskOrganizer.java
@@ -92,13 +92,10 @@
      * has create a starting window for the Task.
      *
      * @param info The information about the Task that's available
-     * @param appToken Token of the application being started.
-     *        context to for resources
      * @hide
      */
     @BinderThread
-    public void addStartingWindow(@NonNull StartingWindowInfo info,
-            @NonNull IBinder appToken) {}
+    public void addStartingWindow(@NonNull StartingWindowInfo info) {}
 
     /**
      * Called when the Task want to remove the starting window.
@@ -297,9 +294,8 @@
 
     private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() {
         @Override
-        public void addStartingWindow(StartingWindowInfo windowInfo,
-                IBinder appToken) {
-            mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo, appToken));
+        public void addStartingWindow(StartingWindowInfo windowInfo) {
+            mExecutor.execute(() -> TaskOrganizer.this.addStartingWindow(windowInfo));
         }
 
         @Override
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java
index 0489dc81..fef5e83 100644
--- a/core/java/com/android/internal/notification/SystemNotificationChannels.java
+++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java
@@ -17,7 +17,6 @@
 import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_CHANNEL_DEVICE_ADMIN;
 
 import android.app.INotificationManager;
-import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.admin.DevicePolicyManager;
@@ -25,7 +24,6 @@
 import android.content.pm.ParceledListSlice;
 import android.media.AudioAttributes;
 import android.os.RemoteException;
-import android.provider.Settings;
 
 import com.android.internal.R;
 
@@ -78,9 +76,7 @@
         final NotificationChannel physicalKeyboardChannel = new NotificationChannel(
                 PHYSICAL_KEYBOARD,
                 context.getString(R.string.notification_channel_physical_keyboard),
-                NotificationManager.IMPORTANCE_DEFAULT);
-        physicalKeyboardChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
-                Notification.AUDIO_ATTRIBUTES_DEFAULT);
+                NotificationManager.IMPORTANCE_LOW);
         physicalKeyboardChannel.setBlockable(true);
         channelsList.add(physicalKeyboardChannel);
 
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 600ae50..5cab674 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -265,6 +265,34 @@
         }
         return null;
     }
+
+    /** Get animation resId by attribute Id from specific LayoutParams */
+    public int getAnimationResId(LayoutParams lp, int animAttr, int transit) {
+        int resId = Resources.ID_NULL;
+        if (animAttr >= 0) {
+            AttributeCache.Entry ent = getCachedAnimations(lp);
+            if (ent != null) {
+                resId = ent.array.getResourceId(animAttr, 0);
+            }
+        }
+        resId = updateToTranslucentAnimIfNeeded(resId, transit);
+        return resId;
+    }
+
+    /** Get default animation resId */
+    public int getDefaultAnimationResId(int animAttr, int transit) {
+        int resId = Resources.ID_NULL;
+        if (animAttr >= 0) {
+            AttributeCache.Entry ent = getCachedAnimations(DEFAULT_PACKAGE,
+                    mDefaultWindowAnimationStyleResId);
+            if (ent != null) {
+                resId = ent.array.getResourceId(animAttr, 0);
+            }
+        }
+        resId = updateToTranslucentAnimIfNeeded(resId, transit);
+        return resId;
+    }
+
     /**
      * Load animation by attribute Id from a specific AnimationStyle resource.
      *
diff --git a/core/jni/android_window_WindowInfosListener.cpp b/core/jni/android_window_WindowInfosListener.cpp
index f2cbe8a..850755a 100644
--- a/core/jni/android_window_WindowInfosListener.cpp
+++ b/core/jni/android_window_WindowInfosListener.cpp
@@ -134,6 +134,7 @@
 
 void destroyNativeService(void* ptr) {
     WindowInfosListener* listener = reinterpret_cast<WindowInfosListener*>(ptr);
+    SurfaceComposerClient::getDefault()->removeWindowInfosListener(listener);
     listener->decStrong((void*)nativeCreate);
 }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index cb6c092..8fdefab 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3772,10 +3772,9 @@
     <permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"
                 android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role"/>
 
-    <!-- Allows an application to hint that a component lifecycle operation such as sending
-         a broadcast is associated with an "interactive" usage scenario.
+    <!-- Allows an application to request interactive options when sending a broadcast.
          @hide -->
-    <permission android:name="android.permission.COMPONENT_OPTION_INTERACTIVE"
+    <permission android:name="android.permission.BROADCAST_OPTION_INTERACTIVE"
                 android:protectionLevel="signature|privileged" />
 
     <!-- @SystemApi Must be required by activities that handle the intent action
@@ -8169,6 +8168,10 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
+        <service android:name="com.android.server.healthconnect.migration.MigrationBroadcastJobService"
+            android:permission="android.permission.BIND_JOB_SERVICE">
+        </service>
+
         <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
             android:exported="false">
             <intent-filter>
diff --git a/core/res/res/layout/transient_notification.xml b/core/res/res/layout/transient_notification.xml
index 3259201..8bedb89 100644
--- a/core/res/res/layout/transient_notification.xml
+++ b/core/res/res/layout/transient_notification.xml
@@ -25,7 +25,7 @@
     android:orientation="horizontal"
     android:gravity="center_vertical"
     android:maxWidth="@dimen/toast_width"
-    android:background="?android:attr/toastFrameBackground"
+    android:background="?android:attr/colorBackground"
     android:elevation="@dimen/toast_elevation"
     android:layout_marginEnd="16dp"
     android:layout_marginStart="16dp"
diff --git a/core/res/res/layout/transient_notification_with_icon.xml b/core/res/res/layout/transient_notification_with_icon.xml
index e9b17df..0dfb3ad 100644
--- a/core/res/res/layout/transient_notification_with_icon.xml
+++ b/core/res/res/layout/transient_notification_with_icon.xml
@@ -22,7 +22,7 @@
     android:orientation="horizontal"
     android:gravity="center_vertical"
     android:maxWidth="@dimen/toast_width"
-    android:background="?android:attr/toastFrameBackground"
+    android:background="?android:attr/colorBackground"
     android:elevation="@dimen/toast_elevation"
     android:layout_marginEnd="16dp"
     android:layout_marginStart="16dp"
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index ffb602d..16511a6 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6346,4 +6346,8 @@
     <bool name="config_batteryStatsResetOnUnplugHighBatteryLevel">true</bool>
     <!-- Whether to reset Battery Stats on unplug if the battery was significantly charged -->
     <bool name="config_batteryStatsResetOnUnplugAfterSignificantCharge">true</bool>
+
+    <!-- Whether we should persist the brightness value in nits for the default display even if
+         the underlying display device changes. -->
+    <bool name="config_persistBrightnessNitsForDefaultDisplay">false</bool>
 </resources>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index ebda172..c0f2157 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -52,6 +52,16 @@
     <integer name="config_delay_for_ims_dereg_millis">0</integer>
     <java-symbol type="integer" name="config_delay_for_ims_dereg_millis" />
 
+    <!-- Define the bar of considering the availability of a subscription is stable in milliseconds,
+         where 0 means immediate switch, and negative milliseconds indicates the auto data switch
+         feature is disabled.-->
+    <integer name="auto_data_switch_availability_stability_time_threshold_millis">10000</integer>
+    <java-symbol type="integer" name="auto_data_switch_availability_stability_time_threshold_millis" />
+
+    <!-- Define the maximum retry times when a validation for switching failed.-->
+    <integer name="auto_data_switch_validation_max_retry">7</integer>
+    <java-symbol type="integer" name="auto_data_switch_validation_max_retry" />
+
     <!-- Boolean indicating whether the Iwlan data service supports persistence of iwlan ipsec
          tunnels across service restart. If iwlan tunnels are not persisted across restart,
          Framework will clean up dangling data connections when service restarts -->
@@ -114,6 +124,11 @@
     <string name="config_pointing_ui_package" translatable="false"></string>
     <java-symbol type="string" name="config_pointing_ui_package" />
 
+    <!-- Telephony resends received satellite datagram to listener
+         if ack is not received within this timeout -->
+    <integer name="config_timeout_to_receive_delivered_ack_millis">300000</integer>
+    <java-symbol type="integer" name="config_timeout_to_receive_delivered_ack_millis" />
+
     <!-- Whether enhanced IWLAN handover check is enabled. If enabled, telephony frameworks
          will not perform handover if the target transport is out of service, or VoPS not
          supported. The network will be torn down on the source transport, and will be
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 07f3530..3074906 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3787,8 +3787,10 @@
     <!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=30] -->
     <string name="hardware">Show virtual keyboard</string>
 
-    <!-- Title of the notification to prompt the user to configure physical keyboard settings. -->
-    <string name="select_keyboard_layout_notification_title">Configure physical keyboard</string>
+    <!-- Title of the notification to prompt the user to configure physical keyboard settings. [CHAR LIMIT=NOTIF_TITLE] -->
+    <string name="select_keyboard_layout_notification_title">Configure <xliff:g id="device_name" example="Foobar USB Keyboard">%s</xliff:g></string>
+    <!-- Title of the notification to prompt the user to configure physical keyboard settings when multiple keyboards connected. [CHAR LIMIT=NOTIF_TITLE] -->
+    <string name="select_multiple_keyboards_layout_notification_title">Configure physical keyboards</string>
     <!-- Message of the notification to prompt the user to configure physical keyboard settings
          where the user can associate language with physical keyboard layout. -->
     <string name="select_keyboard_layout_notification_message">Tap to select language and layout</string>
@@ -6264,4 +6266,19 @@
     <string name="concurrent_display_notification_thermal_content">Dual Screen is unavailable because your phone is getting too warm</string>
     <!-- Text of device state notification turn off button. [CHAR LIMIT=NONE] -->
     <string name="device_state_notification_turn_off_button">Turn off</string>
+
+    <!-- Notification title when a keyboard has been configured [CHAR LIMIT=NOTIF_TITLE] -->
+    <string name="keyboard_layout_notification_selected_title"><xliff:g id="device_name" example="Foobar USB Keyboard">%s</xliff:g> configured</string>
+    <!-- Notification message shown when one layout was configured for a physical keyboard [CHAR LIMIT=NOTIF_BODY] -->
+    <string name="keyboard_layout_notification_one_selected_message">Keyboard layout set to <xliff:g id="layout_1" example="English (US)">%s</xliff:g>. Tap to change.</string>
+    <!-- Notification message shown when two layout were configured for a physical keyboard [CHAR LIMIT=NOTIF_BODY] -->
+    <string name="keyboard_layout_notification_two_selected_message">Keyboard layout set to <xliff:g id="layout_1" example="English (US)">%1$s</xliff:g>, <xliff:g id="layout_2" example="German">%2$s</xliff:g>. Tap to change.</string>
+    <!-- Notification message shown when three layout were configured for a physical keyboard [CHAR LIMIT=NOTIF_BODY] -->
+    <string name="keyboard_layout_notification_three_selected_message">Keyboard layout set to <xliff:g id="layout_1" example="English (US)">%1$s</xliff:g>, <xliff:g id="layout_2" example="German">%2$s</xliff:g>, <xliff:g id="layout_3" example="French">%3$s</xliff:g>. Tap to change.</string>
+    <!-- Notification message shown when more than three layout were configured for a physical keyboard [CHAR LIMIT=NOTIF_BODY] -->
+    <string name="keyboard_layout_notification_more_than_three_selected_message">Keyboard layout set to <xliff:g id="layout_1" example="English (US)">%1$s</xliff:g>, <xliff:g id="layout_2" example="English (US)">%2$s</xliff:g>, <xliff:g id="layout_3" example="French">%3$s</xliff:g>\u2026 Tap to change.</string>
+    <!-- Notification title when multiple keyboards with selected layouts have been connected the first time simultaneously [CHAR LIMIT=NOTIF_TITLE] -->
+    <string name="keyboard_layout_notification_multiple_selected_title">Physical keyboards configured</string>
+    <!-- Notification message when multiple keyboards with selected layouts have been connected the first time simultaneously [CHAR LIMIT=NOTIF_BODY] -->
+    <string name="keyboard_layout_notification_multiple_selected_message">Tap to view keyboards</string>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 92dc569..c34d31c 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2138,6 +2138,7 @@
   <java-symbol type="string" name="report" />
   <java-symbol type="string" name="select_input_method" />
   <java-symbol type="string" name="select_keyboard_layout_notification_title" />
+  <java-symbol type="string" name="select_multiple_keyboards_layout_notification_title" />
   <java-symbol type="string" name="select_keyboard_layout_notification_message" />
   <java-symbol type="string" name="smv_application" />
   <java-symbol type="string" name="smv_process" />
@@ -2229,6 +2230,7 @@
   <java-symbol type="bool" name="config_preventImeStartupUnlessTextEditor" />
   <java-symbol type="array" name="config_nonPreemptibleInputMethods" />
   <java-symbol type="bool" name="config_enhancedConfirmationModeEnabled" />
+  <java-symbol type="bool" name="config_persistBrightnessNitsForDefaultDisplay" />
 
   <java-symbol type="layout" name="resolver_list" />
   <java-symbol type="id" name="resolver_list" />
@@ -4964,10 +4966,19 @@
   <!-- Whether to show weather on the lockscreen by default. -->
   <java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" />
 
+  <!-- For keyboard notification -->
+  <java-symbol type="string" name="keyboard_layout_notification_selected_title"/>
+  <java-symbol type="string" name="keyboard_layout_notification_one_selected_message"/>
+  <java-symbol type="string" name="keyboard_layout_notification_two_selected_message"/>
+  <java-symbol type="string" name="keyboard_layout_notification_three_selected_message"/>
+  <java-symbol type="string" name="keyboard_layout_notification_more_than_three_selected_message"/>
+  <java-symbol type="string" name="keyboard_layout_notification_multiple_selected_title"/>
+  <java-symbol type="string" name="keyboard_layout_notification_multiple_selected_message"/>
+
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
   <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />
 
-  
+
   <java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/>
   <java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/>
   <java-symbol name="materialColorSurfaceContainerLowest" type="attr"/>
diff --git a/core/tests/coretests/src/android/content/pm/TEST_MAPPING b/core/tests/coretests/src/android/content/pm/TEST_MAPPING
index 15e04d1..978d80c 100644
--- a/core/tests/coretests/src/android/content/pm/TEST_MAPPING
+++ b/core/tests/coretests/src/android/content/pm/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-  "presubmit-large": [
+  "presubmit": [
     {
       "name": "FrameworksCoreTests",
       "options": [
diff --git a/core/tests/coretests/src/android/view/AccessibilityInteractionControllerTest.java b/core/tests/coretests/src/android/view/AccessibilityInteractionControllerTest.java
index 7855ef9..d91541f 100644
--- a/core/tests/coretests/src/android/view/AccessibilityInteractionControllerTest.java
+++ b/core/tests/coretests/src/android/view/AccessibilityInteractionControllerTest.java
@@ -16,8 +16,8 @@
 
 package android.view;
 
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Activity;
@@ -98,24 +98,42 @@
     @Test
     public void clearAccessibilityFocus_shouldClearFocus() throws Exception {
         performAccessibilityFocus("com.android.frameworks.coretests:id/appNameBtn");
-        assertTrue("Button should have a11y focus",
-                mButton.isAccessibilityFocused());
+        assertWithMessage("Button should have a11y focus").that(
+                mButton.isAccessibilityFocused()).isTrue();
         mAccessibilityInteractionController.clearAccessibilityFocusClientThread();
         sInstrumentation.waitForIdleSync();
-        assertFalse("Button should not have a11y focus",
-                mButton.isAccessibilityFocused());
+        assertWithMessage("Button should not have a11y focus").that(
+                mButton.isAccessibilityFocused()).isFalse();
     }
 
     @Test
     public void clearAccessibilityFocus_uiThread_shouldClearFocus() throws Exception {
         performAccessibilityFocus("com.android.frameworks.coretests:id/appNameBtn");
-        assertTrue("Button should have a11y focus",
-                mButton.isAccessibilityFocused());
-        sInstrumentation.runOnMainSync(() -> {
-            mAccessibilityInteractionController.clearAccessibilityFocusClientThread();
-        });
-        assertFalse("Button should not have a11y focus",
-                mButton.isAccessibilityFocused());
+        assertWithMessage("Button should have a11y focus").that(
+                mButton.isAccessibilityFocused()).isTrue();
+        sInstrumentation.runOnMainSync(() ->
+                mAccessibilityInteractionController.clearAccessibilityFocusClientThread());
+        assertWithMessage("Button should not have a11y focus").that(
+                mButton.isAccessibilityFocused()).isFalse();
+    }
+
+    @Test
+    public void clearAccessibilityFocus_sensitiveRootView_shouldClearFocus()
+            throws Exception {
+        final View rootView = mButton.getRootView();
+        assertThat(rootView).isNotNull();
+        try {
+            rootView.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_YES);
+            performAccessibilityFocus("com.android.frameworks.coretests:id/appNameBtn");
+            assertWithMessage("Button should have a11y focus").that(
+                    mButton.isAccessibilityFocused()).isTrue();
+            sInstrumentation.runOnMainSync(() ->
+                    mAccessibilityInteractionController.clearAccessibilityFocusClientThread());
+            assertWithMessage("Button should not have a11y focus").that(
+                    mButton.isAccessibilityFocused()).isFalse();
+        } finally {
+            rootView.setAccessibilityDataSensitive(View.ACCESSIBILITY_DATA_SENSITIVE_AUTO);
+        }
     }
 
     private void launchActivity() {
diff --git a/core/tests/coretests/src/android/view/OWNERS b/core/tests/coretests/src/android/view/OWNERS
index a142e27..2ca9994 100644
--- a/core/tests/coretests/src/android/view/OWNERS
+++ b/core/tests/coretests/src/android/view/OWNERS
@@ -1,4 +1,5 @@
 # Accessibility
+per-file AccessibilityInteractionControllerTest.java = file:/services/accessibility/OWNERS
 per-file WindowInfoTest.java = file:/services/accessibility/OWNERS
 
 # Input
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 116aa489..fe639ff 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -24,7 +24,7 @@
         <permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
         <permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
         <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
-        <permission name="android.permission.COMPONENT_OPTION_INTERACTIVE"/>
+        <permission name="android.permission.BROADCAST_OPTION_INTERACTIVE"/>
         <permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
         <permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
         <permission name="android.permission.CONTROL_VPN"/>
diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml
index 6f6390b..9959433 100644
--- a/data/etc/preinstalled-packages-platform-overlays.xml
+++ b/data/etc/preinstalled-packages-platform-overlays.xml
@@ -53,6 +53,9 @@
     <install-in-user-type package="com.android.internal.systemui.onehanded.gestural">
         <install-in user-type="FULL" />
     </install-in-user-type>
+    <install-in-user-type package="com.android.internal.systemui.navbar.transparent">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
     <install-in-user-type package="com.android.theme.color.amethyst">
         <install-in user-type="FULL" />
         <install-in user-type="PROFILE" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 5cb5ffa0..4a98c4d 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -539,6 +539,12 @@
         <permission name="android.permission.BIND_WALLPAPER"/>
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.wallpaper">
+        <permission name="android.permission.SET_WALLPAPER_COMPONENT"/>
+        <permission name="android.permission.BIND_WALLPAPER"/>
+        <permission name="android.permission.CUSTOMIZE_SYSTEM_UI"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.dynsystem">
         <permission name="android.permission.REBOOT"/>
         <permission name="android.permission.MANAGE_DYNAMIC_SYSTEM"/>
diff --git a/data/keyboards/Vendor_004c_Product_0265.idc b/data/keyboards/Vendor_004c_Product_0265.idc
new file mode 120000
index 0000000..707dfcf
--- /dev/null
+++ b/data/keyboards/Vendor_004c_Product_0265.idc
@@ -0,0 +1 @@
+Vendor_05ac_Product_0265.idc
\ No newline at end of file
diff --git a/data/keyboards/Vendor_03f6_Product_a001.idc b/data/keyboards/Vendor_03f6_Product_a001.idc
new file mode 100644
index 0000000..bcb4ee3
--- /dev/null
+++ b/data/keyboards/Vendor_03f6_Product_a001.idc
@@ -0,0 +1,22 @@
+# 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.
+
+#
+# Brydge Touchpad
+#
+
+# Reports from this touchpad sometimes get bunched together due to Bluetooth
+# batching, leading to bad timestamps that mess up finger velocity calculations.
+# To fix this, set a fake delta using the touchpad's known report rate.
+gestureProp.Fake_Timestamp_Delta = 0.010
diff --git a/data/keyboards/Vendor_046d_Product_4011.idc b/data/keyboards/Vendor_046d_Product_4011.idc
new file mode 100644
index 0000000..3a23830
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_4011.idc
@@ -0,0 +1,32 @@
+# 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.
+
+#
+# Logitech Wireless Touchpad
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+gestureProp.Pressure_Calibration_Offset = -313.240741792594
+gestureProp.Pressure_Calibration_Slope = 4.39678062436752
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Palm_Pressure = 100000.0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Vendor_046d_Product_4101.idc b/data/keyboards/Vendor_046d_Product_4101.idc
new file mode 100644
index 0000000..47e2530
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_4101.idc
@@ -0,0 +1,31 @@
+# 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.
+
+#
+# Logitech T650
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+gestureProp.Pressure_Calibration_Offset = -0.439288351750068
+gestureProp.Pressure_Calibration_Slope = 3.05998553523335
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Vendor_046d_Product_4102.idc b/data/keyboards/Vendor_046d_Product_4102.idc
new file mode 100644
index 0000000..e33a28a
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_4102.idc
@@ -0,0 +1,24 @@
+# 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.
+
+#
+# Logitech TK820
+#
+
+gestureProp.Touchpad_Stack_Version = 2
+# Pressure jumps around a lot on this touchpad, so allow that:
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Pressure_Calibration_Offset = -18.8078435
+gestureProp.Pressure_Calibration_Slope = 2.466208137
diff --git a/data/keyboards/Vendor_046d_Product_b00c.idc b/data/keyboards/Vendor_046d_Product_b00c.idc
new file mode 100644
index 0000000..a49970c
--- /dev/null
+++ b/data/keyboards/Vendor_046d_Product_b00c.idc
@@ -0,0 +1,31 @@
+# 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.
+
+#
+# Logitech T651
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+gestureProp.Pressure_Calibration_Offset = -4.46520447177073
+gestureProp.Pressure_Calibration_Slope = 3.21071719332644
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/data/keyboards/Vendor_05ac_Product_0265.idc b/data/keyboards/Vendor_05ac_Product_0265.idc
new file mode 100644
index 0000000..d72de64
--- /dev/null
+++ b/data/keyboards/Vendor_05ac_Product_0265.idc
@@ -0,0 +1,34 @@
+# 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.
+
+#
+# Apple Magic Trackpad 2 configuration file
+#   Bluetooth vendor ID 004c
+#   USB vendor ID 05ac
+#
+
+gestureProp.Pressure_Calibration_Offset = 30
+gestureProp.Palm_Pressure = 250.0
+gestureProp.Palm_Width = 20.0
+gestureProp.Multiple_Palm_Width = 20.0
+
+# Enable Stationary Wiggle Filter
+gestureProp.Stationary_Wiggle_Filter_Enabled = 1
+gestureProp.Finger_Moving_Energy = 0.0008
+gestureProp.Finger_Moving_Hysteresis = 0.0004
+
+# Avoid accidental scroll/move on finger lift
+gestureProp.Max_Stationary_Move_Speed = 47
+gestureProp.Max_Stationary_Move_Speed_Hysteresis = 1
+gestureProp.Max_Stationary_Move_Suppress_Distance = 0.2
diff --git a/data/keyboards/Vendor_05ac_Product_030e.idc b/data/keyboards/Vendor_05ac_Product_030e.idc
new file mode 100644
index 0000000..23a2e18
--- /dev/null
+++ b/data/keyboards/Vendor_05ac_Product_030e.idc
@@ -0,0 +1,38 @@
+# 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.
+
+#
+# Apple Magic Trackpad
+#
+
+gestureProp.Touchpad_Stack_Version = 1
+# We are using raw touch major value as pressure value, so set the Palm
+# pressure threshold high.
+gestureProp.Palm_Pressure = 1000
+gestureProp.Compute_Surface_Area_from_Pressure = 0
+gestureProp.IIR_b0 = 1
+gestureProp.IIR_b1 = 0
+gestureProp.IIR_b2 = 0
+gestureProp.IIR_b3 = 0
+gestureProp.IIR_a1 = 0
+gestureProp.IIR_a2 = 0
+# NOTE: bias on X-axis is uncalibrated
+gestureProp.Touchpad_Device_Output_Bias_on_X-Axis = -283.3226025266607
+gestureProp.Touchpad_Device_Output_Bias_on_Y-Axis = -283.3226025266607
+gestureProp.Max_Allowed_Pressure_Change_Per_Sec = 100000.0
+gestureProp.Max_Hysteresis_Pressure_Per_Sec = 100000.0
+# Drumroll suppression causes janky movement on this touchpad.
+gestureProp.Drumroll_Suppression_Enable = 0
+gestureProp.Two_Finger_Vertical_Close_Distance_Thresh = 35.0
+gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls = 0
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index 4c669b8..24fea01 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -170,7 +170,7 @@
     /**
      * Standard CIE 1931 2° illuminant D65, encoded in xyY.
      * This illuminant has a color temperature of 6504K. This illuminant
-     * is commonly used in RGB color spaces such as sRGB, BT.209, etc.
+     * is commonly used in RGB color spaces such as sRGB, BT.709, etc.
      */
     public static final float[] ILLUMINANT_D65 = { 0.31271f, 0.32902f };
     /**
@@ -862,8 +862,8 @@
     public enum Model {
         /**
          * The RGB model is a color model with 3 components that
-         * refer to the three additive primiaries: red, green
-         * andd blue.
+         * refer to the three additive primaries: red, green
+         * and blue.
          */
         RGB(3),
         /**
@@ -2537,7 +2537,7 @@
          * does not need to be specified and is assumed to be 1.0. Only the xy components
          * are required.</p>
          *
-         * <p class="note">The ID, areturned by {@link #getId()}, of an object created by
+         * <p class="note">The ID, as returned by {@link #getId()}, of an object created by
          * this constructor is always {@link #MIN_ID}.</p>
          *
          * @param name Name of the color space, cannot be null, its length must be >= 1
@@ -3961,7 +3961,7 @@
              *
              * <p>We can only connect color spaces if they use the same profile
              * connection space. We assume the connection space is always
-             * CIE XYZ but we maye need to perform a chromatic adaptation to
+             * CIE XYZ but we maybe need to perform a chromatic adaptation to
              * match the white points. If an adaptation is needed, we use the
              * CIE standard illuminant D50. The unmatched color space is adapted
              * using the von Kries transform and the {@link Adaptation#BRADFORD}
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 caefb2e..404429a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1000,7 +1000,10 @@
     Activity findActivityBelow(@NonNull Activity activity) {
         Activity activityBelow = null;
         final TaskFragmentContainer container = getContainerWithActivity(activity);
-        if (container != null) {
+        // Looking for the activity below from the information we already have if the container
+        // only embeds activities of the same process because activities of other processes are not
+        // available in this embedding host process for security concern.
+        if (container != null && !container.hasCrossProcessActivities()) {
             final List<Activity> containerActivities = container.collectNonFinishingActivities();
             final int index = containerActivities.indexOf(activity);
             if (index > 0) {
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 6c553a8..60be9d1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -153,6 +153,11 @@
     Runnable mAppearEmptyTimeout;
 
     /**
+     * Whether this TaskFragment contains activities of another process/package.
+     */
+    private boolean mHasCrossProcessActivities;
+
+    /**
      * Creates a container with an existing activity that will be re-parented to it in a window
      * container transaction.
      * @param pairedPrimaryContainer    when it is set, the new container will be add right above it
@@ -418,10 +423,18 @@
             mAppearEmptyTimeout = null;
         }
 
+        mHasCrossProcessActivities = false;
         mInfo = info;
         if (mInfo == null || mInfo.isEmpty()) {
             return;
         }
+
+        // Contains activities of another process if the activities size is not matched to the
+        // running activity count
+        if (mInfo.getRunningActivityCount() != mInfo.getActivities().size()) {
+            mHasCrossProcessActivities = true;
+        }
+
         // Only track the pending Intent when the container is empty.
         mPendingAppearedIntent = null;
         if (mPendingAppearedActivities.isEmpty()) {
@@ -751,6 +764,11 @@
         return mPendingAppearedInRequestedTaskFragmentActivities.contains(activityToken);
     }
 
+    /** Whether contains activities of another process */
+    boolean hasCrossProcessActivities() {
+        return mHasCrossProcessActivities;
+    }
+
     /** Gets the parent leaf Task id. */
     int getTaskId() {
         return mTaskContainer.getTaskId();
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 3d0e1c8..cbdc262 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
@@ -1014,6 +1014,25 @@
     }
 
     @Test
+    public void testFindActivityBelow() {
+        // Create a container with two activities
+        final TaskFragmentContainer container = createMockTaskFragmentContainer(mActivity);
+        final Activity pendingAppearedActivity = createMockActivity();
+        container.addPendingAppearedActivity(pendingAppearedActivity);
+
+        // Ensure the activity below matches
+        assertEquals(mActivity,
+                mSplitController.findActivityBelow(pendingAppearedActivity));
+
+        // Ensure that the activity look up won't search for the in-process activities and should
+        // IPC to WM core to get the activity below. It should be `null` for this mock test.
+        spyOn(container);
+        doReturn(true).when(container).hasCrossProcessActivities();
+        assertNotEquals(mActivity,
+                mSplitController.findActivityBelow(pendingAppearedActivity));
+    }
+
+    @Test
     public void testResolveActivityToContainer_inUnknownTaskFragment() {
         doReturn(new Binder()).when(mSplitController)
                 .getTaskFragmentTokenFromActivityClientRecord(mActivity);
diff --git a/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
index 2dba37d..4c28e51 100644
--- a/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
+++ b/libs/WindowManager/Shell/res/drawable/tv_window_button_bg.xml
@@ -17,5 +17,5 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
     <corners android:radius="@dimen/tv_window_menu_button_radius" />
-    <solid android:color="@color/tv_window_menu_icon_bg" />
+    <solid android:color="@android:color/white" />
 </shape>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 585f81c..b6fd0bb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -428,9 +428,9 @@
     }
 
     @Override
-    public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
+    public void addStartingWindow(StartingWindowInfo info) {
         if (mStartingWindow != null) {
-            mStartingWindow.addStartingWindow(info, appToken);
+            mStartingWindow.addStartingWindow(info);
         }
     }
 
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 0b87598..349ff36 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
@@ -120,6 +120,9 @@
     @Nullable
     private IOnBackInvokedCallback mActiveCallback;
 
+    private CrossActivityAnimation mDefaultActivityAnimation;
+    private CustomizeActivityAnimation mCustomizeActivityAnimation;
+
     @VisibleForTesting
     final RemoteCallback mNavigationObserver = new RemoteCallback(
             new RemoteCallback.OnResultListener() {
@@ -194,10 +197,12 @@
                 new CrossTaskBackAnimation(mContext, mAnimationBackground);
         mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
                 crossTaskAnimation.mBackAnimationRunner);
-        final CrossActivityAnimation crossActivityAnimation =
+        mDefaultActivityAnimation =
                 new CrossActivityAnimation(mContext, mAnimationBackground);
         mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY,
-                crossActivityAnimation.mBackAnimationRunner);
+                mDefaultActivityAnimation.mBackAnimationRunner);
+        mCustomizeActivityAnimation =
+                new CustomizeActivityAnimation(mContext, mAnimationBackground);
         // TODO (236760237): register dialog close animation when it's completed.
     }
 
@@ -368,7 +373,6 @@
         final boolean shouldDispatchToAnimator = shouldDispatchToAnimator();
         if (shouldDispatchToAnimator) {
             if (mAnimationDefinition.contains(backType)) {
-                mActiveCallback = mAnimationDefinition.get(backType).getCallback();
                 mAnimationDefinition.get(backType).startGesture();
             } else {
                 mActiveCallback = null;
@@ -542,13 +546,12 @@
         }
 
         final int backType = mBackNavigationInfo.getType();
+        final BackAnimationRunner runner = mAnimationDefinition.get(backType);
         // Simply trigger and finish back navigation when no animator defined.
-        if (!shouldDispatchToAnimator() || mActiveCallback == null) {
+        if (!shouldDispatchToAnimator() || runner == null) {
             invokeOrCancelBack();
             return;
         }
-
-        final BackAnimationRunner runner = mAnimationDefinition.get(backType);
         if (runner.isWaitingAnimation()) {
             ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready.");
             return;
@@ -607,6 +610,12 @@
         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);
+        }
         if (mBackNavigationInfo != null) {
             mBackNavigationInfo.onBackNavigationFinished(mTriggerBack);
             mBackNavigationInfo = null;
@@ -614,14 +623,35 @@
         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(int type, RemoteAnimationTarget[] apps,
+            public void onAnimationStart(RemoteAnimationTarget[] apps,
                     RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
                     IBackAnimationFinishedCallback finishedCallback) {
                 mShellExecutor.execute(() -> {
-                    final BackAnimationRunner runner = mAnimationDefinition.get(type);
+                    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));
@@ -634,6 +664,7 @@
                         }
                         return;
                     }
+                    mActiveCallback = runner.getCallback();
                     mBackAnimationFinishedCallback = finishedCallback;
 
                     ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
@@ -645,11 +676,13 @@
                                 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.
-                        final BackMotionEvent backFinish = mTouchTracker.createProgressEvent();
-                        dispatchOnBackProgressed(mActiveCallback, backFinish);
                         startPostCommitAnimation();
                     }
                 });
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 82c523f..22b841a 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
@@ -99,4 +99,8 @@
     boolean isAnimationCancelled() {
         return mAnimationCancelled;
     }
+
+    void resetWaitingAnimation() {
+        mWaitingAnimation = false;
+    }
 }
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
new file mode 100644
index 0000000..ae33b94
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomizeActivityAnimation.java
@@ -0,0 +1,359 @@
+/*
+ * 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 static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.util.FloatProperty;
+import android.view.Choreographer;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Transformation;
+import android.window.BackEvent;
+import android.window.BackMotionEvent;
+import android.window.BackNavigationInfo;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.dynamicanimation.animation.SpringAnimation;
+import com.android.internal.dynamicanimation.animation.SpringForce;
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.policy.TransitionAnimation;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Class that handle customized close activity transition animation.
+ */
+@ShellMainThread
+class CustomizeActivityAnimation {
+    private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+    final BackAnimationRunner mBackAnimationRunner;
+    private final float mCornerRadius;
+    private final SurfaceControl.Transaction mTransaction;
+    private final BackAnimationBackground mBackground;
+    private RemoteAnimationTarget mEnteringTarget;
+    private RemoteAnimationTarget mClosingTarget;
+    private IRemoteAnimationFinishedCallback mFinishCallback;
+    /** Duration of post animation after gesture committed. */
+    private static final int POST_ANIMATION_DURATION = 250;
+
+    private static final int SCALE_FACTOR = 1000;
+    private final SpringAnimation mProgressSpring;
+    private float mLatestProgress = 0.0f;
+
+    private static final float TARGET_COMMIT_PROGRESS = 0.5f;
+
+    private final float[] mTmpFloat9 = new float[9];
+    private final DecelerateInterpolator mDecelerateInterpolator = new DecelerateInterpolator();
+
+    final CustomAnimationLoader mCustomAnimationLoader;
+    private Animation mEnterAnimation;
+    private Animation mCloseAnimation;
+    final Transformation mTransformation = new Transformation();
+
+    private final Choreographer mChoreographer;
+
+    CustomizeActivityAnimation(Context context, BackAnimationBackground background) {
+        this(context, background, new SurfaceControl.Transaction(), null);
+    }
+
+    CustomizeActivityAnimation(Context context, BackAnimationBackground background,
+            SurfaceControl.Transaction transaction, Choreographer choreographer) {
+        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+        mBackground = background;
+        mBackAnimationRunner = new BackAnimationRunner(new Callback(), new Runner());
+        mCustomAnimationLoader = new CustomAnimationLoader(context);
+
+        mProgressSpring = new SpringAnimation(this, ENTER_PROGRESS_PROP);
+        mProgressSpring.setSpring(new SpringForce()
+                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
+                .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY));
+        mTransaction = transaction == null ? new SurfaceControl.Transaction() : transaction;
+        mChoreographer = choreographer != null ? choreographer : Choreographer.getInstance();
+    }
+
+    private float getLatestProgress() {
+        return mLatestProgress * SCALE_FACTOR;
+    }
+    private void setLatestProgress(float value) {
+        mLatestProgress = value / SCALE_FACTOR;
+        applyTransformTransaction(mLatestProgress);
+    }
+
+    private static final FloatProperty<CustomizeActivityAnimation> ENTER_PROGRESS_PROP =
+            new FloatProperty<>("enter") {
+                @Override
+                public void setValue(CustomizeActivityAnimation anim, float value) {
+                    anim.setLatestProgress(value);
+                }
+
+                @Override
+                public Float get(CustomizeActivityAnimation object) {
+                    return object.getLatestProgress();
+                }
+            };
+
+    // The target will lose focus when alpha == 0, so keep a minimum value for it.
+    private static float keepMinimumAlpha(float transAlpha) {
+        return Math.max(transAlpha, 0.005f);
+    }
+
+    private static void initializeAnimation(Animation animation, Rect bounds) {
+        final int width = bounds.width();
+        final int height = bounds.height();
+        animation.initialize(width, height, width, height);
+    }
+
+    private void startBackAnimation() {
+        if (mEnteringTarget == null || mClosingTarget == null
+                || mCloseAnimation == null || mEnterAnimation == null) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+            return;
+        }
+        initializeAnimation(mCloseAnimation, mClosingTarget.localBounds);
+        initializeAnimation(mEnterAnimation, mEnteringTarget.localBounds);
+
+        // Draw background with task background color.
+        if (mEnteringTarget.taskInfo != null && mEnteringTarget.taskInfo.taskDescription != null) {
+            mBackground.ensureBackground(
+                    mEnteringTarget.taskInfo.taskDescription.getBackgroundColor(), mTransaction);
+        }
+    }
+
+    private void applyTransformTransaction(float progress) {
+        if (mClosingTarget == null || mEnteringTarget == null) {
+            return;
+        }
+        applyTransform(mClosingTarget.leash, progress, mCloseAnimation);
+        applyTransform(mEnteringTarget.leash, progress, mEnterAnimation);
+        mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
+        mTransaction.apply();
+    }
+
+    private void applyTransform(SurfaceControl leash, float progress, Animation animation) {
+        mTransformation.clear();
+        animation.getTransformationAt(progress, mTransformation);
+        mTransaction.setMatrix(leash, mTransformation.getMatrix(), mTmpFloat9);
+        mTransaction.setAlpha(leash, keepMinimumAlpha(mTransformation.getAlpha()));
+        mTransaction.setCornerRadius(leash, mCornerRadius);
+    }
+
+    void finishAnimation() {
+        if (mCloseAnimation != null) {
+            mCloseAnimation.reset();
+            mCloseAnimation = null;
+        }
+        if (mEnterAnimation != null) {
+            mEnterAnimation.reset();
+            mEnterAnimation = null;
+        }
+        if (mEnteringTarget != null) {
+            mEnteringTarget.leash.release();
+            mEnteringTarget = null;
+        }
+        if (mClosingTarget != null) {
+            mClosingTarget.leash.release();
+            mClosingTarget = null;
+        }
+        if (mBackground != null) {
+            mBackground.removeBackground(mTransaction);
+        }
+        mTransaction.setFrameTimelineVsync(mChoreographer.getVsyncId());
+        mTransaction.apply();
+        mTransformation.clear();
+        mLatestProgress = 0;
+        if (mFinishCallback != null) {
+            try {
+                mFinishCallback.onAnimationFinished();
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            mFinishCallback = null;
+        }
+        mProgressSpring.animateToFinalPosition(0);
+        mProgressSpring.skipToEnd();
+    }
+
+    void onGestureProgress(@NonNull BackEvent backEvent) {
+        if (mEnteringTarget == null || mClosingTarget == null
+                || mCloseAnimation == null || mEnterAnimation == null) {
+            return;
+        }
+
+        final float progress = backEvent.getProgress();
+
+        float springProgress = (progress > 0.1f
+                ? mapLinear(progress, 0.1f, 1f, TARGET_COMMIT_PROGRESS, 1f)
+                : mapLinear(progress, 0, 1f, 0f, TARGET_COMMIT_PROGRESS)) * SCALE_FACTOR;
+
+        mProgressSpring.animateToFinalPosition(springProgress);
+    }
+
+    static float mapLinear(float x, float a1, float a2, float b1, float b2) {
+        return b1 + (x - a1) * (b2 - b1) / (a2 - a1);
+    }
+
+    void onGestureCommitted() {
+        if (mEnteringTarget == null || mClosingTarget == null
+                || mCloseAnimation == null || mEnterAnimation == null) {
+            finishAnimation();
+            return;
+        }
+        mProgressSpring.cancel();
+
+        // Enter phase 2 of the animation
+        final ValueAnimator valueAnimator = ValueAnimator.ofFloat(mLatestProgress, 1f)
+                .setDuration(POST_ANIMATION_DURATION);
+        valueAnimator.setInterpolator(mDecelerateInterpolator);
+        valueAnimator.addUpdateListener(animation -> {
+            float progress = (float) animation.getAnimatedValue();
+            applyTransformTransaction(progress);
+        });
+
+        valueAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finishAnimation();
+            }
+        });
+        valueAnimator.start();
+    }
+
+    /**
+     * Load customize animation before animation start.
+     */
+    boolean prepareNextAnimation(BackNavigationInfo.CustomAnimationInfo animationInfo) {
+        mCloseAnimation = mCustomAnimationLoader.load(
+                animationInfo, false /* enterAnimation */);
+        if (mCloseAnimation != null) {
+            mEnterAnimation = mCustomAnimationLoader.load(
+                    animationInfo, true /* enterAnimation */);
+            return true;
+        }
+        return false;
+    }
+
+    private final class Callback extends IOnBackInvokedCallback.Default {
+        @Override
+        public void onBackStarted(BackMotionEvent backEvent) {
+            mProgressAnimator.onBackStarted(backEvent,
+                    CustomizeActivityAnimation.this::onGestureProgress);
+        }
+
+        @Override
+        public void onBackProgressed(@NonNull BackMotionEvent backEvent) {
+            mProgressAnimator.onBackProgressed(backEvent);
+        }
+
+        @Override
+        public void onBackCancelled() {
+            mProgressAnimator.onBackCancelled(CustomizeActivityAnimation.this::finishAnimation);
+        }
+
+        @Override
+        public void onBackInvoked() {
+            mProgressAnimator.reset();
+            onGestureCommitted();
+        }
+    }
+
+    private final class Runner extends IRemoteAnimationRunner.Default {
+        @Override
+        public void onAnimationStart(
+                int transit,
+                RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers,
+                RemoteAnimationTarget[] nonApps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to customize animation.");
+            for (RemoteAnimationTarget a : apps) {
+                if (a.mode == MODE_CLOSING) {
+                    mClosingTarget = a;
+                }
+                if (a.mode == MODE_OPENING) {
+                    mEnteringTarget = a;
+                }
+            }
+            if (mCloseAnimation == null || mEnterAnimation == null) {
+                ProtoLog.d(WM_SHELL_BACK_PREVIEW,
+                        "No animation loaded, should choose cross-activity animation?");
+            }
+
+            startBackAnimation();
+            mFinishCallback = finishedCallback;
+        }
+
+        @Override
+        public void onAnimationCancelled(boolean isKeyguardOccluded) {
+            finishAnimation();
+        }
+    }
+
+    /**
+     * Helper class to load custom animation.
+     */
+    static class CustomAnimationLoader {
+        private final TransitionAnimation mTransitionAnimation;
+
+        CustomAnimationLoader(Context context) {
+            mTransitionAnimation = new TransitionAnimation(
+                    context, false /* debug */, "CustomizeBackAnimation");
+        }
+
+        Animation load(BackNavigationInfo.CustomAnimationInfo animationInfo,
+                boolean enterAnimation) {
+            final String packageName = animationInfo.getPackageName();
+            if (packageName.isEmpty()) {
+                return null;
+            }
+            final int windowAnimations = animationInfo.getWindowAnimations();
+            if (windowAnimations == 0) {
+                return null;
+            }
+            final int attrs = enterAnimation
+                    ? com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation
+                    : com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
+            Animation a = mTransitionAnimation.loadAnimationAttr(packageName, windowAnimations,
+                    attrs, false /* translucent */);
+            // Only allow to load default animation for opening target.
+            if (a == null && enterAnimation) {
+                a = mTransitionAnimation.loadDefaultAnimationAttr(attrs, false /* translucent */);
+            }
+            if (a != null) {
+                ProtoLog.d(WM_SHELL_BACK_PREVIEW, "custom animation loaded %s", a);
+            } else {
+                ProtoLog.e(WM_SHELL_BACK_PREVIEW, "No custom animation loaded");
+            }
+            return a;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
index 22587f4..8b4ac1a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DevicePostureController.java
@@ -39,6 +39,9 @@
  *
  * Note that most of the implementation here inherits from
  * {@link com.android.systemui.statusbar.policy.DevicePostureController}.
+ *
+ * Use the {@link TabletopModeController} if you are interested in tabletop mode change only,
+ * which is more common.
  */
 public class DevicePostureController {
     @IntDef(prefix = {"DEVICE_POSTURE_"}, value = {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
new file mode 100644
index 0000000..bf226283
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TabletopModeController.java
@@ -0,0 +1,208 @@
+/*
+ * 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;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FOLDABLE;
+
+import android.annotation.NonNull;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.util.ArraySet;
+import android.view.Surface;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+import com.android.wm.shell.sysui.ShellInit;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Wrapper class to track the tabletop (aka. flex) mode change on Fold-ables.
+ * See also <a
+ * href="https://developer.android.com/guide/topics/large-screens/learn-about-foldables
+ * #foldable_postures">Foldable states and postures</a> for reference.
+ *
+ * Use the {@link DevicePostureController} for more detailed posture changes.
+ */
+public class TabletopModeController implements
+        DevicePostureController.OnDevicePostureChangedListener,
+        DisplayController.OnDisplaysChangedListener {
+    private static final long TABLETOP_MODE_DELAY_MILLIS = 1_000;
+
+    private final Context mContext;
+
+    private final DevicePostureController mDevicePostureController;
+
+    private final DisplayController mDisplayController;
+
+    private final ShellExecutor mMainExecutor;
+
+    private final Set<Integer> mTabletopModeRotations = new ArraySet<>();
+
+    private final List<OnTabletopModeChangedListener> mListeners = new ArrayList<>();
+
+    @VisibleForTesting
+    final Runnable mOnEnterTabletopModeCallback = () -> {
+        if (isInTabletopMode()) {
+            // We are still in tabletop mode, go ahead.
+            mayBroadcastOnTabletopModeChange(true /* isInTabletopMode */);
+        }
+    };
+
+    @DevicePostureController.DevicePostureInt
+    private int mDevicePosture = DEVICE_POSTURE_UNKNOWN;
+
+    @Surface.Rotation
+    private int mDisplayRotation = WindowConfiguration.ROTATION_UNDEFINED;
+
+    /**
+     * Track the last callback value for {@link OnTabletopModeChangedListener}.
+     * This is to avoid duplicated {@code false} callback to {@link #mListeners}.
+     */
+    private Boolean mLastIsInTabletopModeForCallback;
+
+    public TabletopModeController(Context context,
+            ShellInit shellInit,
+            DevicePostureController postureController,
+            DisplayController displayController,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        mContext = context;
+        mDevicePostureController = postureController;
+        mDisplayController = displayController;
+        mMainExecutor = mainExecutor;
+        shellInit.addInitCallback(this::onInit, this);
+    }
+
+    @VisibleForTesting
+    void onInit() {
+        mDevicePostureController.registerOnDevicePostureChangedListener(this);
+        mDisplayController.addDisplayWindowListener(this);
+        // Aligns with what's in {@link com.android.server.wm.DisplayRotation}.
+        final int[] deviceTabletopRotations = mContext.getResources().getIntArray(
+                com.android.internal.R.array.config_deviceTabletopRotations);
+        if (deviceTabletopRotations == null || deviceTabletopRotations.length == 0) {
+            ProtoLog.e(WM_SHELL_FOLDABLE,
+                    "No valid config_deviceTabletopRotations, can not tell"
+                            + " tabletop mode in WMShell");
+            return;
+        }
+        for (int angle : deviceTabletopRotations) {
+            switch (angle) {
+                case 0:
+                    mTabletopModeRotations.add(Surface.ROTATION_0);
+                    break;
+                case 90:
+                    mTabletopModeRotations.add(Surface.ROTATION_90);
+                    break;
+                case 180:
+                    mTabletopModeRotations.add(Surface.ROTATION_180);
+                    break;
+                case 270:
+                    mTabletopModeRotations.add(Surface.ROTATION_270);
+                    break;
+                default:
+                    ProtoLog.e(WM_SHELL_FOLDABLE,
+                            "Invalid surface rotation angle in "
+                                    + "config_deviceTabletopRotations: %d",
+                            angle);
+                    break;
+            }
+        }
+    }
+
+    /** Register {@link OnTabletopModeChangedListener} to listen for tabletop mode change. */
+    public void registerOnTabletopModeChangedListener(
+            @NonNull OnTabletopModeChangedListener listener) {
+        if (listener == null || mListeners.contains(listener)) return;
+        mListeners.add(listener);
+        listener.onTabletopModeChanged(isInTabletopMode());
+    }
+
+    /** Unregister {@link OnTabletopModeChangedListener} for tabletop mode change. */
+    public void unregisterOnTabletopModeChangedListener(
+            @NonNull OnTabletopModeChangedListener listener) {
+        mListeners.remove(listener);
+    }
+
+    @Override
+    public void onDevicePostureChanged(@DevicePostureController.DevicePostureInt int posture) {
+        if (mDevicePosture != posture) {
+            onDevicePostureOrDisplayRotationChanged(posture, mDisplayRotation);
+        }
+    }
+
+    @Override
+    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+        final int newDisplayRotation = newConfig.windowConfiguration.getDisplayRotation();
+        if (displayId == DEFAULT_DISPLAY && newDisplayRotation != mDisplayRotation) {
+            onDevicePostureOrDisplayRotationChanged(mDevicePosture, newDisplayRotation);
+        }
+    }
+
+    private void onDevicePostureOrDisplayRotationChanged(
+            @DevicePostureController.DevicePostureInt int newPosture,
+            @Surface.Rotation int newDisplayRotation) {
+        final boolean wasInTabletopMode = isInTabletopMode();
+        mDevicePosture = newPosture;
+        mDisplayRotation = newDisplayRotation;
+        final boolean couldBeInTabletopMode = isInTabletopMode();
+        mMainExecutor.removeCallbacks(mOnEnterTabletopModeCallback);
+        if (!wasInTabletopMode && couldBeInTabletopMode) {
+            // May enter tabletop mode, but we need to wait for additional time since this
+            // could be an intermediate state.
+            mMainExecutor.executeDelayed(mOnEnterTabletopModeCallback, TABLETOP_MODE_DELAY_MILLIS);
+        } else {
+            // Cancel entering tabletop mode if any condition's changed.
+            mayBroadcastOnTabletopModeChange(false /* isInTabletopMode */);
+        }
+    }
+
+    private boolean isHalfOpened(@DevicePostureController.DevicePostureInt int posture) {
+        return posture == DEVICE_POSTURE_HALF_OPENED;
+    }
+
+    private boolean isInTabletopMode() {
+        return isHalfOpened(mDevicePosture) && mTabletopModeRotations.contains(mDisplayRotation);
+    }
+
+    private void mayBroadcastOnTabletopModeChange(boolean isInTabletopMode) {
+        if (mLastIsInTabletopModeForCallback == null
+                || mLastIsInTabletopModeForCallback != isInTabletopMode) {
+            mListeners.forEach(l -> l.onTabletopModeChanged(isInTabletopMode));
+            mLastIsInTabletopModeForCallback = isInTabletopMode;
+        }
+    }
+
+    /**
+     * Listener interface for tabletop mode change.
+     */
+    public interface OnTabletopModeChangedListener {
+        /**
+         * Callback when tabletop mode changes. Expect duplicated callbacks with {@code false}.
+         * @param isInTabletopMode {@code true} if enters tabletop mode, {@code false} otherwise.
+         */
+        void onTabletopModeChanged(boolean isInTabletopMode);
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
index 8ba785a..931cf0c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/TvWindowMenuActionButton.java
@@ -70,6 +70,8 @@
             setTextAndDescription(textResId);
         }
         typedArray.recycle();
+
+        setIsCustomCloseAction(false);
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index e44257e..4970fa0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -248,11 +248,11 @@
 
     /** Stops showing resizing hint. */
     public void onResized(SurfaceControl.Transaction t, Runnable animFinishedCallback) {
-        if (mScreenshot != null) {
-            if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
-                mScreenshotAnimator.cancel();
-            }
+        if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+            mScreenshotAnimator.cancel();
+        }
 
+        if (mScreenshot != null) {
             t.setPosition(mScreenshot, mOffsetX, mOffsetY);
 
             final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
@@ -322,6 +322,10 @@
     /** Screenshot host leash and attach on it if meet some conditions */
     public void screenshotIfNeeded(SurfaceControl.Transaction t) {
         if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+            if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+                mScreenshotAnimator.cancel();
+            }
+
             mTempRect.set(mOldBounds);
             mTempRect.offsetTo(0, 0);
             mScreenshot = ScreenshotUtils.takeScreenshot(t, mHostLeash, mTempRect,
@@ -334,6 +338,10 @@
         if (screenshot == null || !screenshot.isValid()) return;
 
         if (!mShown && mIsResizing && !mOldBounds.equals(mResizingBounds)) {
+            if (mScreenshotAnimator != null && mScreenshotAnimator.isRunning()) {
+                mScreenshotAnimator.cancel();
+            }
+
             mScreenshot = screenshot;
             t.reparent(screenshot, mHostLeash);
             t.setLayer(screenshot, Integer.MAX_VALUE - 1);
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 8380225..3d5230d 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
@@ -52,6 +52,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
+import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ShellAnimationThread;
@@ -172,6 +173,18 @@
 
     @WMSingleton
     @Provides
+    static TabletopModeController provideTabletopModeController(
+            Context context,
+            ShellInit shellInit,
+            DevicePostureController postureController,
+            DisplayController displayController,
+            @ShellMainThread ShellExecutor mainExecutor) {
+        return new TabletopModeController(
+                context, shellInit, postureController, displayController, mainExecutor);
+    }
+
+    @WMSingleton
+    @Provides
     static DragAndDropController provideDragAndDropController(Context context,
             ShellInit shellInit,
             ShellController shellController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
index 449a2bf..49d40d3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipCustomAction.java
@@ -60,6 +60,7 @@
         }
         button.setImageIconAsync(mRemoteAction.getIcon(), mainHandler);
         button.setEnabled(isCloseAction() || mRemoteAction.isEnabled());
+        button.setIsCustomCloseAction(isCloseAction());
     }
 
     PendingIntent getPendingIntent() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
index 93b6a90..4b82e4b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java
@@ -61,6 +61,7 @@
         button.setTextAndDescription(mTitleResource);
         button.setImageResource(mIconResource);
         button.setEnabled(true);
+        button.setIsCustomCloseAction(false);
     }
 
     PendingIntent getPendingIntent() {
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 75f9a4c..c9b3a1a 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
@@ -50,6 +50,8 @@
             Consts.TAG_WM_SHELL),
     WM_SHELL_FLOATING_APPS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
             Consts.TAG_WM_SHELL),
+    WM_SHELL_FOLDABLE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+            Consts.TAG_WM_SHELL),
     TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
 
     private final boolean mEnabled;
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 146abea..18a3849 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
@@ -1801,7 +1801,9 @@
             // Split entering background.
             wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
                     true /* setReparentLeafTaskIfRelaunch */);
-            wct.setForceTranslucent(mRootTaskInfo.token, true);
+            if (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping) {
+                wct.setForceTranslucent(mRootTaskInfo.token, true);
+            }
         } else {
             wct.setReparentLeafTaskIfRelaunch(mRootTaskInfo.token,
                     false /* setReparentLeafTaskIfRelaunch */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java
new file mode 100644
index 0000000..1ddd8f9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/AbsSplashWindowCreator.java
@@ -0,0 +1,67 @@
+/*
+ * 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.startingsurface;
+
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+// abstract class to create splash screen window(or windowless window)
+abstract class AbsSplashWindowCreator {
+    protected static final String TAG = StartingWindowController.TAG;
+    protected final SplashscreenContentDrawer mSplashscreenContentDrawer;
+    protected final Context mContext;
+    protected final DisplayManager mDisplayManager;
+    protected final ShellExecutor mSplashScreenExecutor;
+    protected final StartingSurfaceDrawer.StartingWindowRecordManager mStartingWindowRecordManager;
+
+    private StartingSurface.SysuiProxy mSysuiProxy;
+
+    AbsSplashWindowCreator(SplashscreenContentDrawer contentDrawer, Context context,
+            ShellExecutor splashScreenExecutor, DisplayManager displayManager,
+            StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
+        mSplashscreenContentDrawer = contentDrawer;
+        mContext = context;
+        mSplashScreenExecutor = splashScreenExecutor;
+        mDisplayManager = displayManager;
+        mStartingWindowRecordManager = startingWindowRecordManager;
+    }
+
+    int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
+        return splashScreenThemeResId != 0
+                ? splashScreenThemeResId
+                : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
+                        : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
+    }
+
+    protected Display getDisplay(int displayId) {
+        return mDisplayManager.getDisplay(displayId);
+    }
+
+    void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
+        mSysuiProxy = sysuiProxy;
+    }
+
+    protected void requestTopUi(boolean requestTopUi) {
+        if (mSysuiProxy != null) {
+            mSysuiProxy.requestTopUi(requestTopUi, TAG);
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
new file mode 100644
index 0000000..20c4d5a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SnapshotWindowCreator.java
@@ -0,0 +1,70 @@
+/*
+ * 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.startingsurface;
+
+import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+class SnapshotWindowCreator {
+    private final ShellExecutor mMainExecutor;
+    private final StartingSurfaceDrawer.StartingWindowRecordManager
+            mStartingWindowRecordManager;
+
+    SnapshotWindowCreator(ShellExecutor mainExecutor,
+            StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
+        mMainExecutor = mainExecutor;
+        mStartingWindowRecordManager = startingWindowRecordManager;
+    }
+
+    void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
+        final int taskId = startingWindowInfo.taskInfo.taskId;
+        // Remove any existing starting window for this task before adding.
+        mStartingWindowRecordManager.removeWindow(taskId, true);
+        final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo,
+                startingWindowInfo.appToken, snapshot, mMainExecutor,
+                () -> mStartingWindowRecordManager.removeWindow(taskId, true));
+        if (surface != null) {
+            final SnapshotWindowRecord tView = new SnapshotWindowRecord(surface,
+                    startingWindowInfo.taskInfo.topActivityType, mMainExecutor);
+            mStartingWindowRecordManager.addRecord(taskId, tView);
+        }
+    }
+
+    private static class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord {
+        private final TaskSnapshotWindow mTaskSnapshotWindow;
+
+        SnapshotWindowRecord(TaskSnapshotWindow taskSnapshotWindow,
+                int activityType, ShellExecutor removeExecutor) {
+            super(activityType, removeExecutor);
+            mTaskSnapshotWindow = taskSnapshotWindow;
+            mBGColor = mTaskSnapshotWindow.getBackgroundColor();
+        }
+
+        @Override
+        protected void removeImmediately() {
+            super.removeImmediately();
+            mTaskSnapshotWindow.removeImmediately();
+        }
+
+        @Override
+        protected boolean hasImeSurface() {
+            return mTaskSnapshotWindow.hasImeSurface();
+        }
+    }
+}
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 ebb957b..dc91a11 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
@@ -24,9 +24,6 @@
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
 
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION;
-
 import android.annotation.ColorInt;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -96,6 +93,25 @@
 public class SplashscreenContentDrawer {
     private static final String TAG = StartingWindowController.TAG;
 
+    /**
+     * The minimum duration during which the splash screen is shown when the splash screen icon is
+     * animated.
+     */
+    static final long MINIMAL_ANIMATION_DURATION = 400L;
+
+    /**
+     * Allow the icon style splash screen to be displayed for longer to give time for the animation
+     * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly
+     * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration.
+     */
+    static final long TIME_WINDOW_DURATION = 100L;
+
+    /**
+     * The maximum duration during which the splash screen will be shown if the application is ready
+     * to show before the icon animation finishes.
+     */
+    static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
+
     // The acceptable area ratio of foreground_icon_area/background_icon_area, if there is an
     // icon which it's non-transparent foreground area is similar to it's background area, then
     // do not enlarge the foreground drawable.
@@ -368,10 +384,10 @@
 
     private static int estimateWindowBGColor(Drawable themeBGDrawable) {
         final DrawableColorTester themeBGTester = new DrawableColorTester(
-                themeBGDrawable, DrawableColorTester.TRANSPARENT_FILTER /* filterType */);
-        if (themeBGTester.passFilterRatio() == 0) {
-            // the window background is transparent, unable to draw
-            Slog.w(TAG, "Window background is transparent, fill background with black color");
+                themeBGDrawable, DrawableColorTester.TRANSLUCENT_FILTER /* filterType */);
+        if (themeBGTester.passFilterRatio() != 1) {
+            // the window background is translucent, unable to draw
+            Slog.w(TAG, "Window background is translucent, fill background with black color");
             return getSystemBGColor();
         } else {
             return themeBGTester.getDominateColor();
@@ -854,7 +870,7 @@
             @Override
             public float passFilterRatio() {
                 final int alpha = mColorDrawable.getAlpha();
-                return (float) (alpha / 255);
+                return alpha / 255.0f;
             }
 
             @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
new file mode 100644
index 0000000..8a4d4c2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenWindowCreator.java
@@ -0,0 +1,508 @@
+/*
+ * 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.startingsurface;
+
+import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
+
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.ActivityThread;
+import android.app.TaskInfo;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.os.IBinder;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Choreographer;
+import android.view.Display;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowInsetsController;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.widget.FrameLayout;
+import android.window.SplashScreenView;
+import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
+
+import com.android.internal.R;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ContrastColorUtil;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.function.Supplier;
+
+/**
+ * A class which able to draw splash screen as the starting window for a task.
+ *
+ * In order to speed up, there will use two threads to creating a splash screen in parallel.
+ * Right now we are still using PhoneWindow to create splash screen window, so the view is added to
+ * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call
+ * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view
+ * can synchronize on each frame.
+ *
+ * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing
+ * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background
+ * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after
+ * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very
+ * quickly.
+ *
+ * So basically we are using the spare time to prepare the SplashScreenView while splash screen
+ * thread is waiting for
+ * 1. WindowManager#addView(binder call to WM),
+ * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device),
+ * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will
+ * always happen before #draw).
+ * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on
+ * splash-screen background tread can make they execute in parallel, which ensure it is faster then
+ * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame.
+ *
+ * Here is the sequence to compare the difference between using single and two thread.
+ *
+ * Single thread:
+ * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout
+ * -> draw -> AdaptiveIconDrawable#draw
+ *
+ * Two threads:
+ * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw)
+ * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
+ * directly).
+ */
+class SplashscreenWindowCreator extends AbsSplashWindowCreator {
+    private static final int LIGHT_BARS_MASK =
+            WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+                    | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+
+    private final WindowManagerGlobal mWindowManagerGlobal;
+    private Choreographer mChoreographer;
+
+    /**
+     * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is
+     * rendered and that have not yet been removed by their client.
+     */
+    private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
+            new SparseArray<>(1);
+
+    SplashscreenWindowCreator(SplashscreenContentDrawer contentDrawer, Context context,
+            ShellExecutor splashScreenExecutor, DisplayManager displayManager,
+            StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager) {
+        super(contentDrawer, context, splashScreenExecutor, displayManager,
+                startingWindowRecordManager);
+        mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
+        mWindowManagerGlobal = WindowManagerGlobal.getInstance();
+    }
+
+    void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
+            @StartingWindowInfo.StartingWindowType int suggestType) {
+        final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+        final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+                ? windowInfo.targetActivityInfo
+                : taskInfo.topActivityInfo;
+        if (activityInfo == null || activityInfo.packageName == null) {
+            return;
+        }
+        // replace with the default theme if the application didn't set
+        final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
+        final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
+                suggestType, mDisplayManager);
+        if (context == null) {
+            return;
+        }
+        final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
+                context, windowInfo, suggestType, activityInfo.packageName,
+                suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+                        ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, windowInfo.appToken);
+
+        final int displayId = taskInfo.displayId;
+        final int taskId = taskInfo.taskId;
+        final Display display = getDisplay(displayId);
+
+        // TODO(b/173975965) tracking performance
+        // Prepare the splash screen content view on splash screen worker thread in parallel, so the
+        // content view won't be blocked by binder call like addWindow and relayout.
+        // 1. Trigger splash screen worker thread to create SplashScreenView before/while
+        // Session#addWindow.
+        // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
+        // traversal, which will call Session#relayout on splash screen thread.
+        // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
+        // the same time the splash screen thread should be executing Session#relayout. Blocking the
+        // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
+
+        // Record whether create splash screen view success, notify to current thread after
+        // create splash screen view finished.
+        final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
+        final FrameLayout rootLayout = new FrameLayout(
+                mSplashscreenContentDrawer.createViewContextWrapper(context));
+        rootLayout.setPadding(0, 0, 0, 0);
+        rootLayout.setFitsSystemWindows(false);
+        final Runnable setViewSynchronized = () -> {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
+            // waiting for setContentView before relayoutWindow
+            SplashScreenView contentView = viewSupplier.get();
+            final StartingSurfaceDrawer.StartingWindowRecord sRecord =
+                    mStartingWindowRecordManager.getRecord(taskId);
+            final SplashWindowRecord record = sRecord instanceof SplashWindowRecord
+                    ? (SplashWindowRecord) sRecord : null;
+            // If record == null, either the starting window added fail or removed already.
+            // Do not add this view if the token is mismatch.
+            if (record != null && windowInfo.appToken == record.mAppToken) {
+                // if view == null then creation of content view was failed.
+                if (contentView != null) {
+                    try {
+                        rootLayout.addView(contentView);
+                    } catch (RuntimeException e) {
+                        Slog.w(TAG, "failed set content view to starting window "
+                                + "at taskId: " + taskId, e);
+                        contentView = null;
+                    }
+                }
+                record.setSplashScreenView(contentView);
+            }
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+        };
+        requestTopUi(true);
+        mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
+                viewSupplier::setView, viewSupplier::setUiThreadInitTask);
+        try {
+            if (addWindow(taskId, windowInfo.appToken, rootLayout, display, params, suggestType)) {
+                // We use the splash screen worker thread to create SplashScreenView while adding
+                // the window, as otherwise Choreographer#doFrame might be delayed on this thread.
+                // And since Choreographer#doFrame won't happen immediately after adding the window,
+                // if the view is not added to the PhoneWindow on the first #doFrame, the view will
+                // not be rendered on the first frame. So here we need to synchronize the view on
+                // the window before first round relayoutWindow, which will happen after insets
+                // animation.
+                mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
+                final SplashWindowRecord record =
+                        (SplashWindowRecord) mStartingWindowRecordManager.getRecord(taskId);
+                if (record != null) {
+                    record.parseAppSystemBarColor(context);
+                    // Block until we get the background color.
+                    final SplashScreenView contentView = viewSupplier.get();
+                    if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+                        contentView.addOnAttachStateChangeListener(
+                                new View.OnAttachStateChangeListener() {
+                                    @Override
+                                    public void onViewAttachedToWindow(View v) {
+                                        final int lightBarAppearance =
+                                                ContrastColorUtil.isColorLight(
+                                                        contentView.getInitBackgroundColor())
+                                                        ? LIGHT_BARS_MASK : 0;
+                                        contentView.getWindowInsetsController()
+                                                .setSystemBarsAppearance(
+                                                lightBarAppearance, LIGHT_BARS_MASK);
+                                    }
+
+                                    @Override
+                                    public void onViewDetachedFromWindow(View v) {
+                                    }
+                                });
+                    }
+                }
+            } else {
+                // release the icon view host
+                final SplashScreenView contentView = viewSupplier.get();
+                if (contentView.getSurfaceHost() != null) {
+                    SplashScreenView.releaseIconHost(contentView.getSurfaceHost());
+                }
+            }
+        } catch (RuntimeException e) {
+            // don't crash if something else bad happens, for example a
+            // failure loading resources because we are loading from an app
+            // on external storage that has been unmounted.
+            Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e);
+        }
+    }
+
+    int estimateTaskBackgroundColor(TaskInfo taskInfo) {
+        if (taskInfo.topActivityInfo == null) {
+            return Color.TRANSPARENT;
+        }
+        final ActivityInfo activityInfo = taskInfo.topActivityInfo;
+        final String packageName = activityInfo.packageName;
+        final int userId = taskInfo.userId;
+        final Context windowContext;
+        try {
+            windowContext = mContext.createPackageContextAsUser(
+                    packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId));
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.w(TAG, "Failed creating package context with package name "
+                    + packageName + " for user " + taskInfo.userId, e);
+            return Color.TRANSPARENT;
+        }
+        try {
+            final IPackageManager packageManager = ActivityThread.getPackageManager();
+            final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName,
+                    userId);
+            final int splashScreenThemeId = splashScreenThemeName != null
+                    ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null)
+                    : 0;
+
+            final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo);
+
+            if (theme != windowContext.getThemeResId()) {
+                windowContext.setTheme(theme);
+            }
+            return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext);
+        } catch (RuntimeException | RemoteException e) {
+            Slog.w(TAG, "failed get starting window background color at taskId: "
+                    + taskInfo.taskId, e);
+        }
+        return Color.TRANSPARENT;
+    }
+
+    /**
+     * Called when the Task wants to copy the splash screen.
+     */
+    public void copySplashScreenView(int taskId) {
+        final StartingSurfaceDrawer.StartingWindowRecord record =
+                mStartingWindowRecordManager.getRecord(taskId);
+        final SplashWindowRecord preView = record instanceof SplashWindowRecord
+                ? (SplashWindowRecord) record : null;
+        SplashScreenView.SplashScreenViewParcelable parcelable;
+        SplashScreenView splashScreenView = preView != null ? preView.mSplashView : null;
+        if (splashScreenView != null && splashScreenView.isCopyable()) {
+            parcelable = new SplashScreenView.SplashScreenViewParcelable(splashScreenView);
+            parcelable.setClientCallback(
+                    new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
+                            () -> onAppSplashScreenViewRemoved(taskId, false))));
+            splashScreenView.onCopied();
+            mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
+        } else {
+            parcelable = null;
+        }
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+                "Copying splash screen window view for task: %d with parcelable %b",
+                taskId, parcelable != null);
+        ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+    }
+
+    /**
+     * Called when the {@link SplashScreenView} is removed from the client Activity view's hierarchy
+     * or when the Activity is clean up.
+     *
+     * @param taskId The Task id on which the splash screen was attached
+     */
+    public void onAppSplashScreenViewRemoved(int taskId) {
+        onAppSplashScreenViewRemoved(taskId, true /* fromServer */);
+    }
+
+    /**
+     * @param fromServer If true, this means the removal was notified by the server. This is only
+     *                   used for debugging purposes.
+     * @see #onAppSplashScreenViewRemoved(int)
+     */
+    private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) {
+        SurfaceControlViewHost viewHost =
+                mAnimatedSplashScreenSurfaceHosts.get(taskId);
+        if (viewHost == null) {
+            return;
+        }
+        mAnimatedSplashScreenSurfaceHosts.remove(taskId);
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+                "%s the splash screen. Releasing SurfaceControlViewHost for task: %d",
+                fromServer ? "Server cleaned up" : "App removed", taskId);
+        SplashScreenView.releaseIconHost(viewHost);
+    }
+
+    protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
+            WindowManager.LayoutParams params,
+            @StartingWindowInfo.StartingWindowType int suggestType) {
+        boolean shouldSaveView = true;
+        final Context context = view.getContext();
+        try {
+            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
+            mWindowManagerGlobal.addView(view, params, display,
+                    null /* parentWindow */, context.getUserId());
+        } catch (WindowManager.BadTokenException e) {
+            // ignore
+            Slog.w(TAG, appToken + " already running, starting window not displayed. "
+                    + e.getMessage());
+            shouldSaveView = false;
+        } finally {
+            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+            if (view.getParent() == null) {
+                Slog.w(TAG, "view not successfully added to wm, removing view");
+                mWindowManagerGlobal.removeView(view, true /* immediate */);
+                shouldSaveView = false;
+            }
+        }
+        if (shouldSaveView) {
+            mStartingWindowRecordManager.removeWindow(taskId, true);
+            saveSplashScreenRecord(appToken, taskId, view, suggestType);
+        }
+        return shouldSaveView;
+    }
+
+    private void saveSplashScreenRecord(IBinder appToken, int taskId, View view,
+            @StartingWindowInfo.StartingWindowType int suggestType) {
+        final SplashWindowRecord tView =
+                new SplashWindowRecord(appToken, view, suggestType);
+        mStartingWindowRecordManager.addRecord(taskId, tView);
+    }
+
+    private void removeWindowInner(View decorView, boolean hideView) {
+        requestTopUi(false);
+        if (hideView) {
+            decorView.setVisibility(View.GONE);
+        }
+        mWindowManagerGlobal.removeView(decorView, false /* immediate */);
+    }
+
+    private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
+        private SplashScreenView mView;
+        private boolean mIsViewSet;
+        private Runnable mUiThreadInitTask;
+        void setView(SplashScreenView view) {
+            synchronized (this) {
+                mView = view;
+                mIsViewSet = true;
+                notify();
+            }
+        }
+
+        void setUiThreadInitTask(Runnable initTask) {
+            synchronized (this) {
+                mUiThreadInitTask = initTask;
+            }
+        }
+
+        @Override
+        @Nullable
+        public SplashScreenView get() {
+            synchronized (this) {
+                while (!mIsViewSet) {
+                    try {
+                        wait();
+                    } catch (InterruptedException ignored) {
+                    }
+                }
+                if (mUiThreadInitTask != null) {
+                    mUiThreadInitTask.run();
+                    mUiThreadInitTask = null;
+                }
+                return mView;
+            }
+        }
+    }
+
+    private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord {
+        private final IBinder mAppToken;
+        private final View mRootView;
+        @StartingWindowInfo.StartingWindowType private final int mSuggestType;
+        private final long mCreateTime;
+
+        private boolean mSetSplashScreen;
+        private SplashScreenView mSplashView;
+        private int mSystemBarAppearance;
+        private boolean mDrawsSystemBarBackgrounds;
+
+        SplashWindowRecord(IBinder appToken, View decorView,
+                @StartingWindowInfo.StartingWindowType int suggestType) {
+            mAppToken = appToken;
+            mRootView = decorView;
+            mSuggestType = suggestType;
+            mCreateTime = SystemClock.uptimeMillis();
+        }
+
+        void setSplashScreenView(@Nullable SplashScreenView splashScreenView) {
+            if (mSetSplashScreen) {
+                return;
+            }
+            mSplashView = splashScreenView;
+            mBGColor = mSplashView != null ? mSplashView.getInitBackgroundColor()
+                    : Color.TRANSPARENT;
+            mSetSplashScreen = true;
+        }
+
+        void parseAppSystemBarColor(Context context) {
+            final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
+            mDrawsSystemBarBackgrounds = a.getBoolean(
+                    R.styleable.Window_windowDrawsSystemBarBackgrounds, false);
+            if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
+                mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+            }
+            if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
+                mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+            }
+            a.recycle();
+        }
+
+        // Reset the system bar color which set by splash screen, make it align to the app.
+        void clearSystemBarColor() {
+            if (mRootView == null || !mRootView.isAttachedToWindow()) {
+                return;
+            }
+            if (mRootView.getLayoutParams() instanceof WindowManager.LayoutParams) {
+                final WindowManager.LayoutParams lp =
+                        (WindowManager.LayoutParams) mRootView.getLayoutParams();
+                if (mDrawsSystemBarBackgrounds) {
+                    lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+                } else {
+                    lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+                }
+                mRootView.setLayoutParams(lp);
+            }
+            mRootView.getWindowInsetsController().setSystemBarsAppearance(
+                    mSystemBarAppearance, LIGHT_BARS_MASK);
+        }
+
+        @Override
+        public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+            if (mRootView == null) {
+                return;
+            }
+            if (mSplashView == null) {
+                // shouldn't happen, the app window may be drawn earlier than starting window?
+                Slog.e(TAG, "Found empty splash screen, remove!");
+                removeWindowInner(mRootView, false);
+                return;
+            }
+            clearSystemBarColor();
+            if (immediately
+                    || mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
+                removeWindowInner(mRootView, false);
+            } else {
+                if (info.playRevealAnimation) {
+                    mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
+                            info.windowAnimationLeash, info.mainFrame,
+                            () -> removeWindowInner(mRootView, true),
+                            mCreateTime, info.roundedCornerRadius);
+                } else {
+                    // the SplashScreenView has been copied to client, hide the view to skip
+                    // default exit animation
+                    removeWindowInner(mRootView, true);
+                }
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 4f07bfe..ff06db3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -16,169 +16,80 @@
 
 package com.android.wm.shell.startingsurface;
 
-import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.Choreographer.CALLBACK_INSETS_ANIMATION;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN;
-import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
 
-import android.annotation.Nullable;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityTaskManager;
-import android.app.ActivityThread;
+import android.annotation.CallSuper;
 import android.app.TaskInfo;
+import android.app.WindowConfiguration;
 import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
-import android.content.res.TypedArray;
+import android.content.res.Configuration;
 import android.graphics.Color;
-import android.graphics.PixelFormat;
 import android.hardware.display.DisplayManager;
-import android.os.IBinder;
-import android.os.RemoteCallback;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.os.Trace;
-import android.os.UserHandle;
-import android.util.Slog;
 import android.util.SparseArray;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.WindowInsetsController;
+import android.view.IWindow;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
-import android.widget.FrameLayout;
+import android.view.WindowlessWindowManager;
 import android.window.SplashScreenView;
-import android.window.SplashScreenView.SplashScreenViewParcelable;
 import android.window.StartingWindowInfo;
 import android.window.StartingWindowInfo.StartingWindowType;
 import android.window.StartingWindowRemovalInfo;
 import android.window.TaskSnapshot;
 
-import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.ContrastColorUtil;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
-import java.util.function.Supplier;
-
 /**
  * A class which able to draw splash screen or snapshot as the starting window for a task.
- *
- * In order to speed up, there will use two threads to creating a splash screen in parallel.
- * Right now we are still using PhoneWindow to create splash screen window, so the view is added to
- * the ViewRootImpl, and those view won't be draw immediately because the ViewRootImpl will call
- * scheduleTraversal to register a callback from Choreographer, so the drawing result of the view
- * can synchronize on each frame.
- *
- * The bad thing is that we cannot decide when would Choreographer#doFrame happen, and drawing
- * the AdaptiveIconDrawable object can be time consuming, so we use the splash-screen background
- * thread to draw the AdaptiveIconDrawable object to a Bitmap and cache it to a BitmapShader after
- * the SplashScreenView just created, once we get the BitmapShader then the #draw call can be very
- * quickly.
- *
- * So basically we are using the spare time to prepare the SplashScreenView while splash screen
- * thread is waiting for
- * 1. WindowManager#addView(binder call to WM),
- * 2. Choreographer#doFrame happen(uncertain time for next frame, depends on device),
- * 3. Session#relayout(another binder call to WM which under Choreographer#doFrame, but will
- * always happen before #draw).
- * Because above steps are running on splash-screen thread, so pre-draw the BitmapShader on
- * splash-screen background tread can make they execute in parallel, which ensure it is faster then
- * to draw the AdaptiveIconDrawable when receive callback from Choreographer#doFrame.
- *
- * Here is the sequence to compare the difference between using single and two thread.
- *
- * Single thread:
- * => makeSplashScreenContentView -> WM#addView .. waiting for Choreographer#doFrame -> relayout
- * -> draw -> AdaptiveIconDrawable#draw
- *
- * Two threads:
- * => makeSplashScreenContentView -> cachePaint(=AdaptiveIconDrawable#draw)
- * => WM#addView -> .. waiting for Choreographer#doFrame -> relayout -> draw -> (draw the Paint
- * directly).
  */
 @ShellSplashscreenThread
 public class StartingSurfaceDrawer {
-    private static final String TAG = StartingWindowController.TAG;
 
-    private final Context mContext;
-    private final DisplayManager mDisplayManager;
     private final ShellExecutor mSplashScreenExecutor;
     @VisibleForTesting
     final SplashscreenContentDrawer mSplashscreenContentDrawer;
-    private Choreographer mChoreographer;
-    private final WindowManagerGlobal mWindowManagerGlobal;
-    private StartingSurface.SysuiProxy mSysuiProxy;
-    private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
+    @VisibleForTesting
+    final SplashscreenWindowCreator mSplashscreenWindowCreator;
+    private final SnapshotWindowCreator mSnapshotWindowCreator;
+    private final WindowlessSplashWindowCreator mWindowlessSplashWindowCreator;
+    private final WindowlessSnapshotWindowCreator mWindowlessSnapshotWindowCreator;
 
-    private static final int LIGHT_BARS_MASK =
-            WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
-                    | WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-    /**
-     * The minimum duration during which the splash screen is shown when the splash screen icon is
-     * animated.
-     */
-    static final long MINIMAL_ANIMATION_DURATION = 400L;
-
-    /**
-     * Allow the icon style splash screen to be displayed for longer to give time for the animation
-     * to finish, i.e. the extra buffer time to keep the splash screen if the animation is slightly
-     * longer than the {@link #MINIMAL_ANIMATION_DURATION} duration.
-     */
-    static final long TIME_WINDOW_DURATION = 100L;
-
-    /**
-     * The maximum duration during which the splash screen will be shown if the application is ready
-     * to show before the icon animation finishes.
-     */
-    static final long MAX_ANIMATION_DURATION = MINIMAL_ANIMATION_DURATION + TIME_WINDOW_DURATION;
-
+    @VisibleForTesting
+    final StartingWindowRecordManager mWindowRecords = new StartingWindowRecordManager();
+    // Windowless surface could co-exist with starting window in a task.
+    @VisibleForTesting
+    final StartingWindowRecordManager mWindowlessRecords = new StartingWindowRecordManager();
     /**
      * @param splashScreenExecutor The thread used to control add and remove starting window.
      */
     public StartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
             IconProvider iconProvider, TransactionPool pool) {
-        mContext = context;
-        mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mSplashScreenExecutor = splashScreenExecutor;
-        mSplashscreenContentDrawer = new SplashscreenContentDrawer(mContext, iconProvider, pool);
-        mSplashScreenExecutor.execute(() -> mChoreographer = Choreographer.getInstance());
-        mWindowManagerGlobal = WindowManagerGlobal.getInstance();
-        mDisplayManager.getDisplay(DEFAULT_DISPLAY);
-    }
+        final DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+        mSplashscreenContentDrawer = new SplashscreenContentDrawer(context, iconProvider, pool);
+        displayManager.getDisplay(DEFAULT_DISPLAY);
 
-    @VisibleForTesting
-    final SparseArray<StartingWindowRecord> mStartingWindowRecords = new SparseArray<>();
-
-    /**
-     * Records of {@link SurfaceControlViewHost} where the splash screen icon animation is
-     * rendered and that have not yet been removed by their client.
-     */
-    private final SparseArray<SurfaceControlViewHost> mAnimatedSplashScreenSurfaceHosts =
-            new SparseArray<>(1);
-
-    private Display getDisplay(int displayId) {
-        return mDisplayManager.getDisplay(displayId);
-    }
-
-    int getSplashScreenTheme(int splashScreenThemeResId, ActivityInfo activityInfo) {
-        return splashScreenThemeResId != 0
-                ? splashScreenThemeResId
-                : activityInfo.getThemeResource() != 0 ? activityInfo.getThemeResource()
-                        : com.android.internal.R.style.Theme_DeviceDefault_DayNight;
+        mSplashscreenWindowCreator = new SplashscreenWindowCreator(mSplashscreenContentDrawer,
+                context, splashScreenExecutor, displayManager, mWindowRecords);
+        mSnapshotWindowCreator = new SnapshotWindowCreator(splashScreenExecutor,
+                mWindowRecords);
+        mWindowlessSplashWindowCreator = new WindowlessSplashWindowCreator(
+                mSplashscreenContentDrawer, context, splashScreenExecutor, displayManager,
+                mWindowlessRecords, pool);
+        mWindowlessSnapshotWindowCreator = new WindowlessSnapshotWindowCreator(
+                mWindowlessRecords, context, displayManager, mSplashscreenContentDrawer, pool);
     }
 
     void setSysuiProxy(StartingSurface.SysuiProxy sysuiProxy) {
-        mSysuiProxy = sysuiProxy;
+        mSplashscreenWindowCreator.setSysuiProxy(sysuiProxy);
+        mWindowlessSplashWindowCreator.setSysuiProxy(sysuiProxy);
     }
 
     /**
@@ -186,231 +97,55 @@
      *
      * @param suggestType The suggestion type to draw the splash screen.
      */
-    void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, IBinder appToken,
+    void addSplashScreenStartingWindow(StartingWindowInfo windowInfo,
             @StartingWindowType int suggestType) {
-        final RunningTaskInfo taskInfo = windowInfo.taskInfo;
-        final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
-                ? windowInfo.targetActivityInfo
-                : taskInfo.topActivityInfo;
-        if (activityInfo == null || activityInfo.packageName == null) {
-            return;
-        }
-        // replace with the default theme if the application didn't set
-        final int theme = getSplashScreenTheme(windowInfo.splashScreenThemeResId, activityInfo);
-        final Context context = SplashscreenContentDrawer.createContext(mContext, windowInfo, theme,
-                suggestType, mDisplayManager);
-        if (context == null) {
-            return;
-        }
-        final WindowManager.LayoutParams params = SplashscreenContentDrawer.createLayoutParameters(
-                context, windowInfo, suggestType, activityInfo.packageName,
-                suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
-                        ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT, appToken);
-
-        final int displayId = taskInfo.displayId;
-        final int taskId = taskInfo.taskId;
-        final Display display = getDisplay(displayId);
-
-        // TODO(b/173975965) tracking performance
-        // Prepare the splash screen content view on splash screen worker thread in parallel, so the
-        // content view won't be blocked by binder call like addWindow and relayout.
-        // 1. Trigger splash screen worker thread to create SplashScreenView before/while
-        // Session#addWindow.
-        // 2. Synchronize the SplashscreenView to splash screen thread before Choreographer start
-        // traversal, which will call Session#relayout on splash screen thread.
-        // 3. Pre-draw the BitmapShader if the icon is immobile on splash screen worker thread, at
-        // the same time the splash screen thread should be executing Session#relayout. Blocking the
-        // traversal -> draw on splash screen thread until the BitmapShader of the icon is ready.
-
-        // Record whether create splash screen view success, notify to current thread after
-        // create splash screen view finished.
-        final SplashScreenViewSupplier viewSupplier = new SplashScreenViewSupplier();
-        final FrameLayout rootLayout = new FrameLayout(
-                mSplashscreenContentDrawer.createViewContextWrapper(context));
-        rootLayout.setPadding(0, 0, 0, 0);
-        rootLayout.setFitsSystemWindows(false);
-        final Runnable setViewSynchronized = () -> {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addSplashScreenView");
-            // waiting for setContentView before relayoutWindow
-            SplashScreenView contentView = viewSupplier.get();
-            final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
-            // If record == null, either the starting window added fail or removed already.
-            // Do not add this view if the token is mismatch.
-            if (record != null && appToken == record.mAppToken) {
-                // if view == null then creation of content view was failed.
-                if (contentView != null) {
-                    try {
-                        rootLayout.addView(contentView);
-                    } catch (RuntimeException e) {
-                        Slog.w(TAG, "failed set content view to starting window "
-                                + "at taskId: " + taskId, e);
-                        contentView = null;
-                    }
-                }
-                record.setSplashScreenView(contentView);
-            }
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        };
-        if (mSysuiProxy != null) {
-            mSysuiProxy.requestTopUi(true, TAG);
-        }
-        mSplashscreenContentDrawer.createContentView(context, suggestType, windowInfo,
-                viewSupplier::setView, viewSupplier::setUiThreadInitTask);
-        try {
-            if (addWindow(taskId, appToken, rootLayout, display, params, suggestType)) {
-                // We use the splash screen worker thread to create SplashScreenView while adding
-                // the window, as otherwise Choreographer#doFrame might be delayed on this thread.
-                // And since Choreographer#doFrame won't happen immediately after adding the window,
-                // if the view is not added to the PhoneWindow on the first #doFrame, the view will
-                // not be rendered on the first frame. So here we need to synchronize the view on
-                // the window before first round relayoutWindow, which will happen after insets
-                // animation.
-                mChoreographer.postCallback(CALLBACK_INSETS_ANIMATION, setViewSynchronized, null);
-                final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
-                record.parseAppSystemBarColor(context);
-                // Block until we get the background color.
-                final SplashScreenView contentView = viewSupplier.get();
-                if (suggestType != STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
-                    contentView.addOnAttachStateChangeListener(
-                            new View.OnAttachStateChangeListener() {
-                                @Override
-                                public void onViewAttachedToWindow(View v) {
-                                    final int lightBarAppearance = ContrastColorUtil.isColorLight(
-                                            contentView.getInitBackgroundColor())
-                                            ? LIGHT_BARS_MASK : 0;
-                                    contentView.getWindowInsetsController().setSystemBarsAppearance(
-                                            lightBarAppearance, LIGHT_BARS_MASK);
-                                }
-
-                                @Override
-                                public void onViewDetachedFromWindow(View v) {
-                                }
-                            });
-                }
-                record.mBGColor = contentView.getInitBackgroundColor();
-            } else {
-                // release the icon view host
-                final SplashScreenView contentView = viewSupplier.get();
-                if (contentView.getSurfaceHost() != null) {
-                    SplashScreenView.releaseIconHost(contentView.getSurfaceHost());
-                }
-            }
-        } catch (RuntimeException e) {
-            // don't crash if something else bad happens, for example a
-            // failure loading resources because we are loading from an app
-            // on external storage that has been unmounted.
-            Slog.w(TAG, "failed creating starting window at taskId: " + taskId, e);
-        }
+        mSplashscreenWindowCreator.addSplashScreenStartingWindow(windowInfo, suggestType);
     }
 
     int getStartingWindowBackgroundColorForTask(int taskId) {
-        final StartingWindowRecord startingWindowRecord = mStartingWindowRecords.get(taskId);
+        final StartingWindowRecord startingWindowRecord = mWindowRecords.getRecord(taskId);
         if (startingWindowRecord == null) {
             return Color.TRANSPARENT;
         }
-        return startingWindowRecord.mBGColor;
-    }
-
-    private static class SplashScreenViewSupplier implements Supplier<SplashScreenView> {
-        private SplashScreenView mView;
-        private boolean mIsViewSet;
-        private Runnable mUiThreadInitTask;
-        void setView(SplashScreenView view) {
-            synchronized (this) {
-                mView = view;
-                mIsViewSet = true;
-                notify();
-            }
-        }
-
-        void setUiThreadInitTask(Runnable initTask) {
-            synchronized (this) {
-                mUiThreadInitTask = initTask;
-            }
-        }
-
-        @Override
-        @Nullable
-        public SplashScreenView get() {
-            synchronized (this) {
-                while (!mIsViewSet) {
-                    try {
-                        wait();
-                    } catch (InterruptedException ignored) {
-                    }
-                }
-                if (mUiThreadInitTask != null) {
-                    mUiThreadInitTask.run();
-                    mUiThreadInitTask = null;
-                }
-                return mView;
-            }
-        }
+        return startingWindowRecord.getBGColor();
     }
 
     int estimateTaskBackgroundColor(TaskInfo taskInfo) {
-        if (taskInfo.topActivityInfo == null) {
-            return Color.TRANSPARENT;
-        }
-        final ActivityInfo activityInfo = taskInfo.topActivityInfo;
-        final String packageName = activityInfo.packageName;
-        final int userId = taskInfo.userId;
-        final Context windowContext;
-        try {
-            windowContext = mContext.createPackageContextAsUser(
-                    packageName, Context.CONTEXT_RESTRICTED, UserHandle.of(userId));
-        } catch (PackageManager.NameNotFoundException e) {
-            Slog.w(TAG, "Failed creating package context with package name "
-                    + packageName + " for user " + taskInfo.userId, e);
-            return Color.TRANSPARENT;
-        }
-        try {
-            final IPackageManager packageManager = ActivityThread.getPackageManager();
-            final String splashScreenThemeName = packageManager.getSplashScreenTheme(packageName,
-                    userId);
-            final int splashScreenThemeId = splashScreenThemeName != null
-                    ? windowContext.getResources().getIdentifier(splashScreenThemeName, null, null)
-                    : 0;
-
-            final int theme = getSplashScreenTheme(splashScreenThemeId, activityInfo);
-
-            if (theme != windowContext.getThemeResId()) {
-                windowContext.setTheme(theme);
-            }
-            return mSplashscreenContentDrawer.estimateTaskBackgroundColor(windowContext);
-        } catch (RuntimeException | RemoteException e) {
-            Slog.w(TAG, "failed get starting window background color at taskId: "
-                    + taskInfo.taskId, e);
-        }
-        return Color.TRANSPARENT;
+        return mSplashscreenWindowCreator.estimateTaskBackgroundColor(taskInfo);
     }
 
     /**
      * Called when a task need a snapshot starting window.
      */
-    void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, IBinder appToken,
-            TaskSnapshot snapshot) {
-        final int taskId = startingWindowInfo.taskInfo.taskId;
-        // Remove any existing starting window for this task before adding.
-        removeWindowNoAnimate(taskId);
-        final TaskSnapshotWindow surface = TaskSnapshotWindow.create(startingWindowInfo, appToken,
-                snapshot, mSplashScreenExecutor, () -> removeWindowNoAnimate(taskId));
-        if (surface == null) {
-            return;
-        }
-        final StartingWindowRecord tView = new StartingWindowRecord(appToken,
-                null/* decorView */, surface, STARTING_WINDOW_TYPE_SNAPSHOT);
-        mStartingWindowRecords.put(taskId, tView);
+    void makeTaskSnapshotWindow(StartingWindowInfo startingWindowInfo, TaskSnapshot snapshot) {
+        mSnapshotWindowCreator.makeTaskSnapshotWindow(startingWindowInfo, snapshot);
     }
 
     /**
      * Called when the content of a task is ready to show, starting window can be removed.
      */
     public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
-                "Task start finish, remove starting surface for task: %d",
-                removalInfo.taskId);
-        removeWindowSynced(removalInfo, false /* immediately */);
+        if (removalInfo.windowlessSurface) {
+            mWindowlessRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
+        } else {
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+                    "Task start finish, remove starting surface for task: %d",
+                    removalInfo.taskId);
+            mWindowRecords.removeWindow(removalInfo, removalInfo.removeImmediately);
+        }
+    }
+
+    /**
+     * Create a windowless starting surface and attach to the root surface.
+     */
+    void addWindowlessStartingSurface(StartingWindowInfo windowInfo) {
+        if (windowInfo.taskSnapshot != null) {
+            mWindowlessSnapshotWindowCreator.makeTaskSnapshotWindow(windowInfo,
+                    windowInfo.rootSurface, windowInfo.taskSnapshot, mSplashScreenExecutor);
+        } else {
+            mWindowlessSplashWindowCreator.addSplashScreenStartingWindow(
+                    windowInfo, windowInfo.rootSurface);
+        }
     }
 
     /**
@@ -419,37 +154,15 @@
     public void clearAllWindows() {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                 "Clear all starting windows immediately");
-        final int taskSize = mStartingWindowRecords.size();
-        final int[] taskIds = new int[taskSize];
-        for (int i = taskSize - 1; i >= 0; --i) {
-            taskIds[i] = mStartingWindowRecords.keyAt(i);
-        }
-        for (int i = taskSize - 1; i >= 0; --i) {
-            removeWindowNoAnimate(taskIds[i]);
-        }
+        mWindowRecords.clearAllWindows();
+        mWindowlessRecords.clearAllWindows();
     }
 
     /**
      * Called when the Task wants to copy the splash screen.
      */
     public void copySplashScreenView(int taskId) {
-        final StartingWindowRecord preView = mStartingWindowRecords.get(taskId);
-        SplashScreenViewParcelable parcelable;
-        SplashScreenView splashScreenView = preView != null ? preView.mContentView : null;
-        if (splashScreenView != null && splashScreenView.isCopyable()) {
-            parcelable = new SplashScreenViewParcelable(splashScreenView);
-            parcelable.setClientCallback(
-                    new RemoteCallback((bundle) -> mSplashScreenExecutor.execute(
-                            () -> onAppSplashScreenViewRemoved(taskId, false))));
-            splashScreenView.onCopied();
-            mAnimatedSplashScreenSurfaceHosts.append(taskId, splashScreenView.getSurfaceHost());
-        } else {
-            parcelable = null;
-        }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
-                "Copying splash screen window view for task: %d with parcelable %b",
-                taskId, parcelable != null);
-        ActivityTaskManager.getInstance().onSplashScreenViewCopyFinished(taskId, parcelable);
+        mSplashscreenWindowCreator.copySplashScreenView(taskId);
     }
 
     /**
@@ -459,195 +172,148 @@
      * @param taskId The Task id on which the splash screen was attached
      */
     public void onAppSplashScreenViewRemoved(int taskId) {
-        onAppSplashScreenViewRemoved(taskId, true /* fromServer */);
-    }
-
-    /**
-     * @param fromServer If true, this means the removal was notified by the server. This is only
-     *                   used for debugging purposes.
-     * @see #onAppSplashScreenViewRemoved(int)
-     */
-    private void onAppSplashScreenViewRemoved(int taskId, boolean fromServer) {
-        SurfaceControlViewHost viewHost =
-                mAnimatedSplashScreenSurfaceHosts.get(taskId);
-        if (viewHost == null) {
-            return;
-        }
-        mAnimatedSplashScreenSurfaceHosts.remove(taskId);
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
-                "%s the splash screen. Releasing SurfaceControlViewHost for task: %d",
-                fromServer ? "Server cleaned up" : "App removed", taskId);
-        SplashScreenView.releaseIconHost(viewHost);
-    }
-
-    protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
-            WindowManager.LayoutParams params, @StartingWindowType int suggestType) {
-        boolean shouldSaveView = true;
-        final Context context = view.getContext();
-        try {
-            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addRootView");
-            mWindowManagerGlobal.addView(view, params, display,
-                    null /* parentWindow */, context.getUserId());
-        } catch (WindowManager.BadTokenException e) {
-            // ignore
-            Slog.w(TAG, appToken + " already running, starting window not displayed. "
-                    + e.getMessage());
-            shouldSaveView = false;
-        } finally {
-            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-            if (view.getParent() == null) {
-                Slog.w(TAG, "view not successfully added to wm, removing view");
-                mWindowManagerGlobal.removeView(view, true /* immediate */);
-                shouldSaveView = false;
-            }
-        }
-        if (shouldSaveView) {
-            removeWindowNoAnimate(taskId);
-            saveSplashScreenRecord(appToken, taskId, view, suggestType);
-        }
-        return shouldSaveView;
-    }
-
-    @VisibleForTesting
-    void saveSplashScreenRecord(IBinder appToken, int taskId, View view,
-            @StartingWindowType int suggestType) {
-        final StartingWindowRecord tView = new StartingWindowRecord(appToken, view,
-                null/* TaskSnapshotWindow */, suggestType);
-        mStartingWindowRecords.put(taskId, tView);
-    }
-
-    private void removeWindowNoAnimate(int taskId) {
-        mTmpRemovalInfo.taskId = taskId;
-        removeWindowSynced(mTmpRemovalInfo, true /* immediately */);
+        mSplashscreenWindowCreator.onAppSplashScreenViewRemoved(taskId);
     }
 
     void onImeDrawnOnTask(int taskId) {
-        final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
-        if (record != null && record.mTaskSnapshotWindow != null
-                && record.mTaskSnapshotWindow.hasImeSurface()) {
-            removeWindowNoAnimate(taskId);
+        onImeDrawnOnTask(mWindowRecords, taskId);
+        onImeDrawnOnTask(mWindowlessRecords, taskId);
+    }
+
+    private void onImeDrawnOnTask(StartingWindowRecordManager records, int taskId) {
+        final StartingSurfaceDrawer.StartingWindowRecord sRecord =
+                records.getRecord(taskId);
+        final SnapshotRecord record = sRecord instanceof SnapshotRecord
+                ? (SnapshotRecord) sRecord : null;
+        if (record != null && record.hasImeSurface()) {
+            records.removeWindow(taskId, true);
         }
     }
 
-    protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo, boolean immediately) {
-        final int taskId = removalInfo.taskId;
-        final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
-        if (record != null) {
-            if (record.mDecorView != null) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
-                        "Removing splash screen window for task: %d", taskId);
-                if (record.mContentView != null) {
-                    record.clearSystemBarColor();
-                    if (immediately
-                            || record.mSuggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN) {
-                        removeWindowInner(record.mDecorView, false);
-                    } else {
-                        if (removalInfo.playRevealAnimation) {
-                            mSplashscreenContentDrawer.applyExitAnimation(record.mContentView,
-                                    removalInfo.windowAnimationLeash, removalInfo.mainFrame,
-                                    () -> removeWindowInner(record.mDecorView, true),
-                                    record.mCreateTime, removalInfo.roundedCornerRadius);
-                        } else {
-                            // the SplashScreenView has been copied to client, hide the view to skip
-                            // default exit animation
-                            removeWindowInner(record.mDecorView, true);
-                        }
-                    }
-                } else {
-                    // shouldn't happen
-                    Slog.e(TAG, "Found empty splash screen, remove!");
-                    removeWindowInner(record.mDecorView, false);
-                }
+    static class WindowlessStartingWindow extends WindowlessWindowManager {
+        SurfaceControl mChildSurface;
 
+        WindowlessStartingWindow(Configuration c, SurfaceControl rootSurface) {
+            super(c, rootSurface, null /* hostInputToken */);
+        }
+
+        @Override
+        protected SurfaceControl getParentSurface(IWindow window,
+                WindowManager.LayoutParams attrs) {
+            final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
+                    .setContainerLayer()
+                    .setName("Windowless window")
+                    .setHidden(false)
+                    .setParent(mRootSurface)
+                    .setCallsite("WindowlessStartingWindow#attachToParentSurface");
+            mChildSurface = builder.build();
+            try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
+                t.setLayer(mChildSurface, Integer.MAX_VALUE);
+                t.apply();
             }
-            if (record.mTaskSnapshotWindow != null) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
-                        "Removing task snapshot window for %d", taskId);
-                if (immediately) {
-                    record.mTaskSnapshotWindow.removeImmediately();
-                } else {
-                    record.mTaskSnapshotWindow.scheduleRemove(removalInfo.deferRemoveForIme);
-                }
-            }
-            mStartingWindowRecords.remove(taskId);
+            return mChildSurface;
+        }
+    }
+    abstract static class StartingWindowRecord {
+        protected int mBGColor;
+        abstract void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately);
+        int getBGColor() {
+            return mBGColor;
         }
     }
 
-    private void removeWindowInner(View decorView, boolean hideView) {
-        if (mSysuiProxy != null) {
-            mSysuiProxy.requestTopUi(false, TAG);
-        }
-        if (hideView) {
-            decorView.setVisibility(View.GONE);
-        }
-        mWindowManagerGlobal.removeView(decorView, false /* immediate */);
-    }
+    abstract static class SnapshotRecord extends StartingWindowRecord {
+        private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
+        /**
+         * The max delay time in milliseconds for removing the task snapshot window with IME
+         * visible.
+         * Ideally the delay time will be shorter when receiving
+         * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
+         */
+        private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
+        private final Runnable mScheduledRunnable = this::removeImmediately;
 
-    /**
-     * Record the view or surface for a starting window.
-     */
-    private static class StartingWindowRecord {
-        private final IBinder mAppToken;
-        private final View mDecorView;
-        private final TaskSnapshotWindow mTaskSnapshotWindow;
-        private SplashScreenView mContentView;
-        private boolean mSetSplashScreen;
-        @StartingWindowType private int mSuggestType;
-        private int mBGColor;
-        private final long mCreateTime;
-        private int mSystemBarAppearance;
-        private boolean mDrawsSystemBarBackgrounds;
+        @WindowConfiguration.ActivityType protected final int mActivityType;
+        protected final ShellExecutor mRemoveExecutor;
 
-        StartingWindowRecord(IBinder appToken, View decorView,
-                TaskSnapshotWindow taskSnapshotWindow, @StartingWindowType int suggestType) {
-            mAppToken = appToken;
-            mDecorView = decorView;
-            mTaskSnapshotWindow = taskSnapshotWindow;
-            if (mTaskSnapshotWindow != null) {
-                mBGColor = mTaskSnapshotWindow.getBackgroundColor();
+        SnapshotRecord(int activityType, ShellExecutor removeExecutor) {
+            mActivityType = activityType;
+            mRemoveExecutor = removeExecutor;
+        }
+
+        @Override
+        public final void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+            if (immediately) {
+                removeImmediately();
+            } else {
+                scheduleRemove(info.deferRemoveForIme);
             }
-            mSuggestType = suggestType;
-            mCreateTime = SystemClock.uptimeMillis();
         }
 
-        private void setSplashScreenView(SplashScreenView splashScreenView) {
-            if (mSetSplashScreen) {
+        void scheduleRemove(boolean deferRemoveForIme) {
+            // Show the latest content as soon as possible for unlocking to home.
+            if (mActivityType == ACTIVITY_TYPE_HOME) {
+                removeImmediately();
                 return;
             }
-            mContentView = splashScreenView;
-            mSetSplashScreen = true;
+            mRemoveExecutor.removeCallbacks(mScheduledRunnable);
+            final long delayRemovalTime = hasImeSurface() && deferRemoveForIme
+                    ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
+                    : DELAY_REMOVAL_TIME_GENERAL;
+            mRemoveExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
+                    "Defer removing snapshot surface in %d", delayRemovalTime);
         }
 
-        private void parseAppSystemBarColor(Context context) {
-            final TypedArray a = context.obtainStyledAttributes(R.styleable.Window);
-            mDrawsSystemBarBackgrounds = a.getBoolean(
-                    R.styleable.Window_windowDrawsSystemBarBackgrounds, false);
-            if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
-                mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
+        protected abstract boolean hasImeSurface();
+
+        @CallSuper
+        protected void removeImmediately() {
+            mRemoveExecutor.removeCallbacks(mScheduledRunnable);
+        }
+    }
+
+    static class StartingWindowRecordManager {
+        private final StartingWindowRemovalInfo mTmpRemovalInfo = new StartingWindowRemovalInfo();
+        private final SparseArray<StartingWindowRecord> mStartingWindowRecords =
+                new SparseArray<>();
+
+        void clearAllWindows() {
+            final int taskSize = mStartingWindowRecords.size();
+            final int[] taskIds = new int[taskSize];
+            for (int i = taskSize - 1; i >= 0; --i) {
+                taskIds[i] = mStartingWindowRecords.keyAt(i);
             }
-            if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
-                mSystemBarAppearance |= WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
+            for (int i = taskSize - 1; i >= 0; --i) {
+                removeWindow(taskIds[i], true);
             }
-            a.recycle();
         }
 
-        // Reset the system bar color which set by splash screen, make it align to the app.
-        private void clearSystemBarColor() {
-            if (mDecorView == null || !mDecorView.isAttachedToWindow()) {
-                return;
+        void addRecord(int taskId, StartingWindowRecord record) {
+            mStartingWindowRecords.put(taskId, record);
+        }
+
+        void removeWindow(StartingWindowRemovalInfo removeInfo, boolean immediately) {
+            final int taskId = removeInfo.taskId;
+            final StartingWindowRecord record = mStartingWindowRecords.get(taskId);
+            if (record != null) {
+                record.removeIfPossible(removeInfo, immediately);
+                mStartingWindowRecords.remove(taskId);
             }
-            if (mDecorView.getLayoutParams() instanceof WindowManager.LayoutParams) {
-                final WindowManager.LayoutParams lp =
-                        (WindowManager.LayoutParams) mDecorView.getLayoutParams();
-                if (mDrawsSystemBarBackgrounds) {
-                    lp.flags |= WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-                } else {
-                    lp.flags &= ~WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
-                }
-                mDecorView.setLayoutParams(lp);
-            }
-            mDecorView.getWindowInsetsController().setSystemBarsAppearance(
-                    mSystemBarAppearance, LIGHT_BARS_MASK);
+        }
+
+        void removeWindow(int taskId, boolean immediately) {
+            mTmpRemovalInfo.taskId = taskId;
+            removeWindow(mTmpRemovalInfo, immediately);
+        }
+
+        StartingWindowRecord getRecord(int taskId) {
+            return mStartingWindowRecords.get(taskId);
+        }
+
+        @VisibleForTesting
+        int recordSize() {
+            return mStartingWindowRecords.size();
         }
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index be2e793..bec4ba3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -21,6 +21,7 @@
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
 
 import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
 import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
@@ -29,7 +30,6 @@
 import android.app.TaskInfo;
 import android.content.Context;
 import android.graphics.Color;
-import android.os.IBinder;
 import android.os.Trace;
 import android.util.SparseIntArray;
 import android.window.StartingWindowInfo;
@@ -152,22 +152,23 @@
     /**
      * Called when a task need a starting window.
      */
-    public void addStartingWindow(StartingWindowInfo windowInfo, IBinder appToken) {
+    public void addStartingWindow(StartingWindowInfo windowInfo) {
         mSplashScreenExecutor.execute(() -> {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow");
 
             final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType(
                     windowInfo);
             final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo;
-            if (isSplashScreenType(suggestionType)) {
-                mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, appToken,
-                        suggestionType);
+            if (suggestionType == STARTING_WINDOW_TYPE_WINDOWLESS) {
+                mStartingSurfaceDrawer.addWindowlessStartingSurface(windowInfo);
+            } else if (isSplashScreenType(suggestionType)) {
+                mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, suggestionType);
             } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) {
                 final TaskSnapshot snapshot = windowInfo.taskSnapshot;
-                mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, appToken,
-                        snapshot);
+                mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot);
             }
-            if (suggestionType != STARTING_WINDOW_TYPE_NONE) {
+            if (suggestionType != STARTING_WINDOW_TYPE_NONE
+                    && suggestionType != STARTING_WINDOW_TYPE_WINDOWLESS) {
                 int taskId = runningTaskInfo.taskId;
                 int color = mStartingSurfaceDrawer
                         .getStartingWindowBackgroundColorForTask(taskId);
@@ -218,11 +219,13 @@
     public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) {
         mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow(
                 removalInfo));
-        mSplashScreenExecutor.executeDelayed(() -> {
-            synchronized (mTaskBackgroundColors) {
-                mTaskBackgroundColors.delete(removalInfo.taskId);
-            }
-        }, TASK_BG_COLOR_RETAIN_TIME_MS);
+        if (!removalInfo.windowlessSurface) {
+            mSplashScreenExecutor.executeDelayed(() -> {
+                synchronized (mTaskBackgroundColors) {
+                    mTaskBackgroundColors.delete(removalInfo.taskId);
+                }
+            }, TASK_BG_COLOR_RETAIN_TIME_MS);
+        }
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index a05ed4f..c964df1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.startingsurface;
 
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.graphics.Color.WHITE;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -63,24 +62,14 @@
     private static final String TAG = StartingWindowController.TAG;
     private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=";
 
-    private static final long DELAY_REMOVAL_TIME_GENERAL = 100;
-    /**
-     * The max delay time in milliseconds for removing the task snapshot window with IME visible.
-     * Ideally the delay time will be shorter when receiving
-     * {@link StartingSurfaceDrawer#onImeDrawnOnTask(int)}.
-     */
-    private static final long MAX_DELAY_REMOVAL_TIME_IME_VISIBLE = 600;
-
     private final Window mWindow;
     private final Runnable mClearWindowHandler;
     private final ShellExecutor mSplashScreenExecutor;
     private final IWindowSession mSession;
     private boolean mHasDrawn;
     private final Paint mBackgroundPaint = new Paint();
-    private final int mActivityType;
     private final int mOrientationOnCreation;
 
-    private final Runnable mScheduledRunnable = this::removeImmediately;
     private final boolean mHasImeSurface;
 
     static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken,
@@ -104,7 +93,6 @@
         final Point taskSize = snapshot.getTaskSize();
         final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y);
         final int orientation = snapshot.getOrientation();
-        final int activityType = runningTaskInfo.topActivityType;
         final int displayId = runningTaskInfo.displayId;
 
         final IWindowSession session = WindowManagerGlobal.getWindowSession();
@@ -114,16 +102,11 @@
         final InsetsSourceControl.Array tmpControls = new InsetsSourceControl.Array();
         final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
 
-        final TaskDescription taskDescription;
-        if (runningTaskInfo.taskDescription != null) {
-            taskDescription = runningTaskInfo.taskDescription;
-        } else {
-            taskDescription = new TaskDescription();
-            taskDescription.setBackgroundColor(WHITE);
-        }
+        final TaskDescription taskDescription =
+                SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo);
 
         final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
-                snapshot, taskDescription, orientation, activityType,
+                snapshot, taskDescription, orientation,
                 clearWindowHandler, splashScreenExecutor);
         final Window window = snapshotSurface.mWindow;
 
@@ -153,6 +136,8 @@
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         } catch (RemoteException e) {
             snapshotSurface.clearWindowSynced();
+            Slog.w(TAG, "Failed to relayout snapshot starting window");
+            return null;
         }
 
         SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot,
@@ -164,7 +149,7 @@
     }
 
     public TaskSnapshotWindow(TaskSnapshot snapshot, TaskDescription taskDescription,
-            int currentOrientation, int activityType, Runnable clearWindowHandler,
+            int currentOrientation, Runnable clearWindowHandler,
             ShellExecutor splashScreenExecutor) {
         mSplashScreenExecutor = splashScreenExecutor;
         mSession = WindowManagerGlobal.getWindowSession();
@@ -173,7 +158,6 @@
         int backgroundColor = taskDescription.getBackgroundColor();
         mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
         mOrientationOnCreation = currentOrientation;
-        mActivityType = activityType;
         mClearWindowHandler = clearWindowHandler;
         mHasImeSurface = snapshot.hasImeSurface();
     }
@@ -186,23 +170,7 @@
 	return mHasImeSurface;
     }
 
-    void scheduleRemove(boolean deferRemoveForIme) {
-        // Show the latest content as soon as possible for unlocking to home.
-        if (mActivityType == ACTIVITY_TYPE_HOME) {
-            removeImmediately();
-            return;
-        }
-        mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
-        final long delayRemovalTime = mHasImeSurface && deferRemoveForIme
-                ? MAX_DELAY_REMOVAL_TIME_IME_VISIBLE
-                : DELAY_REMOVAL_TIME_GENERAL;
-        mSplashScreenExecutor.executeDelayed(mScheduledRunnable, delayRemovalTime);
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
-                "Defer removing snapshot surface in %d", delayRemovalTime);
-    }
-
     void removeImmediately() {
-        mSplashScreenExecutor.removeCallbacks(mScheduledRunnable);
         try {
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
                     "Removing taskSnapshot surface, mHasDrawn=%b", mHasDrawn);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
new file mode 100644
index 0000000..1445478
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
@@ -0,0 +1,165 @@
+/*
+ * 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.startingsurface;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.view.Display;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.window.SnapshotDrawerUtils;
+import android.window.StartingWindowInfo;
+import android.window.TaskSnapshot;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+
+class WindowlessSnapshotWindowCreator {
+    private static final int DEFAULT_FADEOUT_DURATION = 233;
+    private final StartingSurfaceDrawer.StartingWindowRecordManager
+            mStartingWindowRecordManager;
+    private final DisplayManager mDisplayManager;
+    private final Context mContext;
+    private final SplashscreenContentDrawer mSplashscreenContentDrawer;
+    private final TransactionPool mTransactionPool;
+
+    WindowlessSnapshotWindowCreator(
+            StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager,
+            Context context,
+            DisplayManager displayManager, SplashscreenContentDrawer splashscreenContentDrawer,
+            TransactionPool transactionPool) {
+        mStartingWindowRecordManager = startingWindowRecordManager;
+        mContext = context;
+        mDisplayManager = displayManager;
+        mSplashscreenContentDrawer = splashscreenContentDrawer;
+        mTransactionPool = transactionPool;
+    }
+
+    void makeTaskSnapshotWindow(StartingWindowInfo info, SurfaceControl rootSurface,
+            TaskSnapshot snapshot, ShellExecutor removeExecutor) {
+        final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
+        final int taskId = runningTaskInfo.taskId;
+        final String title = "Windowless Snapshot " + taskId;
+        final WindowManager.LayoutParams lp = SnapshotDrawerUtils.createLayoutParameters(
+                info, title, TYPE_APPLICATION_OVERLAY, snapshot.getHardwareBuffer().getFormat(),
+                null /* token */);
+        if (lp == null) {
+            return;
+        }
+        final Display display = mDisplayManager.getDisplay(runningTaskInfo.displayId);
+        final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
+                new StartingSurfaceDrawer.WindowlessStartingWindow(
+                runningTaskInfo.configuration, rootSurface);
+        final SurfaceControlViewHost mViewHost = new SurfaceControlViewHost(
+                mContext, display, wlw, "WindowlessSnapshotWindowCreator");
+        final Point taskSize = snapshot.getTaskSize();
+        final Rect snapshotBounds = new Rect(0, 0, taskSize.x, taskSize.y);
+        final Rect windowBounds = runningTaskInfo.configuration.windowConfiguration.getBounds();
+        final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
+        final FrameLayout rootLayout = new FrameLayout(
+                mSplashscreenContentDrawer.createViewContextWrapper(mContext));
+        mViewHost.setView(rootLayout, lp);
+        SnapshotDrawerUtils.drawSnapshotOnSurface(info, lp, wlw.mChildSurface, snapshot,
+                snapshotBounds, windowBounds, topWindowInsetsState, false /* releaseAfterDraw */);
+
+        final ActivityManager.TaskDescription taskDescription =
+                SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo);
+
+        final SnapshotWindowRecord record = new SnapshotWindowRecord(mViewHost, wlw.mChildSurface,
+                taskDescription.getBackgroundColor(), snapshot.hasImeSurface(),
+                runningTaskInfo.topActivityType, removeExecutor);
+        mStartingWindowRecordManager.addRecord(taskId, record);
+        info.notifyAddComplete(wlw.mChildSurface);
+    }
+
+    private class SnapshotWindowRecord extends StartingSurfaceDrawer.SnapshotRecord {
+        private SurfaceControlViewHost mViewHost;
+        private SurfaceControl mChildSurface;
+        private final boolean mHasImeSurface;
+
+        SnapshotWindowRecord(SurfaceControlViewHost viewHost, SurfaceControl childSurface,
+                int bgColor, boolean hasImeSurface, int activityType,
+                ShellExecutor removeExecutor) {
+            super(activityType, removeExecutor);
+            mViewHost = viewHost;
+            mChildSurface = childSurface;
+            mBGColor = bgColor;
+            mHasImeSurface = hasImeSurface;
+        }
+
+        @Override
+        protected void removeImmediately() {
+            super.removeImmediately();
+            fadeoutThenRelease();
+        }
+
+        void fadeoutThenRelease() {
+            final ValueAnimator fadeOutAnimator = ValueAnimator.ofFloat(1f, 0f);
+            fadeOutAnimator.setDuration(DEFAULT_FADEOUT_DURATION);
+            final SurfaceControl.Transaction t = mTransactionPool.acquire();
+            fadeOutAnimator.addUpdateListener(animation -> {
+                if (mChildSurface == null || !mChildSurface.isValid()) {
+                    fadeOutAnimator.cancel();
+                    return;
+                }
+                t.setAlpha(mChildSurface, (float) animation.getAnimatedValue());
+                t.apply();
+            });
+
+            fadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    if (mChildSurface == null || !mChildSurface.isValid()) {
+                        fadeOutAnimator.cancel();
+                    }
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    mTransactionPool.release(t);
+                    if (mChildSurface != null) {
+                        final SurfaceControl.Transaction t = mTransactionPool.acquire();
+                        t.remove(mChildSurface).apply();
+                        mTransactionPool.release(t);
+                        mChildSurface = null;
+                    }
+                    if (mViewHost != null) {
+                        mViewHost.release();
+                        mViewHost = null;
+                    }
+                }
+            });
+            fadeOutAnimator.start();
+        }
+
+        @Override
+        protected boolean hasImeSurface() {
+            return mHasImeSurface;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
new file mode 100644
index 0000000..12a0d40
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
@@ -0,0 +1,150 @@
+/*
+ * 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.startingsurface;
+
+import static android.graphics.Color.WHITE;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.os.Binder;
+import android.os.SystemClock;
+import android.view.Display;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.window.SplashScreenView;
+import android.window.StartingWindowInfo;
+import android.window.StartingWindowRemovalInfo;
+
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+
+class WindowlessSplashWindowCreator extends AbsSplashWindowCreator {
+
+    private final TransactionPool mTransactionPool;
+
+    WindowlessSplashWindowCreator(SplashscreenContentDrawer contentDrawer,
+            Context context,
+            ShellExecutor splashScreenExecutor,
+            DisplayManager displayManager,
+            StartingSurfaceDrawer.StartingWindowRecordManager startingWindowRecordManager,
+            TransactionPool pool) {
+        super(contentDrawer, context, splashScreenExecutor, displayManager,
+                startingWindowRecordManager);
+        mTransactionPool = pool;
+    }
+
+    void addSplashScreenStartingWindow(StartingWindowInfo windowInfo, SurfaceControl rootSurface) {
+        final ActivityManager.RunningTaskInfo taskInfo = windowInfo.taskInfo;
+        final ActivityInfo activityInfo = windowInfo.targetActivityInfo != null
+                ? windowInfo.targetActivityInfo
+                : taskInfo.topActivityInfo;
+        if (activityInfo == null || activityInfo.packageName == null) {
+            return;
+        }
+
+        final int displayId = taskInfo.displayId;
+        final Display display = mDisplayManager.getDisplay(displayId);
+        if (display == null) {
+            // Can't show splash screen on requested display, so skip showing at all.
+            return;
+        }
+        final Context myContext = SplashscreenContentDrawer.createContext(mContext, windowInfo,
+                0 /* theme */, STARTING_WINDOW_TYPE_SPLASH_SCREEN, mDisplayManager);
+        if (myContext == null) {
+            return;
+        }
+        final StartingSurfaceDrawer.WindowlessStartingWindow wlw =
+                new StartingSurfaceDrawer.WindowlessStartingWindow(
+                        taskInfo.configuration, rootSurface);
+        final SurfaceControlViewHost viewHost = new SurfaceControlViewHost(
+                myContext, display, wlw, "WindowlessSplashWindowCreator");
+        final String title = "Windowless Splash " + taskInfo.taskId;
+        final WindowManager.LayoutParams lp = SplashscreenContentDrawer.createLayoutParameters(
+                myContext, windowInfo, STARTING_WINDOW_TYPE_SPLASH_SCREEN, title,
+                PixelFormat.TRANSLUCENT, new Binder());
+        final Rect windowBounds = taskInfo.configuration.windowConfiguration.getBounds();
+        lp.width = windowBounds.width();
+        lp.height = windowBounds.height();
+        final ActivityManager.TaskDescription taskDescription;
+        if (taskInfo.taskDescription != null) {
+            taskDescription = taskInfo.taskDescription;
+        } else {
+            taskDescription = new ActivityManager.TaskDescription();
+            taskDescription.setBackgroundColor(WHITE);
+        }
+
+        final FrameLayout rootLayout = new FrameLayout(
+                mSplashscreenContentDrawer.createViewContextWrapper(mContext));
+        viewHost.setView(rootLayout, lp);
+
+        final int bgColor = taskDescription.getBackgroundColor();
+        final SplashScreenView splashScreenView = mSplashscreenContentDrawer
+                .makeSimpleSplashScreenContentView(myContext, windowInfo, bgColor);
+        rootLayout.addView(splashScreenView);
+        final SplashWindowRecord record = new SplashWindowRecord(viewHost, splashScreenView,
+                wlw.mChildSurface, bgColor);
+        mStartingWindowRecordManager.addRecord(taskInfo.taskId, record);
+        windowInfo.notifyAddComplete(wlw.mChildSurface);
+    }
+
+    private class SplashWindowRecord extends StartingSurfaceDrawer.StartingWindowRecord {
+        private SurfaceControlViewHost mViewHost;
+        private final long mCreateTime;
+        private SurfaceControl mChildSurface;
+        private final SplashScreenView mSplashView;
+
+        SplashWindowRecord(SurfaceControlViewHost viewHost, SplashScreenView splashView,
+                SurfaceControl childSurface, int bgColor) {
+            mViewHost = viewHost;
+            mSplashView = splashView;
+            mChildSurface = childSurface;
+            mBGColor = bgColor;
+            mCreateTime = SystemClock.uptimeMillis();
+        }
+
+        @Override
+        public void removeIfPossible(StartingWindowRemovalInfo info, boolean immediately) {
+            if (!immediately) {
+                mSplashscreenContentDrawer.applyExitAnimation(mSplashView,
+                        info.windowAnimationLeash, info.mainFrame,
+                        this::release, mCreateTime, 0 /* roundedCornerRadius */);
+            } else {
+                release();
+            }
+        }
+
+        void release() {
+            if (mChildSurface != null) {
+                final SurfaceControl.Transaction t = mTransactionPool.acquire();
+                t.remove(mChildSurface).apply();
+                mTransactionPool.release(t);
+                mChildSurface = null;
+            }
+            if (mViewHost != null) {
+                mViewHost.release();
+                mViewHost = null;
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
index bb43d7c..72fc8686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/phone/PhoneStartingWindowTypeAlgorithm.java
@@ -22,6 +22,7 @@
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
@@ -30,6 +31,7 @@
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
 import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
+import static android.window.StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS;
 
 import android.window.StartingWindowInfo;
 
@@ -55,6 +57,7 @@
         final boolean legacySplashScreen =
                 ((parameter & TYPE_PARAMETER_LEGACY_SPLASH_SCREEN) != 0);
         final boolean activityDrawn = (parameter & TYPE_PARAMETER_ACTIVITY_DRAWN) != 0;
+        final boolean windowlessSurface = (parameter & TYPE_PARAMETER_WINDOWLESS) != 0;
         final boolean topIsHome = windowInfo.taskInfo.topActivityType == ACTIVITY_TYPE_HOME;
 
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
@@ -67,10 +70,15 @@
                         + "isSolidColorSplashScreen=%b, "
                         + "legacySplashScreen=%b, "
                         + "activityDrawn=%b, "
+                        + "windowless=%b, "
                         + "topIsHome=%b",
                 newTask, taskSwitch, processRunning, allowTaskSnapshot, activityCreated,
-                isSolidColorSplashScreen, legacySplashScreen, activityDrawn, topIsHome);
+                isSolidColorSplashScreen, legacySplashScreen, activityDrawn, windowlessSurface,
+                topIsHome);
 
+        if (windowlessSurface) {
+            return STARTING_WINDOW_TYPE_WINDOWLESS;
+        }
         if (!topIsHome) {
             if (!processRunning || newTask || (taskSwitch && !activityCreated)) {
                 return getSplashscreenType(isSolidColorSplashScreen, legacySplashScreen);
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 155990a..27b82c0 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
@@ -79,7 +79,24 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 
-/** Plays transition animations */
+/**
+ * Plays transition animations. Within this player, each transition has a lifecycle.
+ * 1. When a transition is directly started or requested, it is added to "pending" state.
+ * 2. Once WMCore applies the transition and notifies, the transition moves to "ready" state.
+ * 3. When a transition starts animating, it is moved to the "active" state.
+ *
+ * Basically: --start--> PENDING --onTransitionReady--> READY --play--> ACTIVE --finish--> |
+ *                                                            --merge--> MERGED --^
+ *
+ * At the moment, only one transition can be animating at a time. While a transition is animating,
+ * transitions will be queued in the "ready" state for their turn. At the same time, whenever a
+ * transition makes it to the head of the "ready" queue, it will attempt to merge to with the
+ * "active" transition. If the merge succeeds, it will be moved to the "active" transition's
+ * "merged" and then the next "ready" transition can attempt to merge.
+ *
+ * Once the "active" transition animation is finished, it will be removed from the "active" list
+ * and then the next "ready" transition can play.
+ */
 public class Transitions implements RemoteCallable<Transitions> {
     static final String TAG = "ShellTransitions";
 
@@ -150,14 +167,22 @@
     private static final class ActiveTransition {
         IBinder mToken;
         TransitionHandler mHandler;
-        boolean mMerged;
         boolean mAborted;
         TransitionInfo mInfo;
         SurfaceControl.Transaction mStartT;
         SurfaceControl.Transaction mFinishT;
+
+        /** Ordered list of transitions which have been merged into this one. */
+        private ArrayList<ActiveTransition> mMerged;
     }
 
-    /** Keeps track of currently playing transitions in the order of receipt. */
+    /** Keeps track of transitions which have been started, but aren't ready yet. */
+    private final ArrayList<ActiveTransition> mPendingTransitions = new ArrayList<>();
+
+    /** Keeps track of transitions which are ready to play but still waiting for their turn. */
+    private final ArrayList<ActiveTransition> mReadyTransitions = new ArrayList<>();
+
+    /** Keeps track of currently playing transitions. For now, there can only be 1 max. */
     private final ArrayList<ActiveTransition> mActiveTransitions = new ArrayList<>();
 
     public Transitions(@NonNull Context context,
@@ -322,7 +347,8 @@
      * will be executed when the last active transition is finished.
      */
     public void runOnIdle(Runnable runnable) {
-        if (mActiveTransitions.isEmpty()) {
+        if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()
+                && mReadyTransitions.isEmpty()) {
             runnable.run();
         } else {
             mRunWhenIdleQueue.add(runnable);
@@ -441,9 +467,9 @@
         }
     }
 
-    private int findActiveTransition(IBinder token) {
-        for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
-            if (mActiveTransitions.get(i).mToken == token) return i;
+    private static int findByToken(ArrayList<ActiveTransition> list, IBinder token) {
+        for (int i = list.size() - 1; i >= 0; --i) {
+            if (list.get(i).mToken == token) return i;
         }
         return -1;
     }
@@ -481,14 +507,24 @@
             @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady %s: %s",
                 transitionToken, info);
-        final int activeIdx = findActiveTransition(transitionToken);
+        final int activeIdx = findByToken(mPendingTransitions, transitionToken);
         if (activeIdx < 0) {
-            throw new IllegalStateException("Got transitionReady for non-active transition "
+            throw new IllegalStateException("Got transitionReady for non-pending transition "
                     + transitionToken + ". expecting one of "
-                    + Arrays.toString(mActiveTransitions.stream().map(
+                    + Arrays.toString(mPendingTransitions.stream().map(
                             activeTransition -> activeTransition.mToken).toArray()));
         }
-        final ActiveTransition active = mActiveTransitions.get(activeIdx);
+        if (activeIdx > 0) {
+            Log.e(TAG, "Transition became ready out-of-order " + transitionToken + ". Expected"
+                    + " order: " + Arrays.toString(mPendingTransitions.stream().map(
+                            activeTransition -> activeTransition.mToken).toArray()));
+        }
+        // Move from pending to ready
+        final ActiveTransition active = mPendingTransitions.remove(activeIdx);
+        mReadyTransitions.add(active);
+        active.mInfo = info;
+        active.mStartT = t;
+        active.mFinishT = finishT;
 
         for (int i = 0; i < mObservers.size(); ++i) {
             mObservers.get(i).onTransitionReady(transitionToken, info, t, finishT);
@@ -496,9 +532,6 @@
 
         if (info.getType() == TRANSIT_SLEEP) {
             if (activeIdx > 0) {
-                active.mInfo = info;
-                active.mStartT = t;
-                active.mFinishT = finishT;
                 if (!info.getRootLeash().isValid()) {
                     // Shell has some debug settings which makes calling binders with invalid
                     // surfaces crash, so replace it with a "real" one.
@@ -518,9 +551,7 @@
             // housekeeping and return.
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
                     transitionToken, info);
-            t.apply();
-            finishT.apply();
-            onAbort(transitionToken);
+            onAbort(active);
             return;
         }
 
@@ -546,40 +577,90 @@
                 // changes are underneath another change.
                 || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT)
                 && allOccluded)) {
-            t.apply();
-            finishT.apply();
             // Treat this as an abort since we are bypassing any merge logic and effectively
             // finishing immediately.
-            onAbort(transitionToken);
-            releaseSurfaces(info);
+            onAbort(active);
             return;
         }
 
-        active.mInfo = info;
-        active.mStartT = t;
-        active.mFinishT = finishT;
         setupStartState(active.mInfo, active.mStartT, active.mFinishT);
 
-        if (activeIdx > 0) {
-            // This is now playing at the same time as an existing animation, so try merging it.
-            attemptMergeTransition(mActiveTransitions.get(0), active);
+        if (mReadyTransitions.size() > 1) {
+            // There are already transitions waiting in the queue, so just return.
             return;
         }
-        // The normal case, just play it.
-        playTransition(active);
+        processReadyQueue();
     }
 
-    /**
-     * Attempt to merge by delegating the transition start to the handler of the currently
-     * playing transition.
-     */
-    void attemptMergeTransition(@NonNull ActiveTransition playing,
-            @NonNull ActiveTransition merging) {
+    void processReadyQueue() {
+        if (mReadyTransitions.isEmpty()) {
+            // Check if idle.
+            if (mActiveTransitions.isEmpty() && mPendingTransitions.isEmpty()) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition "
+                        + "animations finished");
+                // Run all runnables from the run-when-idle queue.
+                for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
+                    mRunWhenIdleQueue.get(i).run();
+                }
+                mRunWhenIdleQueue.clear();
+            }
+            return;
+        }
+        final ActiveTransition ready = mReadyTransitions.get(0);
+        if (mActiveTransitions.isEmpty()) {
+            // The normal case, just play it (currently we only support 1 active transition).
+            mReadyTransitions.remove(0);
+            mActiveTransitions.add(ready);
+            if (ready.mAborted) {
+                // finish now since there's nothing to animate. Calls back into processReadyQueue
+                onFinish(ready, null, null);
+                return;
+            }
+            playTransition(ready);
+            // Attempt to merge any more queued-up transitions.
+            processReadyQueue();
+            return;
+        }
+        // An existing animation is playing, so see if we can merge.
+        final ActiveTransition playing = mActiveTransitions.get(0);
+        if (ready.mAborted) {
+            // record as merged since it is no-op. Calls back into processReadyQueue
+            onMerged(playing, ready);
+            return;
+        }
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while"
                 + " another transition %s is still animating. Notify the animating transition"
-                + " in case they can be merged", merging.mToken, playing.mToken);
-        playing.mHandler.mergeAnimation(merging.mToken, merging.mInfo, merging.mStartT,
-                playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb));
+                + " in case they can be merged", ready.mToken, playing.mToken);
+        playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT,
+                playing.mToken, (wct, cb) -> onMerged(playing, ready));
+    }
+
+    private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) {
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged %s",
+                merged.mToken);
+        int readyIdx = 0;
+        if (mReadyTransitions.isEmpty() || mReadyTransitions.get(0) != merged) {
+            Log.e(TAG, "Merged transition out-of-order?");
+            readyIdx = mReadyTransitions.indexOf(merged);
+            if (readyIdx < 0) {
+                Log.e(TAG, "Merged a transition that is no-longer queued?");
+                return;
+            }
+        }
+        mReadyTransitions.remove(readyIdx);
+        if (playing.mMerged == null) {
+            playing.mMerged = new ArrayList<>();
+        }
+        playing.mMerged.add(merged);
+        // if it was aborted, then onConsumed has already been reported.
+        if (merged.mHandler != null && !merged.mAborted) {
+            merged.mHandler.onTransitionConsumed(merged.mToken, false /* abort */, merged.mFinishT);
+        }
+        for (int i = 0; i < mObservers.size(); ++i) {
+            mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken);
+        }
+        // See if we should merge another transition.
+        processReadyQueue();
     }
 
     private void playTransition(@NonNull ActiveTransition active) {
@@ -594,7 +675,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.mToken, wct, cb));
+                    active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active, wct, cb));
             if (consumed) {
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
                 return;
@@ -602,7 +683,7 @@
         }
         // Otherwise give every other handler a chance
         active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
-                active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb), active.mHandler);
+                active.mFinishT, (wct, cb) -> onFinish(active, wct, cb), active.mHandler);
     }
 
     /**
@@ -645,15 +726,28 @@
         return null;
     }
 
-    /** Special version of finish just for dealing with no-op/invalid transitions. */
-    private void onAbort(IBinder transition) {
-        onFinish(transition, null /* wct */, null /* wctCB */, true /* abort */);
-    }
+    /** Aborts a transition. This will still queue it up to maintain order. */
+    private void onAbort(ActiveTransition transition) {
+        // apply immediately since they may be "parallel" operations: We currently we use abort for
+        // thing which are independent to other transitions (like starting-window transfer).
+        transition.mStartT.apply();
+        transition.mFinishT.apply();
+        transition.mAborted = true;
 
-    private void onFinish(IBinder transition,
-            @Nullable WindowContainerTransaction wct,
-            @Nullable WindowContainerTransactionCallback wctCB) {
-        onFinish(transition, wct, wctCB, false /* abort */);
+        if (transition.mHandler != null) {
+            // Notifies to clean-up the aborted transition.
+            transition.mHandler.onTransitionConsumed(
+                    transition.mToken, true /* aborted */, null /* finishTransaction */);
+        }
+
+        releaseSurfaces(transition.mInfo);
+
+        // This still went into the queue (to maintain the correct finish ordering).
+        if (mReadyTransitions.size() > 1) {
+            // There are already transitions waiting in the queue, so just return.
+            return;
+        }
+        processReadyQueue();
     }
 
     /**
@@ -665,167 +759,97 @@
         info.releaseAnimSurfaces();
     }
 
-    private void onFinish(IBinder transition,
+    private void onFinish(ActiveTransition active,
             @Nullable WindowContainerTransaction wct,
-            @Nullable WindowContainerTransactionCallback wctCB,
-            boolean abort) {
-        int activeIdx = findActiveTransition(transition);
+            @Nullable WindowContainerTransactionCallback wctCB) {
+        int activeIdx = mActiveTransitions.indexOf(active);
         if (activeIdx < 0) {
             Log.e(TAG, "Trying to finish a non-running transition. Either remote crashed or "
-                    + " a handler didn't properly deal with a merge.", new RuntimeException());
+                    + " a handler didn't properly deal with a merge. " + active.mToken,
+                    new RuntimeException());
             return;
-        } else if (activeIdx > 0) {
-            // This transition was merged.
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged (abort=%b:"
-                    + " %s", abort, transition);
-            final ActiveTransition active = mActiveTransitions.get(activeIdx);
-            active.mMerged = true;
-            active.mAborted = abort;
-            if (active.mHandler != null) {
-                active.mHandler.onTransitionConsumed(
-                        active.mToken, abort, abort ? null : active.mFinishT);
-            }
-            for (int i = 0; i < mObservers.size(); ++i) {
-                mObservers.get(i).onTransitionMerged(
-                        active.mToken, mActiveTransitions.get(0).mToken);
-            }
-            return;
+        } else if (activeIdx != 0) {
+            // Relevant right now since we only allow 1 active transition at a time.
+            Log.e(TAG, "Finishing a transition out of order. " + active.mToken);
         }
-        final ActiveTransition active = mActiveTransitions.get(activeIdx);
-        active.mAborted = abort;
-        if (active.mAborted && active.mHandler != null) {
-            // Notifies to clean-up the aborted transition.
-            active.mHandler.onTransitionConsumed(
-                    transition, true /* aborted */, null /* finishTransaction */);
-        }
+        mActiveTransitions.remove(activeIdx);
+
         for (int i = 0; i < mObservers.size(); ++i) {
             mObservers.get(i).onTransitionFinished(active.mToken, active.mAborted);
         }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
-                "Transition animation finished (abort=%b), notifying core %s", abort, transition);
+        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition animation finished "
+                + "(aborted=%b), notifying core %s", active.mAborted, active.mToken);
         if (active.mStartT != null) {
             // Applied by now, so clear immediately to remove any references. Do not set to null
             // yet, though, since nullness is used later to disambiguate malformed transitions.
             active.mStartT.clear();
         }
-        // Merge all relevant transactions together
+        // Merge all associated transactions together
         SurfaceControl.Transaction fullFinish = active.mFinishT;
-        for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
-            final ActiveTransition toMerge = mActiveTransitions.get(iA);
-            if (!toMerge.mMerged) break;
-            // Include start. It will be a no-op if it was already applied. Otherwise, we need it
-            // to maintain consistent state.
-            if (toMerge.mStartT != null) {
-                if (fullFinish == null) {
-                    fullFinish = toMerge.mStartT;
-                } else {
-                    fullFinish.merge(toMerge.mStartT);
+        if (active.mMerged != null) {
+            for (int iM = 0; iM < active.mMerged.size(); ++iM) {
+                final ActiveTransition toMerge = active.mMerged.get(iM);
+                // Include start. It will be a no-op if it was already applied. Otherwise, we need
+                // it to maintain consistent state.
+                if (toMerge.mStartT != null) {
+                    if (fullFinish == null) {
+                        fullFinish = toMerge.mStartT;
+                    } else {
+                        fullFinish.merge(toMerge.mStartT);
+                    }
                 }
-            }
-            if (toMerge.mFinishT != null) {
-                if (fullFinish == null) {
-                    fullFinish = toMerge.mFinishT;
-                } else {
-                    fullFinish.merge(toMerge.mFinishT);
+                if (toMerge.mFinishT != null) {
+                    if (fullFinish == null) {
+                        fullFinish = toMerge.mFinishT;
+                    } else {
+                        fullFinish.merge(toMerge.mFinishT);
+                    }
                 }
             }
         }
         if (fullFinish != null) {
             fullFinish.apply();
         }
-        // Now perform all the finishes.
+        // Now perform all the finish callbacks (starting with the playing one and then all the
+        // transitions merged into it).
         releaseSurfaces(active.mInfo);
-        mActiveTransitions.remove(activeIdx);
-        mOrganizer.finishTransition(transition, wct, wctCB);
-        while (activeIdx < mActiveTransitions.size()) {
-            if (!mActiveTransitions.get(activeIdx).mMerged) break;
-            ActiveTransition merged = mActiveTransitions.remove(activeIdx);
-            mOrganizer.finishTransition(merged.mToken, null /* wct */, null /* wctCB */);
-            releaseSurfaces(merged.mInfo);
-        }
-        // sift through aborted transitions
-        while (mActiveTransitions.size() > activeIdx
-                && mActiveTransitions.get(activeIdx).mAborted) {
-            ActiveTransition aborted = mActiveTransitions.remove(activeIdx);
-            // Notifies to clean-up the aborted transition.
-            if (aborted.mHandler != null) {
-                aborted.mHandler.onTransitionConsumed(
-                        transition, true /* aborted */, null /* finishTransaction */);
+        mOrganizer.finishTransition(active.mToken, wct, wctCB);
+        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 */);
+                releaseSurfaces(merged.mInfo);
             }
-            mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
-            for (int i = 0; i < mObservers.size(); ++i) {
-                mObservers.get(i).onTransitionFinished(aborted.mToken, true);
-            }
-            releaseSurfaces(aborted.mInfo);
-        }
-        if (mActiveTransitions.size() <= activeIdx) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition animations "
-                    + "finished");
-            // Run all runnables from the run-when-idle queue.
-            for (int i = 0; i < mRunWhenIdleQueue.size(); i++) {
-                mRunWhenIdleQueue.get(i).run();
-            }
-            mRunWhenIdleQueue.clear();
-            return;
-        }
-        // Start animating the next active transition
-        final ActiveTransition next = mActiveTransitions.get(activeIdx);
-        if (next.mInfo == null) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transition after one"
-                    + " finished, but it isn't ready yet.");
-            return;
-        }
-        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Pending transitions after one"
-                + " finished, so start the next one.");
-        playTransition(next);
-        // Now try to merge the rest of the transitions (re-acquire activeIdx since next may have
-        // finished immediately)
-        activeIdx = findActiveTransition(next.mToken);
-        if (activeIdx < 0) {
-            // This means 'next' finished immediately and thus re-entered this function. Since
-            // that is the case, just return here since all relevant logic has already run in the
-            // re-entered call.
-            return;
+            active.mMerged.clear();
         }
 
-        // This logic is also convoluted because 'next' may finish immediately in response to any of
-        // the merge requests (eg. if it decided to "cancel" itself).
-        int mergeIdx = activeIdx + 1;
-        while (mergeIdx < mActiveTransitions.size()) {
-            ActiveTransition mergeCandidate = mActiveTransitions.get(mergeIdx);
-            if (mergeCandidate.mAborted) {
-                // transition was aborted, so we can skip for now (still leave it in the list
-                // so that it gets cleaned-up in the right order).
-                ++mergeIdx;
-                continue;
-            }
-            if (mergeCandidate.mInfo == null) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition merge candidate"
-                        + " %s is not ready yet", mergeCandidate.mToken);
-                // The later transition should not be merged if the prior one is not ready.
-                return;
-            }
-            if (mergeCandidate.mMerged) {
-                throw new IllegalStateException("Can't merge a transition after not-merging"
-                        + " a preceding one.");
-            }
-            attemptMergeTransition(next, mergeCandidate);
-            mergeIdx = findActiveTransition(mergeCandidate.mToken);
-            if (mergeIdx < 0) {
-                // This means 'next' finished immediately and thus re-entered this function. Since
-                // that is the case, just return here since all relevant logic has already run in
-                // the re-entered call.
-                return;
-            }
-            ++mergeIdx;
+        // Now that this is done, check the ready queue for more work.
+        processReadyQueue();
+    }
+
+    private boolean isTransitionKnown(IBinder token) {
+        for (int i = 0; i < mPendingTransitions.size(); ++i) {
+            if (mPendingTransitions.get(i).mToken == token) return true;
         }
+        for (int i = 0; i < mReadyTransitions.size(); ++i) {
+            if (mReadyTransitions.get(i).mToken == token) return true;
+        }
+        for (int i = 0; i < mActiveTransitions.size(); ++i) {
+            final ActiveTransition active = mActiveTransitions.get(i);
+            if (active.mToken == token) return true;
+            if (active.mMerged == null) continue;
+            for (int m = 0; m < active.mMerged.size(); ++m) {
+                if (active.mMerged.get(m).mToken == token) return true;
+            }
+        }
+        return false;
     }
 
     void requestStartTransition(@NonNull IBinder transitionToken,
             @Nullable TransitionRequestInfo request) {
         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition requested: %s %s",
                 transitionToken, request);
-        if (findActiveTransition(transitionToken) >= 0) {
+        if (isTransitionKnown(transitionToken)) {
             throw new RuntimeException("Transition already started " + transitionToken);
         }
         final ActiveTransition active = new ActiveTransition();
@@ -858,15 +882,13 @@
         }
         mOrganizer.startTransition(transitionToken, wct != null && wct.isEmpty() ? null : wct);
         active.mToken = transitionToken;
-        int insertIdx = 0;
-        for (; insertIdx < mActiveTransitions.size(); ++insertIdx) {
-            if (mActiveTransitions.get(insertIdx).mInfo == null) {
-                // A `startNewTransition` was sent to WMCore, but wasn't acknowledged before WMCore
-                // made this request, so insert this request beforehand to keep order in sync.
-                break;
-            }
-        }
-        mActiveTransitions.add(insertIdx, active);
+        // Currently, WMCore only does one transition at a time. If it makes a requestStart, it
+        // is already collecting that transition on core-side, so it will be the next one to
+        // become ready. There may already be pending transitions added as part of direct
+        // `startNewTransition` but if we have a request now, it means WM created the request
+        // transition before it acknowledged any of the pending `startNew` transitions. So, insert
+        // it at the front.
+        mPendingTransitions.add(0, active);
     }
 
     /** Start a new transition directly. */
@@ -875,7 +897,7 @@
         final ActiveTransition active = new ActiveTransition();
         active.mHandler = handler;
         active.mToken = mOrganizer.startNewTransition(type, wct);
-        mActiveTransitions.add(active);
+        mPendingTransitions.add(active);
         return active.mToken;
     }
 
@@ -894,27 +916,38 @@
      * @param forceFinish When non-null, this is the transition that we last sent the SLEEP merge
      *                    signal to -- so it will be force-finished if it's still running.
      */
-    private void finishForSleep(@Nullable IBinder forceFinish) {
-        if (mActiveTransitions.isEmpty() || mSleepHandler.mSleepTransitions.isEmpty()) {
+    private void finishForSleep(@Nullable ActiveTransition forceFinish) {
+        if ((mActiveTransitions.isEmpty() && mReadyTransitions.isEmpty())
+                || mSleepHandler.mSleepTransitions.isEmpty()) {
+            // Done finishing things.
+            // Prevent any weird leaks... shouldn't happen though.
+            mSleepHandler.mSleepTransitions.clear();
             return;
         }
-        if (forceFinish != null && mActiveTransitions.get(0).mToken == forceFinish) {
+        if (forceFinish != null && mActiveTransitions.contains(forceFinish)) {
             Log.e(TAG, "Forcing transition to finish due to sleep timeout: "
-                    + mActiveTransitions.get(0).mToken);
-            onFinish(mActiveTransitions.get(0).mToken, null, null, true);
+                    + forceFinish.mToken);
+            forceFinish.mAborted = true;
+            // Last notify of it being consumed. Note: mHandler should never be null,
+            // but check just to be safe.
+            if (forceFinish.mHandler != null) {
+                forceFinish.mHandler.onTransitionConsumed(
+                        forceFinish.mToken, true /* aborted */, null /* finishTransaction */);
+            }
+            onFinish(forceFinish, null, null);
         }
         final SurfaceControl.Transaction dummyT = new SurfaceControl.Transaction();
         while (!mActiveTransitions.isEmpty() && !mSleepHandler.mSleepTransitions.isEmpty()) {
             final ActiveTransition playing = mActiveTransitions.get(0);
-            int sleepIdx = findActiveTransition(mSleepHandler.mSleepTransitions.get(0));
+            int sleepIdx = findByToken(mReadyTransitions, mSleepHandler.mSleepTransitions.get(0));
             if (sleepIdx >= 0) {
                 // Try to signal that we are sleeping by attempting to merge the sleep transition
                 // into the playing one.
-                final ActiveTransition nextSleep = mActiveTransitions.get(sleepIdx);
+                final ActiveTransition nextSleep = mReadyTransitions.get(sleepIdx);
                 playing.mHandler.mergeAnimation(nextSleep.mToken, nextSleep.mInfo, dummyT,
                         playing.mToken, (wct, cb) -> {});
             } else {
-                Log.e(TAG, "Couldn't find sleep transition in active list: "
+                Log.e(TAG, "Couldn't find sleep transition in ready list: "
                         + mSleepHandler.mSleepTransitions.get(0));
             }
             // it's possible to complete immediately. If that happens, just repeat the signal
@@ -922,8 +955,7 @@
             // finishing immediately.
             if (!mActiveTransitions.isEmpty() && mActiveTransitions.get(0) == playing) {
                 // Give it a (very) short amount of time to process it before forcing.
-                mMainExecutor.executeDelayed(
-                        () -> finishForSleep(playing.mToken), SLEEP_ALLOWANCE_MS);
+                mMainExecutor.executeDelayed(() -> finishForSleep(playing), SLEEP_ALLOWANCE_MS);
                 break;
             }
         }
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 5a4a44f..6dae479 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
@@ -422,7 +422,7 @@
         RemoteAnimationTarget animationTarget = createAnimationTarget();
         RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
         if (mController.mBackAnimationAdapter != null) {
-            mController.mBackAnimationAdapter.getRunner().onAnimationStart(type,
+            mController.mBackAnimationAdapter.getRunner().onAnimationStart(
                     targets, null, null, mBackAnimationFinishedCallback);
             mShellExecutor.flushAll();
         }
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
new file mode 100644
index 0000000..2814ef9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomizeActivityAnimationTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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 static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.app.WindowConfiguration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.RemoteException;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Choreographer;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.Animation;
+import android.window.BackNavigationInfo;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class CustomizeActivityAnimationTest extends ShellTestCase {
+    private static final int BOUND_SIZE = 100;
+    @Mock
+    private BackAnimationBackground mBackAnimationBackground;
+    @Mock
+    private Animation mMockCloseAnimation;
+    @Mock
+    private Animation mMockOpenAnimation;
+
+    private CustomizeActivityAnimation mCustomizeActivityAnimation;
+
+    @Before
+    public void setUp() throws Exception {
+        mCustomizeActivityAnimation = new CustomizeActivityAnimation(mContext,
+                mBackAnimationBackground, mock(SurfaceControl.Transaction.class),
+                mock(Choreographer.class));
+        spyOn(mCustomizeActivityAnimation);
+        spyOn(mCustomizeActivityAnimation.mCustomAnimationLoader);
+        doReturn(mMockCloseAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+                .load(any(), eq(false));
+        doReturn(mMockOpenAnimation).when(mCustomizeActivityAnimation.mCustomAnimationLoader)
+                .load(any(), eq(true));
+    }
+
+    RemoteAnimationTarget createAnimationTarget(boolean open) {
+        SurfaceControl topWindowLeash = new SurfaceControl();
+        return new RemoteAnimationTarget(1,
+                open ? RemoteAnimationTarget.MODE_OPENING : RemoteAnimationTarget.MODE_CLOSING,
+                topWindowLeash, false, new Rect(), new Rect(), -1,
+                new Point(0, 0), new Rect(0, 0, BOUND_SIZE, BOUND_SIZE), new Rect(),
+                new WindowConfiguration(), true, null, null, null, false, -1);
+    }
+
+    @Test
+    public void receiveFinishAfterInvoke() throws InterruptedException {
+        mCustomizeActivityAnimation.prepareNextAnimation(
+                new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
+        final RemoteAnimationTarget close = createAnimationTarget(false);
+        final RemoteAnimationTarget open = createAnimationTarget(true);
+        // 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);
+        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();
+        } catch (RemoteException r) {
+            fail("onBackInvoked throw remote exception");
+        }
+        verify(mCustomizeActivityAnimation).onGestureCommitted();
+        finishCalled.await(1, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void receiveFinishAfterCancel() throws InterruptedException {
+        mCustomizeActivityAnimation.prepareNextAnimation(
+                new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
+        final RemoteAnimationTarget close = createAnimationTarget(false);
+        final RemoteAnimationTarget open = createAnimationTarget(true);
+        // 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);
+        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();
+        } catch (RemoteException r) {
+            fail("onBackCancelled throw remote exception");
+        }
+        finishCalled.await(1, TimeUnit.SECONDS);
+    }
+
+    @Test
+    public void receiveFinishWithoutAnimationAfterInvoke() throws InterruptedException {
+        mCustomizeActivityAnimation.prepareNextAnimation(
+                new BackNavigationInfo.CustomAnimationInfo("TestPackage"));
+        // 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);
+
+        try {
+            mCustomizeActivityAnimation.mBackAnimationRunner.getCallback().onBackInvoked();
+        } catch (RemoteException r) {
+            fail("onBackInvoked throw remote exception");
+        }
+        verify(mCustomizeActivityAnimation).onGestureCommitted();
+        finishCalled.await(1, TimeUnit.SECONDS);
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
new file mode 100644
index 0000000..96d202c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TabletopModeControllerTest.java
@@ -0,0 +1,270 @@
+/*
+ * 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;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_CLOSED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
+import static com.android.wm.shell.common.DevicePostureController.DEVICE_POSTURE_OPENED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.Surface;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link TabletopModeController}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class TabletopModeControllerTest extends ShellTestCase {
+    // It's considered tabletop mode if the display rotation angle matches what's in this array.
+    // It's defined as com.android.internal.R.array.config_deviceTabletopRotations on real devices.
+    private static final int[] TABLETOP_MODE_ROTATIONS = new int[] {
+            90 /* Surface.ROTATION_90 */,
+            270 /* Surface.ROTATION_270 */
+    };
+
+    private TestShellExecutor mMainExecutor;
+
+    private Configuration mConfiguration;
+
+    private TabletopModeController mPipTabletopController;
+
+    @Mock
+    private Context mContext;
+
+    @Mock
+    private ShellInit mShellInit;
+
+    @Mock
+    private Resources mResources;
+
+    @Mock
+    private DevicePostureController mDevicePostureController;
+
+    @Mock
+    private DisplayController mDisplayController;
+
+    @Mock
+    private TabletopModeController.OnTabletopModeChangedListener mOnTabletopModeChangedListener;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mResources.getIntArray(com.android.internal.R.array.config_deviceTabletopRotations))
+                .thenReturn(TABLETOP_MODE_ROTATIONS);
+        when(mContext.getResources()).thenReturn(mResources);
+        mMainExecutor = new TestShellExecutor();
+        mConfiguration = new Configuration();
+        mPipTabletopController = new TabletopModeController(mContext, mShellInit,
+                mDevicePostureController, mDisplayController, mMainExecutor);
+        mPipTabletopController.onInit();
+    }
+
+    @Test
+    public void instantiateController_addInitCallback() {
+        verify(mShellInit, times(1)).addInitCallback(any(), eq(mPipTabletopController));
+    }
+
+    @Test
+    public void registerOnTabletopModeChangedListener_notInTabletopMode_callbackFalse() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.registerOnTabletopModeChangedListener(
+                mOnTabletopModeChangedListener);
+
+        verify(mOnTabletopModeChangedListener, times(1))
+                .onTabletopModeChanged(false);
+    }
+
+    @Test
+    public void registerOnTabletopModeChangedListener_inTabletopMode_callbackTrue() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.registerOnTabletopModeChangedListener(
+                mOnTabletopModeChangedListener);
+
+        verify(mOnTabletopModeChangedListener, times(1))
+                .onTabletopModeChanged(true);
+    }
+
+    @Test
+    public void registerOnTabletopModeChangedListener_notInTabletopModeTwice_callbackOnce() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.registerOnTabletopModeChangedListener(
+                mOnTabletopModeChangedListener);
+        clearInvocations(mOnTabletopModeChangedListener);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        verifyZeroInteractions(mOnTabletopModeChangedListener);
+    }
+
+    // Test cases starting from folded state (DEVICE_POSTURE_CLOSED)
+    @Test
+    public void foldedRotation90_halfOpen_scheduleTabletopModeChange() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+        assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+    }
+
+    @Test
+    public void foldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+        assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+    }
+
+    @Test
+    public void foldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+        assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+    }
+
+    @Test
+    public void foldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+        assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+    }
+
+    @Test
+    public void foldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+    }
+
+    // Test cases starting from unfolded state (DEVICE_POSTURE_OPENED)
+    @Test
+    public void unfoldedRotation90_halfOpen_scheduleTabletopModeChange() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+        assertTrue(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+    }
+
+    @Test
+    public void unfoldedRotation0_halfOpen_noScheduleTabletopModeChange() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+
+        assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+    }
+
+    @Test
+    public void unfoldedRotation90_halfOpenThenUnfold_cancelTabletopModeChange() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+
+        assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+    }
+
+    @Test
+    public void unfoldedRotation90_halfOpenThenFold_cancelTabletopModeChange() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_CLOSED);
+
+        assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+    }
+
+    @Test
+    public void unfoldedRotation90_halfOpenThenRotate_cancelTabletopModeChange() {
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_OPENED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_90);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        mPipTabletopController.onDevicePostureChanged(DEVICE_POSTURE_HALF_OPENED);
+        mConfiguration.windowConfiguration.setDisplayRotation(Surface.ROTATION_0);
+        mPipTabletopController.onDisplayConfigurationChanged(DEFAULT_DISPLAY, mConfiguration);
+
+        assertFalse(mMainExecutor.hasCallback(mPipTabletopController.mOnEnterTabletopModeCallback));
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 11fda8b..bf62acf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -24,8 +24,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MAX_ANIMATION_DURATION;
-import static com.android.wm.shell.startingsurface.StartingSurfaceDrawer.MINIMAL_ANIMATION_DURATION;
+import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MAX_ANIMATION_DURATION;
+import static com.android.wm.shell.startingsurface.SplashscreenContentDrawer.MINIMAL_ANIMATION_DURATION;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -56,11 +56,9 @@
 import android.os.Looper;
 import android.os.UserHandle;
 import android.testing.TestableContext;
-import android.view.Display;
 import android.view.IWindowSession;
 import android.view.InsetsState;
 import android.view.Surface;
-import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.WindowMetrics;
@@ -106,36 +104,7 @@
     private ShellExecutor mTestExecutor;
     private final TestableContext mTestContext = new TestContext(
             InstrumentationRegistry.getInstrumentation().getTargetContext());
-    TestStartingSurfaceDrawer mStartingSurfaceDrawer;
-
-    static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{
-        int mAddWindowForTask = 0;
-
-        TestStartingSurfaceDrawer(Context context, ShellExecutor splashScreenExecutor,
-                IconProvider iconProvider, TransactionPool pool) {
-            super(context, splashScreenExecutor, iconProvider, pool);
-        }
-
-        @Override
-        protected boolean addWindow(int taskId, IBinder appToken, View view, Display display,
-                WindowManager.LayoutParams params, int suggestType) {
-            // listen for addView
-            mAddWindowForTask = taskId;
-            saveSplashScreenRecord(appToken, taskId, view, suggestType);
-            // Do not wait for background color
-            return false;
-        }
-
-        @Override
-        protected void removeWindowSynced(StartingWindowRemovalInfo removalInfo,
-                boolean immediately) {
-            // listen for removeView
-            if (mAddWindowForTask == removalInfo.taskId) {
-                mAddWindowForTask = 0;
-            }
-            mStartingWindowRecords.remove(removalInfo.taskId);
-        }
-    }
+    StartingSurfaceDrawer mStartingSurfaceDrawer;
 
     private static class TestContext extends TestableContext {
         TestContext(Context context) {
@@ -165,44 +134,51 @@
         doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
         doNothing().when(mMockWindowManager).addView(any(), any());
         mTestExecutor = new HandlerExecutor(mTestHandler);
+        mStartingSurfaceDrawer = new StartingSurfaceDrawer(mTestContext, mTestExecutor,
+                mIconProvider, mTransactionPool);
         mStartingSurfaceDrawer = spy(
-                new TestStartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider,
+                new StartingSurfaceDrawer(mTestContext, mTestExecutor, mIconProvider,
                         mTransactionPool));
+        spyOn(mStartingSurfaceDrawer.mSplashscreenWindowCreator);
+        spyOn(mStartingSurfaceDrawer.mWindowRecords);
+        spyOn(mStartingSurfaceDrawer.mWindowlessRecords);
     }
 
     @Test
     public void testAddSplashScreenSurface() {
         final int taskId = 1;
         final StartingWindowInfo windowInfo =
-                createWindowInfo(taskId, android.R.style.Theme);
-        mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
+                createWindowInfo(taskId, android.R.style.Theme, mBinder);
+        mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo,
                 STARTING_WINDOW_TYPE_SPLASH_SCREEN);
         waitHandlerIdle(mTestHandler);
-        verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(),
+        verify(mStartingSurfaceDrawer.mSplashscreenWindowCreator).addWindow(
+                eq(taskId), eq(mBinder), any(), any(), any(),
                 eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
-        assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
 
         StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
         removalInfo.taskId = windowInfo.taskInfo.taskId;
         mStartingSurfaceDrawer.removeStartingWindow(removalInfo);
         waitHandlerIdle(mTestHandler);
-        verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(false));
-        assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0);
+        verify(mStartingSurfaceDrawer.mWindowRecords).removeWindow(any(), eq(false));
+        assertEquals(mStartingSurfaceDrawer.mWindowRecords.recordSize(), 0);
     }
 
     @Test
     public void testFallbackDefaultTheme() {
         final int taskId = 1;
         final StartingWindowInfo windowInfo =
-                createWindowInfo(taskId, 0);
+                createWindowInfo(taskId, 0, mBinder);
         final int[] theme = new int[1];
         doAnswer(invocation -> theme[0] = (Integer) invocation.callRealMethod())
-                .when(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
+                .when(mStartingSurfaceDrawer.mSplashscreenWindowCreator)
+                .getSplashScreenTheme(eq(0), any());
 
-        mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
+        mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo,
                 STARTING_WINDOW_TYPE_SPLASH_SCREEN);
         waitHandlerIdle(mTestHandler);
-        verify(mStartingSurfaceDrawer).getSplashScreenTheme(eq(0), any());
+        verify(mStartingSurfaceDrawer.mSplashscreenWindowCreator)
+                .getSplashScreenTheme(eq(0), any());
         assertNotEquals(theme[0], 0);
     }
 
@@ -241,7 +217,7 @@
     public void testRemoveTaskSnapshotWithImeSurfaceWhenOnImeDrawn() throws Exception {
         final int taskId = 1;
         final StartingWindowInfo windowInfo =
-                createWindowInfo(taskId, android.R.style.Theme);
+                createWindowInfo(taskId, android.R.style.Theme, mBinder);
         TaskSnapshot snapshot = createTaskSnapshot(100, 100, new Point(100, 100),
                 new Rect(0, 0, 0, 50), true /* hasImeSurface */);
         final IWindowSession session = WindowManagerGlobal.getWindowSession();
@@ -270,7 +246,7 @@
             when(TaskSnapshotWindow.create(eq(windowInfo), eq(mBinder), eq(snapshot), any(),
                     any())).thenReturn(mockSnapshotWindow);
             // Simulate a task snapshot window created with IME snapshot shown.
-            mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, mBinder, snapshot);
+            mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot);
             waitHandlerIdle(mTestHandler);
 
             // Verify the task snapshot with IME snapshot will be removed when received the real IME
@@ -278,27 +254,36 @@
             // makeTaskSnapshotWindow shall call removeWindowSynced before there add a new
             // StartingWindowRecord for the task.
             mStartingSurfaceDrawer.onImeDrawnOnTask(1);
-            verify(mStartingSurfaceDrawer, times(2))
-                    .removeWindowSynced(any(), eq(true));
+            verify(mStartingSurfaceDrawer.mWindowRecords, times(2))
+                    .removeWindow(any(), eq(true));
         }
     }
 
     @Test
     public void testClearAllWindows() {
         final int taskId = 1;
-        final StartingWindowInfo windowInfo =
-                createWindowInfo(taskId, android.R.style.Theme);
-        mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, mBinder,
-                STARTING_WINDOW_TYPE_SPLASH_SCREEN);
-        waitHandlerIdle(mTestHandler);
-        verify(mStartingSurfaceDrawer).addWindow(eq(taskId), eq(mBinder), any(), any(), any(),
-                eq(STARTING_WINDOW_TYPE_SPLASH_SCREEN));
-        assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
+        mStartingSurfaceDrawer.mWindowRecords.addRecord(taskId,
+                new StartingSurfaceDrawer.StartingWindowRecord() {
+                    @Override
+                    public void removeIfPossible(StartingWindowRemovalInfo info,
+                            boolean immediately) {
 
+                    }
+                });
+        mStartingSurfaceDrawer.mWindowlessRecords.addRecord(taskId,
+                new StartingSurfaceDrawer.StartingWindowRecord() {
+                    @Override
+                    public void removeIfPossible(StartingWindowRemovalInfo info,
+                            boolean immediately) {
+
+                    }
+                });
         mStartingSurfaceDrawer.clearAllWindows();
         waitHandlerIdle(mTestHandler);
-        verify(mStartingSurfaceDrawer).removeWindowSynced(any(), eq(true));
-        assertEquals(mStartingSurfaceDrawer.mStartingWindowRecords.size(), 0);
+        verify(mStartingSurfaceDrawer.mWindowRecords).removeWindow(any(), eq(true));
+        assertEquals(mStartingSurfaceDrawer.mWindowRecords.recordSize(), 0);
+        verify(mStartingSurfaceDrawer.mWindowlessRecords).removeWindow(any(), eq(true));
+        assertEquals(mStartingSurfaceDrawer.mWindowlessRecords.recordSize(), 0);
     }
 
     @Test
@@ -351,7 +336,7 @@
                 longAppDuration, longAppDuration));
     }
 
-    private StartingWindowInfo createWindowInfo(int taskId, int themeResId) {
+    private StartingWindowInfo createWindowInfo(int taskId, int themeResId, IBinder appToken) {
         StartingWindowInfo windowInfo = new StartingWindowInfo();
         final ActivityInfo info = new ActivityInfo();
         info.applicationInfo = new ApplicationInfo();
@@ -360,6 +345,7 @@
         final ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
         taskInfo.topActivityInfo = info;
         taskInfo.taskId = taskId;
+        windowInfo.appToken = appToken;
         windowInfo.targetActivityInfo = info;
         windowInfo.taskInfo = taskInfo;
         windowInfo.topOpaqueWindowInsetsState = new InsetsState();
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 e63bbeb..44f1f01 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
@@ -62,6 +62,7 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.util.ArraySet;
 import android.view.Surface;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
@@ -99,6 +100,7 @@
 import org.mockito.InOrder;
 
 import java.util.ArrayList;
+import java.util.function.Function;
 
 /**
  * Tests for the shell transitions.
@@ -591,6 +593,68 @@
     }
 
     @Test
+    public void testInterleavedMerging() {
+        Transitions transitions = createTestTransitions();
+        transitions.replaceDefaultHandlerForTest(mDefaultHandler);
+
+        Function<Boolean, IBinder> startATransition = (doMerge) -> {
+            IBinder token = new Binder();
+            if (doMerge) {
+                mDefaultHandler.setShouldMerge(token);
+            }
+            transitions.requestStartTransition(token,
+                    new TransitionRequestInfo(TRANSIT_OPEN, null /* trigger */, null /* remote */));
+            TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+                    .addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
+            transitions.onTransitionReady(token, info, mock(SurfaceControl.Transaction.class),
+                    mock(SurfaceControl.Transaction.class));
+            return token;
+        };
+
+        IBinder transitToken1 = startATransition.apply(false);
+        // merge first one
+        IBinder transitToken2 = startATransition.apply(true);
+        assertEquals(1, mDefaultHandler.activeCount());
+        assertEquals(1, mDefaultHandler.mergeCount());
+
+        // don't merge next one
+        IBinder transitToken3 = startATransition.apply(false);
+        // make sure nothing happened (since it wasn't merged)
+        assertEquals(1, mDefaultHandler.activeCount());
+        assertEquals(1, mDefaultHandler.mergeCount());
+
+        // make a mergable
+        IBinder transitToken4 = startATransition.apply(true);
+        // make sure nothing happened since there is a non-mergable pending.
+        assertEquals(1, mDefaultHandler.activeCount());
+        assertEquals(1, mDefaultHandler.mergeCount());
+
+        // Queue up another mergable
+        IBinder transitToken5 = startATransition.apply(true);
+
+        // Queue up a non-mergable
+        IBinder transitToken6 = startATransition.apply(false);
+
+        // Our active now looks like: [playing, merged]
+        //           and ready queue: [non-mergable, mergable, mergable, non-mergable]
+        // finish the playing one
+        mDefaultHandler.finishOne();
+        mMainExecutor.flushAll();
+        // Now we should have the non-mergable playing now with 2 merged:
+        //    active: [playing, merged, merged]   queue: [non-mergable]
+        assertEquals(1, mDefaultHandler.activeCount());
+        assertEquals(2, mDefaultHandler.mergeCount());
+
+        mDefaultHandler.finishOne();
+        mMainExecutor.flushAll();
+        assertEquals(1, mDefaultHandler.activeCount());
+        assertEquals(0, mDefaultHandler.mergeCount());
+
+        mDefaultHandler.finishOne();
+        mMainExecutor.flushAll();
+    }
+
+    @Test
     public void testTransitionOrderMatchesCore() {
         Transitions transitions = createTestTransitions();
         transitions.replaceDefaultHandlerForTest(mDefaultHandler);
@@ -1016,6 +1080,7 @@
         ArrayList<Transitions.TransitionFinishCallback> mFinishes = new ArrayList<>();
         final ArrayList<IBinder> mMerged = new ArrayList<>();
         boolean mSimulateMerge = false;
+        final ArraySet<IBinder> mShouldMerge = new ArraySet<>();
 
         @Override
         public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@@ -1030,7 +1095,7 @@
         public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
                 @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
                 @NonNull Transitions.TransitionFinishCallback finishCallback) {
-            if (!mSimulateMerge) return;
+            if (!(mSimulateMerge || mShouldMerge.contains(transition))) return;
             mMerged.add(transition);
             finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
         }
@@ -1046,12 +1111,23 @@
             mSimulateMerge = sim;
         }
 
+        void setShouldMerge(IBinder toMerge) {
+            mShouldMerge.add(toMerge);
+        }
+
         void finishAll() {
             final ArrayList<Transitions.TransitionFinishCallback> finishes = mFinishes;
             mFinishes = new ArrayList<>();
             for (int i = finishes.size() - 1; i >= 0; --i) {
                 finishes.get(i).onTransitionFinished(null /* wct */, null /* wctCB */);
             }
+            mShouldMerge.clear();
+        }
+
+        void finishOne() {
+            Transitions.TransitionFinishCallback fin = mFinishes.remove(0);
+            mMerged.clear();
+            fin.onTransitionFinished(null /* wct */, null /* wctCB */);
         }
 
         int activeCount() {
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
index 8266beb..9a06be0 100644
--- a/libs/hwui/hwui/ImageDecoder.cpp
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -498,7 +498,7 @@
     return result;
 }
 
-SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination) {
+SkCodec::Result ImageDecoder::extractGainmap(Bitmap* destination, bool isShared) {
     ATRACE_CALL();
     SkGainmapInfo gainmapInfo;
     std::unique_ptr<SkStream> gainmapStream;
@@ -553,9 +553,12 @@
         return SkCodec::kInternalError;
     }
 
-    // TODO: We don't currently parcel the gainmap, but if we should then also support
-    // the shared allocator
-    sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+    sk_sp<Bitmap> nativeBitmap;
+    if (isShared) {
+        nativeBitmap = Bitmap::allocateAshmemBitmap(&bm);
+    } else {
+        nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+    }
     if (!nativeBitmap) {
         ALOGE("OOM allocating Bitmap with dimensions %i x %i", bitmapInfo.width(),
               bitmapInfo.height());
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
index 97573e1..b3781b5 100644
--- a/libs/hwui/hwui/ImageDecoder.h
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -79,7 +79,7 @@
     // Set whether the ImageDecoder should handle RestorePrevious frames.
     void setHandleRestorePrevious(bool handle);
 
-    SkCodec::Result extractGainmap(Bitmap* destination);
+    SkCodec::Result extractGainmap(Bitmap* destination, bool isShared);
 
 private:
     // State machine for keeping track of how to handle RestorePrevious (RP)
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
index ad80460..db1c188 100644
--- a/libs/hwui/jni/ImageDecoder.cpp
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -354,7 +354,8 @@
     // cost of RAM
     if (result == SkCodec::kSuccess && !jpostProcess && !preferRamOverQuality) {
         // The gainmap costs RAM to improve quality, so skip this if we're prioritizing RAM instead
-        result = decoder->extractGainmap(nativeBitmap.get());
+        result = decoder->extractGainmap(nativeBitmap.get(),
+                                         allocator == kSharedMemory_Allocator ? true : false);
         jexception = get_and_clear_exception(env);
     }
 
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index 178a6d97..9d0662b 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -20,43 +20,73 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
 import android.hardware.display.VirtualDisplayConfig;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Slog;
 import android.view.ContentRecordingSession;
 import android.view.Surface;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.Map;
+import java.util.Objects;
 
 /**
  * A token granting applications the ability to capture screen contents and/or
  * record system audio. The exact capabilities granted depend on the type of
  * MediaProjection.
  *
- * <p>
- * A screen capture session can be started through {@link
+ * <p>A screen capture session can be started through {@link
  * MediaProjectionManager#createScreenCaptureIntent}. This grants the ability to
  * capture screen contents, but not system audio.
- * </p>
  */
 public final class MediaProjection {
     private static final String TAG = "MediaProjection";
 
+    /**
+     * Requires an app registers a {@link Callback} before invoking
+     * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback,
+     * Handler) createVirtualDisplay}.
+     *
+     * <p>Enabled after version 33 (Android T), so applies to target SDK of 34+ (Android U+).
+     *
+     * @hide
+     */
+    @VisibleForTesting
+    @ChangeId
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    static final long MEDIA_PROJECTION_REQUIRES_CALLBACK = 269849258L; // buganizer id
+
     private final IMediaProjection mImpl;
     private final Context mContext;
-    private final Map<Callback, CallbackRecord> mCallbacks;
-    @Nullable private IMediaProjectionManager mProjectionService = null;
+    private final DisplayManager mDisplayManager;
+    private final IMediaProjectionManager mProjectionService;
+    @NonNull
+    private final Map<Callback, CallbackRecord> mCallbacks = new ArrayMap<>();
 
     /** @hide */
     public MediaProjection(Context context, IMediaProjection impl) {
-        mCallbacks = new ArrayMap<Callback, CallbackRecord>();
+        this(context, impl, IMediaProjectionManager.Stub.asInterface(
+                        ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)),
+                context.getSystemService(DisplayManager.class));
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public MediaProjection(Context context, IMediaProjection impl, IMediaProjectionManager service,
+            DisplayManager displayManager) {
         mContext = context;
         mImpl = impl;
         try {
@@ -64,46 +94,44 @@
         } catch (RemoteException e) {
             throw new RuntimeException("Failed to start media projection", e);
         }
+        mProjectionService = service;
+        mDisplayManager = displayManager;
     }
 
     /**
      * Register a listener to receive notifications about when the {@link MediaProjection} or
      * captured content changes state.
-     * <p>
-     * The callback should be registered before invoking
+     *
+     * <p>The callback must be registered before invoking
      * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback,
-     * Handler)}
-     * to ensure that any notifications on the callback are not missed.
-     * </p>
+     * Handler)} to ensure that any notifications on the callback are not missed. The client must
+     * implement {@link Callback#onStop()} and clean up any resources it is holding, e.g. the
+     * {@link VirtualDisplay} and {@link Surface}.
      *
      * @param callback The callback to call.
      * @param handler  The handler on which the callback should be invoked, or
      *                 null if the callback should be invoked on the calling thread's looper.
-     * @throws IllegalArgumentException If the given callback is null.
+     * @throws NullPointerException If the given callback is null.
      * @see #unregisterCallback
      */
-    public void registerCallback(Callback callback, Handler handler) {
-        if (callback == null) {
-            throw new IllegalArgumentException("callback should not be null");
-        }
+    public void registerCallback(@NonNull Callback callback, @Nullable Handler handler) {
+        final Callback c = Objects.requireNonNull(callback);
         if (handler == null) {
             handler = new Handler();
         }
-        mCallbacks.put(callback, new CallbackRecord(callback, handler));
+        mCallbacks.put(c, new CallbackRecord(c, handler));
     }
 
     /**
      * Unregister a {@link MediaProjection} listener.
      *
      * @param callback The callback to unregister.
-     * @throws IllegalArgumentException If the given callback is null.
+     * @throws NullPointerException If the given callback is null.
      * @see #registerCallback
      */
-    public void unregisterCallback(Callback callback) {
-        if (callback == null) {
-            throw new IllegalArgumentException("callback should not be null");
-        }
-        mCallbacks.remove(callback);
+    public void unregisterCallback(@NonNull Callback callback) {
+        final Callback c = Objects.requireNonNull(callback);
+        mCallbacks.remove(c);
     }
 
     /**
@@ -122,43 +150,55 @@
         if (surface != null) {
             builder.setSurface(surface);
         }
-        VirtualDisplay virtualDisplay = createVirtualDisplay(builder, callback, handler);
-        return virtualDisplay;
+        return createVirtualDisplay(builder, callback, handler);
     }
 
     /**
      * Creates a {@link android.hardware.display.VirtualDisplay} to capture the
      * contents of the screen.
      *
-     * @param name The name of the virtual display, must be non-empty.
-     * @param width The width of the virtual display in pixels. Must be
-     * greater than 0.
-     * @param height The height of the virtual display in pixels. Must be
-     * greater than 0.
-     * @param dpi The density of the virtual display in dpi. Must be greater
-     * than 0.
-     * @param surface The surface to which the content of the virtual display
-     * should be rendered, or null if there is none initially.
-     * @param flags A combination of virtual display flags. See {@link DisplayManager} for the full
-     * list of flags.
-     * @param callback Callback to call when the virtual display's state
-     * changes, or null if none.
-     * @param handler The {@link android.os.Handler} on which the callback should be
-     * invoked, or null if the callback should be invoked on the calling
-     * thread's main {@link android.os.Looper}.
+     * <p>To correctly clean up resources associated with a capture, the application must register a
+     * {@link Callback} before invocation. The app must override {@link Callback#onStop()} to clean
+     * up (by invoking{@link VirtualDisplay#release()}, {@link Surface#release()} and related
+     * resources).
      *
-     * @see android.hardware.display.VirtualDisplay
+     * @param name     The name of the virtual display, must be non-empty.
+     * @param width    The width of the virtual display in pixels. Must be greater than 0.
+     * @param height   The height of the virtual display in pixels. Must be greater than 0.
+     * @param dpi      The density of the virtual display in dpi. Must be greater than 0.
+     * @param surface  The surface to which the content of the virtual display should be rendered,
+     *                 or null if there is none initially.
+     * @param flags    A combination of virtual display flags. See {@link DisplayManager} for the
+     *                 full list of flags.
+     * @param callback Callback invoked when the virtual display's state changes, or null.
+     * @param handler  The {@link android.os.Handler} on which the callback should be invoked, or
+     *                 null if the callback should be invoked on the calling thread's main
+     *                 {@link android.os.Looper}.
+     * @throws IllegalStateException If the target SDK is
+     *                               {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U} and
+     *                               up and no {@link Callback}
+     *                               is registered. If the target SDK is less than
+     *                               {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE U}, no
+     *                               exception is thrown.
+     * @see VirtualDisplay
+     * @see VirtualDisplay.Callback
      */
     public VirtualDisplay createVirtualDisplay(@NonNull String name,
             int width, int height, int dpi, int flags, @Nullable Surface surface,
             @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
+        if (shouldMediaProjectionRequireCallback()) {
+            if (mCallbacks.isEmpty()) {
+                throw new IllegalStateException(
+                        "Must register a callback before starting capture, to manage resources in"
+                                + " response to MediaProjection states.");
+            }
+        }
         final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
                 height, dpi).setFlags(flags);
         if (surface != null) {
             builder.setSurface(surface);
         }
-        VirtualDisplay virtualDisplay = createVirtualDisplay(builder, callback, handler);
-        return virtualDisplay;
+        return createVirtualDisplay(builder, callback, handler);
     }
 
     /**
@@ -191,13 +231,21 @@
             } else {
                 session = ContentRecordingSession.createTaskSession(launchCookie);
             }
-            // Pass in the current session details, so they are guaranteed to only be set in WMS
-            // AFTER a VirtualDisplay is constructed (assuming there are no errors during set-up).
+            // Pass in the current session details, so they are guaranteed to only be set in
+            // WindowManagerService AFTER a VirtualDisplay is constructed (assuming there are no
+            // errors during set-up).
             virtualDisplayConfig.setContentRecordingSession(session);
             virtualDisplayConfig.setWindowManagerMirroringEnabled(true);
-            final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
-            final VirtualDisplay virtualDisplay = dm.createVirtualDisplay(this,
+            final VirtualDisplay virtualDisplay = mDisplayManager.createVirtualDisplay(this,
                     virtualDisplayConfig.build(), callback, handler, windowContext);
+            if (virtualDisplay == null) {
+                // Since WindowManager handling a new display and DisplayManager creating a new
+                // VirtualDisplay is async, WindowManager may have tried to start task recording
+                // and encountered an error that required stopping recording entirely. The
+                // VirtualDisplay would then be null and the MediaProjection is no longer active.
+                Slog.w(TAG, "Failed to create virtual display.");
+                return null;
+            }
             return virtualDisplay;
         } catch (RemoteException e) {
             // Can not capture if WMS is not accessible, so bail out.
@@ -205,12 +253,14 @@
         }
     }
 
-    private IMediaProjectionManager getProjectionService() {
-        if (mProjectionService == null) {
-            mProjectionService = IMediaProjectionManager.Stub.asInterface(
-                    ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE));
-        }
-        return mProjectionService;
+    /**
+     * Returns {@code true} when MediaProjection requires the app registers a callback before
+     * beginning to capture via
+     * {@link #createVirtualDisplay(String, int, int, int, int, Surface, VirtualDisplay.Callback,
+     * Handler)}.
+     */
+    private boolean shouldMediaProjectionRequireCallback() {
+        return CompatChanges.isChangeEnabled(MEDIA_PROJECTION_REQUIRES_CALLBACK);
     }
 
     /**
@@ -238,28 +288,26 @@
     public abstract static class Callback {
         /**
          * Called when the MediaProjection session is no longer valid.
-         * <p>
-         * Once a MediaProjection has been stopped, it's up to the application to release any
-         * resources it may be holding (e.g. {@link android.hardware.display.VirtualDisplay}s).
-         * </p>
+         *
+         * <p>Once a MediaProjection has been stopped, it's up to the application to release any
+         * resources it may be holding (e.g. releasing the {@link VirtualDisplay} and
+         * {@link Surface}).
          */
         public void onStop() { }
 
         /**
          * Invoked immediately after capture begins or when the size of the captured region changes,
          * providing the accurate sizing for the streamed capture.
-         * <p>
-         * The given width and height, in pixels, corresponds to the same width and height that
+         *
+         * <p>The given width and height, in pixels, corresponds to the same width and height that
          * would be returned from {@link android.view.WindowMetrics#getBounds()} of the captured
          * region.
-         * </p>
-         * <p>
-         * If the recorded content has a different aspect ratio from either the
+         *
+         * <p>If the recorded content has a different aspect ratio from either the
          * {@link VirtualDisplay} or output {@link Surface}, the captured stream has letterboxing
          * (black bars) around the recorded content. The application can avoid the letterboxing
          * around the recorded content by updating the size of both the {@link VirtualDisplay} and
          * output {@link Surface}:
-         * </p>
          *
          * <pre>
          * &#x40;Override
@@ -284,13 +332,12 @@
         /**
          * Invoked immediately after capture begins or when the visibility of the captured region
          * changes, providing the current visibility of the captured region.
-         * <p>
-         * Applications can take advantage of this callback by showing or hiding the captured
+         *
+         * <p>Applications can take advantage of this callback by showing or hiding the captured
          * content from the output {@link Surface}, based on if the captured region is currently
          * visible to the user.
-         * </p>
-         * <p>
-         * For example, if the user elected to capture a single app (from the activity shown from
+         *
+         * <p>For example, if the user elected to capture a single app (from the activity shown from
          * {@link MediaProjectionManager#createScreenCaptureIntent()}), the following scenarios
          * trigger the callback:
          * <ul>
@@ -307,7 +354,6 @@
          *         captured app, or the user navigates away from the captured app.
          *     </li>
          * </ul>
-         * </p>
          */
         public void onCapturedContentVisibilityChanged(boolean isVisible) { }
     }
@@ -335,7 +381,7 @@
         }
     }
 
-    private final static class CallbackRecord {
+    private static final class CallbackRecord extends Callback {
         private final Callback mCallback;
         private final Handler mHandler;
 
@@ -344,6 +390,8 @@
             mHandler = handler;
         }
 
+
+        @Override
         public void onStop() {
             mHandler.post(new Runnable() {
                 @Override
@@ -353,10 +401,12 @@
             });
         }
 
+        @Override
         public void onCapturedContentResize(int width, int height) {
             mHandler.post(() -> mCallback.onCapturedContentResize(width, height));
         }
 
+        @Override
         public void onCapturedContentVisibilityChanged(boolean isVisible) {
             mHandler.post(() -> mCallback.onCapturedContentVisibilityChanged(isVisible));
         }
diff --git a/media/tests/projection/Android.bp b/media/tests/projection/Android.bp
index 08d9501..e313c46 100644
--- a/media/tests/projection/Android.bp
+++ b/media/tests/projection/Android.bp
@@ -29,7 +29,9 @@
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
         "testng",
+        "testables",
         "truth-prebuilt",
+        "platform-compat-test-rules",
     ],
 
     // Needed for mockito-target-extended-minus-junit4
diff --git a/media/tests/projection/AndroidManifest.xml b/media/tests/projection/AndroidManifest.xml
index 62f148c..0c97604 100644
--- a/media/tests/projection/AndroidManifest.xml
+++ b/media/tests/projection/AndroidManifest.xml
@@ -19,6 +19,7 @@
           package="android.media.projection.mediaprojectiontests"
           android:sharedUserId="com.android.uid.test">
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
+    <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION" />
 
     <application android:debuggable="true"
                  android:testOnly="true">
diff --git a/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
new file mode 100644
index 0000000..3cfc0fe
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/FakeIMediaProjection.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * The connection between MediaProjection and system server is represented by IMediaProjection;
+ * outside the test it is implemented by the system server.
+ */
+public final class FakeIMediaProjection extends IMediaProjection.Stub {
+    boolean mIsStarted = false;
+    IBinder mLaunchCookie = null;
+    IMediaProjectionCallback mIMediaProjectionCallback = null;
+
+    @Override
+    public void start(IMediaProjectionCallback callback) throws RemoteException {
+        mIMediaProjectionCallback = callback;
+        mIsStarted = true;
+    }
+
+    @Override
+    public void stop() throws RemoteException {
+        // Pass along to the client's callback wrapper.
+        mIMediaProjectionCallback.onStop();
+    }
+
+    @Override
+    public boolean canProjectAudio() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean canProjectVideo() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public boolean canProjectSecureVideo() throws RemoteException {
+        return false;
+    }
+
+    @Override
+    public int applyVirtualDisplayFlags(int flags) throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public void registerCallback(IMediaProjectionCallback callback) throws RemoteException {
+
+    }
+
+    @Override
+    public void unregisterCallback(IMediaProjectionCallback callback) throws RemoteException {
+
+    }
+
+    @Override
+    public IBinder getLaunchCookie() throws RemoteException {
+        return mLaunchCookie;
+    }
+
+    @Override
+    public void setLaunchCookie(IBinder launchCookie) throws RemoteException {
+        mLaunchCookie = launchCookie;
+    }
+}
diff --git a/media/tests/projection/src/android/media/projection/MediaProjectionTest.java b/media/tests/projection/src/android/media/projection/MediaProjectionTest.java
new file mode 100644
index 0000000..bf616d7
--- /dev/null
+++ b/media/tests/projection/src/android/media/projection/MediaProjectionTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.projection;
+
+
+import static android.media.projection.MediaProjection.MEDIA_PROJECTION_REQUIRES_CALLBACK;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import static libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.annotation.Nullable;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.hardware.display.VirtualDisplayConfig;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.testing.TestableContext;
+import android.view.Display;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+
+/**
+ * Tests for the {@link MediaProjection} class.
+ *
+ * Build/Install/Run:
+ * atest MediaProjectionTests:MediaProjectionTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionTest {
+    // Values for creating a VirtualDisplay.
+    private static final String VIRTUAL_DISPLAY_NAME = "MEDIA_PROJECTION_VIRTUAL_DISPLAY";
+    private static final int VIRTUAL_DISPLAY_WIDTH = 500;
+    private static final int VIRTUAL_DISPLAY_HEIGHT = 600;
+    private static final int VIRTUAL_DISPLAY_DENSITY = 100;
+    private static final int VIRTUAL_DISPLAY_FLAGS = 0;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    // Fake the connection to the system server.
+    private final FakeIMediaProjection mFakeIMediaProjection = new FakeIMediaProjection();
+    // Callback registered by an app.
+    private MediaProjection mMediaProjection;
+
+    @Mock
+    private MediaProjection.Callback mMediaProjectionCallback;
+    @Mock
+    private IMediaProjectionManager mIMediaProjectionManager;
+    @Mock
+    private Display mDisplay;
+    @Mock
+    private VirtualDisplay.Callback mVirtualDisplayCallback;
+    @Mock
+    private VirtualDisplay mVirtualDisplay;
+    @Mock
+    private DisplayManager mDisplayManager;
+
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    @Rule
+    public final TestableContext mTestableContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getTargetContext());
+
+    private MockitoSession mMockingSession;
+
+    @Before
+    public void setup() throws Exception {
+        mMockingSession =
+                mockitoSession()
+                        .initMocks(this)
+                        .strictness(Strictness.LENIENT)
+                        .startMocking();
+
+        doReturn(mock(IBinder.class)).when(mIMediaProjectionManager).asBinder();
+
+        // Support the MediaProjection instance.
+        mFakeIMediaProjection.setLaunchCookie(mock(IBinder.class));
+        mMediaProjection = new MediaProjection(mTestableContext, mFakeIMediaProjection,
+                mIMediaProjectionManager, mDisplayManager);
+
+        // Support creation of the VirtualDisplay.
+        mTestableContext.addMockSystemService(DisplayManager.class, mDisplayManager);
+        doReturn(mDisplay).when(mVirtualDisplay).getDisplay();
+        doReturn(DEFAULT_DISPLAY + 7).when(mDisplay).getDisplayId();
+        doReturn(mVirtualDisplay).when(mDisplayManager).createVirtualDisplay(
+                any(MediaProjection.class), any(VirtualDisplayConfig.class),
+                nullable(VirtualDisplay.Callback.class), nullable(Handler.class),
+                nullable(Context.class));
+    }
+
+    @After
+    public void tearDown() {
+        mMockingSession.finishMocking();
+    }
+
+    @Test
+    public void testConstruction() throws RemoteException {
+        assertThat(mMediaProjection).isNotNull();
+        assertThat(mFakeIMediaProjection.mIsStarted).isTrue();
+    }
+
+    @Test
+    public void testRegisterCallback_null() {
+        assertThrows(NullPointerException.class,
+                () -> mMediaProjection.registerCallback(null, mHandler));
+    }
+
+    @Test
+    public void testUnregisterCallback_null() {
+        mMediaProjection.registerCallback(mMediaProjectionCallback, mHandler);
+        assertThrows(NullPointerException.class,
+                () -> mMediaProjection.unregisterCallback(null));
+    }
+
+    @Test
+    @DisableCompatChanges({MEDIA_PROJECTION_REQUIRES_CALLBACK})
+    public void createVirtualDisplay_noCallbackRequired_missingMediaProjectionCallback() {
+        assertThat(createVirtualDisplay(null)).isNotNull();
+        assertThat(createVirtualDisplay(mVirtualDisplayCallback)).isNotNull();
+    }
+
+    @Test
+    @DisableCompatChanges({MEDIA_PROJECTION_REQUIRES_CALLBACK})
+    public void createVirtualDisplay_noCallbackRequired_givenMediaProjectionCallback() {
+        mMediaProjection.registerCallback(mMediaProjectionCallback, mHandler);
+        assertThat(createVirtualDisplay(null)).isNotNull();
+        assertThat(createVirtualDisplay(mVirtualDisplayCallback)).isNotNull();
+    }
+
+    @Test
+    @EnableCompatChanges({MEDIA_PROJECTION_REQUIRES_CALLBACK})
+    public void createVirtualDisplay_callbackRequired_missingMediaProjectionCallback() {
+        assertThrows(IllegalStateException.class,
+                () -> createVirtualDisplay(mVirtualDisplayCallback));
+    }
+
+    @Test
+    @EnableCompatChanges({MEDIA_PROJECTION_REQUIRES_CALLBACK})
+    public void createVirtualDisplay_callbackRequired_givenMediaProjectionCallback() {
+        mMediaProjection.registerCallback(mMediaProjectionCallback, mHandler);
+        assertThat(createVirtualDisplay(null)).isNotNull();
+        assertThat(createVirtualDisplay(mVirtualDisplayCallback)).isNotNull();
+    }
+
+    private VirtualDisplay createVirtualDisplay(@Nullable VirtualDisplay.Callback callback) {
+        // No recording will take place with a null surface.
+        return mMediaProjection.createVirtualDisplay(
+                VIRTUAL_DISPLAY_NAME, VIRTUAL_DISPLAY_WIDTH,
+                VIRTUAL_DISPLAY_HEIGHT, VIRTUAL_DISPLAY_DENSITY,
+                VIRTUAL_DISPLAY_FLAGS, /* surface = */ null,
+                callback, /* handler= */ mHandler);
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index e7de8b3..e49e3f1 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -37,6 +37,7 @@
 import com.android.credentialmanager.createflow.CreateScreenState
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.getflow.GetScreenState
+import com.android.credentialmanager.logging.LifecycleEvent
 import com.android.credentialmanager.logging.UIMetrics
 import com.android.internal.logging.UiEventLogger.UiEventEnum
 
@@ -61,6 +62,11 @@
 
     var uiMetrics: UIMetrics = UIMetrics()
 
+    init{
+        uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INIT,
+                credManRepo.requestInfo.appPackageName)
+    }
+
     /**************************************************************************/
     /*****                       Shared Callbacks                         *****/
     /**************************************************************************/
@@ -84,6 +90,8 @@
 
         if (this.credManRepo.requestInfo.token != credManRepo.requestInfo.token) {
             this.uiMetrics.resetInstanceId()
+            this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_NEW_REQUEST,
+                    credManRepo.requestInfo.appPackageName)
         }
     }
 
@@ -156,6 +164,8 @@
 
     private fun onInternalError() {
         Log.w(Constants.LOG_TAG, "UI closed due to illegal internal state")
+        this.uiMetrics.logNormal(LifecycleEvent.CREDMAN_ACTIVITY_INTERNAL_ERROR,
+                credManRepo.requestInfo.appPackageName)
         credManRepo.onParsingFailureCancel()
         uiState = uiState.copy(dialogState = DialogState.COMPLETE)
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 5240777..16827da 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -171,9 +171,6 @@
                                 onLog = { viewModel.logUiEvent(it) },
                         )
                     }
-                    viewModel.uiMetrics.log(
-                            CreateCredentialEvent
-                                    .CREDMAN_CREATE_CRED_PROVIDER_ACTIVITY_NOT_APPLICABLE)
                 }
                 ProviderActivityState.READY_TO_LAUNCH -> {
                     // Launch only once per providerActivityState change so that the provider
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index a9f994d..96798a3 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -62,6 +62,8 @@
 import com.android.credentialmanager.common.ui.Snackbar
 import com.android.credentialmanager.common.ui.setTransparentSystemBarsColor
 import com.android.credentialmanager.common.ui.setBottomSheetSystemBarsColor
+import com.android.credentialmanager.logging.GetCredentialEvent
+import com.android.internal.logging.UiEventLogger.UiEventEnum
 
 @Composable
 fun GetCredentialScreen(
@@ -75,7 +77,9 @@
         RemoteCredentialSnackBarScreen(
             onClick = viewModel::getFlowOnMoreOptionOnSnackBarSelected,
             onCancel = viewModel::onUserCancel,
+            onLog = { viewModel.logUiEvent(it) },
         )
+        viewModel.uiMetrics.log(GetCredentialEvent.CREDMAN_GET_CRED_SCREEN_REMOTE_ONLY)
     } else if (getCredentialUiState.currentScreenState
         == GetScreenState.UNLOCKED_AUTH_ENTRIES_ONLY) {
         setTransparentSystemBarsColor(sysUiController)
@@ -84,7 +88,10 @@
             getCredentialUiState.providerDisplayInfo.authenticationEntryList,
             onCancel = viewModel::silentlyFinishActivity,
             onLastLockedAuthEntryNotFound = viewModel::onLastLockedAuthEntryNotFoundError,
+            onLog = { viewModel.logUiEvent(it) },
         )
+        viewModel.uiMetrics.log(GetCredentialEvent
+                .CREDMAN_GET_CRED_SCREEN_UNLOCKED_AUTH_ENTRIES_ONLY)
     } else {
         setBottomSheetSystemBarsColor(sysUiController)
         ModalBottomSheet(
@@ -104,7 +111,10 @@
                                 onEntrySelected = viewModel::getFlowOnEntrySelected,
                                 onConfirm = viewModel::getFlowOnConfirmEntrySelected,
                                 onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected,
+                                onLog = { viewModel.logUiEvent(it) },
                             )
+                            viewModel.uiMetrics.log(GetCredentialEvent
+                                    .CREDMAN_GET_CRED_SCREEN_PRIMARY_SELECTION)
                         } else {
                             AllSignInOptionCard(
                                 providerInfoList = getCredentialUiState.providerInfoList,
@@ -114,7 +124,10 @@
                                 viewModel::getFlowOnBackToPrimarySelectionScreen,
                                 onCancel = viewModel::onUserCancel,
                                 isNoAccount = getCredentialUiState.isNoAccount,
+                                onLog = { viewModel.logUiEvent(it) },
                             )
+                            viewModel.uiMetrics.log(GetCredentialEvent
+                                    .CREDMAN_GET_CRED_SCREEN_ALL_SIGN_IN_OPTIONS)
                         }
                     }
                     ProviderActivityState.READY_TO_LAUNCH -> {
@@ -123,9 +136,13 @@
                         LaunchedEffect(viewModel.uiState.providerActivityState) {
                             viewModel.launchProviderUi(providerActivityLauncher)
                         }
+                        viewModel.uiMetrics.log(GetCredentialEvent
+                                .CREDMAN_GET_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH)
                     }
                     ProviderActivityState.PENDING -> {
                         // Hide our content when the provider activity is active.
+                        viewModel.uiMetrics.log(GetCredentialEvent
+                                .CREDMAN_GET_CRED_PROVIDER_ACTIVITY_PENDING)
                     }
                 }
             },
@@ -144,6 +161,7 @@
     onEntrySelected: (BaseEntry) -> Unit,
     onConfirm: () -> Unit,
     onMoreOptionSelected: () -> Unit,
+    onLog: @Composable (UiEventEnum) -> Unit,
 ) {
     val sortedUserNameToCredentialEntryList =
         providerDisplayInfo.sortedUserNameToCredentialEntryList
@@ -248,6 +266,7 @@
             )
         }
     }
+    onLog(GetCredentialEvent.CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD)
 }
 
 /** Draws the secondary credential selection page, where all sign-in options are listed. */
@@ -259,6 +278,7 @@
     onBackButtonClicked: () -> Unit,
     onCancel: () -> Unit,
     isNoAccount: Boolean,
+    onLog: @Composable (UiEventEnum) -> Unit,
 ) {
     val sortedUserNameToCredentialEntryList =
         providerDisplayInfo.sortedUserNameToCredentialEntryList
@@ -303,6 +323,7 @@
             )
         }
     }
+    onLog(GetCredentialEvent.CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD)
 }
 
 // TODO: create separate rows for primary and secondary pages.
@@ -466,6 +487,7 @@
 fun RemoteCredentialSnackBarScreen(
     onClick: (Boolean) -> Unit,
     onCancel: () -> Unit,
+    onLog: @Composable (UiEventEnum) -> Unit,
 ) {
     Snackbar(
         action = {
@@ -482,6 +504,7 @@
         onDismiss = onCancel,
         contentText = stringResource(R.string.get_dialog_use_saved_passkey_for),
     )
+    onLog(GetCredentialEvent.CREDMAN_GET_CRED_REMOTE_CRED_SNACKBAR_SCREEN)
 }
 
 @Composable
@@ -489,6 +512,7 @@
     authenticationEntryList: List<AuthenticationEntryInfo>,
     onCancel: () -> Unit,
     onLastLockedAuthEntryNotFound: () -> Unit,
+    onLog: @Composable (UiEventEnum) -> Unit,
 ) {
     val lastLocked = authenticationEntryList.firstOrNull({ it.isLastUnlocked })
     if (lastLocked == null) {
@@ -500,4 +524,5 @@
         onDismiss = onCancel,
         contentText = stringResource(R.string.no_sign_in_info_in, lastLocked.providerDisplayName),
     )
+    onLog(GetCredentialEvent.CREDMAN_GET_CRED_SCREEN_EMPTY_AUTH_SNACKBAR_SCREEN)
 }
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
new file mode 100644
index 0000000..8de8895
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.credentialmanager.logging
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class GetCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+
+    @UiEvent(doc = "The The snackbar only page when there's no account but only a remoteEntry " +
+            "visible on the screen.")
+    CREDMAN_GET_CRED_SCREEN_REMOTE_ONLY(1332),
+
+    @UiEvent(doc = "The snackbar when there are only auth entries and all of them are empty.")
+    CREDMAN_GET_CRED_SCREEN_UNLOCKED_AUTH_ENTRIES_ONLY(1333),
+
+    @UiEvent(doc = "The primary credential selection page is displayed on screen.")
+    CREDMAN_GET_CRED_SCREEN_PRIMARY_SELECTION(1334),
+
+    @UiEvent(doc = "The secondary credential selection page, where all sign-in options are " +
+            "listed is displayed on the screen.")
+    CREDMAN_GET_CRED_SCREEN_ALL_SIGN_IN_OPTIONS(1335),
+
+    @UiEvent(doc = "The provider activity is not active nor is any ready for launch on the screen.")
+    CREDMAN_GET_CRED_PROVIDER_ACTIVITY_NOT_APPLICABLE(1336),
+
+    @UiEvent(doc = "The provider activity is ready to be launched on the screen.")
+    CREDMAN_GET_CRED_PROVIDER_ACTIVITY_READY_TO_LAUNCH(1337),
+
+    @UiEvent(doc = "The provider activity is launched and we are waiting for its result. " +
+            "Contents Hidden.")
+    CREDMAN_GET_CRED_PROVIDER_ACTIVITY_PENDING(1338),
+
+    @UiEvent(doc = "The remote credential snackbar screen is visible.")
+    CREDMAN_GET_CRED_REMOTE_CRED_SNACKBAR_SCREEN(1339),
+
+    @UiEvent(doc = "The empty auth snackbar screen is visible.")
+    CREDMAN_GET_CRED_SCREEN_EMPTY_AUTH_SNACKBAR_SCREEN(1340),
+
+    @UiEvent(doc = "The primary selection card is visible on screen.")
+    CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD(1341),
+
+    @UiEvent(doc = "The all sign in option card is visible on screen.")
+    CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD(1342);
+
+    override fun getId(): Int {
+        return this.id
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt
new file mode 100644
index 0000000..1ede64d
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.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.credentialmanager.logging
+
+import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+enum class LifecycleEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+
+    @UiEvent(doc = "A new activity is initialized for processing the request.")
+    CREDMAN_ACTIVITY_INIT(1343),
+
+    @UiEvent(doc = "An existing activity received a new request to process.")
+    CREDMAN_ACTIVITY_NEW_REQUEST(1344),
+
+    @UiEvent(doc = "The UI closed due to illegal internal state.")
+    CREDMAN_ACTIVITY_INTERNAL_ERROR(1345);
+
+    override fun getId(): Int {
+        return this.id
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt
index 4351e84..035c1e4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/UIMetrics.kt
@@ -56,4 +56,8 @@
             mUiEventLogger.logWithInstanceId(event, /*uid=*/0, packageName, instanceId)
         }
     }
+
+    fun logNormal(event: UiEventLogger.UiEventEnum, packageName: String) {
+        mUiEventLogger.logWithInstanceId(event, /*uid=*/0, packageName, mInstanceId)
+    }
 }
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 8e7d36e..ff80f52 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -868,6 +868,11 @@
     <!-- UI debug setting: force right to left layout summary [CHAR LIMIT=100] -->
     <string name="force_rtl_layout_all_locales_summary">Force screen layout direction to RTL for all locales</string>
 
+    <!-- UI debug setting: make navigation bar background color transparent by default [CHAR LIMIT=50] -->
+    <string name="transparent_navigation_bar">Transparent navigation bar</string>
+    <!-- UI debug setting: make navigation bar background color transparent by default summary [CHAR LIMIT=100] -->
+    <string name="transparent_navigation_bar_summary">Make navigation bar background color transparent by default</string>
+
     <!-- UI debug setting: enable or disable window blurs [CHAR LIMIT=50] -->
     <string name="window_blurs">Allow window-level blurs</string>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 63bb2fe..a3d632c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -210,7 +210,7 @@
 
         synchronized (mProfileLock) {
             if (profile instanceof A2dpProfile || profile instanceof HeadsetProfile
-                    || profile instanceof HearingAidProfile) {
+                    || profile instanceof HearingAidProfile || profile instanceof LeAudioProfile) {
                 setProfileConnectedStatus(profile.getProfileId(), false);
                 switch (newProfileState) {
                     case BluetoothProfile.STATE_CONNECTED:
@@ -228,7 +228,20 @@
                     case BluetoothProfile.STATE_DISCONNECTED:
                         if (mHandler.hasMessages(profile.getProfileId())) {
                             mHandler.removeMessages(profile.getProfileId());
-                            setProfileConnectedStatus(profile.getProfileId(), true);
+                            if (profile.getConnectionPolicy(mDevice) >
+                                BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) {
+                                /*
+                                 * If we received state DISCONNECTED and previous state was
+                                 * CONNECTING and connection policy is FORBIDDEN or UNKNOWN
+                                 * then it's not really a failure to connect.
+                                 *
+                                 * Connection profile is considered as failed when connection
+                                 * policy indicates that profile should be connected
+                                 * but it got disconnected.
+                                 */
+                                Log.w(TAG, "onProfileStateChanged(): Failed to connect profile");
+                                setProfileConnectedStatus(profile.getProfileId(), true);
+                            }
                         }
                         break;
                     default:
@@ -1205,6 +1218,13 @@
     }
 
     private boolean isProfileConnectedFail() {
+        Log.d(TAG, "anonymizedAddress=" + mDevice.getAnonymizedAddress()
+                + " mIsA2dpProfileConnectedFail=" + mIsA2dpProfileConnectedFail
+                + " mIsHearingAidProfileConnectedFail=" + mIsHearingAidProfileConnectedFail
+                + " mIsLeAudioProfileConnectedFail=" + mIsLeAudioProfileConnectedFail
+                + " mIsHeadsetProfileConnectedFail=" + mIsHeadsetProfileConnectedFail
+                + " isConnectedSapDevice()=" + isConnectedSapDevice());
+
         return mIsA2dpProfileConnectedFail || mIsHearingAidProfileConnectedFail
                 || (!isConnectedSapDevice() && mIsHeadsetProfileConnectedFail)
                 || mIsLeAudioProfileConnectedFail;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 77c19a1..1c179f8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -97,15 +97,76 @@
         mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter());
         when(mDevice.getAddress()).thenReturn(DEVICE_ADDRESS);
         when(mHfpProfile.isProfileReady()).thenReturn(true);
+        when(mHfpProfile.getProfileId()).thenReturn(BluetoothProfile.HEADSET);
         when(mA2dpProfile.isProfileReady()).thenReturn(true);
+        when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP);
         when(mPanProfile.isProfileReady()).thenReturn(true);
+        when(mPanProfile.getProfileId()).thenReturn(BluetoothProfile.PAN);
         when(mHearingAidProfile.isProfileReady()).thenReturn(true);
+        when(mHearingAidProfile.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID);
+        when(mLeAudioProfile.isProfileReady()).thenReturn(true);
+        when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO);
         mCachedDevice = spy(new CachedBluetoothDevice(mContext, mProfileManager, mDevice));
         mSubCachedDevice = spy(new CachedBluetoothDevice(mContext, mProfileManager, mSubDevice));
         doAnswer((invocation) -> mBatteryLevel).when(mCachedDevice).getBatteryLevel();
         doAnswer((invocation) -> mBatteryLevel).when(mSubCachedDevice).getBatteryLevel();
     }
 
+    private void testTransitionFromConnectingToDisconnected(
+        LocalBluetoothProfile connectingProfile, LocalBluetoothProfile connectedProfile,
+        int connectionPolicy, String expectedSummary) {
+        // Arrange:
+        // At least one profile has to be connected
+        updateProfileStatus(connectedProfile, BluetoothProfile.STATE_CONNECTED);
+        // Set profile under test to CONNECTING
+        updateProfileStatus(connectingProfile, BluetoothProfile.STATE_CONNECTING);
+        // Set connection policy
+        when(connectingProfile.getConnectionPolicy(mDevice)).thenReturn(connectionPolicy);
+
+        // Act & Assert:
+        //   Get the expected connection summary.
+        updateProfileStatus(connectingProfile, BluetoothProfile.STATE_DISCONNECTED);
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo(expectedSummary);
+    }
+
+    @Test
+    public void onProfileStateChanged_testConnectingToDisconnected_policyAllowed_problem() {
+        String connectTimeoutString = mContext.getString(R.string.profile_connect_timeout_subtext);
+
+        testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
+        BluetoothProfile.CONNECTION_POLICY_ALLOWED, connectTimeoutString);
+        testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
+        BluetoothProfile.CONNECTION_POLICY_ALLOWED, connectTimeoutString);
+        testTransitionFromConnectingToDisconnected(mHfpProfile, mLeAudioProfile,
+        BluetoothProfile.CONNECTION_POLICY_ALLOWED, connectTimeoutString);
+        testTransitionFromConnectingToDisconnected(mLeAudioProfile, mA2dpProfile,
+        BluetoothProfile.CONNECTION_POLICY_ALLOWED, connectTimeoutString);
+    }
+
+    @Test
+    public void onProfileStateChanged_testConnectingToDisconnected_policyForbidden_noProblem() {
+        testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
+        BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
+        testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
+        BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
+        testTransitionFromConnectingToDisconnected(mHfpProfile, mLeAudioProfile,
+        BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
+        testTransitionFromConnectingToDisconnected(mLeAudioProfile, mA2dpProfile,
+        BluetoothProfile.CONNECTION_POLICY_FORBIDDEN, null);
+    }
+
+    @Test
+    public void onProfileStateChanged_testConnectingToDisconnected_policyUnknown_noProblem() {
+        testTransitionFromConnectingToDisconnected(mA2dpProfile, mLeAudioProfile,
+        BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
+        testTransitionFromConnectingToDisconnected(mHearingAidProfile, mLeAudioProfile,
+        BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
+        testTransitionFromConnectingToDisconnected(mHfpProfile, mLeAudioProfile,
+        BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
+        testTransitionFromConnectingToDisconnected(mLeAudioProfile, mA2dpProfile,
+        BluetoothProfile.CONNECTION_POLICY_UNKNOWN, null);
+    }
+
     @Test
     public void getConnectionSummary_testProfilesInactive_returnPairing() {
         // Arrange:
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 71f438e..6176c61 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -181,8 +181,8 @@
     <!-- Doze mode temp whitelisting for notification dispatching. -->
     <uses-permission android:name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST" />
 
-    <!-- Tag user-initiated PendingIntent invocations as "interactive" when appropriate -->
-    <uses-permission android:name="android.permission.COMPONENT_OPTION_INTERACTIVE" />
+    <!-- Adjust delivery policies for broadcast intents -->
+    <uses-permission android:name="android.permission.BROADCAST_OPTION_INTERACTIVE" />
 
     <!-- Listen for keyboard attachment / detachment -->
     <uses-permission android:name="android.permission.TABLET_MODE" />
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index 2903288..f0a8211 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -19,14 +19,15 @@
 import android.graphics.fonts.Font
 import android.graphics.fonts.FontVariationAxis
 import android.util.MathUtils
+import android.util.MathUtils.abs
+import java.lang.Float.max
+import java.lang.Float.min
 
 private const val TAG_WGHT = "wght"
 private const val TAG_ITAL = "ital"
 
-private const val FONT_WEIGHT_MAX = 1000f
-private const val FONT_WEIGHT_MIN = 0f
-private const val FONT_WEIGHT_ANIMATION_STEP = 10f
 private const val FONT_WEIGHT_DEFAULT_VALUE = 400f
+private const val FONT_WEIGHT_ANIMATION_FRAME_COUNT = 100
 
 private const val FONT_ITALIC_MAX = 1f
 private const val FONT_ITALIC_MIN = 0f
@@ -118,14 +119,17 @@
             lerp(startAxes, endAxes) { tag, startValue, endValue ->
                 when (tag) {
                     // TODO: Good to parse 'fvar' table for retrieving default value.
-                    TAG_WGHT ->
-                        adjustWeight(
+                    TAG_WGHT -> {
+                        adaptiveAdjustWeight(
                             MathUtils.lerp(
                                 startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
                                 endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
                                 progress
-                            )
+                            ),
+                            startValue ?: FONT_WEIGHT_DEFAULT_VALUE,
+                            endValue ?: FONT_WEIGHT_DEFAULT_VALUE,
                         )
+                    }
                     TAG_ITAL ->
                         adjustItalic(
                             MathUtils.lerp(
@@ -205,10 +209,14 @@
         return result
     }
 
-    // For the performance reasons, we animate weight with FONT_WEIGHT_ANIMATION_STEP. This helps
+    // For the performance reasons, we animate weight with adaptive step. This helps
     // Cache hit ratio in the Skia glyph cache.
-    private fun adjustWeight(value: Float) =
-        coerceInWithStep(value, FONT_WEIGHT_MIN, FONT_WEIGHT_MAX, FONT_WEIGHT_ANIMATION_STEP)
+    // The reason we don't use fix step is because the range of weight axis is not normalized,
+    // some are from 50 to 100, others are from 0 to 1000, so we cannot give a constant proper step
+    private fun adaptiveAdjustWeight(value: Float, start: Float, end: Float): Float {
+        val step = max(abs(end - start) / FONT_WEIGHT_ANIMATION_FRAME_COUNT, 1F)
+        return coerceInWithStep(value, min(start, end), max(start, end), step)
+    }
 
     // For the performance reasons, we animate italic with FONT_ITALIC_ANIMATION_STEP. This helps
     // Cache hit ratio in the Skia glyph cache.
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 a08b598..7fe94d3 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -190,6 +190,7 @@
      * @param textSize an optional font size.
      * @param colors an optional colors array that must be the same size as numLines passed to
      *               the TextInterpolator
+     * @param strokeWidth an optional paint stroke width
      * @param animate an optional boolean indicating true for showing style transition as animation,
      *                false for immediate style transition. True by default.
      * @param duration an optional animation duration in milliseconds. This is ignored if animate is
@@ -201,6 +202,7 @@
         weight: Int = -1,
         textSize: Float = -1f,
         color: Int? = null,
+        strokeWidth: Float = -1f,
         animate: Boolean = true,
         duration: Long = -1L,
         interpolator: TimeInterpolator? = null,
@@ -254,6 +256,9 @@
         if (color != null) {
             textInterpolator.targetPaint.color = color
         }
+        if (strokeWidth >= 0F) {
+            textInterpolator.targetPaint.strokeWidth = strokeWidth
+        }
         textInterpolator.onTargetPaintModified()
 
         if (animate) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 468a8b1..3eb7fd8 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -473,6 +473,7 @@
         // TODO(172943390): Add other interpolation or support custom interpolator.
         out.textSize = MathUtils.lerp(from.textSize, to.textSize, progress)
         out.color = ColorUtils.blendARGB(from.color, to.color, progress)
+        out.strokeWidth = MathUtils.lerp(from.strokeWidth, to.strokeWidth, progress)
     }
 
     // Shape the text and stores the result to out argument.
diff --git a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
index f64ea45..459a38e 100644
--- a/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
+++ b/packages/SystemUI/checks/src/com/android/internal/systemui/lint/DemotingTestWithoutBugDetector.kt
@@ -25,6 +25,7 @@
 import com.android.tools.lint.detector.api.Scope
 import com.android.tools.lint.detector.api.Severity
 import com.android.tools.lint.detector.api.SourceCodeScanner
+import java.util.regex.Pattern
 import org.jetbrains.uast.UAnnotation
 import org.jetbrains.uast.UElement
 
@@ -36,22 +37,38 @@
     override fun createUastHandler(context: JavaContext): UElementHandler {
         return object : UElementHandler() {
             override fun visitAnnotation(node: UAnnotation) {
-                if (node.qualifiedName !in DEMOTING_ANNOTATION) {
-                    return
+                // Annotations having int bugId field
+                if (node.qualifiedName in DEMOTING_ANNOTATION_BUG_ID) {
+                    val bugId = node.findAttributeValue("bugId")!!.evaluate() as Int
+                    if (bugId <= 0) {
+                        val location = context.getLocation(node)
+                        val message = "Please attach a bug id to track demoted test"
+                        context.report(ISSUE, node, location, message)
+                    }
                 }
-                val bugId = node.findAttributeValue("bugId")!!.evaluate() as Int
-                if (bugId <= 0) {
-                    val location = context.getLocation(node)
-                    val message = "Please attach a bug id to track demoted test"
-                    context.report(ISSUE, node, location, message)
+                // @Ignore has a String field for reason
+                if (node.qualifiedName == DEMOTING_ANNOTATION_IGNORE) {
+                    val reason = node.findAttributeValue("value")!!.evaluate() as String
+                    val bugPattern = Pattern.compile("b/\\d+")
+                    if (!bugPattern.matcher(reason).find()) {
+                        val location = context.getLocation(node)
+                        val message = "Please attach a bug (e.g. b/123) to track demoted test"
+                        context.report(ISSUE, node, location, message)
+                    }
                 }
             }
         }
     }
 
     companion object {
-        val DEMOTING_ANNOTATION =
-            listOf("androidx.test.filters.FlakyTest", "android.platform.test.annotations.FlakyTest")
+        val DEMOTING_ANNOTATION_BUG_ID =
+            listOf(
+                "androidx.test.filters.FlakyTest",
+                "android.platform.test.annotations.FlakyTest",
+                "android.platform.test.rule.PlatinumRule.Platinum",
+            )
+
+        const val DEMOTING_ANNOTATION_IGNORE = "org.junit.Ignore"
 
         @JvmField
         val ISSUE: Issue =
diff --git a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
index 557c300..63eb263 100644
--- a/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
+++ b/packages/SystemUI/checks/tests/com/android/internal/systemui/lint/DemotingTestWithoutBugDetectorTest.kt
@@ -127,6 +127,139 @@
             )
     }
 
+    @Test
+    fun testExcludeDevices_withBugId() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+                        import android.platform.test.rule.PlatinumRule.Platinum;
+
+                        @Platinum(devices = "foo,bar", bugId = 123)
+                        public class TestClass {
+                            public void testCase() {}
+                        }
+                    """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(DemotingTestWithoutBugDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testExcludeDevices_withoutBugId() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+                        import android.platform.test.rule.PlatinumRule.Platinum;
+
+                        @Platinum(devices = "foo,bar")
+                        public class TestClass {
+                            public void testCase() {}
+                        }
+                    """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(DemotingTestWithoutBugDetector.ISSUE)
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass.java:4: Warning: Please attach a bug id to track demoted test [DemotingTestWithoutBug]
+                @Platinum(devices = "foo,bar")
+                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+                """
+            )
+    }
+
+    @Test
+    fun testIgnore_withBug() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+                        import org.junit.Ignore;
+
+                        @Ignore("Blocked by b/123.")
+                        public class TestClass {
+                            public void testCase() {}
+                        }
+                    """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(DemotingTestWithoutBugDetector.ISSUE)
+            .run()
+            .expectClean()
+    }
+
+    @Test
+    fun testIgnore_withoutBug() {
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+                        import org.junit.Ignore;
+
+                        @Ignore
+                        public class TestClass {
+                            public void testCase() {}
+                        }
+                    """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(DemotingTestWithoutBugDetector.ISSUE)
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass.java:4: Warning: Please attach a bug (e.g. b/123) to track demoted test [DemotingTestWithoutBug]
+                @Ignore
+                ~~~~~~~
+                0 errors, 1 warnings
+                """
+            )
+
+        lint()
+            .files(
+                TestFiles.java(
+                        """
+                        package test.pkg;
+                        import org.junit.Ignore;
+
+                        @Ignore("Not ready")
+                        public class TestClass {
+                            public void testCase() {}
+                        }
+                    """
+                    )
+                    .indented(),
+                *stubs
+            )
+            .issues(DemotingTestWithoutBugDetector.ISSUE)
+            .run()
+            .expect(
+                """
+                src/test/pkg/TestClass.java:4: Warning: Please attach a bug (e.g. b/123) to track demoted test [DemotingTestWithoutBug]
+                @Ignore("Not ready")
+                ~~~~~~~~~~~~~~~~~~~~
+                0 errors, 1 warnings
+                """
+            )
+    }
+
     private val filtersFlakyTestStub: TestFile =
         java(
             """
@@ -147,5 +280,34 @@
         }
         """
         )
-    private val stubs = arrayOf(filtersFlakyTestStub, annotationsFlakyTestStub)
+    private val annotationsPlatinumStub: TestFile =
+        java(
+            """
+        package android.platform.test.rule;
+
+        public class PlatinumRule {
+            public @interface Platinum {
+                String devices();
+                int bugId() default -1;
+            }
+        }
+        """
+        )
+    private val annotationsIgnoreStub: TestFile =
+        java(
+            """
+        package org.junit;
+
+        public @interface Ignore {
+            String value() default "";
+        }
+        """
+        )
+    private val stubs =
+        arrayOf(
+            filtersFlakyTestStub,
+            annotationsFlakyTestStub,
+            annotationsPlatinumStub,
+            annotationsIgnoreStub
+        )
 }
diff --git a/packages/SystemUI/res-keyguard/values-land/dimens.xml b/packages/SystemUI/res-keyguard/values-land/dimens.xml
index f1aa544..a4e7a5f 100644
--- a/packages/SystemUI/res-keyguard/values-land/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values-land/dimens.xml
@@ -27,6 +27,4 @@
     <integer name="scaled_password_text_size">26</integer>
 
     <dimen name="bouncer_user_switcher_y_trans">@dimen/status_bar_height</dimen>
-    <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
-    <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index caf3233..1f44f05 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -127,9 +127,7 @@
     <dimen name="bouncer_user_switcher_item_padding_vertical">10dp</dimen>
     <dimen name="bouncer_user_switcher_item_padding_horizontal">12dp</dimen>
     <dimen name="bouncer_user_switcher_header_padding_end">44dp</dimen>
-    <dimen name="bouncer_user_switcher_y_trans">0dp</dimen>
-    <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
-    <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
+    <dimen name="bouncer_user_switcher_y_trans">80dp</dimen>
 
     <!-- 2 * the margin + size should equal the plus_margin -->
     <dimen name="user_switcher_icon_large_margin">16dp</dimen>
diff --git a/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
new file mode 100644
index 0000000..d123caf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_keyboard_backlight.xml
@@ -0,0 +1,12 @@
+<vector android:height="11dp" android:viewportHeight="12"
+    android:viewportWidth="22" android:width="20.166666dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <group>
+        <clip-path android:pathData="M0,0.5h22v11h-22z"/>
+        <path android:fillColor="#231F20" android:pathData="M6.397,9.908H0V11.5H6.397V9.908Z"/>
+        <path android:fillColor="#231F20" android:pathData="M14.199,9.908H7.801V11.5H14.199V9.908Z"/>
+        <path android:fillColor="#231F20" android:pathData="M11.858,0.5H10.142V6.434H11.858V0.5Z"/>
+        <path android:fillColor="#231F20" android:pathData="M8.348,7.129L3.885,2.975L3.823,2.932L2.668,4.003L2.621,4.046L7.084,8.2L7.146,8.243L8.301,7.172L8.348,7.129Z"/>
+        <path android:fillColor="#231F20" android:pathData="M18.224,2.975L18.177,2.932L13.653,7.129L14.807,8.2L14.854,8.243L19.379,4.046L18.224,2.975Z"/>
+        <path android:fillColor="#231F20" android:pathData="M22,9.908H15.603V11.5H22V9.908Z"/>
+    </group>
+</vector>
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 4f38e60..908aac4 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -66,4 +66,8 @@
 
     <dimen name="controls_header_horizontal_padding">12dp</dimen>
     <dimen name="controls_content_margin_horizontal">16dp</dimen>
+
+    <!-- Bouncer user switcher margins -->
+    <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+    <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
index b98165f..ca62d28 100644
--- a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
@@ -21,6 +21,6 @@
     <!-- Space between status view and notification shelf -->
     <dimen name="keyguard_status_view_bottom_margin">70dp</dimen>
     <dimen name="keyguard_clock_top_margin">80dp</dimen>
-    <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">186dp</dimen>
-    <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">110dp</dimen>
+    <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">155dp</dimen>
+    <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">85dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index d4ebd10..5d188fe 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -206,6 +206,11 @@
     <color name="control_thumbnail_shadow_color">@*android:color/black</color>
     <color name="controls_task_view_bg">#CC191C1D</color>
 
+    <!-- Keyboard backlight indicator-->
+    <color name="backlight_indicator_step_filled">#F6E388</color>
+    <color name="backlight_indicator_step_empty">#494740</color>
+    <color name="backlight_indicator_background">#32302A</color>
+
     <!-- Docked misalignment message -->
     <color name="misalignment_text_color">#F28B82</color>
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 0f2ce44..e78be4c 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1698,6 +1698,19 @@
     <dimen name="media_output_broadcast_info_summary_height">20dp</dimen>
     <dimen name="media_output_broadcast_info_edit">18dp</dimen>
 
+    <!-- Keyboard backlight indicator-->
+    <dimen name="backlight_indicator_root_corner_radius">48dp</dimen>
+    <dimen name="backlight_indicator_root_vertical_padding">8dp</dimen>
+    <dimen name="backlight_indicator_root_horizontal_padding">4dp</dimen>
+    <dimen name="backlight_indicator_icon_width">22dp</dimen>
+    <dimen name="backlight_indicator_icon_height">11dp</dimen>
+    <dimen name="backlight_indicator_icon_left_margin">2dp</dimen>
+    <dimen name="backlight_indicator_step_width">52dp</dimen>
+    <dimen name="backlight_indicator_step_height">40dp</dimen>
+    <dimen name="backlight_indicator_step_horizontal_margin">4dp</dimen>
+    <dimen name="backlight_indicator_step_small_radius">4dp</dimen>
+    <dimen name="backlight_indicator_step_large_radius">48dp</dimen>
+
     <!-- Broadcast dialog -->
     <dimen name="broadcast_dialog_title_img_margin_top">18dp</dimen>
     <dimen name="broadcast_dialog_title_text_size">24sp</dimen>
@@ -1746,4 +1759,9 @@
     it is long-pressed.
     -->
     <dimen name="keyguard_long_press_settings_popup_vertical_offset">96dp</dimen>
+
+
+    <!-- Bouncer user switcher margins -->
+    <dimen name="bouncer_user_switcher_view_mode_user_switcher_bottom_margin">0dp</dimen>
+    <dimen name="bouncer_user_switcher_view_mode_view_flipper_bottom_margin">0dp</dimen>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f4b3b87..a02c429 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -191,8 +191,8 @@
 
     <!-- Power menu item for taking a screenshot [CHAR LIMIT=20]-->
     <string name="global_action_screenshot">Screenshot</string>
-    <!-- Message shown in power menu when smart lock has been disabled [CHAR_LIMIT=NONE] -->
-    <string name="global_action_smart_lock_disabled">Smart Lock disabled</string>
+    <!-- Message shown in power menu when Extend Unlock has been disabled [CHAR_LIMIT=NONE] -->
+    <string name="global_action_smart_lock_disabled">Extend Unlock disabled</string>
 
     <!-- text to show in place of RemoteInput images when they cannot be shown.
          [CHAR LIMIT=50] -->
@@ -2431,7 +2431,7 @@
 
     <!-- Shows in a dialog presented to the user to authorize this app to display a Device controls
          panel (embedded activity) instead of controls rendered by SystemUI [CHAR LIMIT=NONE] -->
-    <string name="controls_panel_authorization">When you add <xliff:g id="appName" example="My app">%s</xliff:g>, it can add controls and content to this panel. In some apps, you can choose which controls show up here.</string>
+    <string name="controls_panel_authorization"><xliff:g id="appName" example="My app">%s</xliff:g>can choose which controls and content show here.</string>
 
     <!-- Shows in a dialog presented to the user to authorize this app removal from a Device
          controls panel [CHAR LIMIT=NONE] -->
@@ -2491,7 +2491,7 @@
     <!-- Title of the dialog to control certain devices from lock screen without auth [CHAR LIMIT=NONE] -->
     <string name="controls_settings_trivial_controls_dialog_title">Control devices from lock screen?</string>
     <!-- Message of the dialog to control certain devices from lock screen without auth [CHAR LIMIT=NONE] -->
-    <string name="controls_settings_trivial_controls_dialog_message">You can control some devices without unlocking your phone or tablet.\n\nYour device app determines which devices can be controlled in this way.</string>
+    <string name="controls_settings_trivial_controls_dialog_message">You can control some devices without unlocking your phone or tablet. Your device app determines which devices can be controlled in this way.</string>
     <!-- Neutral button title of the controls dialog [CHAR LIMIT=NONE] -->
     <string name="controls_settings_dialog_neutral_button">No thanks</string>
     <!-- Positive button title of the controls dialog  [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index 9a581aa..482158e 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -91,6 +91,22 @@
         val sampledRegion = calculateSampledRegion(sampledView)
         val regions = ArrayList<RectF>()
         val sampledRegionWithOffset = convertBounds(sampledRegion)
+
+        if (
+            sampledRegionWithOffset.left < 0.0 ||
+                sampledRegionWithOffset.right > 1.0 ||
+                sampledRegionWithOffset.top < 0.0 ||
+                sampledRegionWithOffset.bottom > 1.0
+        ) {
+            android.util.Log.e(
+                "RegionSampler",
+                "view out of bounds: $sampledRegion | " +
+                    "screen width: ${displaySize.x}, screen height: ${displaySize.y}",
+                Exception()
+            )
+            return
+        }
+
         regions.add(sampledRegionWithOffset)
 
         wallpaperManager?.removeOnColorsChangedListener(this)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 359da13..5b27c40 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -29,8 +29,11 @@
 import android.annotation.DrawableRes;
 import android.annotation.SuppressLint;
 import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
@@ -86,6 +89,7 @@
     private RotationButton mRotationButton;
 
     private boolean mIsRecentsAnimationRunning;
+    private boolean mDocked;
     private boolean mHomeRotationEnabled;
     private int mLastRotationSuggestion;
     private boolean mPendingRotationSuggestion;
@@ -123,6 +127,12 @@
             () -> mPendingRotationSuggestion = false;
     private Animator mRotateHideAnimator;
 
+    private final BroadcastReceiver mDockedReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            updateDockedState(intent);
+        }
+    };
 
     private final IRotationWatcher.Stub mRotationWatcher = new IRotationWatcher.Stub() {
         @Override
@@ -136,7 +146,8 @@
                 // The isVisible check makes the rotation button disappear when we are not locked
                 // (e.g. for tabletop auto-rotate).
                 if (rotationLocked || mRotationButton.isVisible()) {
-                    if (shouldOverrideUserLockPrefs(rotation) && rotationLocked) {
+                    // Do not allow a change in rotation to set user rotation when docked.
+                    if (shouldOverrideUserLockPrefs(rotation) && rotationLocked && !mDocked) {
                         setRotationLockedAtAngle(rotation);
                     }
                     setRotateSuggestionButtonState(false /* visible */, true /* forced */);
@@ -214,6 +225,10 @@
         }
 
         mListenersRegistered = true;
+
+        updateDockedState(mContext.registerReceiver(mDockedReceiver,
+                new IntentFilter(Intent.ACTION_DOCK_EVENT)));
+
         try {
             WindowManagerGlobal.getWindowManagerService()
                     .watchRotation(mRotationWatcher, DEFAULT_DISPLAY);
@@ -234,6 +249,8 @@
         }
 
         mListenersRegistered = false;
+
+        mContext.unregisterReceiver(mDockedReceiver);
         try {
             WindowManagerGlobal.getWindowManagerService().removeRotationWatcher(mRotationWatcher);
         } catch (RemoteException e) {
@@ -345,6 +362,15 @@
         updateRotationButtonStateInOverview();
     }
 
+    private void updateDockedState(Intent intent) {
+        if (intent == null) {
+            return;
+        }
+
+        mDocked = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED)
+                != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+    }
+
     private void updateRotationButtonStateInOverview() {
         if (mIsRecentsAnimationRunning && !mHomeRotationEnabled) {
             setRotateSuggestionButtonState(false, true /* hideImmediately */);
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index 8323d09..f005bab 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -23,6 +23,8 @@
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Named
 
 @Module(includes = [
     FeatureFlagsDebugStartableModule::class,
@@ -35,7 +37,8 @@
     abstract fun bindsFeatureFlagDebug(impl: FeatureFlagsDebug): FeatureFlags
 
     @Binds
-    abstract fun bindsRestarter(debugRestarter: FeatureFlagsDebugRestarter): Restarter
+    @IntoSet
+    abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
 
     @Module
     companion object {
@@ -44,5 +47,10 @@
         fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager {
             return FlagManager(context, handler)
         }
+
+        @JvmStatic
+        @Provides
+        @Named(ConditionalRestarter.RESTART_DELAY)
+        fun provideRestartDelaySec(): Long = 1
     }
 }
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 87beff7..927d4604b 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -18,6 +18,9 @@
 
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoSet
+import javax.inject.Named
 
 @Module(includes = [
     FeatureFlagsReleaseStartableModule::class,
@@ -29,5 +32,18 @@
     abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
 
     @Binds
-    abstract fun bindsRestarter(debugRestarter: FeatureFlagsReleaseRestarter): Restarter
+    @IntoSet
+    abstract fun bindsScreenIdleCondition(impl: ScreenIdleCondition): ConditionalRestarter.Condition
+
+    @Binds
+    @IntoSet
+    abstract fun bindsPluggedInCondition(impl: PluggedInCondition): ConditionalRestarter.Condition
+
+    @Module
+    companion object {
+        @JvmStatic
+        @Provides
+        @Named(ConditionalRestarter.RESTART_DELAY)
+        fun provideRestartDelaySec(): Long = 30
+    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 92ee373..4aaa566 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -21,6 +21,7 @@
 import android.content.Intent
 import android.content.IntentFilter
 import android.content.res.Resources
+import android.graphics.Rect
 import android.text.format.DateFormat
 import android.util.TypedValue
 import android.view.View
@@ -119,10 +120,6 @@
 
     private val mLayoutChangedListener =
         object : View.OnLayoutChangeListener {
-            private var currentSmallClockView: View? = null
-            private var currentLargeClockView: View? = null
-            private var currentSmallClockLocation = IntArray(2)
-            private var currentLargeClockLocation = IntArray(2)
 
             override fun onLayoutChange(
                 view: View?,
@@ -135,6 +132,8 @@
                 oldRight: Int,
                 oldBottom: Int
             ) {
+                view?.removeOnLayoutChangeListener(this)
+
                 val parent = (view?.parent) as FrameLayout
 
                 // don't pass in negative bounds when clocks are in transition state
@@ -142,31 +141,12 @@
                     return
                 }
 
-                // SMALL CLOCK
-                if (parent.id == R.id.lockscreen_clock_view) {
-                    // view bounds have changed due to clock size changing (i.e. different character
-                    // widths)
-                    // AND/OR the view has been translated when transitioning between small and
-                    // large clock
-                    if (
-                        view != currentSmallClockView ||
-                            !view.locationOnScreen.contentEquals(currentSmallClockLocation)
-                    ) {
-                        currentSmallClockView = view
-                        currentSmallClockLocation = view.locationOnScreen
-                        updateRegionSampler(view)
-                    }
-                }
-                // LARGE CLOCK
-                else if (parent.id == R.id.lockscreen_clock_view_large) {
-                    if (
-                        view != currentLargeClockView ||
-                            !view.locationOnScreen.contentEquals(currentLargeClockLocation)
-                    ) {
-                        currentLargeClockView = view
-                        currentLargeClockLocation = view.locationOnScreen
-                        updateRegionSampler(view)
-                    }
+                val currentViewRect = Rect(left, top, right, bottom)
+                val oldViewRect = Rect(oldLeft, oldTop, oldRight, oldBottom)
+
+                if (currentViewRect.width() != oldViewRect.width() ||
+                    currentViewRect.height() != oldViewRect.height()) {
+                    updateRegionSampler(view)
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index 67e3400..0394754 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -217,9 +217,11 @@
     private void animate(float progress) {
         Interpolator standardDecelerate = Interpolators.STANDARD_DECELERATE;
         Interpolator legacyDecelerate = Interpolators.LEGACY_DECELERATE;
+        float standardProgress = standardDecelerate.getInterpolation(progress);
 
         mBouncerMessageView.setTranslationY(
-                mYTrans - mYTrans * standardDecelerate.getInterpolation(progress));
+                mYTrans - mYTrans * standardProgress);
+        mBouncerMessageView.setAlpha(standardProgress);
 
         for (int i = 0; i < mViews.length; i++) {
             View[] row = mViews[i];
@@ -236,7 +238,7 @@
                 view.setAlpha(scaledProgress);
                 int yDistance = mYTrans + mYTransOffset * i;
                 view.setTranslationY(
-                        yDistance - (yDistance * standardDecelerate.getInterpolation(progress)));
+                        yDistance - (yDistance * standardProgress));
                 if (view instanceof NumPadAnimationListener) {
                     ((NumPadAnimationListener) view).setProgress(scaledProgress);
                 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 66d5d09..ba5a8c9 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -39,6 +39,7 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.ValueAnimator;
 import android.app.Activity;
@@ -1067,10 +1068,14 @@
 
             int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation);
 
+            AnimatorSet anims = new AnimatorSet();
             ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation);
-            yAnim.setInterpolator(Interpolators.STANDARD_ACCELERATE);
-            yAnim.setDuration(500);
-            yAnim.start();
+            ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA,
+                    0f);
+
+            anims.setInterpolator(Interpolators.STANDARD_ACCELERATE);
+            anims.playTogether(alphaAnim, yAnim);
+            anims.start();
         }
 
         private void setupUserSwitcher() {
@@ -1220,8 +1225,7 @@
                 constraintSet.connect(rightElement, LEFT, leftElement, RIGHT);
                 constraintSet.connect(rightElement, RIGHT, PARENT_ID, RIGHT);
                 constraintSet.connect(mUserSwitcherViewGroup.getId(), TOP, PARENT_ID, TOP);
-                constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM,
-                        yTrans);
+                constraintSet.connect(mUserSwitcherViewGroup.getId(), BOTTOM, PARENT_ID, BOTTOM);
                 constraintSet.connect(mViewFlipper.getId(), TOP, PARENT_ID, TOP);
                 constraintSet.connect(mViewFlipper.getId(), BOTTOM, PARENT_ID, BOTTOM);
                 constraintSet.setHorizontalChainStyle(mUserSwitcherViewGroup.getId(), CHAIN_SPREAD);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 06258b2..98866c6 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -127,6 +127,7 @@
     private View.OnKeyListener mOnKeyListener = (v, keyCode, event) -> interceptMediaKey(event);
     private ActivityStarter.OnDismissAction mDismissAction;
     private Runnable mCancelAction;
+    private boolean mWillRunDismissFromKeyguard;
 
     private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
 
@@ -262,8 +263,10 @@
             // If there's a pending runnable because the user interacted with a widget
             // and we're leaving keyguard, then run it.
             boolean deferKeyguardDone = false;
+            mWillRunDismissFromKeyguard = false;
             if (mDismissAction != null) {
                 deferKeyguardDone = mDismissAction.onDismiss();
+                mWillRunDismissFromKeyguard = mDismissAction.willRunAnimationOnKeyguard();
                 mDismissAction = null;
                 mCancelAction = null;
             }
@@ -527,6 +530,13 @@
     }
 
     /**
+     * @return will the dismissal run from the keyguard layout (instead of from bouncer)
+     */
+    public boolean willRunDismissFromKeyguard() {
+        return mWillRunDismissFromKeyguard;
+    }
+
+    /**
      * Remove any dismiss action or cancel action that was set.
      */
     public void cancelDismissAction() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 30e2a0b..64fe645 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -717,6 +717,12 @@
         if (mKeyguardGoingAway) {
             updateFaceListeningState(BIOMETRIC_ACTION_STOP,
                     FACE_AUTH_STOPPED_KEYGUARD_GOING_AWAY);
+            for (int i = 0; i < mCallbacks.size(); i++) {
+                KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
+                if (cb != null) {
+                    cb.onKeyguardGoingAway();
+                }
+            }
         }
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
     }
@@ -3680,7 +3686,9 @@
      * Register to receive notifications about general keyguard information
      * (see {@link KeyguardUpdateMonitorCallback}.
      *
-     * @param callback The callback to register
+     * @param callback The callback to register. Stay away from passing anonymous instances
+     *                 as they will likely be dereferenced. Ensure that the callback is a class
+     *                 field to persist it.
      */
     public void registerCallback(KeyguardUpdateMonitorCallback callback) {
         Assert.isMainThread();
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 0d4889a..feff216 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -317,4 +317,9 @@
      * Called when the non-strong biometric state changed.
      */
     public void onNonStrongBiometricAllowedChanged(int userId) { }
+
+    /**
+     * Called when keyguard is going away or not going away.
+     */
+    public void onKeyguardGoingAway() { }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 3555d0a..2d37c29 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -173,12 +173,6 @@
     fun removeFavorites(componentName: ComponentName): Boolean
 
     /**
-     * Checks if the favorites can be removed. You can't remove components from the preferred list.
-     * @param componentName the name of the service that provides the [Control]
-     */
-    fun canRemoveFavorites(componentName: ComponentName): Boolean
-
-    /**
      * Replaces the favorites for the given structure.
      *
      * Calling this method will eliminate the previous selection of favorites and replace it with a
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 a171f43..ac1150e 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -38,6 +38,7 @@
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.dagger.SysUISingleton
@@ -57,16 +58,17 @@
 
 @SysUISingleton
 class ControlsControllerImpl @Inject constructor (
-    private val context: Context,
-    @Background private val executor: DelayableExecutor,
-    private val uiController: ControlsUiController,
-    private val bindingController: ControlsBindingController,
-    private val listingController: ControlsListingController,
-    private val userFileManager: UserFileManager,
-    private val userTracker: UserTracker,
-    private val authorizedPanelsRepository: AuthorizedPanelsRepository,
-    optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
-    dumpManager: DumpManager,
+        private val context: Context,
+        @Background private val executor: DelayableExecutor,
+        private val uiController: ControlsUiController,
+        private val selectedComponentRepository: SelectedComponentRepository,
+        private val bindingController: ControlsBindingController,
+        private val listingController: ControlsListingController,
+        private val userFileManager: UserFileManager,
+        private val userTracker: UserTracker,
+        private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+        optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+        dumpManager: DumpManager,
 ) : Dumpable, ControlsController {
 
     companion object {
@@ -499,17 +501,14 @@
         }
     }
 
-    override fun canRemoveFavorites(componentName: ComponentName): Boolean =
-            !authorizedPanelsRepository.getPreferredPackages().contains(componentName.packageName)
-
     override fun removeFavorites(componentName: ComponentName): Boolean {
         if (!confirmAvailability()) return false
-        if (!canRemoveFavorites(componentName)) return false
 
         executor.execute {
-            Favorites.removeStructures(componentName)
+            if (Favorites.removeStructures(componentName)) {
+                persistenceWrapper.storeFavorites(Favorites.getAllStructures())
+            }
             authorizedPanelsRepository.removeAuthorizedPanels(setOf(componentName.packageName))
-            persistenceWrapper.storeFavorites(Favorites.getAllStructures())
         }
         return true
     }
@@ -576,7 +575,9 @@
     }
 
     override fun setPreferredSelection(selectedItem: SelectedItem) {
-        uiController.updatePreferences(selectedItem)
+        selectedComponentRepository.setSelectedComponent(
+                SelectedComponentRepository.SelectedComponent(selectedItem)
+        )
     }
 
     override fun dump(pw: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index d949d11..2af49aa 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -36,6 +36,8 @@
 import com.android.systemui.controls.management.ControlsRequestDialog
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
 import com.android.systemui.controls.panels.AuthorizedPanelsRepositoryImpl
+import com.android.systemui.controls.panels.SelectedComponentRepository
+import com.android.systemui.controls.panels.SelectedComponentRepositoryImpl
 import com.android.systemui.controls.settings.ControlsSettingsDialogManager
 import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl
 import com.android.systemui.controls.ui.ControlActionCoordinator
@@ -114,6 +116,11 @@
         repository: AuthorizedPanelsRepositoryImpl
     ): AuthorizedPanelsRepository
 
+    @Binds
+    abstract fun providePreferredPanelRepository(
+        repository: SelectedComponentRepositoryImpl
+    ): SelectedComponentRepository
+
     @BindsOptionalOf
     abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
index e51e832..5c2402b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImpl.kt
@@ -20,6 +20,8 @@
 import android.content.Context
 import android.content.SharedPreferences
 import com.android.systemui.R
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
@@ -30,7 +32,8 @@
 constructor(
     private val context: Context,
     private val userFileManager: UserFileManager,
-    private val userTracker: UserTracker
+    private val userTracker: UserTracker,
+    private val featureFlags: FeatureFlags,
 ) : AuthorizedPanelsRepository {
 
     override fun getAuthorizedPanels(): Set<String> {
@@ -71,8 +74,18 @@
                 userTracker.userId,
             )
 
-        // If we've never run this (i.e., the key doesn't exist), add the default packages
-        if (sharedPref.getStringSet(KEY, null) == null) {
+        // We should add default packages in two cases:
+        // 1) We've never run this
+        // 2) APP_PANELS_REMOVE_APPS_ALLOWED got disabled after user removed all apps
+        val needToSetup =
+            if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
+                sharedPref.getStringSet(KEY, null) == null
+            } else {
+                // There might be an empty set that need to be overridden after the feature has been
+                // turned off after being turned on
+                sharedPref.getStringSet(KEY, null).isNullOrEmpty()
+            }
+        if (needToSetup) {
             sharedPref.edit().putStringSet(KEY, getPreferredPackages()).apply()
         }
         return sharedPref
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt
new file mode 100644
index 0000000..5bb6eec
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepository.kt
@@ -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.systemui.controls.panels
+
+import android.content.ComponentName
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.SelectedItem
+import com.android.systemui.flags.Flags
+
+/** Stores user-selected preferred component. */
+interface SelectedComponentRepository {
+
+    /**
+     * Returns currently set preferred component, or null when nothing is set. Consider using
+     * [ControlsUiController.getPreferredSelectedItem] to get domain specific data
+     */
+    fun getSelectedComponent(): SelectedComponent?
+
+    /** Sets preferred component. Use [getSelectedComponent] to get current one */
+    fun setSelectedComponent(selectedComponent: SelectedComponent)
+
+    /** Clears current preferred component. [getSelectedComponent] will return null afterwards */
+    fun removeSelectedComponent()
+
+    /**
+     * Return true when default preferred component should be set up and false the otherwise. This
+     * is always true when [Flags.APP_PANELS_REMOVE_APPS_ALLOWED] is disabled
+     */
+    fun shouldAddDefaultComponent(): Boolean
+
+    /**
+     * Sets if default component should be added. This is ignored when
+     * [Flags.APP_PANELS_REMOVE_APPS_ALLOWED] is disabled
+     */
+    fun setShouldAddDefaultComponent(shouldAdd: Boolean)
+
+    data class SelectedComponent(
+        val name: String,
+        val componentName: ComponentName?,
+        val isPanel: Boolean,
+    ) {
+        constructor(
+            selectedItem: SelectedItem
+        ) : this(
+            name = selectedItem.name.toString(),
+            componentName = selectedItem.componentName,
+            isPanel = selectedItem is SelectedItem.PanelItem,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
new file mode 100644
index 0000000..0fb5b66
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/panels/SelectedComponentRepositoryImpl.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.panels
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.SharedPreferences
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import javax.inject.Inject
+
+@SysUISingleton
+class SelectedComponentRepositoryImpl
+@Inject
+constructor(
+    private val userFileManager: UserFileManager,
+    private val userTracker: UserTracker,
+    private val featureFlags: FeatureFlags,
+) : SelectedComponentRepository {
+
+    private companion object {
+        const val PREF_COMPONENT = "controls_component"
+        const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
+        const val PREF_IS_PANEL = "controls_is_panel"
+        const val SHOULD_ADD_DEFAULT_PANEL = "should_add_default_panel"
+    }
+
+    private val sharedPreferences: SharedPreferences
+        get() =
+            userFileManager.getSharedPreferences(
+                fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+                mode = Context.MODE_PRIVATE,
+                userId = userTracker.userId
+            )
+
+    override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? {
+        with(sharedPreferences) {
+            val componentString = getString(PREF_COMPONENT, null) ?: return null
+            return SelectedComponentRepository.SelectedComponent(
+                name = getString(PREF_STRUCTURE_OR_APP_NAME, "")!!,
+                componentName = ComponentName.unflattenFromString(componentString),
+                isPanel = getBoolean(PREF_IS_PANEL, false)
+            )
+        }
+    }
+
+    override fun setSelectedComponent(
+        selectedComponent: SelectedComponentRepository.SelectedComponent
+    ) {
+        sharedPreferences
+            .edit()
+            .putString(PREF_COMPONENT, selectedComponent.componentName?.flattenToString())
+            .putString(PREF_STRUCTURE_OR_APP_NAME, selectedComponent.name)
+            .putBoolean(PREF_IS_PANEL, selectedComponent.isPanel)
+            .apply()
+    }
+
+    override fun removeSelectedComponent() {
+        sharedPreferences
+            .edit()
+            .remove(PREF_COMPONENT)
+            .remove(PREF_STRUCTURE_OR_APP_NAME)
+            .remove(PREF_IS_PANEL)
+            .apply()
+    }
+
+    override fun shouldAddDefaultComponent(): Boolean =
+        if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
+            sharedPreferences.getBoolean(SHOULD_ADD_DEFAULT_PANEL, true)
+        } else {
+            true
+        }
+
+    override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
+        sharedPreferences.edit().putBoolean(SHOULD_ADD_DEFAULT_PANEL, shouldAdd).apply()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
index 9d99253..3a4a00c 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
@@ -18,17 +18,16 @@
 package com.android.systemui.controls.start
 
 import android.content.Context
-import android.content.res.Resources
 import android.os.UserHandle
 import com.android.systemui.CoreStartable
-import com.android.systemui.R
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
 import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.settings.UserTracker
 import java.util.concurrent.Executor
 import javax.inject.Inject
@@ -37,7 +36,7 @@
  * Started with SystemUI to perform early operations for device controls subsystem (only if enabled)
  *
  * In particular, it will perform the following:
- * * If there is no preferred selection for provider and at least one of the preferred packages 
+ * * If there is no preferred selection for provider and at least one of the preferred packages
  * provides a panel, it will select the first one that does.
  * * If the preferred selection provides a panel, it will bind to that service (to reduce latency on
  * displaying the panel).
@@ -48,10 +47,11 @@
 class ControlsStartable
 @Inject
 constructor(
-    @Main private val resources: Resources,
-    @Background private val executor: Executor,
-    private val controlsComponent: ControlsComponent,
-    private val userTracker: UserTracker
+        @Background private val executor: Executor,
+        private val controlsComponent: ControlsComponent,
+        private val userTracker: UserTracker,
+        private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+        private val selectedComponentRepository: SelectedComponentRepository,
 ) : CoreStartable {
 
     // These two controllers can only be accessed after `start` method once we've checked if the
@@ -85,12 +85,15 @@
     }
 
     private fun selectDefaultPanelIfNecessary() {
+        if (!selectedComponentRepository.shouldAddDefaultComponent()) {
+            return
+        }
         val currentSelection = controlsController.getPreferredSelection()
         if (currentSelection == SelectedItem.EMPTY_SELECTION) {
             val availableServices = controlsListingController.getCurrentServices()
             val panels = availableServices.filter { it.panelActivity != null }
-            resources
-                .getStringArray(R.array.config_controlsPreferredPackages)
+            authorizedPanelsRepository
+                .getPreferredPackages()
                 // Looking for the first element in the string array such that there is one package
                 // that has a panel. It will return null if there are no packages in the array,
                 // or if no packages in the array have a panel associated with it.
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
index 58673bb..0d53117 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -64,8 +64,6 @@
      * This element will be the one that appears when the user first opens the controls activity.
      */
     fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem
-
-    fun updatePreferences(selectedItem: SelectedItem)
 }
 
 sealed class SelectedItem {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index c61dad6..5da86de9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -64,6 +64,7 @@
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
 import com.android.systemui.controls.settings.ControlsSettingsRepository
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -73,9 +74,7 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.globalactions.GlobalActionsPopupMenu
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.concurrency.DelayableExecutor
@@ -84,7 +83,7 @@
 import dagger.Lazy
 import java.io.PrintWriter
 import java.text.Collator
-import java.util.*
+import java.util.Optional
 import java.util.function.Consumer
 import javax.inject.Inject
 
@@ -98,25 +97,22 @@
         @Main val uiExecutor: DelayableExecutor,
         @Background val bgExecutor: DelayableExecutor,
         val controlsListingController: Lazy<ControlsListingController>,
-        val controlActionCoordinator: ControlActionCoordinator,
+        private val controlActionCoordinator: ControlActionCoordinator,
         private val activityStarter: ActivityStarter,
         private val iconCache: CustomIconCache,
         private val controlsMetricsLogger: ControlsMetricsLogger,
         private val keyguardStateController: KeyguardStateController,
-        private val userFileManager: UserFileManager,
         private val userTracker: UserTracker,
         private val taskViewFactory: Optional<TaskViewFactory>,
         private val controlsSettingsRepository: ControlsSettingsRepository,
         private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+        private val selectedComponentRepository: SelectedComponentRepository,
         private val featureFlags: FeatureFlags,
         private val dialogsFactory: ControlsDialogsFactory,
         dumpManager: DumpManager
 ) : ControlsUiController, Dumpable {
 
     companion object {
-        private const val PREF_COMPONENT = "controls_component"
-        private const val PREF_STRUCTURE_OR_APP_NAME = "controls_structure"
-        private const val PREF_IS_PANEL = "controls_is_panel"
 
         private const val FADE_IN_MILLIS = 200L
 
@@ -138,12 +134,6 @@
     private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
     private var retainCache = false
     private var lastSelections = emptyList<SelectionItem>()
-    private val sharedPreferences
-        get() = userFileManager.getSharedPreferences(
-            fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
-            mode = 0,
-            userId = userTracker.userId
-        )
 
     private var taskViewController: PanelTaskViewController? = null
 
@@ -341,20 +331,18 @@
             if (!controlsController.get().removeFavorites(componentName)) {
                 return@createRemoveAppDialog
             }
-            if (
-                    sharedPreferences.getString(PREF_COMPONENT, "") ==
-                    componentName.flattenToString()
-            ) {
-                sharedPreferences
-                        .edit()
-                        .remove(PREF_COMPONENT)
-                        .remove(PREF_STRUCTURE_OR_APP_NAME)
-                        .remove(PREF_IS_PANEL)
-                        .commit()
+
+            if (selectedComponentRepository.getSelectedComponent()?.componentName ==
+                    componentName) {
+                selectedComponentRepository.removeSelectedComponent()
             }
 
-            allStructures = controlsController.get().getFavorites()
-            selectedItem = getPreferredSelectedItem(allStructures)
+            val selectedItem = getPreferredSelectedItem(controlsController.get().getFavorites())
+            if (selectedItem == SelectedItem.EMPTY_SELECTION) {
+                // User removed the last panel. In this case we start app selection flow and don't
+                // want to auto-add it again
+                selectedComponentRepository.setShouldAddDefaultComponent(false)
+            }
             reload(parent)
         }.apply { show() }
     }
@@ -522,8 +510,7 @@
                             ADD_APP_ID
                     ))
                 }
-                if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED) &&
-                        controlsController.get().canRemoveFavorites(selectedItem.componentName)) {
+                if (featureFlags.isEnabled(Flags.APP_PANELS_REMOVE_APPS_ALLOWED)) {
                     add(OverflowMenuAdapter.MenuItem(
                             context.getText(R.string.controls_menu_remove),
                             REMOVE_APP_ID,
@@ -569,7 +556,7 @@
                                 ADD_CONTROLS_ID -> startFavoritingActivity(selectedStructure)
                                 EDIT_CONTROLS_ID -> startEditingActivity(selectedStructure)
                                 REMOVE_APP_ID -> startRemovingApp(
-                                        selectedStructure.componentName, selectionItem.appName
+                                        selectionItem.componentName, selectionItem.appName
                                 )
                             }
                             dismiss()
@@ -714,29 +701,22 @@
     }
 
     override fun getPreferredSelectedItem(structures: List<StructureInfo>): SelectedItem {
-        val sp = sharedPreferences
-
-        val component = sp.getString(PREF_COMPONENT, null)?.let {
-            ComponentName.unflattenFromString(it)
-        } ?: EMPTY_COMPONENT
-        val name = sp.getString(PREF_STRUCTURE_OR_APP_NAME, "")!!
-        val isPanel = sp.getBoolean(PREF_IS_PANEL, false)
-        return if (isPanel) {
-            SelectedItem.PanelItem(name, component)
+        val preferredPanel = selectedComponentRepository.getSelectedComponent()
+        val component = preferredPanel?.componentName ?: EMPTY_COMPONENT
+        return if (preferredPanel?.isPanel == true) {
+            SelectedItem.PanelItem(preferredPanel.name, component)
         } else {
             if (structures.isEmpty()) return SelectedItem.EMPTY_SELECTION
             SelectedItem.StructureItem(structures.firstOrNull {
-                component == it.componentName && name == it.structure
-            } ?: structures.get(0))
+                component == it.componentName && preferredPanel?.name == it.structure
+            } ?: structures[0])
         }
     }
 
-    override fun updatePreferences(selectedItem: SelectedItem) {
-        sharedPreferences.edit()
-                .putString(PREF_COMPONENT, selectedItem.componentName.flattenToString())
-                .putString(PREF_STRUCTURE_OR_APP_NAME, selectedItem.name.toString())
-                .putBoolean(PREF_IS_PANEL, selectedItem is SelectedItem.PanelItem)
-                .apply()
+    private fun updatePreferences(selectedItem: SelectedItem) {
+        selectedComponentRepository.setSelectedComponent(
+                SelectedComponentRepository.SelectedComponent(selectedItem)
+        )
     }
 
     private fun maybeUpdateSelectedItem(item: SelectionItem): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 7a1abf4..63a4fd2 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -70,6 +70,8 @@
 import com.android.systemui.security.data.repository.SecurityRepositoryModule;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolatorImpl;
 import com.android.systemui.smartspace.dagger.SmartspaceModule;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -309,4 +311,8 @@
 
     @Binds
     abstract FgsManagerController bindFgsManagerController(FgsManagerControllerImpl impl);
+
+    @Binds
+    abstract LargeScreenShadeInterpolator largeScreensShadeInterpolator(
+            LargeScreenShadeInterpolatorImpl impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
new file mode 100644
index 0000000..b20e33a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/ConditionalRestarter.kt
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+import javax.inject.Named
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+/** Restarts the process after all passed in [Condition]s are true. */
+class ConditionalRestarter
+@Inject
+constructor(
+    private val systemExitRestarter: SystemExitRestarter,
+    private val conditions: Set<@JvmSuppressWildcards Condition>,
+    @Named(RESTART_DELAY) private val restartDelaySec: Long,
+    @Application private val applicationScope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) : Restarter {
+
+    private var restartJob: Job? = null
+    private var pendingReason = ""
+    private var androidRestartRequested = false
+
+    override fun restartSystemUI(reason: String) {
+        Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting when idle.")
+        scheduleRestart(reason)
+    }
+
+    override fun restartAndroid(reason: String) {
+        Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting when idle.")
+        androidRestartRequested = true
+        scheduleRestart(reason)
+    }
+
+    private fun scheduleRestart(reason: String = "") {
+        pendingReason = if (reason.isEmpty()) pendingReason else reason
+
+        if (conditions.all { c -> c.canRestartNow(this::scheduleRestart) }) {
+            if (restartJob == null) {
+                restartJob =
+                    applicationScope.launch(backgroundDispatcher) {
+                        delay(TimeUnit.SECONDS.toMillis(restartDelaySec))
+                        restartNow()
+                    }
+            }
+        } else {
+            restartJob?.cancel()
+            restartJob = null
+        }
+    }
+
+    private fun restartNow() {
+        if (androidRestartRequested) {
+            systemExitRestarter.restartAndroid(pendingReason)
+        } else {
+            systemExitRestarter.restartSystemUI(pendingReason)
+        }
+    }
+
+    interface Condition {
+        /**
+         * Should return true if the system is ready to restart.
+         *
+         * A call to this function means that we want to restart and are waiting for this condition
+         * to return true.
+         *
+         * retryFn should be cached if it is _not_ ready to restart, and later called when it _is_
+         * ready to restart. At that point, this method will be called again to verify that the
+         * system is ready.
+         *
+         * Multiple calls to an instance of this method may happen for a single restart attempt if
+         * multiple [Condition]s are being checked. If any one [Condition] returns false, all the
+         * [Condition]s will need to be rechecked on the next restart attempt.
+         */
+        fun canRestartNow(retryFn: () -> Unit): Boolean
+    }
+
+    companion object {
+        const val RESTART_DELAY = "restarter_restart_delay"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
deleted file mode 100644
index a6956a4..0000000
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugRestarter.kt
+++ /dev/null
@@ -1,70 +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.flags
-
-import android.util.Log
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import javax.inject.Inject
-
-/** Restarts SystemUI when the screen is locked. */
-class FeatureFlagsDebugRestarter
-@Inject
-constructor(
-    private val wakefulnessLifecycle: WakefulnessLifecycle,
-    private val systemExitRestarter: SystemExitRestarter,
-) : Restarter {
-
-    private var androidRestartRequested = false
-    private var pendingReason = ""
-
-    val observer =
-        object : WakefulnessLifecycle.Observer {
-            override fun onFinishedGoingToSleep() {
-                Log.d(FeatureFlagsDebug.TAG, "Restarting due to systemui flag change")
-                restartNow()
-            }
-        }
-
-    override fun restartSystemUI(reason: String) {
-        Log.d(FeatureFlagsDebug.TAG, "SystemUI Restart requested. Restarting on next screen off.")
-        Log.i(FeatureFlagsDebug.TAG, reason)
-        scheduleRestart(reason)
-    }
-
-    override fun restartAndroid(reason: String) {
-        Log.d(FeatureFlagsDebug.TAG, "Android Restart requested. Restarting on next screen off.")
-        androidRestartRequested = true
-        scheduleRestart(reason)
-    }
-
-    fun scheduleRestart(reason: String) {
-        pendingReason = reason
-        if (wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP) {
-            restartNow()
-        } else {
-            wakefulnessLifecycle.addObserver(observer)
-        }
-    }
-
-    private fun restartNow() {
-        if (androidRestartRequested) {
-            systemExitRestarter.restartAndroid(pendingReason)
-        } else {
-            systemExitRestarter.restartSystemUI(pendingReason)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
deleted file mode 100644
index c08266c..0000000
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsReleaseRestarter.kt
+++ /dev/null
@@ -1,101 +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.flags
-
-import android.util.Log
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.util.concurrency.DelayableExecutor
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
-
-/** Restarts SystemUI when the device appears idle. */
-class FeatureFlagsReleaseRestarter
-@Inject
-constructor(
-    private val wakefulnessLifecycle: WakefulnessLifecycle,
-    private val batteryController: BatteryController,
-    @Background private val bgExecutor: DelayableExecutor,
-    private val systemExitRestarter: SystemExitRestarter
-) : Restarter {
-    var listenersAdded = false
-    var pendingRestart: Runnable? = null
-    private var pendingReason = ""
-    var androidRestartRequested = false
-
-    val observer =
-        object : WakefulnessLifecycle.Observer {
-            override fun onFinishedGoingToSleep() {
-                scheduleRestart(pendingReason)
-            }
-        }
-
-    val batteryCallback =
-        object : BatteryController.BatteryStateChangeCallback {
-            override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
-                scheduleRestart(pendingReason)
-            }
-        }
-
-    override fun restartSystemUI(reason: String) {
-        Log.d(
-            FeatureFlagsDebug.TAG,
-            "SystemUI Restart requested. Restarting when plugged in and idle."
-        )
-        scheduleRestart(reason)
-    }
-
-    override fun restartAndroid(reason: String) {
-        Log.d(
-            FeatureFlagsDebug.TAG,
-            "Android Restart requested. Restarting when plugged in and idle."
-        )
-        androidRestartRequested = true
-        scheduleRestart(reason)
-    }
-
-    private fun scheduleRestart(reason: String) {
-        // Don't bother adding listeners twice.
-        pendingReason = reason
-        if (!listenersAdded) {
-            listenersAdded = true
-            wakefulnessLifecycle.addObserver(observer)
-            batteryController.addCallback(batteryCallback)
-        }
-        if (
-            wakefulnessLifecycle.wakefulness == WAKEFULNESS_ASLEEP && batteryController.isPluggedIn
-        ) {
-            if (pendingRestart == null) {
-                pendingRestart = bgExecutor.executeDelayed(this::restartNow, 30L, TimeUnit.SECONDS)
-            }
-        } else if (pendingRestart != null) {
-            pendingRestart?.run()
-            pendingRestart = null
-        }
-    }
-
-    private fun restartNow() {
-        Log.d(FeatureFlagsRelease.TAG, "Restarting due to systemui flag change")
-        if (androidRestartRequested) {
-            systemExitRestarter.restartAndroid(pendingReason)
-        } else {
-            systemExitRestarter.restartSystemUI(pendingReason)
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 9a6d90a..b6edcf5 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -112,6 +112,9 @@
     val ANIMATED_NOTIFICATION_SHADE_INSETS =
         unreleasedFlag(270682168, "animated_notification_shade_insets", teamfood = true)
 
+    // TODO(b/268005230): Tracking Bug
+    @JvmField val SENSITIVE_REVEAL_ANIM = unreleasedFlag(268005230, "sensitive_reveal_anim")
+
     // 200 - keyguard/lockscreen
     // ** Flag retired **
     // public static final BooleanFlag KEYGUARD_LAYOUT =
@@ -385,13 +388,16 @@
     @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple")
 
     // TODO(b/270882464): Tracking Bug
-    val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2")
+    val ENABLE_DOCK_SETUP_V2 = unreleasedFlag(1005, "enable_dock_setup_v2", teamfood = true)
 
     // TODO(b/265045965): Tracking Bug
     val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot")
 
     @JvmField
-    val ENABLE_LOW_LIGHT_CLOCK_UNDOCKED = unreleasedFlag(1004, "enable_low_light_clock_undocked")
+    // TODO(b/271428141): Tracking Bug
+    val ENABLE_LOW_LIGHT_CLOCK_UNDOCKED = unreleasedFlag(
+        1004,
+        "enable_low_light_clock_undocked", teamfood = true)
 
     // 1100 - windowing
     @Keep
@@ -579,7 +585,7 @@
     @JvmField
     val LEAVE_SHADE_OPEN_FOR_BUGREPORT = releasedFlag(1800, "leave_shade_open_for_bugreport")
     // TODO(b/265944639): Tracking Bug
-    @JvmField val DUAL_SHADE = releasedFlag(1801, "dual_shade")
+    @JvmField val DUAL_SHADE = unreleasedFlag(1801, "dual_shade")
 
     // 1900
     @JvmField val NOTE_TASKS = releasedFlag(1900, "keycode_flag")
@@ -653,4 +659,9 @@
     // TODO(b/259428678): Tracking Bug
     @JvmField
     val KEYBOARD_BACKLIGHT_INDICATOR = unreleasedFlag(2601, "keyboard_backlight_indicator")
+
+    // TODO(b/272036292): Tracking Bug
+    @JvmField
+    val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION =
+            unreleasedFlag(2602, "large_shade_granular_alpha_interpolation")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
index 0054d26..3c50125 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -15,6 +15,7 @@
  */
 package com.android.systemui.flags
 
+import dagger.Binds
 import dagger.Module
 import dagger.Provides
 import javax.inject.Named
@@ -22,6 +23,8 @@
 /** Module containing shared code for all FeatureFlag implementations. */
 @Module
 interface FlagsCommonModule {
+    @Binds fun bindsRestarter(impl: ConditionalRestarter): Restarter
+
     companion object {
         const val ALL_FLAGS = "all_flags"
 
diff --git a/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
new file mode 100644
index 0000000..3120638
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/PluggedInCondition.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import com.android.systemui.statusbar.policy.BatteryController
+import javax.inject.Inject
+
+/** Returns true when the device is plugged in. */
+class PluggedInCondition
+@Inject
+constructor(
+    private val batteryController: BatteryController,
+) : ConditionalRestarter.Condition {
+
+    var listenersAdded = false
+    var retryFn: (() -> Unit)? = null
+
+    val batteryCallback =
+        object : BatteryController.BatteryStateChangeCallback {
+            override fun onBatteryLevelChanged(level: Int, pluggedIn: Boolean, charging: Boolean) {
+                retryFn?.invoke()
+            }
+        }
+
+    override fun canRestartNow(retryFn: () -> Unit): Boolean {
+        if (!listenersAdded) {
+            listenersAdded = true
+            batteryController.addCallback(batteryCallback)
+        }
+
+        this.retryFn = retryFn
+
+        return batteryController.isPluggedIn
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
new file mode 100644
index 0000000..49e61af
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/ScreenIdleCondition.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.flags
+
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import javax.inject.Inject
+
+/** Returns true when the device is "asleep" as defined by the [WakefullnessLifecycle]. */
+class ScreenIdleCondition
+@Inject
+constructor(
+    private val wakefulnessLifecycle: WakefulnessLifecycle,
+) : ConditionalRestarter.Condition {
+
+    var listenersAdded = false
+    var retryFn: (() -> Unit)? = null
+
+    val observer =
+        object : WakefulnessLifecycle.Observer {
+            override fun onFinishedGoingToSleep() {
+                retryFn?.invoke()
+            }
+        }
+
+    override fun canRestartNow(retryFn: () -> Unit): Boolean {
+        if (!listenersAdded) {
+            listenersAdded = true
+            wakefulnessLifecycle.addObserver(observer)
+        }
+
+        this.retryFn = retryFn
+
+        return wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index e43f83b..07753ca 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -788,6 +788,11 @@
 
         @Override
         public boolean onLongPress() {
+            // don't actually trigger the reboot if we are running stability
+            // tests via monkey
+            if (ActivityManager.isUserAMonkey()) {
+                return false;
+            }
             mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_LONG_PRESS);
             if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
                 mWindowManagerFuncs.reboot(true);
@@ -808,6 +813,11 @@
 
         @Override
         public void onPress() {
+            // don't actually trigger the shutdown if we are running stability
+            // tests via monkey
+            if (ActivityManager.isUserAMonkey()) {
+                return;
+            }
             mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_PRESS);
             // shutdown by making sure radio and power are handled accordingly.
             mWindowManagerFuncs.shutdown();
@@ -919,6 +929,11 @@
 
         @Override
         public boolean onLongPress() {
+            // don't actually trigger the reboot if we are running stability
+            // tests via monkey
+            if (ActivityManager.isUserAMonkey()) {
+                return false;
+            }
             mUiEventLogger.log(GlobalActionsEvent.GA_REBOOT_LONG_PRESS);
             if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
                 mWindowManagerFuncs.reboot(true);
@@ -939,6 +954,11 @@
 
         @Override
         public void onPress() {
+            // don't actually trigger the reboot if we are running stability
+            // tests via monkey
+            if (ActivityManager.isUserAMonkey()) {
+                return;
+            }
             mUiEventLogger.log(GlobalActionsEvent.GA_REBOOT_PRESS);
             mWindowManagerFuncs.reboot(false);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
index 85d0379..5e806b6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinator.kt
@@ -46,8 +46,15 @@
             viewModel.dialogContent.collect { dialogViewModel ->
                 if (dialogViewModel != null) {
                     if (dialog == null) {
-                        dialog = KeyboardBacklightDialog(context, dialogViewModel)
-                        // pass viewModel and show
+                        dialog =
+                            KeyboardBacklightDialog(
+                                context,
+                                initialCurrentLevel = dialogViewModel.currentValue,
+                                initialMaxLevel = dialogViewModel.maxValue
+                            )
+                        dialog?.show()
+                    } else {
+                        dialog?.updateState(dialogViewModel.currentValue, dialogViewModel.maxValue)
                     }
                 } else {
                     dialog?.dismiss()
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 b68a2a8..a173f8b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/backlight/ui/view/KeyboardBacklightDialog.kt
@@ -17,16 +17,260 @@
 
 package com.android.systemui.keyboard.backlight.ui.view
 
+import android.annotation.ColorInt
 import android.app.Dialog
 import android.content.Context
+import android.graphics.drawable.ShapeDrawable
+import android.graphics.drawable.shapes.RoundRectShape
 import android.os.Bundle
-import com.android.systemui.keyboard.backlight.ui.viewmodel.BacklightDialogContentViewModel
+import android.view.Gravity
+import android.view.Window
+import android.view.WindowManager
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.LinearLayout.LayoutParams
+import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT
+import com.android.systemui.R
+import com.android.systemui.util.children
 
-class KeyboardBacklightDialog(context: Context, val viewModel: BacklightDialogContentViewModel) :
-    Dialog(context) {
+class KeyboardBacklightDialog(
+    context: Context,
+    initialCurrentLevel: Int,
+    initialMaxLevel: Int,
+) : Dialog(context) {
+
+    private data class RootProperties(
+        val cornerRadius: Float,
+        val verticalPadding: Int,
+        val horizontalPadding: Int,
+    )
+
+    private data class BacklightIconProperties(
+        val width: Int,
+        val height: Int,
+        val leftMargin: Int,
+    )
+
+    private data class StepViewProperties(
+        val width: Int,
+        val height: Int,
+        val horizontalMargin: Int,
+        val smallRadius: Float,
+        val largeRadius: Float,
+    )
+
+    private var currentLevel: Int = 0
+    private var maxLevel: Int = 0
+
+    private lateinit var rootView: LinearLayout
+
+    private var dialogBottomMargin = 208
+    private lateinit var rootProperties: RootProperties
+    private lateinit var iconProperties: BacklightIconProperties
+    private lateinit var stepProperties: StepViewProperties
+    @ColorInt var filledRectangleColor: Int = 0
+    @ColorInt var emptyRectangleColor: Int = 0
+    @ColorInt var backgroundColor: Int = 0
+
+    init {
+        currentLevel = initialCurrentLevel
+        maxLevel = initialMaxLevel
+    }
 
     override fun onCreate(savedInstanceState: Bundle?) {
+        setUpWindowProperties(this)
+        setWindowTitle()
+        updateResources()
+        rootView = buildRootView()
+        setContentView(rootView)
         super.onCreate(savedInstanceState)
-        // TODO(b/268650355) Implement the dialog
+        updateState(currentLevel, maxLevel, forceRefresh = true)
+    }
+
+    private fun updateResources() {
+        context.resources.apply {
+            filledRectangleColor = getColor(R.color.backlight_indicator_step_filled)
+            emptyRectangleColor = getColor(R.color.backlight_indicator_step_empty)
+            backgroundColor = getColor(R.color.backlight_indicator_background)
+            rootProperties =
+                RootProperties(
+                    cornerRadius =
+                        getDimensionPixelSize(R.dimen.backlight_indicator_root_corner_radius)
+                            .toFloat(),
+                    verticalPadding =
+                        getDimensionPixelSize(R.dimen.backlight_indicator_root_vertical_padding),
+                    horizontalPadding =
+                        getDimensionPixelSize(R.dimen.backlight_indicator_root_horizontal_padding)
+                )
+            iconProperties =
+                BacklightIconProperties(
+                    width = getDimensionPixelSize(R.dimen.backlight_indicator_icon_width),
+                    height = getDimensionPixelSize(R.dimen.backlight_indicator_icon_height),
+                    leftMargin =
+                        getDimensionPixelSize(R.dimen.backlight_indicator_icon_left_margin),
+                )
+            stepProperties =
+                StepViewProperties(
+                    width = getDimensionPixelSize(R.dimen.backlight_indicator_step_width),
+                    height = getDimensionPixelSize(R.dimen.backlight_indicator_step_height),
+                    horizontalMargin =
+                        getDimensionPixelSize(R.dimen.backlight_indicator_step_horizontal_margin),
+                    smallRadius =
+                        getDimensionPixelSize(R.dimen.backlight_indicator_step_small_radius)
+                            .toFloat(),
+                    largeRadius =
+                        getDimensionPixelSize(R.dimen.backlight_indicator_step_large_radius)
+                            .toFloat(),
+                )
+        }
+    }
+
+    fun updateState(current: Int, max: Int, forceRefresh: Boolean = false) {
+        if (maxLevel != max || forceRefresh) {
+            maxLevel = max
+            rootView.removeAllViews()
+            buildStepViews().forEach { rootView.addView(it) }
+        }
+        currentLevel = current
+        updateLevel()
+    }
+
+    private fun updateLevel() {
+        rootView.children.forEachIndexed(
+            action = { index, v ->
+                val drawable = v.background as ShapeDrawable
+                if (index <= currentLevel) {
+                    updateColor(drawable, filledRectangleColor)
+                } else {
+                    updateColor(drawable, emptyRectangleColor)
+                }
+            }
+        )
+    }
+
+    private fun updateColor(drawable: ShapeDrawable, @ColorInt color: Int) {
+        if (drawable.paint.color != color) {
+            drawable.paint.color = color
+            drawable.invalidateSelf()
+        }
+    }
+
+    private fun buildRootView(): LinearLayout {
+        val linearLayout =
+            LinearLayout(context).apply {
+                orientation = LinearLayout.HORIZONTAL
+                layoutParams = LayoutParams(WRAP_CONTENT, WRAP_CONTENT)
+                setPadding(
+                    /* left= */ rootProperties.horizontalPadding,
+                    /* top= */ rootProperties.verticalPadding,
+                    /* right= */ rootProperties.horizontalPadding,
+                    /* bottom= */ rootProperties.verticalPadding
+                )
+            }
+        val drawable =
+            ShapeDrawable(
+                RoundRectShape(
+                    /* outerRadii= */ FloatArray(8) { rootProperties.cornerRadius },
+                    /* inset= */ null,
+                    /* innerRadii= */ null
+                )
+            )
+        drawable.paint.color = backgroundColor
+        linearLayout.background = drawable
+        return linearLayout
+    }
+
+    private fun buildStepViews(): List<FrameLayout> {
+        val stepViews = (0..maxLevel).map { i -> createStepViewAt(i) }
+        stepViews[0].addView(createBacklightIconView())
+        return stepViews
+    }
+
+    private fun createStepViewAt(i: Int): FrameLayout {
+        return FrameLayout(context).apply {
+            layoutParams =
+                FrameLayout.LayoutParams(stepProperties.width, stepProperties.height).apply {
+                    setMargins(
+                        /* left= */ stepProperties.horizontalMargin,
+                        /* top= */ 0,
+                        /* right= */ stepProperties.horizontalMargin,
+                        /* bottom= */ 0
+                    )
+                }
+            val drawable =
+                ShapeDrawable(
+                    RoundRectShape(
+                        /* outerRadii= */ radiiForIndex(i, maxLevel),
+                        /* inset= */ null,
+                        /* innerRadii= */ null
+                    )
+                )
+            drawable.paint.color = emptyRectangleColor
+            background = drawable
+        }
+    }
+
+    private fun createBacklightIconView(): ImageView {
+        return ImageView(context).apply {
+            setImageResource(R.drawable.ic_keyboard_backlight)
+            layoutParams =
+                FrameLayout.LayoutParams(iconProperties.width, iconProperties.height).apply {
+                    gravity = Gravity.CENTER
+                    leftMargin = iconProperties.leftMargin
+                }
+        }
+    }
+
+    private fun setWindowTitle() {
+        val attrs = window.attributes
+        // TODO(b/271796169): check if title needs to be a translatable resource.
+        attrs.title = "KeyboardBacklightDialog"
+        attrs?.y = dialogBottomMargin
+        window.attributes = attrs
+    }
+
+    private fun setUpWindowProperties(dialog: Dialog) {
+        val window = dialog.window
+        window.requestFeature(Window.FEATURE_NO_TITLE) // otherwise fails while creating actionBar
+        window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL)
+        window.addFlags(
+            WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM or
+                WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+        )
+        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
+        window.setBackgroundDrawableResource(android.R.color.transparent)
+        window.setGravity(Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL)
+        setCanceledOnTouchOutside(true)
+    }
+
+    private fun radiiForIndex(i: Int, last: Int): FloatArray {
+        val smallRadius = stepProperties.smallRadius
+        val largeRadius = stepProperties.largeRadius
+        return when (i) {
+            0 -> // left radii bigger
+            floatArrayOf(
+                    largeRadius,
+                    largeRadius,
+                    smallRadius,
+                    smallRadius,
+                    smallRadius,
+                    smallRadius,
+                    largeRadius,
+                    largeRadius
+                )
+            last -> // right radii bigger
+            floatArrayOf(
+                    smallRadius,
+                    smallRadius,
+                    largeRadius,
+                    largeRadius,
+                    largeRadius,
+                    largeRadius,
+                    smallRadius,
+                    smallRadius
+                )
+            else -> FloatArray(8) { smallRadius } // all radii equal
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index faeb485..a2589d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -52,6 +52,7 @@
         cancelAction: Runnable?,
     )
     fun willDismissWithActions(): Boolean
+    fun willRunDismissFromKeyguard(): Boolean
     /** @return the {@link OnBackAnimationCallback} to animate Bouncer during a back gesture. */
     fun getBackCallback(): OnBackAnimationCallback
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 2dc8fee..1fbfff9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -47,6 +47,7 @@
         listenForOccludedToLockscreen()
         listenForOccludedToDreaming()
         listenForOccludedToAodOrDozing()
+        listenForOccludedToGone()
     }
 
     private fun listenForOccludedToDreaming() {
@@ -72,11 +73,22 @@
     private fun listenForOccludedToLockscreen() {
         scope.launch {
             keyguardInteractor.isKeyguardOccluded
-                .sample(keyguardTransitionInteractor.startedKeyguardTransitionStep, ::Pair)
-                .collect { (isOccluded, lastStartedKeyguardState) ->
+                .sample(
+                    combine(
+                        keyguardInteractor.isKeyguardShowing,
+                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
                     // Occlusion signals come from the framework, and should interrupt any
                     // existing transition
-                    if (!isOccluded && lastStartedKeyguardState.to == KeyguardState.OCCLUDED) {
+                    if (
+                        !isOccluded &&
+                            isShowing &&
+                            lastStartedKeyguardState.to == KeyguardState.OCCLUDED
+                    ) {
                         keyguardTransitionRepository.startTransition(
                             TransitionInfo(
                                 name,
@@ -90,6 +102,38 @@
         }
     }
 
+    private fun listenForOccludedToGone() {
+        scope.launch {
+            keyguardInteractor.isKeyguardOccluded
+                .sample(
+                    combine(
+                        keyguardInteractor.isKeyguardShowing,
+                        keyguardTransitionInteractor.startedKeyguardTransitionStep,
+                        ::Pair
+                    ),
+                    ::toTriple
+                )
+                .collect { (isOccluded, isShowing, lastStartedKeyguardState) ->
+                    // Occlusion signals come from the framework, and should interrupt any
+                    // existing transition
+                    if (
+                        !isOccluded &&
+                            !isShowing &&
+                            lastStartedKeyguardState.to == KeyguardState.OCCLUDED
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.OCCLUDED,
+                                KeyguardState.GONE,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
     private fun listenForOccludedToAodOrDozing() {
         scope.launch {
             keyguardInteractor.wakefulnessModel
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 ec99049..c42e502 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
@@ -142,6 +142,8 @@
     val alternateBouncerShowing: Flow<Boolean> = bouncerRepository.alternateBouncerVisible
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState> = repository.statusBarState
+    /** Whether or not quick settings or quick quick settings are showing. */
+    val isQuickSettingsVisible: Flow<Boolean> = repository.isQuickSettingsVisible
     /**
      * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
      * side, under display) is used to unlock the device.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 1735b5d..7064827 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -96,8 +96,9 @@
             quickAffordanceAlwaysVisible(position),
             keyguardInteractor.isDozing,
             keyguardInteractor.isKeyguardShowing,
-        ) { affordance, isDozing, isKeyguardShowing ->
-            if (!isDozing && isKeyguardShowing) {
+            keyguardInteractor.isQuickSettingsVisible
+        ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible ->
+            if (!isDozing && isKeyguardShowing && !isQuickSettingsVisible) {
                 affordance
             } else {
                 KeyguardQuickAffordanceModel.Hidden
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index edd2897..3d2c472 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
@@ -120,21 +120,24 @@
     val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
     val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
 
-    init {
-        keyguardUpdateMonitor.registerCallback(
-            object : KeyguardUpdateMonitorCallback() {
-                override fun onBiometricRunningStateChanged(
-                    running: Boolean,
-                    biometricSourceType: BiometricSourceType?
-                ) {
-                    updateSideFpsVisibility()
-                }
+    /**
+     * This callback needs to be a class field so it does not get garbage collected.
+     */
+    val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
+        override fun onBiometricRunningStateChanged(
+            running: Boolean,
+            biometricSourceType: BiometricSourceType?
+        ) {
+            updateSideFpsVisibility()
+        }
 
-                override fun onStrongAuthStateChanged(userId: Int) {
-                    updateSideFpsVisibility()
-                }
-            }
-        )
+        override fun onStrongAuthStateChanged(userId: Int) {
+            updateSideFpsVisibility()
+        }
+    }
+
+    init {
+        keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
     }
 
     // TODO(b/243685699): Move isScrimmed logic to data layer.
@@ -369,6 +372,11 @@
         return primaryBouncerView.delegate?.willDismissWithActions() == true
     }
 
+    /** Will the dismissal run from the keyguard layout (instead of from bouncer) */
+    fun willRunDismissFromKeyguard(): Boolean {
+        return primaryBouncerView.delegate?.willRunDismissFromKeyguard() == true
+    }
+
     /** Returns whether the bouncer should be full screen. */
     private fun needsFullscreenBouncer(): Boolean {
         val mode: KeyguardSecurityModel.SecurityMode =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt
new file mode 100644
index 0000000..1db7733
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/ScrimAlpha.kt
@@ -0,0 +1,23 @@
+/*
+ * 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
+
+/** Alpha values for scrim updates */
+data class ScrimAlpha(
+    val frontAlpha: Float = 0f,
+    val behindAlpha: Float = 0f,
+    val notificationsAlpha: Float = 0f,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index 2337ffc..bb617bd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -97,6 +97,10 @@
                 override fun willDismissWithActions(): Boolean {
                     return securityContainerController.hasDismissActions()
                 }
+
+                override fun willRunDismissFromKeyguard(): Boolean {
+                    return securityContainerController.willRunDismissFromKeyguard()
+                }
             }
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.CREATED) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index 92038e2..b23247c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -20,11 +20,14 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
 
 /**
  * Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to
@@ -36,6 +39,7 @@
 constructor(
     private val interactor: KeyguardTransitionInteractor,
     private val statusBarStateController: SysuiStatusBarStateController,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
 ) {
     private val transitionAnimation =
         KeyguardTransitionAnimationFlow(
@@ -44,26 +48,49 @@
         )
 
     private var leaveShadeOpen: Boolean = false
+    private var willRunDismissFromKeyguard: Boolean = false
 
     /** Bouncer container alpha */
     val bouncerAlpha: Flow<Float> =
         transitionAnimation.createFlow(
             duration = 200.milliseconds,
-            onStep = { 1f - it },
-        )
-
-    /** Scrim behind alpha */
-    val scrimBehindAlpha: Flow<Float> =
-        transitionAnimation.createFlow(
-            duration = TO_GONE_DURATION,
-            interpolator = EMPHASIZED_ACCELERATE,
-            onStart = { leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide() },
+            onStart = {
+                willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard()
+            },
             onStep = {
-                if (leaveShadeOpen) {
-                    1f
+                if (willRunDismissFromKeyguard) {
+                    0f
                 } else {
                     1f - it
                 }
             },
         )
+
+    /** Scrim alpha values */
+    val scrimAlpha: Flow<ScrimAlpha> =
+        transitionAnimation
+            .createFlow(
+                duration = TO_GONE_DURATION,
+                interpolator = EMPHASIZED_ACCELERATE,
+                onStart = {
+                    leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
+                    willRunDismissFromKeyguard =
+                        primaryBouncerInteractor.willRunDismissFromKeyguard()
+                },
+                onStep = { 1f - it },
+            )
+            .map {
+                if (willRunDismissFromKeyguard) {
+                    ScrimAlpha(
+                        notificationsAlpha = 1f,
+                    )
+                } else if (leaveShadeOpen) {
+                    ScrimAlpha(
+                        behindAlpha = 1f,
+                        notificationsAlpha = 1f,
+                    )
+                } else {
+                    ScrimAlpha(behindAlpha = it)
+                }
+            }
 }
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 72c4aab..4cc0410 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
@@ -1346,13 +1346,9 @@
     fun onNotificationRemoved(key: String) {
         Assert.isMainThread()
         val removed = mediaEntries.remove(key) ?: return
-        val isEligibleForResume =
-            removed.isLocalSession() ||
-                (mediaFlags.isRemoteResumeAllowed() &&
-                    removed.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
         if (keyguardUpdateMonitor.isUserInLockdown(removed.userId)) {
             logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
-        } else if (useMediaResumption && removed.resumeAction != null && isEligibleForResume) {
+        } else if (isAbleToResume(removed)) {
             convertToResumePlayer(key, removed)
         } else if (mediaFlags.isRetainingPlayersEnabled()) {
             handlePossibleRemoval(key, removed, notificationRemoved = true)
@@ -1372,6 +1368,14 @@
         handlePossibleRemoval(key, updated)
     }
 
+    private fun isAbleToResume(data: MediaData): Boolean {
+        val isEligibleForResume =
+            data.isLocalSession() ||
+                (mediaFlags.isRemoteResumeAllowed() &&
+                    data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
+        return useMediaResumption && data.resumeAction != null && isEligibleForResume
+    }
+
     /**
      * Convert to resume state if the player is no longer valid and active, then notify listeners
      * that the data was updated. Does not convert to resume state if the player is still valid, or
@@ -1394,8 +1398,9 @@
             if (DEBUG) Log.d(TAG, "Session destroyed but using notification actions $key")
             mediaEntries.put(key, removed)
             notifyMediaDataLoaded(key, key, removed)
-        } else if (removed.active) {
-            // This player was still active - it didn't last long enough to time out: remove
+        } else if (removed.active && !isAbleToResume(removed)) {
+            // This player was still active - it didn't last long enough to time out,
+            // and its app doesn't normally support resume: remove
             if (DEBUG) Log.d(TAG, "Removing still-active player $key")
             notifyMediaDataRemoved(key)
             logger.logMediaRemoved(removed.appUid, removed.packageName, removed.instanceId)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
index 92e0c85..b0389b5 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/MediaResumeListener.kt
@@ -239,6 +239,8 @@
                         data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE)
             if (data.resumeAction == null && !data.hasCheckedForResume && isEligibleForResume) {
                 // TODO also check for a media button receiver intended for restarting (b/154127084)
+                // Set null action to prevent additional attempts to connect
+                mediaDataManager.setResumeAction(key, null)
                 Log.d(TAG, "Checking for service component for " + data.packageName)
                 val pm = context.packageManager
                 val serviceIntent = Intent(MediaBrowserService.SERVICE_INTERFACE)
@@ -249,9 +251,6 @@
                     backgroundExecutor.execute {
                         tryUpdateResumptionList(key, inf!!.get(0).componentInfo.componentName)
                     }
-                } else {
-                    // No service found
-                    mediaDataManager.setResumeAction(key, null)
                 }
             }
         }
@@ -263,8 +262,6 @@
      */
     private fun tryUpdateResumptionList(key: String, componentName: ComponentName) {
         Log.d(TAG, "Testing if we can connect to $componentName")
-        // Set null action to prevent additional attempts to connect
-        mediaDataManager.setResumeAction(key, null)
         mediaBrowser =
             mediaBrowserFactory.create(
                 object : ResumeMediaBrowser.Callback() {
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
index 3493b24..d460b5b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/resume/ResumeMediaBrowser.java
@@ -85,16 +85,13 @@
      * ResumeMediaBrowser#disconnect will be called automatically with this function.
      */
     public void findRecentMedia() {
-        disconnect();
         Bundle rootHints = new Bundle();
         rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
-        mMediaBrowser = mBrowserFactory.create(
+        MediaBrowser browser = mBrowserFactory.create(
                 mComponentName,
                 mConnectionCallback,
                 rootHints);
-        updateMediaController();
-        mLogger.logConnection(mComponentName, "findRecentMedia");
-        mMediaBrowser.connect();
+        connectBrowser(browser, "findRecentMedia");
     }
 
     private final MediaBrowser.SubscriptionCallback mSubscriptionCallback =
@@ -202,6 +199,21 @@
     };
 
     /**
+     * Connect using a new media browser. Disconnects the existing browser first, if it exists.
+     * @param browser media browser to connect
+     * @param reason Reason to log for connection
+     */
+    private void connectBrowser(MediaBrowser browser, String reason) {
+        mLogger.logConnection(mComponentName, reason);
+        disconnect();
+        mMediaBrowser = browser;
+        if (browser != null) {
+            browser.connect();
+        }
+        updateMediaController();
+    }
+
+    /**
      * Disconnect the media browser. This should be done after callbacks have completed to
      * disconnect from the media browser service.
      */
@@ -222,10 +234,9 @@
      * getting a media update from the app
      */
     public void restart() {
-        disconnect();
         Bundle rootHints = new Bundle();
         rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
-        mMediaBrowser = mBrowserFactory.create(mComponentName,
+        MediaBrowser browser = mBrowserFactory.create(mComponentName,
                 new MediaBrowser.ConnectionCallback() {
                     @Override
                     public void onConnected() {
@@ -265,9 +276,7 @@
                         disconnect();
                     }
                 }, rootHints);
-        updateMediaController();
-        mLogger.logConnection(mComponentName, "restart");
-        mMediaBrowser.connect();
+        connectBrowser(browser, "restart");
     }
 
     @VisibleForTesting
@@ -305,16 +314,13 @@
      * ResumeMediaBrowser#disconnect should be called after this to ensure the connection is closed.
      */
     public void testConnection() {
-        disconnect();
         Bundle rootHints = new Bundle();
         rootHints.putBoolean(MediaBrowserService.BrowserRoot.EXTRA_RECENT, true);
-        mMediaBrowser = mBrowserFactory.create(
+        MediaBrowser browser = mBrowserFactory.create(
                 mComponentName,
                 mConnectionCallback,
                 rootHints);
-        updateMediaController();
-        mLogger.logConnection(mComponentName, "testConnection");
-        mMediaBrowser.connect();
+        connectBrowser(browser, "testConnection");
     }
 
     /** Updates mMediaController based on our current browser values. */
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 006cedf..e4351d2 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -25,11 +25,6 @@
 import static android.app.StatusBarManager.WindowVisibleState;
 import static android.app.StatusBarManager.windowStateToString;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
-import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
-import static android.view.InsetsState.ITYPE_LEFT_GESTURES;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_RIGHT_GESTURES;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
@@ -292,6 +287,7 @@
     private final DeadZone mDeadZone;
     private boolean mImeVisible;
     private final Rect mSamplingBounds = new Rect();
+    private final Binder mInsetsSourceOwner = new Binder();
 
     /**
      * When quickswitching between apps of different orientations, we draw a secondary home handle
@@ -1709,28 +1705,21 @@
     }
 
     private InsetsFrameProvider[] getInsetsFrameProvider(int insetsHeight, Context userContext) {
-        final InsetsFrameProvider navBarProvider;
+        final InsetsFrameProvider navBarProvider =
+                new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars())
+                        .setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] {
+                                new InsetsFrameProvider.InsetsSizeOverride(
+                                        TYPE_INPUT_METHOD, null)});
         if (insetsHeight != -1 && !mIsButtonForceVisible) {
-            navBarProvider = new InsetsFrameProvider(
-                    ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, insetsHeight));
-            // Use window frame for IME.
-            navBarProvider.insetsSizeOverrides = new InsetsFrameProvider.InsetsSizeOverride[] {
-                    new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
-            };
-        } else {
-            navBarProvider = new InsetsFrameProvider(ITYPE_NAVIGATION_BAR);
-            navBarProvider.insetsSizeOverrides = new InsetsFrameProvider.InsetsSizeOverride[]{
-                    new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
-            };
+            navBarProvider.setInsetsSize(Insets.of(0, 0, 0, insetsHeight));
         }
+
+        final InsetsFrameProvider tappableElementProvider = new InsetsFrameProvider(
+                mInsetsSourceOwner, 0, WindowInsets.Type.tappableElement());
         final boolean navBarTapThrough = userContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_navBarTapThrough);
-        final InsetsFrameProvider bottomTappableProvider;
         if (navBarTapThrough) {
-            bottomTappableProvider = new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT,
-                    Insets.of(0, 0, 0, 0));
-        } else {
-            bottomTappableProvider = new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT);
+            tappableElementProvider.setInsetsSize(Insets.NONE);
         }
 
         final DisplayCutout cutout = userContext.getDisplay().getCutout();
@@ -1745,13 +1734,16 @@
                 ? mEdgeBackGestureHandler.getEdgeWidthRight() + safeInsetsRight : 0;
         return new InsetsFrameProvider[] {
                 navBarProvider,
+                tappableElementProvider,
                 new InsetsFrameProvider(
-                        ITYPE_BOTTOM_MANDATORY_GESTURES, Insets.of(0, 0, 0, gestureHeight)),
-                new InsetsFrameProvider(ITYPE_LEFT_GESTURES, InsetsFrameProvider.SOURCE_DISPLAY,
-                        Insets.of(gestureInsetsLeft, 0, 0, 0), null),
-                new InsetsFrameProvider(ITYPE_RIGHT_GESTURES, InsetsFrameProvider.SOURCE_DISPLAY,
-                        Insets.of(0, 0, gestureInsetsRight, 0), null),
-                bottomTappableProvider
+                        mInsetsSourceOwner, 0, WindowInsets.Type.mandatorySystemGestures())
+                        .setInsetsSize(Insets.of(0, 0, 0, gestureHeight)),
+                new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.systemGestures())
+                        .setSource(InsetsFrameProvider.SOURCE_DISPLAY)
+                        .setInsetsSize(Insets.of(gestureInsetsLeft, 0, 0, 0)),
+                new InsetsFrameProvider(mInsetsSourceOwner, 1, WindowInsets.Type.systemGestures())
+                        .setSource(InsetsFrameProvider.SOURCE_DISPLAY)
+                        .setInsetsSize(Insets.of(0, 0, gestureInsetsRight, 0))
         };
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
index 5b36e93..6cd04c8 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt
@@ -187,25 +187,16 @@
 
     companion object {
         val TAG = NoteTaskController::class.simpleName.orEmpty()
-
-        // TODO(b/254604589): Use final KeyEvent.KEYCODE_* instead.
-        const val NOTE_TASK_KEY_EVENT = 311
-
-        // TODO(b/265912743): Use Intent.ACTION_CREATE_NOTE instead.
-        const val ACTION_CREATE_NOTE = "android.intent.action.CREATE_NOTE"
-
-        // TODO(b/265912743): Use Intent.INTENT_EXTRA_USE_STYLUS_MODE instead.
-        const val INTENT_EXTRA_USE_STYLUS_MODE = "android.intent.extra.USE_STYLUS_MODE"
     }
 }
 
 private fun createNoteIntent(info: NoteTaskInfo): Intent =
-    Intent(NoteTaskController.ACTION_CREATE_NOTE).apply {
+    Intent(Intent.ACTION_CREATE_NOTE).apply {
         setPackage(info.packageName)
 
         // EXTRA_USE_STYLUS_MODE does not mean a stylus is in-use, but a stylus entrypoint
         // was used to start it.
-        putExtra(NoteTaskController.INTENT_EXTRA_USE_STYLUS_MODE, true)
+        putExtra(Intent.EXTRA_USE_STYLUS_MODE, true)
 
         addFlags(FLAG_ACTIVITY_NEW_TASK)
         // We should ensure the note experience can be open both as a full screen (lock screen)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
index 7be491f..b98a0fd 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInfoResolver.kt
@@ -56,9 +56,6 @@
     companion object {
         private val TAG = NoteTaskInfoResolver::class.simpleName.orEmpty()
 
-        // TODO(b/265912743): Use RoleManager.NOTES_ROLE instead.
-        const val ROLE_NOTES = "android.app.role.NOTES"
-
         private val EMPTY_APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0)!!
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index e55445c..baa812c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -50,6 +50,8 @@
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.ui.MediaHost;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.plugins.qs.QSContainerController;
@@ -59,6 +61,7 @@
 import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -111,6 +114,8 @@
     private final MediaHost mQqsMediaHost;
     private final QSFragmentComponent.Factory mQsComponentFactory;
     private final QSFragmentDisableFlagsLogger mQsFragmentDisableFlagsLogger;
+    private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+    private final FeatureFlags mFeatureFlags;
     private final QSLogger mLogger;
     private final FooterActionsController mFooterActionsController;
     private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
@@ -158,12 +163,7 @@
     // visible;
     private boolean mQsVisible;
 
-    /**
-     * Whether the notification panel uses the full width of the screen.
-     *
-     * Usually {@code true} on small screens, and {@code false} on large screens.
-     */
-    private boolean mIsNotificationPanelFullWidth;
+    private boolean mIsSmallScreen;
 
     @Inject
     public QSFragment(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -175,13 +175,17 @@
             QSFragmentDisableFlagsLogger qsFragmentDisableFlagsLogger,
             DumpManager dumpManager, QSLogger qsLogger,
             FooterActionsController footerActionsController,
-            FooterActionsViewModel.Factory footerActionsViewModelFactory) {
+            FooterActionsViewModel.Factory footerActionsViewModelFactory,
+            LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+            FeatureFlags featureFlags) {
         mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
         mQsMediaHost = qsMediaHost;
         mQqsMediaHost = qqsMediaHost;
         mQsComponentFactory = qsComponentFactory;
         mQsFragmentDisableFlagsLogger = qsFragmentDisableFlagsLogger;
         mLogger = qsLogger;
+        mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+        mFeatureFlags = featureFlags;
         commandQueue.observe(getLifecycle(), this);
         mBypassController = keyguardBypassController;
         mStatusBarStateController = statusBarStateController;
@@ -606,7 +610,7 @@
 
     @Override
     public void setIsNotificationPanelFullWidth(boolean isFullWidth) {
-        mIsNotificationPanelFullWidth = isFullWidth;
+        mIsSmallScreen = isFullWidth;
     }
 
     @Override
@@ -709,7 +713,7 @@
     }
 
     private float calculateAlphaProgress(float panelExpansionFraction) {
-        if (mIsNotificationPanelFullWidth) {
+        if (mIsSmallScreen) {
             // Small screens. QS alpha is not animated.
             return 1;
         }
@@ -744,7 +748,12 @@
             // Alpha progress should be linear on lockscreen shade expansion.
             return progress;
         }
-        return ShadeInterpolation.getContentAlpha(progress);
+        if (mIsSmallScreen || !mFeatureFlags.isEnabled(
+                Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+            return ShadeInterpolation.getContentAlpha(progress);
+        } else {
+            return mLargeScreenShadeInterpolator.getQsAlpha(progress);
+        }
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 8721d71..557e95c 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -419,6 +419,10 @@
             return;
         }
 
+        mScreenBitmap = screenshot.getBitmap();
+        String oldPackageName = mPackageName;
+        mPackageName = screenshot.getPackageNameString();
+
         if (!isUserSetupComplete(Process.myUserHandle())) {
             Log.w(TAG, "User setup not complete, displaying toast only");
             // User setup isn't complete, so we don't want to show any UI beyond a toast, as editing
@@ -433,10 +437,6 @@
         mScreenshotTakenInPortrait =
                 mContext.getResources().getConfiguration().orientation == ORIENTATION_PORTRAIT;
 
-        String oldPackageName = mPackageName;
-        mPackageName = screenshot.getPackageNameString();
-
-        mScreenBitmap = screenshot.getBitmap();
         // Optimizations
         mScreenBitmap.setHasAlpha(false);
         mScreenBitmap.prepareToDraw();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ee9e54a..4a7dd97 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -17,6 +17,7 @@
 package com.android.systemui.shade;
 
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
+import static android.view.MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
@@ -305,6 +306,7 @@
      */
 
     public final boolean mAnimateBack;
+    private final boolean mTrackpadGestureBack;
     /**
      * The minimum scale to "squish" the Shade and associated elements down to, for Back gesture
      */
@@ -849,6 +851,7 @@
         mLayoutInflater = layoutInflater;
         mFeatureFlags = featureFlags;
         mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE);
+        mTrackpadGestureBack = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_BACK);
         mFalsingCollector = falsingCollector;
         mPowerManager = powerManager;
         mWakeUpCoordinator = coordinator;
@@ -2953,7 +2956,10 @@
         mHeadsUpStartHeight = startHeight;
         float scrimMinFraction;
         if (mSplitShadeEnabled) {
-            boolean highHun = mHeadsUpStartHeight * 2.5 > mSplitShadeScrimTransitionDistance;
+            boolean highHun = mHeadsUpStartHeight * 2.5
+                    >
+                    (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
+                    ? mSplitShadeFullTransitionDistance : mSplitShadeScrimTransitionDistance);
             // if HUN height is higher than 40% of predefined transition distance, it means HUN
             // is too high for regular transition. In that case we need to calculate transition
             // distance - here we take scrim transition distance as equal to shade transition
@@ -4720,13 +4726,8 @@
              gesture possible. */
             int pointerIndex = event.findPointerIndex(mTrackingPointer);
             if (pointerIndex < 0) {
-                if (mTrackingPointer < 0) {
-                    pointerIndex = 0;
-                    mTrackingPointer = event.getPointerId(pointerIndex);
-                } else {
-                    mShadeLog.logMotionEvent(event, "Skipping intercept of multitouch pointer");
-                    return false;
-                }
+                pointerIndex = 0;
+                mTrackingPointer = event.getPointerId(pointerIndex);
             }
             final float x = event.getX(pointerIndex);
             final float y = event.getY(pointerIndex);
@@ -4763,6 +4764,9 @@
                     addMovement(event);
                     break;
                 case MotionEvent.ACTION_POINTER_UP:
+                    if (isTrackpadMotionEvent(event)) {
+                        break;
+                    }
                     final int upPointer = event.getPointerId(event.getActionIndex());
                     if (mTrackingPointer == upPointer) {
                         // gesture is ongoing, find a new pointer to track
@@ -4776,7 +4780,8 @@
                     mShadeLog.logMotionEventStatusBarState(event,
                             mStatusBarStateController.getState(),
                             "onInterceptTouchEvent: pointer down action");
-                    if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+                    if (!isTrackpadMotionEvent(event)
+                            && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                         mMotionAborted = true;
                         mVelocityTracker.clear();
                     }
@@ -4979,6 +4984,9 @@
                     break;
 
                 case MotionEvent.ACTION_POINTER_UP:
+                    if (isTrackpadMotionEvent(event)) {
+                        break;
+                    }
                     final int upPointer = event.getPointerId(event.getActionIndex());
                     if (mTrackingPointer == upPointer) {
                         // gesture is ongoing, find a new pointer to track
@@ -4995,7 +5003,8 @@
                     mShadeLog.logMotionEventStatusBarState(event,
                             mStatusBarStateController.getState(),
                             "handleTouch: pointer down action");
-                    if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+                    if (!isTrackpadMotionEvent(event)
+                            && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
                         mMotionAborted = true;
                         endMotionEvent(event, x, y, true /* forceCancel */);
                         return false;
@@ -5069,6 +5078,11 @@
             }
             return !mGestureWaitForTouchSlop || mTracking;
         }
+
+        private boolean isTrackpadMotionEvent(MotionEvent ev) {
+            return mTrackpadGestureBack
+                    && ev.getClassification() == CLASSIFICATION_MULTI_FINGER_SWIPE;
+        }
     }
 
     static class SplitShadeTransitionAdapter extends Transition {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.kt
new file mode 100644
index 0000000..0519131
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenPortraitShadeInterpolator.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.shade.transition
+
+import android.util.MathUtils
+import com.android.systemui.animation.ShadeInterpolation
+import javax.inject.Inject
+
+/** Interpolator responsible for the shade when in portrait on a large screen. */
+internal class LargeScreenPortraitShadeInterpolator @Inject internal constructor() :
+    LargeScreenShadeInterpolator {
+
+    override fun getBehindScrimAlpha(fraction: Float): Float {
+        return MathUtils.constrainedMap(0f, 1f, 0f, 0.3f, fraction)
+    }
+
+    override fun getNotificationScrimAlpha(fraction: Float): Float {
+        return MathUtils.constrainedMap(0f, 1f, 0.3f, 0.75f, fraction)
+    }
+
+    override fun getNotificationContentAlpha(fraction: Float): Float {
+        return ShadeInterpolation.getContentAlpha(fraction)
+    }
+
+    override fun getNotificationFooterAlpha(fraction: Float): Float {
+        return ShadeInterpolation.getContentAlpha(fraction)
+    }
+
+    override fun getQsAlpha(fraction: Float): Float {
+        return ShadeInterpolation.getContentAlpha(fraction)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.kt
new file mode 100644
index 0000000..671dfc9c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolator.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.shade.transition
+
+/** An interpolator interface for the shade expansion. */
+interface LargeScreenShadeInterpolator {
+
+    /** Returns the alpha for the behind/back scrim. */
+    fun getBehindScrimAlpha(fraction: Float): Float
+
+    /** Returns the alpha for the notification scrim. */
+    fun getNotificationScrimAlpha(fraction: Float): Float
+
+    /** Returns the alpha for the notifications. */
+    fun getNotificationContentAlpha(fraction: Float): Float
+
+    /** Returns the alpha for the notifications footer (Manager, Clear All). */
+    fun getNotificationFooterAlpha(fraction: Float): Float
+
+    /** Returns the alpha for the QS panel. */
+    fun getQsAlpha(fraction: Float): Float
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.kt
new file mode 100644
index 0000000..fd57f21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImpl.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.systemui.shade.transition
+
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.LargeScreenUtils
+import javax.inject.Inject
+
+/** Interpolator responsible for the shade when on large screens. */
+@SysUISingleton
+internal class LargeScreenShadeInterpolatorImpl
+@Inject
+internal constructor(
+    configurationController: ConfigurationController,
+    private val context: Context,
+    private val splitShadeInterpolator: SplitShadeInterpolator,
+    private val portraitShadeInterpolator: LargeScreenPortraitShadeInterpolator,
+) : LargeScreenShadeInterpolator {
+
+    private var inSplitShade = false
+
+    init {
+        configurationController.addCallback(
+            object : ConfigurationController.ConfigurationListener {
+                override fun onConfigChanged(newConfig: Configuration?) {
+                    updateResources()
+                }
+            }
+        )
+        updateResources()
+    }
+
+    private fun updateResources() {
+        inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(context.resources)
+    }
+
+    private val impl: LargeScreenShadeInterpolator
+        get() =
+            if (inSplitShade) {
+                splitShadeInterpolator
+            } else {
+                portraitShadeInterpolator
+            }
+
+    override fun getBehindScrimAlpha(fraction: Float) = impl.getBehindScrimAlpha(fraction)
+
+    override fun getNotificationScrimAlpha(fraction: Float) =
+        impl.getNotificationScrimAlpha(fraction)
+
+    override fun getNotificationContentAlpha(fraction: Float) =
+        impl.getNotificationContentAlpha(fraction)
+
+    override fun getNotificationFooterAlpha(fraction: Float) =
+        impl.getNotificationFooterAlpha(fraction)
+
+    override fun getQsAlpha(fraction: Float) = impl.getQsAlpha(fraction)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index 218e897..4e1c272 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -23,6 +23,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.shade.PanelState
 import com.android.systemui.shade.STATE_OPENING
 import com.android.systemui.shade.ShadeExpansionChangeEvent
@@ -45,7 +47,8 @@
     private val scrimController: ScrimController,
     @Main private val resources: Resources,
     private val statusBarStateController: SysuiStatusBarStateController,
-    private val headsUpManager: HeadsUpManager
+    private val headsUpManager: HeadsUpManager,
+    private val featureFlags: FeatureFlags,
 ) {
 
     private var inSplitShade = false
@@ -106,7 +109,8 @@
                 // in case of HUN we can't always use predefined distances to manage scrim
                 // transition because dragDownPxAmount can start from value bigger than
                 // splitShadeScrimTransitionDistance
-                !headsUpManager.isTrackingHeadsUp
+                !headsUpManager.isTrackingHeadsUp &&
+                !featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)
 
     private fun isScreenUnlocked() =
         statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt
new file mode 100644
index 0000000..423ba8d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeInterpolator.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.transition
+
+import android.util.MathUtils
+import javax.inject.Inject
+
+/** Interpolator responsible for the split shade. */
+internal class SplitShadeInterpolator @Inject internal constructor() :
+    LargeScreenShadeInterpolator {
+
+    override fun getBehindScrimAlpha(fraction: Float): Float {
+        // Start delay: 0%
+        // Duration: 40%
+        // End: 40%
+        return mapFraction(start = 0f, end = 0.4f, fraction)
+    }
+
+    override fun getNotificationScrimAlpha(fraction: Float): Float {
+        // Start delay: 39%
+        // Duration: 27%
+        // End: 66%
+        return mapFraction(start = 0.39f, end = 0.66f, fraction)
+    }
+
+    override fun getNotificationContentAlpha(fraction: Float): Float {
+        return getNotificationScrimAlpha(fraction)
+    }
+
+    override fun getNotificationFooterAlpha(fraction: Float): Float {
+        // Start delay: 57.6%
+        // Duration: 32.1%
+        // End: 89.7%
+        return mapFraction(start = 0.576f, end = 0.897f, fraction)
+    }
+
+    override fun getQsAlpha(fraction: Float): Float {
+        return getNotificationScrimAlpha(fraction)
+    }
+
+    private fun mapFraction(start: Float, end: Float, fraction: Float) =
+        MathUtils.constrainedMap(
+            /* rangeMin= */ 0f,
+            /* rangeMax= */ 1f,
+            /* valueMin= */ start,
+            /* valueMax= */ end,
+            /* value= */ fraction
+        )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 8f1e0a1..3709a13 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -39,7 +39,10 @@
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
 import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.notification.LegacySourceType;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.SourceType;
@@ -216,7 +219,15 @@
                 if (ambientState.isBouncerInTransit()) {
                     viewState.setAlpha(aboutToShowBouncerProgress(expansion));
                 } else {
-                    viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
+                    FeatureFlags flags = ambientState.getFeatureFlags();
+                    if (ambientState.isSmallScreen() || !flags.isEnabled(
+                            Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+                        viewState.setAlpha(ShadeInterpolation.getContentAlpha(expansion));
+                    } else {
+                        LargeScreenShadeInterpolator interpolator =
+                                ambientState.getLargeScreenShadeInterpolator();
+                        viewState.setAlpha(interpolator.getNotificationContentAlpha(expansion));
+                    }
                 }
             } else {
                 viewState.setAlpha(1f - ambientState.getHideAmount());
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 82c5ee6..826e289 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -501,8 +501,10 @@
 
     private fun updateTextColorFromRegionSampler() {
         smartspaceViews.forEach {
-            val textColor = regionSamplers.getValue(it).currentForegroundColor()
-            it.setPrimaryTextColor(textColor)
+            val textColor = regionSamplers.get(it)?.currentForegroundColor()
+            if (textColor != null) {
+                it.setPrimaryTextColor(textColor)
+            }
         }
     }
 
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 6deaa23..68552d7 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
@@ -153,7 +153,6 @@
     // We don't correctly track dark mode until the content views are inflated, so always update
     // the background on first content update just in case it happens to be during a theme change.
     private boolean mUpdateSelfBackgroundOnUpdate = true;
-    private boolean mNotificationTranslationFinished = false;
     private boolean mIsSnoozed;
     private boolean mIsFaded;
     private boolean mAnimatePinnedRoundness = false;
@@ -210,11 +209,6 @@
      */
     private boolean mUserExpanded;
     /**
-     * Whether the blocking helper is showing on this notification (even if dismissed)
-     */
-    private boolean mIsBlockingHelperShowing;
-
-    /**
      * Has this notification been expanded while it was pinned
      */
     private boolean mExpandedWhenPinned;
@@ -1579,18 +1573,6 @@
         }
     }
 
-    public void setBlockingHelperShowing(boolean isBlockingHelperShowing) {
-        mIsBlockingHelperShowing = isBlockingHelperShowing;
-    }
-
-    public boolean isBlockingHelperShowing() {
-        return mIsBlockingHelperShowing;
-    }
-
-    public boolean isBlockingHelperShowingAndTranslationFinished() {
-        return mIsBlockingHelperShowing && mNotificationTranslationFinished;
-    }
-
     @Override
     public View getShelfTransformationTarget() {
         if (mIsSummaryWithChildren && !shouldShowPublic()) {
@@ -2171,10 +2153,7 @@
     @Override
     public void setTranslation(float translationX) {
         invalidate();
-        if (isBlockingHelperShowingAndTranslationFinished()) {
-            mGuts.setTranslationX(translationX);
-            return;
-        } else if (mDismissUsingRowTranslationX) {
+        if (mDismissUsingRowTranslationX) {
             setTranslationX(translationX);
         } else if (mTranslateableViews != null) {
             // Translate the group of views
@@ -2202,10 +2181,6 @@
             return getTranslationX();
         }
 
-        if (isBlockingHelperShowingAndCanTranslate()) {
-            return mGuts.getTranslationX();
-        }
-
         if (mTranslateableViews != null && mTranslateableViews.size() > 0) {
             // All of the views in the list should have same translation, just use first one.
             return mTranslateableViews.get(0).getTranslationX();
@@ -2214,10 +2189,6 @@
         return 0;
     }
 
-    private boolean isBlockingHelperShowingAndCanTranslate() {
-        return areGutsExposed() && mIsBlockingHelperShowing && mNotificationTranslationFinished;
-    }
-
     public Animator getTranslateViewAnimator(final float leftTarget,
                                              AnimatorUpdateListener listener) {
         if (mTranslateAnim != null) {
@@ -2239,9 +2210,6 @@
 
             @Override
             public void onAnimationEnd(Animator anim) {
-                if (mIsBlockingHelperShowing) {
-                    mNotificationTranslationFinished = true;
-                }
                 if (!cancelled && leftTarget == 0) {
                     if (mMenuRow != null) {
                         mMenuRow.resetMenu();
@@ -2821,9 +2789,10 @@
         int intrinsicBefore = getIntrinsicHeight();
         mSensitive = sensitive;
         mSensitiveHiddenInGeneral = hideSensitive;
-        if (intrinsicBefore != getIntrinsicHeight()) {
-            // The animation has a few flaws and is highly visible, so jump cut instead.
-            notifyHeightChanged(false /* needsAnimation */);
+        int intrinsicAfter = getIntrinsicHeight();
+        if (intrinsicBefore != intrinsicAfter) {
+            boolean needsAnimation = mFeatureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM);
+            notifyHeightChanged(needsAnimation);
         }
     }
 
@@ -2880,13 +2849,19 @@
         View[] publicViews = new View[]{mPublicLayout};
         View[] hiddenChildren = showingPublic ? privateViews : publicViews;
         View[] shownChildren = showingPublic ? publicViews : privateViews;
+        // disappear/appear overlap: 10 percent of duration
+        long overlap = duration / 10;
+        // disappear duration: 1/3 of duration + half of overlap
+        long disappearDuration = duration / 3 + overlap / 2;
+        // appear duration: 2/3 of duration + half of overlap
+        long appearDuration = (duration - disappearDuration) + overlap / 2;
         for (final View hiddenView : hiddenChildren) {
             hiddenView.setVisibility(View.VISIBLE);
             hiddenView.animate().cancel();
             hiddenView.animate()
                     .alpha(0f)
                     .setStartDelay(delay)
-                    .setDuration(duration)
+                    .setDuration(disappearDuration)
                     .withEndAction(() -> {
                         hiddenView.setVisibility(View.INVISIBLE);
                         resetAllContentAlphas();
@@ -2898,8 +2873,8 @@
             showView.animate().cancel();
             showView.animate()
                     .alpha(1f)
-                    .setStartDelay(delay)
-                    .setDuration(duration);
+                    .setStartDelay(delay + duration - appearDuration)
+                    .setDuration(appearDuration);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 39e4000..4522e41 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -443,6 +443,7 @@
         CancellationSignal cancellationSignal = new CancellationSignal();
         cancellationSignal.setOnCancelListener(
                 () -> runningInflations.values().forEach(CancellationSignal::cancel));
+
         return cancellationSignal;
     }
 
@@ -783,6 +784,7 @@
     public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
             implements InflationCallback, InflationTask {
 
+        private static final long IMG_PRELOAD_TIMEOUT_MS = 1000L;
         private final NotificationEntry mEntry;
         private final Context mContext;
         private final boolean mInflateSynchronously;
@@ -876,7 +878,7 @@
                         recoveredBuilder, mIsLowPriority, mUsesIncreasedHeight,
                         mUsesIncreasedHeadsUpHeight, packageContext);
                 InflatedSmartReplyState previousSmartReplyState = mRow.getExistingSmartReplyState();
-                return inflateSmartReplyViews(
+                InflationProgress result = inflateSmartReplyViews(
                         inflationProgress,
                         mReInflateFlags,
                         mEntry,
@@ -884,6 +886,11 @@
                         packageContext,
                         previousSmartReplyState,
                         mSmartRepliesInflater);
+
+                // wait for image resolver to finish preloading
+                mRow.getImageResolver().waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
+
+                return result;
             } catch (Exception e) {
                 mError = e;
                 return null;
@@ -918,6 +925,9 @@
                 mCallback.handleInflationException(mRow.getEntry(),
                         new InflationException("Couldn't inflate contentViews" + e));
             }
+
+            // Cancel any image loading tasks, not useful any more
+            mRow.getImageResolver().cancelRunningTasks();
         }
 
         @Override
@@ -944,6 +954,9 @@
             // Notify the resolver that the inflation task has finished,
             // try to purge unnecessary cached entries.
             mRow.getImageResolver().purgeCache();
+
+            // Cancel any image loading tasks that have not completed at this point
+            mRow.getImageResolver().cancelRunningTasks();
         }
 
         private static class RtlEnabledContext extends ContextWrapper {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
index 93f0812..596bdc0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java
@@ -238,12 +238,11 @@
     }
 
     public void openControls(
-            boolean shouldDoCircularReveal,
             int x,
             int y,
             boolean needsFalsingProtection,
             @Nullable Runnable onAnimationEnd) {
-        animateOpen(shouldDoCircularReveal, x, y, onAnimationEnd);
+        animateOpen(x, y, onAnimationEnd);
         setExposed(true /* exposed */, needsFalsingProtection);
     }
 
@@ -300,7 +299,7 @@
         if (mGutsContent == null
                 || !mGutsContent.handleCloseControls(save, force)) {
             // We only want to do a circular reveal if we're not showing the blocking helper.
-            animateClose(x, y, true /* shouldDoCircularReveal */);
+            animateClose(x, y);
 
             setExposed(false, mNeedsFalsingProtection);
             if (mClosedListener != null) {
@@ -309,66 +308,45 @@
         }
     }
 
-    /** Animates in the guts view via either a fade or a circular reveal. */
-    private void animateOpen(
-            boolean shouldDoCircularReveal, int x, int y, @Nullable Runnable onAnimationEnd) {
+    /** Animates in the guts view with a circular reveal. */
+    private void animateOpen(int x, int y, @Nullable Runnable onAnimationEnd) {
         if (isAttachedToWindow()) {
-            if (shouldDoCircularReveal) {
-                double horz = Math.max(getWidth() - x, x);
-                double vert = Math.max(getHeight() - y, y);
-                float r = (float) Math.hypot(horz, vert);
-                // Make sure we'll be visible after the circular reveal
-                setAlpha(1f);
-                // Circular reveal originating at (x, y)
-                Animator a = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
-                a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
-                a.addListener(new AnimateOpenListener(onAnimationEnd));
-                a.start();
-            } else {
-                // Fade in content
-                this.setAlpha(0f);
-                this.animate()
-                        .alpha(1f)
-                        .setDuration(StackStateAnimator.ANIMATION_DURATION_BLOCKING_HELPER_FADE)
-                        .setInterpolator(Interpolators.ALPHA_IN)
-                        .setListener(new AnimateOpenListener(onAnimationEnd))
-                        .start();
-            }
+            double horz = Math.max(getWidth() - x, x);
+            double vert = Math.max(getHeight() - y, y);
+            float r = (float) Math.hypot(horz, vert);
+            // Make sure we'll be visible after the circular reveal
+            setAlpha(1f);
+            // Circular reveal originating at (x, y)
+            Animator a = ViewAnimationUtils.createCircularReveal(this, x, y, 0, r);
+            a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+            a.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+            a.addListener(new AnimateOpenListener(onAnimationEnd));
+            a.start();
+
         } else {
             Log.w(TAG, "Failed to animate guts open");
         }
     }
 
 
-    /** Animates out the guts view via either a fade or a circular reveal. */
+    /** Animates out the guts view with a circular reveal. */
     @VisibleForTesting
-    void animateClose(int x, int y, boolean shouldDoCircularReveal) {
+    void animateClose(int x, int y) {
         if (isAttachedToWindow()) {
-            if (shouldDoCircularReveal) {
-                // Circular reveal originating at (x, y)
-                if (x == -1 || y == -1) {
-                    x = (getLeft() + getRight()) / 2;
-                    y = (getTop() + getHeight() / 2);
-                }
-                double horz = Math.max(getWidth() - x, x);
-                double vert = Math.max(getHeight() - y, y);
-                float r = (float) Math.hypot(horz, vert);
-                Animator a = ViewAnimationUtils.createCircularReveal(this,
-                        x, y, r, 0);
-                a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-                a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
-                a.addListener(new AnimateCloseListener(this /* view */, mGutsContent));
-                a.start();
-            } else {
-                // Fade in the blocking helper.
-                this.animate()
-                        .alpha(0f)
-                        .setDuration(StackStateAnimator.ANIMATION_DURATION_BLOCKING_HELPER_FADE)
-                        .setInterpolator(Interpolators.ALPHA_OUT)
-                        .setListener(new AnimateCloseListener(this, /* view */mGutsContent))
-                        .start();
+            // Circular reveal originating at (x, y)
+            if (x == -1 || y == -1) {
+                x = (getLeft() + getRight()) / 2;
+                y = (getTop() + getHeight() / 2);
             }
+            double horz = Math.max(getWidth() - x, x);
+            double vert = Math.max(getHeight() - y, y);
+            float r = (float) Math.hypot(horz, vert);
+            Animator a = ViewAnimationUtils.createCircularReveal(this,
+                    x, y, r, 0);
+            a.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+            a.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
+            a.addListener(new AnimateCloseListener(this /* view */, mGutsContent));
+            a.start();
         } else {
             Log.w(TAG, "Failed to animate guts close");
             mGutsContent.onFinishedClosing();
@@ -449,7 +427,7 @@
         return mGutsContent != null && mGutsContent.isLeavebehind();
     }
 
-    /** Listener for animations executed in {@link #animateOpen(boolean, int, int, Runnable)}. */
+    /** Listener for animations executed in {@link #animateOpen(int, int, Runnable)}. */
     private static class AnimateOpenListener extends AnimatorListenerAdapter {
         final Runnable mOnAnimationEnd;
 
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 06d4080..46f1bb5 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
@@ -629,7 +629,6 @@
                                 !mAccessibilityManager.isTouchExplorationEnabled());
 
                 guts.openControls(
-                        !row.isBlockingHelperShowing(),
                         x,
                         y,
                         needsFalsingProtection,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
index 41eeada0..fe0b312 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
@@ -22,8 +22,11 @@
 import android.util.Log;
 
 import java.util.Set;
+import java.util.concurrent.CancellationException;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 
 /**
  * A cache for inline images of image messages.
@@ -56,12 +59,13 @@
     }
 
     @Override
-    public Drawable get(Uri uri) {
+    public Drawable get(Uri uri, long timeoutMs) {
         Drawable result = null;
         try {
-            result = mCache.get(uri).get();
-        } catch (InterruptedException | ExecutionException ex) {
-            Log.d(TAG, "get: Failed get image from " + uri);
+            result = mCache.get(uri).get(timeoutMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException | ExecutionException
+                | TimeoutException | CancellationException ex) {
+            Log.d(TAG, "get: Failed get image from " + uri + " " + ex);
         }
         return result;
     }
@@ -72,6 +76,15 @@
         mCache.entrySet().removeIf(entry -> !wantedSet.contains(entry.getKey()));
     }
 
+    @Override
+    public void cancelRunningTasks() {
+        mCache.forEach((key, value) -> {
+            if (value.getStatus() != AsyncTask.Status.FINISHED) {
+                value.cancel(true);
+            }
+        });
+    }
+
     private static class PreloadImageTask extends AsyncTask<Uri, Void, Drawable> {
         private final NotificationInlineImageResolver mResolver;
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
index b05e64ab..c620f44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
@@ -23,6 +23,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.os.SystemClock;
 import android.util.Log;
 
 import com.android.internal.R;
@@ -45,6 +46,9 @@
 public class NotificationInlineImageResolver implements ImageResolver {
     private static final String TAG = NotificationInlineImageResolver.class.getSimpleName();
 
+    // Timeout for loading images from ImageCache when calling from UI thread
+    private static final long MAX_UI_THREAD_TIMEOUT_MS = 100L;
+
     private final Context mContext;
     private final ImageCache mImageCache;
     private Set<Uri> mWantedUriSet;
@@ -123,17 +127,25 @@
         return null;
     }
 
+    /**
+     * Loads an image from the Uri.
+     * This method is synchronous and is usually called from the Main thread.
+     * It will time-out after MAX_UI_THREAD_TIMEOUT_MS.
+     *
+     * @param uri Uri of the target image.
+     * @return drawable of the image, null if loading failed/timeout
+     */
     @Override
     public Drawable loadImage(Uri uri) {
-        return hasCache() ? loadImageFromCache(uri) : resolveImage(uri);
+        return hasCache() ? loadImageFromCache(uri, MAX_UI_THREAD_TIMEOUT_MS) : resolveImage(uri);
     }
 
-    private Drawable loadImageFromCache(Uri uri) {
+    private Drawable loadImageFromCache(Uri uri, long timeoutMs) {
         // if the uri isn't currently cached, try caching it first
         if (!mImageCache.hasEntry(uri)) {
             mImageCache.preload((uri));
         }
-        return mImageCache.get(uri);
+        return mImageCache.get(uri, timeoutMs);
     }
 
     /**
@@ -208,6 +220,30 @@
     }
 
     /**
+     * Wait for a maximum timeout for images to finish preloading
+     * @param timeoutMs total timeout time
+     */
+    void waitForPreloadedImages(long timeoutMs) {
+        if (!hasCache()) {
+            return;
+        }
+        Set<Uri> preloadedUris = getWantedUriSet();
+        if (preloadedUris != null) {
+            // Decrement remaining timeout after each image check
+            long endTimeMs = SystemClock.elapsedRealtime() + timeoutMs;
+            preloadedUris.forEach(
+                    uri -> loadImageFromCache(uri, endTimeMs - SystemClock.elapsedRealtime()));
+        }
+    }
+
+    void cancelRunningTasks() {
+        if (!hasCache()) {
+            return;
+        }
+        mImageCache.cancelRunningTasks();
+    }
+
+    /**
      * A interface for internal cache implementation of this resolver.
      */
     interface ImageCache {
@@ -216,7 +252,7 @@
          * @param uri The uri of the image.
          * @return Drawable of the image.
          */
-        Drawable get(Uri uri);
+        Drawable get(Uri uri, long timeoutMs);
 
         /**
          * Set the image resolver that actually resolves image from specified uri.
@@ -241,6 +277,11 @@
          * Purge unnecessary entries in the cache.
          */
         void purge();
+
+        /**
+         * Cancel all unfinished image loading tasks
+         */
+        void cancelRunningTasks();
     }
 
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
index 6f4d6d9..77ede04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AmbientState.java
@@ -29,6 +29,8 @@
 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.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -55,6 +57,8 @@
 
     private final SectionProvider mSectionProvider;
     private final BypassController mBypassController;
+    private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+    private final FeatureFlags mFeatureFlags;
     /**
      *  Used to read bouncer states.
      */
@@ -84,7 +88,7 @@
     private float mExpandingVelocity;
     private boolean mPanelTracking;
     private boolean mExpansionChanging;
-    private boolean mPanelFullWidth;
+    private boolean mIsSmallScreen;
     private boolean mPulsing;
     private boolean mUnlockHintRunning;
     private float mHideAmount;
@@ -252,10 +256,14 @@
             @NonNull DumpManager dumpManager,
             @NonNull SectionProvider sectionProvider,
             @NonNull BypassController bypassController,
-            @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+            @Nullable StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            @NonNull LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+            @NonNull FeatureFlags featureFlags) {
         mSectionProvider = sectionProvider;
         mBypassController = bypassController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+        mFeatureFlags = featureFlags;
         reload(context);
         dumpManager.registerDumpable(this);
     }
@@ -574,12 +582,12 @@
         return mPanelTracking;
     }
 
-    public boolean isPanelFullWidth() {
-        return mPanelFullWidth;
+    public boolean isSmallScreen() {
+        return mIsSmallScreen;
     }
 
-    public void setPanelFullWidth(boolean panelFullWidth) {
-        mPanelFullWidth = panelFullWidth;
+    public void setSmallScreen(boolean smallScreen) {
+        mIsSmallScreen = smallScreen;
     }
 
     public void setUnlockHintRunning(boolean unlockHintRunning) {
@@ -736,6 +744,14 @@
         return mIsClosing;
     }
 
+    public LargeScreenShadeInterpolator getLargeScreenShadeInterpolator() {
+        return mLargeScreenShadeInterpolator;
+    }
+
+    public FeatureFlags getFeatureFlags() {
+        return mFeatureFlags;
+    }
+
     @Override
     public void dump(PrintWriter pw, String[] args) {
         pw.println("mTopPadding=" + mTopPadding);
@@ -751,7 +767,7 @@
         pw.println("mDimmed=" + mDimmed);
         pw.println("mStatusBarState=" + mStatusBarState);
         pw.println("mExpansionChanging=" + mExpansionChanging);
-        pw.println("mPanelFullWidth=" + mPanelFullWidth);
+        pw.println("mPanelFullWidth=" + mIsSmallScreen);
         pw.println("mPulsing=" + mPulsing);
         pw.println("mPulseHeight=" + mPulseHeight);
         pw.println("mTrackedHeadsUpRow.key=" + logKey(mTrackedHeadsUpRow));
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 8d782e1..e2e2a23 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
@@ -5223,7 +5223,7 @@
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
     public void setIsFullWidth(boolean isFullWidth) {
-        mAmbientState.setPanelFullWidth(isFullWidth);
+        mAmbientState.setSmallScreen(isFullWidth);
     }
 
     @ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 92c5b63..e8058b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -70,7 +70,6 @@
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.transition.ShadeTransitionController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -172,7 +171,6 @@
     private final CentralSurfaces mCentralSurfaces;
     private final SectionHeaderController mSilentHeaderController;
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    private final ShadeTransitionController mShadeTransitionController;
     private final InteractionJankMonitor mJankMonitor;
     private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
     private final StackStateLogger mStackStateLogger;
@@ -664,7 +662,6 @@
             NotifPipelineFlags notifPipelineFlags,
             NotifCollection notifCollection,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
-            ShadeTransitionController shadeTransitionController,
             UiEventLogger uiEventLogger,
             NotificationRemoteInputManager remoteInputManager,
             VisibilityLocationProviderDelegator visibilityLocationProviderDelegator,
@@ -697,7 +694,6 @@
         mMetricsLogger = metricsLogger;
         mDumpManager = dumpManager;
         mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
-        mShadeTransitionController = shadeTransitionController;
         mFalsingCollector = falsingCollector;
         mFalsingManager = falsingManager;
         mResources = resources;
@@ -781,7 +777,6 @@
         mScrimController.setScrimBehindChangeRunnable(mView::updateBackgroundDimming);
 
         mLockscreenShadeTransitionController.setStackScroller(this);
-        mShadeTransitionController.setNotificationStackScrollLayoutController(this);
 
         mLockscreenUserManager.addUserChangedListener(mLockscreenUserChangeListener);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index d2bff0e..8ef28ec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -29,6 +29,9 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator;
 import com.android.systemui.R;
 import com.android.systemui.animation.ShadeInterpolation;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.LegacySourceType;
@@ -135,7 +138,6 @@
                                   AmbientState ambientState) {
         for (ExpandableView view : algorithmState.visibleChildren) {
             final ViewState viewState = view.getViewState();
-
             final boolean isHunGoingToShade = ambientState.isShadeExpanded()
                     && view == ambientState.getTrackedHeadsUpRow();
 
@@ -148,9 +150,14 @@
             } else if (ambientState.isExpansionChanging()) {
                 // Adjust alpha for shade open & close.
                 float expansion = ambientState.getExpansionFraction();
-                viewState.setAlpha(ambientState.isBouncerInTransit()
-                        ? BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion)
-                        : ShadeInterpolation.getContentAlpha(expansion));
+                if (ambientState.isBouncerInTransit()) {
+                    viewState.setAlpha(
+                            BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion));
+                } else if (view instanceof FooterView) {
+                    viewState.setAlpha(interpolateFooterAlpha(ambientState));
+                } else {
+                    viewState.setAlpha(interpolateNotificationContentAlpha(ambientState));
+                }
             }
 
             // For EmptyShadeView if on keyguard, we need to control the alpha to create
@@ -182,6 +189,28 @@
         }
     }
 
+    private float interpolateFooterAlpha(AmbientState ambientState) {
+        float expansion = ambientState.getExpansionFraction();
+        FeatureFlags flags = ambientState.getFeatureFlags();
+        if (ambientState.isSmallScreen()
+                || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+            return ShadeInterpolation.getContentAlpha(expansion);
+        }
+        LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
+        return interpolator.getNotificationFooterAlpha(expansion);
+    }
+
+    private float interpolateNotificationContentAlpha(AmbientState ambientState) {
+        float expansion = ambientState.getExpansionFraction();
+        FeatureFlags flags = ambientState.getFeatureFlags();
+        if (ambientState.isSmallScreen()
+                || !flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+            return ShadeInterpolation.getContentAlpha(expansion);
+        }
+        LargeScreenShadeInterpolator interpolator = ambientState.getLargeScreenShadeInterpolator();
+        return interpolator.getNotificationContentAlpha(expansion);
+    }
+
     /**
      * How expanded or collapsed notifications are when pulling down the shade.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 9e62817..55fa479 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -122,7 +122,6 @@
         options.setLaunchDisplayId(displayId);
         options.setCallerDisplayId(displayId);
         options.setPendingIntentBackgroundActivityLaunchAllowed(true);
-        options.setInteractive(true);
         return options.toBundle();
     }
 
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 9fb942c..0bded73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -54,15 +54,18 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.keyguard.shared.model.ScrimAlpha;
 import com.android.systemui.keyguard.shared.model.TransitionState;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
 import com.android.systemui.scrim.ScrimView;
 import com.android.systemui.shade.NotificationPanelViewController;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.notification.stack.ViewState;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -206,7 +209,6 @@
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    private final SysuiStatusBarStateController mStatusBarStateController;
 
     private GradientColors mColors;
     private boolean mNeedsDrawableColorUpdate;
@@ -245,6 +247,8 @@
     private boolean mWallpaperVisibilityTimedOut;
     private int mScrimsVisibility;
     private final TriConsumer<ScrimState, Float, GradientColors> mScrimStateListener;
+    private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+    private final FeatureFlags mFeatureFlags;
     private Consumer<Integer> mScrimVisibleListener;
     private boolean mBlankScreen;
     private boolean mScreenBlankingCallbackCalled;
@@ -265,12 +269,16 @@
     private CoroutineDispatcher mMainDispatcher;
     private boolean mIsBouncerToGoneTransitionRunning = false;
     private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
-    private final Consumer<Float> mScrimAlphaConsumer =
-            (Float alpha) -> {
+    private final Consumer<ScrimAlpha> mScrimAlphaConsumer =
+            (ScrimAlpha alphas) -> {
+                mInFrontAlpha = alphas.getFrontAlpha();
                 mScrimInFront.setViewAlpha(mInFrontAlpha);
+
+                mNotificationsAlpha = alphas.getNotificationsAlpha();
                 mNotificationsScrim.setViewAlpha(mNotificationsAlpha);
-                mBehindAlpha = alpha;
-                mScrimBehind.setViewAlpha(alpha);
+
+                mBehindAlpha = alphas.getBehindAlpha();
+                mScrimBehind.setViewAlpha(mBehindAlpha);
             };
 
     Consumer<TransitionStep> mPrimaryBouncerToGoneTransition;
@@ -292,15 +300,17 @@
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
-            SysuiStatusBarStateController sysuiStatusBarStateController,
-            @Main CoroutineDispatcher mainDispatcher) {
+            @Main CoroutineDispatcher mainDispatcher,
+            LargeScreenShadeInterpolator largeScreenShadeInterpolator,
+            FeatureFlags featureFlags) {
         mScrimStateListener = lightBarController::setScrimState;
+        mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
+        mFeatureFlags = featureFlags;
         mDefaultScrimAlpha = BUSY_SCRIM_ALPHA;
 
         mKeyguardStateController = keyguardStateController;
         mDarkenWhileDragging = !mKeyguardStateController.canDismissLockScreen();
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mStatusBarStateController = sysuiStatusBarStateController;
         mKeyguardVisibilityCallback = new KeyguardVisibilityCallback();
         mHandler = handler;
         mMainExecutor = mainExecutor;
@@ -400,7 +410,7 @@
 
         collectFlow(behindScrim, mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition(),
                 mPrimaryBouncerToGoneTransition, mMainDispatcher);
-        collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha(),
+        collectFlow(behindScrim, mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha(),
                 mScrimAlphaConsumer, mMainDispatcher);
     }
 
@@ -850,16 +860,21 @@
             if (!mScreenOffAnimationController.shouldExpandNotifications()
                     && !mAnimatingPanelExpansionOnUnlock
                     && !occluding) {
-                if (mClipsQsScrim) {
+                if (mTransparentScrimBackground) {
+                    mBehindAlpha = 0;
+                    mNotificationsAlpha = 0;
+                } else if (mClipsQsScrim) {
                     float behindFraction = getInterpolatedFraction();
                     behindFraction = (float) Math.pow(behindFraction, 0.8f);
-                    mBehindAlpha = mTransparentScrimBackground ? 0 : 1;
-                    mNotificationsAlpha =
-                            mTransparentScrimBackground ? 0 : behindFraction * mDefaultScrimAlpha;
+                    mBehindAlpha = 1;
+                    mNotificationsAlpha = behindFraction * mDefaultScrimAlpha;
                 } else {
-                    if (mTransparentScrimBackground) {
-                        mBehindAlpha = 0;
-                        mNotificationsAlpha = 0;
+                    if (mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)) {
+                        mBehindAlpha = mLargeScreenShadeInterpolator.getBehindScrimAlpha(
+                                mPanelExpansionFraction * mDefaultScrimAlpha);
+                        mNotificationsAlpha =
+                                mLargeScreenShadeInterpolator.getNotificationScrimAlpha(
+                                        mPanelExpansionFraction);
                     } else {
                         // Behind scrim will finish fading in at 30% expansion.
                         float behindFraction = MathUtils
@@ -1113,8 +1128,7 @@
             mBehindAlpha = 1;
         }
         // Prevent notification scrim flicker when transitioning away from keyguard.
-        if (mKeyguardStateController.isKeyguardGoingAway()
-                && !mStatusBarStateController.leaveOpenOnKeyguardHide()) {
+        if (mKeyguardStateController.isKeyguardGoingAway()) {
             mNotificationsAlpha = 0;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
index 159f689..4156fc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLogger.kt
@@ -51,8 +51,8 @@
         )
     }
 
-    fun logOnLost(network: Network) {
-        LoggerHelper.logOnLost(buffer, TAG, network)
+    fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
+        LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
     }
 
     fun logOnServiceStateChanged(serviceState: ServiceState, subId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
index 85729c1..19f0242 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -24,9 +24,11 @@
 import android.telephony.TelephonyManager.DATA_SUSPENDED
 import android.telephony.TelephonyManager.DATA_UNKNOWN
 import android.telephony.TelephonyManager.DataState
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
 
 /** Internal enum representation of the telephony data connection states */
-enum class DataConnectionState {
+enum class DataConnectionState : Diffable<DataConnectionState> {
     Connected,
     Connecting,
     Disconnected,
@@ -34,7 +36,17 @@
     Suspended,
     HandoverInProgress,
     Unknown,
-    Invalid,
+    Invalid;
+
+    override fun logDiffs(prevVal: DataConnectionState, row: TableRowLogger) {
+        if (prevVal != this) {
+            row.logChange(COL_CONNECTION_STATE, name)
+        }
+    }
+
+    companion object {
+        private const val COL_CONNECTION_STATE = "connectionState"
+    }
 }
 
 fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
deleted file mode 100644
index ed7f60b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModel.kt
+++ /dev/null
@@ -1,176 +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.statusbar.pipeline.mobile.data.model
-
-import android.annotation.IntRange
-import android.telephony.CellSignalStrength
-import android.telephony.TelephonyCallback.CarrierNetworkListener
-import android.telephony.TelephonyCallback.DataActivityListener
-import android.telephony.TelephonyCallback.DataConnectionStateListener
-import android.telephony.TelephonyCallback.DisplayInfoListener
-import android.telephony.TelephonyCallback.ServiceStateListener
-import android.telephony.TelephonyCallback.SignalStrengthsListener
-import android.telephony.TelephonyDisplayInfo
-import android.telephony.TelephonyManager
-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.model.DataConnectionState.Disconnected
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
-
-/**
- * Data class containing all of the relevant information for a particular line of service, known as
- * a Subscription in the telephony world. These models are the result of a single telephony listener
- * which has many callbacks which each modify some particular field on this object.
- *
- * The design goal here is to de-normalize fields from the system into our model fields below. So
- * any new field that needs to be tracked should be copied into this data class rather than
- * threading complex system objects through the pipeline.
- */
-data class MobileConnectionModel(
-    /** Fields below are from [ServiceStateListener.onServiceStateChanged] */
-    val isEmergencyOnly: Boolean = false,
-    val isRoaming: Boolean = false,
-    /**
-     * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the
-     * current registered operator name in short alphanumeric format. In some cases this name might
-     * be preferred over other methods of calculating the network name
-     */
-    val operatorAlphaShort: String? = null,
-
-    /**
-     * TODO (b/263167683): Clarify this field
-     *
-     * This check comes from [com.android.settingslib.Utils.isInService]. It is intended to be a
-     * mapping from a ServiceState to a notion of connectivity. Notably, it will consider a
-     * connection to be in-service if either the voice registration state is IN_SERVICE or the data
-     * registration state is IN_SERVICE and NOT IWLAN.
-     */
-    val isInService: Boolean = false,
-
-    /** Fields below from [SignalStrengthsListener.onSignalStrengthsChanged] */
-    val isGsm: Boolean = false,
-    @IntRange(from = 0, to = 4)
-    val cdmaLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
-    @IntRange(from = 0, to = 4)
-    val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
-
-    /** Fields below from [DataConnectionStateListener.onDataConnectionStateChanged] */
-    val dataConnectionState: DataConnectionState = Disconnected,
-
-    /**
-     * Fields below from [DataActivityListener.onDataActivity]. See [TelephonyManager] for the
-     * values
-     */
-    val dataActivityDirection: DataActivityModel =
-        DataActivityModel(
-            hasActivityIn = false,
-            hasActivityOut = false,
-        ),
-
-    /** Fields below from [CarrierNetworkListener.onCarrierNetworkChange] */
-    val carrierNetworkChangeActive: Boolean = false,
-
-    /** Fields below from [DisplayInfoListener.onDisplayInfoChanged]. */
-
-    /**
-     * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
-     * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
-     */
-    val resolvedNetworkType: ResolvedNetworkType = ResolvedNetworkType.UnknownNetworkType,
-) : Diffable<MobileConnectionModel> {
-    override fun logDiffs(prevVal: MobileConnectionModel, row: TableRowLogger) {
-        if (prevVal.dataConnectionState != dataConnectionState) {
-            row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
-        }
-
-        if (prevVal.isEmergencyOnly != isEmergencyOnly) {
-            row.logChange(COL_EMERGENCY, isEmergencyOnly)
-        }
-
-        if (prevVal.isRoaming != isRoaming) {
-            row.logChange(COL_ROAMING, isRoaming)
-        }
-
-        if (prevVal.operatorAlphaShort != operatorAlphaShort) {
-            row.logChange(COL_OPERATOR, operatorAlphaShort)
-        }
-
-        if (prevVal.isInService != isInService) {
-            row.logChange(COL_IS_IN_SERVICE, isInService)
-        }
-
-        if (prevVal.isGsm != isGsm) {
-            row.logChange(COL_IS_GSM, isGsm)
-        }
-
-        if (prevVal.cdmaLevel != cdmaLevel) {
-            row.logChange(COL_CDMA_LEVEL, cdmaLevel)
-        }
-
-        if (prevVal.primaryLevel != primaryLevel) {
-            row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
-        }
-
-        if (prevVal.dataActivityDirection.hasActivityIn != dataActivityDirection.hasActivityIn) {
-            row.logChange(COL_ACTIVITY_DIRECTION_IN, dataActivityDirection.hasActivityIn)
-        }
-
-        if (prevVal.dataActivityDirection.hasActivityOut != dataActivityDirection.hasActivityOut) {
-            row.logChange(COL_ACTIVITY_DIRECTION_OUT, dataActivityDirection.hasActivityOut)
-        }
-
-        if (prevVal.carrierNetworkChangeActive != carrierNetworkChangeActive) {
-            row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
-        }
-
-        if (prevVal.resolvedNetworkType != resolvedNetworkType) {
-            row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
-        }
-    }
-
-    override fun logFull(row: TableRowLogger) {
-        row.logChange(COL_CONNECTION_STATE, dataConnectionState.name)
-        row.logChange(COL_EMERGENCY, isEmergencyOnly)
-        row.logChange(COL_ROAMING, isRoaming)
-        row.logChange(COL_OPERATOR, operatorAlphaShort)
-        row.logChange(COL_IS_IN_SERVICE, isInService)
-        row.logChange(COL_IS_GSM, isGsm)
-        row.logChange(COL_CDMA_LEVEL, cdmaLevel)
-        row.logChange(COL_PRIMARY_LEVEL, primaryLevel)
-        row.logChange(COL_ACTIVITY_DIRECTION_IN, dataActivityDirection.hasActivityIn)
-        row.logChange(COL_ACTIVITY_DIRECTION_OUT, dataActivityDirection.hasActivityOut)
-        row.logChange(COL_CARRIER_NETWORK_CHANGE, carrierNetworkChangeActive)
-        row.logChange(COL_RESOLVED_NETWORK_TYPE, resolvedNetworkType.toString())
-    }
-
-    @VisibleForTesting
-    companion object {
-        const val COL_EMERGENCY = "EmergencyOnly"
-        const val COL_ROAMING = "Roaming"
-        const val COL_OPERATOR = "OperatorName"
-        const val COL_IS_IN_SERVICE = "IsInService"
-        const val COL_IS_GSM = "IsGsm"
-        const val COL_CDMA_LEVEL = "CdmaLevel"
-        const val COL_PRIMARY_LEVEL = "PrimaryLevel"
-        const val COL_CONNECTION_STATE = "ConnectionState"
-        const val COL_ACTIVITY_DIRECTION_IN = "DataActivity.In"
-        const val COL_ACTIVITY_DIRECTION_OUT = "DataActivity.Out"
-        const val COL_CARRIER_NETWORK_CHANGE = "CarrierNetworkChangeActive"
-        const val COL_RESOLVED_NETWORK_TYPE = "NetworkType"
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
index 5562e73..cf7a313 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/ResolvedNetworkType.kt
@@ -17,8 +17,12 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.model
 
 import android.telephony.Annotation.NetworkType
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import com.android.settingslib.SignalIcon
+import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.TelephonyIcons
+import com.android.systemui.log.table.Diffable
+import com.android.systemui.log.table.TableRowLogger
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 
 /**
@@ -26,11 +30,19 @@
  * on whether or not the display info contains an override type, we may have to call different
  * methods on [MobileMappingsProxy] to generate an icon lookup key.
  */
-sealed interface ResolvedNetworkType {
+sealed interface ResolvedNetworkType : Diffable<ResolvedNetworkType> {
     val lookupKey: String
 
+    override fun logDiffs(prevVal: ResolvedNetworkType, row: TableRowLogger) {
+        if (prevVal != this) {
+            row.logChange(COL_NETWORK_TYPE, this.toString())
+        }
+    }
+
     object UnknownNetworkType : ResolvedNetworkType {
-        override val lookupKey: String = "unknown"
+        override val lookupKey: String = MobileMappings.toIconKey(NETWORK_TYPE_UNKNOWN)
+
+        override fun toString(): String = "Unknown"
     }
 
     data class DefaultNetworkType(
@@ -47,5 +59,11 @@
         override val lookupKey: String = "cwf"
 
         val iconGroupOverride: SignalIcon.MobileIconGroup = TelephonyIcons.CARRIER_MERGED_WIFI
+
+        override fun toString(): String = "CarrierMerged"
+    }
+
+    companion object {
+        private const val COL_NETWORK_TYPE = "networkType"
     }
 }
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 6187f64..90c32dc 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
@@ -17,11 +17,12 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
 import android.telephony.SubscriptionInfo
-import android.telephony.TelephonyCallback
 import android.telephony.TelephonyManager
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+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
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import kotlinx.coroutines.flow.StateFlow
 
 /**
@@ -45,11 +46,57 @@
      */
     val tableLogBuffer: TableLogBuffer
 
+    /** True if the [android.telephony.ServiceState] says this connection is emergency calls only */
+    val isEmergencyOnly: StateFlow<Boolean>
+
+    /** True if [android.telephony.ServiceState] says we are roaming */
+    val isRoaming: StateFlow<Boolean>
+
     /**
-     * A flow that aggregates all necessary callbacks from [TelephonyCallback] into a single
-     * listener + model.
+     * See [android.telephony.ServiceState.getOperatorAlphaShort], this value is defined as the
+     * current registered operator name in short alphanumeric format. In some cases this name might
+     * be preferred over other methods of calculating the network name
      */
-    val connectionInfo: StateFlow<MobileConnectionModel>
+    val operatorAlphaShort: StateFlow<String?>
+
+    /**
+     * TODO (b/263167683): Clarify this field
+     *
+     * This check comes from [com.android.settingslib.Utils.isInService]. It is intended to be a
+     * mapping from a ServiceState to a notion of connectivity. Notably, it will consider a
+     * connection to be in-service if either the voice registration state is IN_SERVICE or the data
+     * registration state is IN_SERVICE and NOT IWLAN.
+     */
+    val isInService: StateFlow<Boolean>
+
+    /** True if [android.telephony.SignalStrength] told us that this connection is using GSM */
+    val isGsm: StateFlow<Boolean>
+
+    /**
+     * There is still specific logic in the pipeline that calls out CDMA level explicitly. This
+     * field is not completely orthogonal to [primaryLevel], because CDMA could be primary.
+     */
+    // @IntRange(from = 0, to = 4)
+    val cdmaLevel: StateFlow<Int>
+
+    /** [android.telephony.SignalStrength]'s concept of the overall signal level */
+    // @IntRange(from = 0, to = 4)
+    val primaryLevel: StateFlow<Int>
+
+    /** The current data connection state. See [DataConnectionState] */
+    val dataConnectionState: StateFlow<DataConnectionState>
+
+    /** The current data activity direction. See [DataActivityModel] */
+    val dataActivityDirection: StateFlow<DataActivityModel>
+
+    /** True if there is currently a carrier network change in process */
+    val carrierNetworkChangeActive: StateFlow<Boolean>
+
+    /**
+     * [resolvedNetworkType] is the [TelephonyDisplayInfo.getOverrideNetworkType] if it exists or
+     * [TelephonyDisplayInfo.getNetworkType]. This is used to look up the proper network type icon
+     */
+    val resolvedNetworkType: StateFlow<ResolvedNetworkType>
 
     /** The total number of levels. Used with [SignalDrawable]. */
     val numberOfLevels: StateFlow<Int>
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
new file mode 100644
index 0000000..809772e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionRepository.kt
@@ -0,0 +1,231 @@
+/*
+ * 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.mobile.data.repository.demo
+
+import android.telephony.CellSignalStrength
+import android.telephony.TelephonyManager
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
+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
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CARRIER_NETWORK_CHANGE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_CDMA_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_GSM
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_IS_IN_SERVICE
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_ROAMING
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
+import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
+import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
+
+/**
+ * Demo version of [MobileConnectionRepository]. Note that this class shares all of its flows using
+ * [SharingStarted.WhileSubscribed()] to give the same semantics as using a regular
+ * [MutableStateFlow] while still logging all of the inputs in the same manor as the production
+ * repos.
+ */
+class DemoMobileConnectionRepository(
+    override val subId: Int,
+    override val tableLogBuffer: TableLogBuffer,
+    val scope: CoroutineScope,
+) : MobileConnectionRepository {
+    private val _isEmergencyOnly = MutableStateFlow(false)
+    override val isEmergencyOnly =
+        _isEmergencyOnly
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_EMERGENCY,
+                _isEmergencyOnly.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _isEmergencyOnly.value)
+
+    private val _isRoaming = MutableStateFlow(false)
+    override val isRoaming =
+        _isRoaming
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_ROAMING,
+                _isRoaming.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _isRoaming.value)
+
+    private val _operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
+    override val operatorAlphaShort =
+        _operatorAlphaShort
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_OPERATOR,
+                _operatorAlphaShort.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _operatorAlphaShort.value)
+
+    private val _isInService = MutableStateFlow(false)
+    override val isInService =
+        _isInService
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_IS_IN_SERVICE,
+                _isInService.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _isInService.value)
+
+    private val _isGsm = MutableStateFlow(false)
+    override val isGsm =
+        _isGsm
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_IS_GSM,
+                _isGsm.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _isGsm.value)
+
+    private val _cdmaLevel = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+    override val cdmaLevel =
+        _cdmaLevel
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_CDMA_LEVEL,
+                _cdmaLevel.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _cdmaLevel.value)
+
+    private val _primaryLevel = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+    override val primaryLevel =
+        _primaryLevel
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_PRIMARY_LEVEL,
+                _primaryLevel.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _primaryLevel.value)
+
+    private val _dataConnectionState = MutableStateFlow(DataConnectionState.Disconnected)
+    override val dataConnectionState =
+        _dataConnectionState
+            .logDiffsForTable(tableLogBuffer, columnPrefix = "", _dataConnectionState.value)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _dataConnectionState.value)
+
+    private val _dataActivityDirection =
+        MutableStateFlow(
+            DataActivityModel(
+                hasActivityIn = false,
+                hasActivityOut = false,
+            )
+        )
+    override val dataActivityDirection =
+        _dataActivityDirection
+            .logDiffsForTable(tableLogBuffer, columnPrefix = "", _dataActivityDirection.value)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _dataActivityDirection.value)
+
+    private val _carrierNetworkChangeActive = MutableStateFlow(false)
+    override val carrierNetworkChangeActive =
+        _carrierNetworkChangeActive
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_CARRIER_NETWORK_CHANGE,
+                _carrierNetworkChangeActive.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _carrierNetworkChangeActive.value)
+
+    private val _resolvedNetworkType: MutableStateFlow<ResolvedNetworkType> =
+        MutableStateFlow(ResolvedNetworkType.UnknownNetworkType)
+    override val resolvedNetworkType =
+        _resolvedNetworkType
+            .logDiffsForTable(tableLogBuffer, columnPrefix = "", _resolvedNetworkType.value)
+            .stateIn(scope, SharingStarted.WhileSubscribed(), _resolvedNetworkType.value)
+
+    override val numberOfLevels = MutableStateFlow(MobileConnectionRepository.DEFAULT_NUM_LEVELS)
+
+    override val dataEnabled = MutableStateFlow(true)
+
+    override val cdmaRoaming = MutableStateFlow(false)
+
+    override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network"))
+
+    /**
+     * 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
+     * repository.
+     */
+    fun processDemoMobileEvent(
+        event: FakeNetworkEventModel.Mobile,
+        resolvedNetworkType: ResolvedNetworkType,
+    ) {
+        // This is always true here, because we split out disabled states at the data-source level
+        dataEnabled.value = true
+        networkName.value = NetworkNameModel.IntentDerived(event.name)
+
+        cdmaRoaming.value = event.roaming
+        _isRoaming.value = event.roaming
+        // TODO(b/261029387): not yet supported
+        _isEmergencyOnly.value = false
+        _operatorAlphaShort.value = event.name
+        _isInService.value = (event.level ?: 0) > 0
+        // TODO(b/261029387): not yet supported
+        _isGsm.value = false
+        _cdmaLevel.value = event.level ?: 0
+        _primaryLevel.value = event.level ?: 0
+        // TODO(b/261029387): not yet supported
+        _dataConnectionState.value = DataConnectionState.Connected
+        _dataActivityDirection.value =
+            (event.activity ?: TelephonyManager.DATA_ACTIVITY_NONE).toMobileDataActivityModel()
+        _carrierNetworkChangeActive.value = event.carrierNetworkChange
+        _resolvedNetworkType.value = resolvedNetworkType
+    }
+
+    fun processCarrierMergedEvent(event: FakeWifiEventModel.CarrierMerged) {
+        // This is always true here, because we split out disabled states at the data-source level
+        dataEnabled.value = true
+        networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME)
+        numberOfLevels.value = event.numberOfLevels
+        cdmaRoaming.value = false
+        _primaryLevel.value = event.level
+        _cdmaLevel.value = event.level
+        _dataActivityDirection.value = event.activity.toMobileDataActivityModel()
+
+        // These fields are always the same for carrier-merged networks
+        _resolvedNetworkType.value = ResolvedNetworkType.CarrierMergedNetworkType
+        _dataConnectionState.value = DataConnectionState.Connected
+        _isRoaming.value = false
+        _isEmergencyOnly.value = false
+        _operatorAlphaShort.value = null
+        _isInService.value = true
+        _isGsm.value = false
+        _carrierNetworkChangeActive.value = false
+    }
+
+    companion object {
+        private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
index e924832..3cafb73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepository.kt
@@ -18,30 +18,22 @@
 
 import android.content.Context
 import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
-import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
 import android.util.Log
 import com.android.settingslib.SignalIcon
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.Mobile
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel.MobileDisabled
-import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.CarrierMergedConnectionRepository.Companion.createCarrierMergedConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Factory.Companion.MOBILE_CONNECTION_BUFFER_SIZE
-import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
 import javax.inject.Inject
@@ -183,7 +175,7 @@
     private fun createDemoMobileConnectionRepo(subId: Int): CacheContainer {
         val tableLogBuffer =
             logFactory.getOrCreate(
-                "DemoMobileConnectionLog [$subId]",
+                "DemoMobileConnectionLog[$subId]",
                 MOBILE_CONNECTION_BUFFER_SIZE,
             )
 
@@ -191,6 +183,7 @@
             DemoMobileConnectionRepository(
                 subId,
                 tableLogBuffer,
+                scope,
             )
         return CacheContainer(repo, lastMobileState = null)
     }
@@ -237,23 +230,18 @@
         }
     }
 
-    private fun processEnabledMobileState(state: Mobile) {
+    private fun processEnabledMobileState(event: Mobile) {
         // get or create the connection repo, and set its values
-        val subId = state.subId ?: DEFAULT_SUB_ID
+        val subId = event.subId ?: DEFAULT_SUB_ID
         maybeCreateSubscription(subId)
 
         val connection = getRepoForSubId(subId)
-        connectionRepoCache[subId]?.lastMobileState = state
+        connectionRepoCache[subId]?.lastMobileState = event
 
         // TODO(b/261029387): until we have a command, use the most recent subId
         defaultDataSubId.value = subId
 
-        // This is always true here, because we split out disabled states at the data-source level
-        connection.dataEnabled.value = true
-        connection.networkName.value = NetworkNameModel.IntentDerived(state.name)
-
-        connection.cdmaRoaming.value = state.roaming
-        connection.connectionInfo.value = state.toMobileConnectionModel()
+        connection.processDemoMobileEvent(event, event.dataType.toResolvedNetworkType())
     }
 
     private fun processCarrierMergedWifiState(event: FakeWifiEventModel.CarrierMerged) {
@@ -272,13 +260,7 @@
         defaultDataSubId.value = subId
 
         val connection = getRepoForSubId(subId)
-        // This is always true here, because we split out disabled states at the data-source level
-        connection.dataEnabled.value = true
-        connection.networkName.value = NetworkNameModel.IntentDerived(CARRIER_MERGED_NAME)
-        connection.numberOfLevels.value = event.numberOfLevels
-        connection.cdmaRoaming.value = false
-        connection.connectionInfo.value = event.toMobileConnectionModel()
-        Log.e("CCS", "output connection info = ${connection.connectionInfo.value}")
+        connection.processCarrierMergedEvent(event)
     }
 
     private fun maybeRemoveSubscription(subId: Int?) {
@@ -332,29 +314,6 @@
     private fun subIdsString(): String =
         _subscriptions.value.joinToString(",") { it.subscriptionId.toString() }
 
-    private fun Mobile.toMobileConnectionModel(): MobileConnectionModel {
-        return MobileConnectionModel(
-            isEmergencyOnly = false, // TODO(b/261029387): not yet supported
-            isRoaming = roaming,
-            isInService = (level ?: 0) > 0,
-            isGsm = false, // TODO(b/261029387): not yet supported
-            cdmaLevel = level ?: 0,
-            primaryLevel = level ?: 0,
-            dataConnectionState =
-                DataConnectionState.Connected, // TODO(b/261029387): not yet supported
-            dataActivityDirection = (activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel(),
-            carrierNetworkChangeActive = carrierNetworkChange,
-            resolvedNetworkType = dataType.toResolvedNetworkType()
-        )
-    }
-
-    private fun FakeWifiEventModel.CarrierMerged.toMobileConnectionModel(): MobileConnectionModel {
-        return createCarrierMergedConnectionModel(
-            this.level,
-            activity.toMobileDataActivityModel(),
-        )
-    }
-
     private fun SignalIcon.MobileIconGroup?.toResolvedNetworkType(): ResolvedNetworkType {
         val key = mobileMappingsReverseLookup.value[this] ?: "dis"
         return DefaultNetworkType(key)
@@ -364,8 +323,6 @@
         private const val TAG = "DemoMobileConnectionsRepo"
 
         private const val DEFAULT_SUB_ID = 1
-
-        private const val CARRIER_MERGED_NAME = "Carrier Merged Network"
     }
 }
 
@@ -374,18 +331,3 @@
     /** The last received [Mobile] event. Used when switching from carrier merged back to mobile. */
     var lastMobileState: Mobile?,
 )
-
-class DemoMobileConnectionRepository(
-    override val subId: Int,
-    override val tableLogBuffer: TableLogBuffer,
-) : MobileConnectionRepository {
-    override val connectionInfo = MutableStateFlow(MobileConnectionModel())
-
-    override val numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
-
-    override val dataEnabled = MutableStateFlow(true)
-
-    override val cdmaRoaming = MutableStateFlow(false)
-
-    override val networkName = MutableStateFlow(NetworkNameModel.IntentDerived("demo network"))
-}
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 8f6a87b..94d6d0b 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
@@ -16,18 +16,17 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository.prod
 
+import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
 import android.telephony.TelephonyManager
 import android.util.Log
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 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.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
-import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
 import javax.inject.Inject
@@ -94,16 +93,6 @@
             }
         }
 
-    override val connectionInfo: StateFlow<MobileConnectionModel> =
-        combine(network, wifiRepository.wifiActivity) { network, activity ->
-                if (network == null) {
-                    MobileConnectionModel()
-                } else {
-                    createCarrierMergedConnectionModel(network.level, activity)
-                }
-            }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectionModel())
-
     override val cdmaRoaming: StateFlow<Boolean> = MutableStateFlow(ROAMING).asStateFlow()
 
     override val networkName: StateFlow<NetworkNameModel> =
@@ -129,34 +118,54 @@
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), DEFAULT_NUM_LEVELS)
 
+    override val primaryLevel =
+        network
+            .map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+    override val cdmaLevel =
+        network
+            .map { it?.level ?: SIGNAL_STRENGTH_NONE_OR_UNKNOWN }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+    override val dataActivityDirection = wifiRepository.wifiActivity
+
+    override val resolvedNetworkType =
+        network
+            .map {
+                if (it != null) {
+                    ResolvedNetworkType.CarrierMergedNetworkType
+                } else {
+                    ResolvedNetworkType.UnknownNetworkType
+                }
+            }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                ResolvedNetworkType.UnknownNetworkType
+            )
+
+    override val dataConnectionState =
+        network
+            .map {
+                if (it != null) {
+                    DataConnectionState.Connected
+                } else {
+                    DataConnectionState.Disconnected
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), DataConnectionState.Disconnected)
+
+    override val isRoaming = MutableStateFlow(false).asStateFlow()
+    override val isEmergencyOnly = MutableStateFlow(false).asStateFlow()
+    override val operatorAlphaShort = MutableStateFlow(null).asStateFlow()
+    override val isInService = MutableStateFlow(true).asStateFlow()
+    override val isGsm = MutableStateFlow(false).asStateFlow()
+    override val carrierNetworkChangeActive = MutableStateFlow(false).asStateFlow()
+
     override val dataEnabled: StateFlow<Boolean> = wifiRepository.isWifiEnabled
 
     companion object {
-        /**
-         * Creates an instance of [MobileConnectionModel] that represents a carrier merged network
-         * with the given [level] and [activity].
-         */
-        fun createCarrierMergedConnectionModel(
-            level: Int,
-            activity: DataActivityModel,
-        ): MobileConnectionModel {
-            return MobileConnectionModel(
-                primaryLevel = level,
-                cdmaLevel = level,
-                dataActivityDirection = activity,
-                // Here and below: These values are always the same for every carrier-merged
-                // connection.
-                resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
-                dataConnectionState = DataConnectionState.Connected,
-                isRoaming = ROAMING,
-                isEmergencyOnly = false,
-                operatorAlphaShort = null,
-                isInService = true,
-                isGsm = false,
-                carrierNetworkChangeActive = false,
-            )
-        }
-
         // Carrier merged is never roaming
         private const val ROAMING = false
     }
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 a39ea0a..b3737ec 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
@@ -114,15 +114,147 @@
             .flatMapLatest { it.cdmaRoaming }
             .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaRoaming.value)
 
-    override val connectionInfo =
+    override val isEmergencyOnly =
         activeRepo
-            .flatMapLatest { it.connectionInfo }
+            .flatMapLatest { it.isEmergencyOnly }
             .logDiffsForTable(
                 tableLogBuffer,
                 columnPrefix = "",
-                initialValue = activeRepo.value.connectionInfo.value,
+                columnName = COL_EMERGENCY,
+                activeRepo.value.isEmergencyOnly.value
             )
-            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.connectionInfo.value)
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.isEmergencyOnly.value
+            )
+
+    override val isRoaming =
+        activeRepo
+            .flatMapLatest { it.isRoaming }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_ROAMING,
+                activeRepo.value.isRoaming.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isRoaming.value)
+
+    override val operatorAlphaShort =
+        activeRepo
+            .flatMapLatest { it.operatorAlphaShort }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_OPERATOR,
+                activeRepo.value.operatorAlphaShort.value
+            )
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.operatorAlphaShort.value
+            )
+
+    override val isInService =
+        activeRepo
+            .flatMapLatest { it.isInService }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_IS_IN_SERVICE,
+                activeRepo.value.isInService.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isInService.value)
+
+    override val isGsm =
+        activeRepo
+            .flatMapLatest { it.isGsm }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_IS_GSM,
+                activeRepo.value.isGsm.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.isGsm.value)
+
+    override val cdmaLevel =
+        activeRepo
+            .flatMapLatest { it.cdmaLevel }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_CDMA_LEVEL,
+                activeRepo.value.cdmaLevel.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.cdmaLevel.value)
+
+    override val primaryLevel =
+        activeRepo
+            .flatMapLatest { it.primaryLevel }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_PRIMARY_LEVEL,
+                activeRepo.value.primaryLevel.value
+            )
+            .stateIn(scope, SharingStarted.WhileSubscribed(), activeRepo.value.primaryLevel.value)
+
+    override val dataConnectionState =
+        activeRepo
+            .flatMapLatest { it.dataConnectionState }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                activeRepo.value.dataConnectionState.value
+            )
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.dataConnectionState.value
+            )
+
+    override val dataActivityDirection =
+        activeRepo
+            .flatMapLatest { it.dataActivityDirection }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                activeRepo.value.dataActivityDirection.value
+            )
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.dataActivityDirection.value
+            )
+
+    override val carrierNetworkChangeActive =
+        activeRepo
+            .flatMapLatest { it.carrierNetworkChangeActive }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                columnName = COL_CARRIER_NETWORK_CHANGE,
+                activeRepo.value.carrierNetworkChangeActive.value
+            )
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.carrierNetworkChangeActive.value
+            )
+
+    override val resolvedNetworkType =
+        activeRepo
+            .flatMapLatest { it.resolvedNetworkType }
+            .logDiffsForTable(
+                tableLogBuffer,
+                columnPrefix = "",
+                activeRepo.value.resolvedNetworkType.value
+            )
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                activeRepo.value.resolvedNetworkType.value
+            )
 
     override val dataEnabled =
         activeRepo
@@ -187,4 +319,15 @@
             fun tableBufferLogName(subId: Int): String = "MobileConnectionLog[$subId]"
         }
     }
+
+    companion object {
+        const val COL_EMERGENCY = "emergencyOnly"
+        const val COL_ROAMING = "roaming"
+        const val COL_OPERATOR = "operatorName"
+        const val COL_IS_IN_SERVICE = "isInService"
+        const val COL_IS_GSM = "isGsm"
+        const val COL_CDMA_LEVEL = "cdmaLevel"
+        const val COL_PRIMARY_LEVEL = "primaryLevel"
+        const val COL_CARRIER_NETWORK_CHANGE = "carrierNetworkChangeActive"
+    }
 }
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 e182bc6..f1fc386 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
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.content.IntentFilter
 import android.telephony.CellSignalStrength
+import android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
 import android.telephony.CellSignalStrengthCdma
 import android.telephony.ServiceState
 import android.telephony.SignalStrength
@@ -37,7 +38,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 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.MobileConnectionModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
@@ -49,6 +50,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -62,10 +64,10 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterIsInstance
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.mapNotNull
-import kotlinx.coroutines.flow.scan
 import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 
@@ -165,83 +167,100 @@
             }
             .shareIn(scope, SharingStarted.WhileSubscribed())
 
-    private fun updateConnectionState(
-        prevState: MobileConnectionModel,
-        callbackEvent: CallbackEvent,
-    ): MobileConnectionModel =
-        when (callbackEvent) {
-            is CallbackEvent.OnServiceStateChanged -> {
-                val serviceState = callbackEvent.serviceState
-                prevState.copy(
-                    isEmergencyOnly = serviceState.isEmergencyOnly,
-                    isRoaming = serviceState.roaming,
-                    operatorAlphaShort = serviceState.operatorAlphaShort,
-                    isInService = Utils.isInService(serviceState),
-                )
-            }
-            is CallbackEvent.OnSignalStrengthChanged -> {
-                val signalStrength = callbackEvent.signalStrength
-                val cdmaLevel =
-                    signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
-                        strengths ->
-                        if (!strengths.isEmpty()) {
-                            strengths[0].level
-                        } else {
-                            CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
-                        }
-                    }
-
-                val primaryLevel = signalStrength.level
-
-                prevState.copy(
-                    cdmaLevel = cdmaLevel,
-                    primaryLevel = primaryLevel,
-                    isGsm = signalStrength.isGsm,
-                )
-            }
-            is CallbackEvent.OnDataConnectionStateChanged -> {
-                prevState.copy(dataConnectionState = callbackEvent.dataState.toDataConnectionType())
-            }
-            is CallbackEvent.OnDataActivity -> {
-                prevState.copy(
-                    dataActivityDirection = callbackEvent.direction.toMobileDataActivityModel()
-                )
-            }
-            is CallbackEvent.OnCarrierNetworkChange -> {
-                prevState.copy(carrierNetworkChangeActive = callbackEvent.active)
-            }
-            is CallbackEvent.OnDisplayInfoChanged -> {
-                val telephonyDisplayInfo = callbackEvent.telephonyDisplayInfo
-                val networkType =
-                    if (telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
-                        UnknownNetworkType
-                    } else if (
-                        telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
-                    ) {
-                        DefaultNetworkType(
-                            mobileMappingsProxy.toIconKey(telephonyDisplayInfo.networkType)
-                        )
-                    } else {
-                        OverrideNetworkType(
-                            mobileMappingsProxy.toIconKeyOverride(
-                                telephonyDisplayInfo.overrideNetworkType
-                            )
-                        )
-                    }
-                prevState.copy(resolvedNetworkType = networkType)
-            }
-            is CallbackEvent.OnDataEnabledChanged -> {
-                // Not part of this object, handled in a separate flow
-                prevState
-            }
-        }
-
-    override val connectionInfo = run {
-        val initial = MobileConnectionModel()
+    override val isEmergencyOnly =
         callbackEvents
-            .scan(initial, ::updateConnectionState)
-            .stateIn(scope, SharingStarted.WhileSubscribed(), initial)
-    }
+            .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+            .map { it.serviceState.isEmergencyOnly }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val isRoaming =
+        callbackEvents
+            .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+            .map { it.serviceState.roaming }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val operatorAlphaShort =
+        callbackEvents
+            .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+            .map { it.serviceState.operatorAlphaShort }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+    override val isInService =
+        callbackEvents
+            .filterIsInstance<CallbackEvent.OnServiceStateChanged>()
+            .map { Utils.isInService(it.serviceState) }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val isGsm =
+        callbackEvents
+            .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+            .map { it.signalStrength.isGsm }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val cdmaLevel =
+        callbackEvents
+            .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+            .map {
+                it.signalStrength.getCellSignalStrengths(CellSignalStrengthCdma::class.java).let {
+                    strengths ->
+                    if (strengths.isNotEmpty()) {
+                        strengths[0].level
+                    } else {
+                        CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN
+                    }
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+    override val primaryLevel =
+        callbackEvents
+            .filterIsInstance<CallbackEvent.OnSignalStrengthChanged>()
+            .map { it.signalStrength.level }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+
+    override val dataConnectionState =
+        callbackEvents
+            .filterIsInstance<CallbackEvent.OnDataConnectionStateChanged>()
+            .map { it.dataState.toDataConnectionType() }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), Disconnected)
+
+    override val dataActivityDirection =
+        callbackEvents
+            .filterIsInstance<CallbackEvent.OnDataActivity>()
+            .map { it.direction.toMobileDataActivityModel() }
+            .stateIn(
+                scope,
+                SharingStarted.WhileSubscribed(),
+                DataActivityModel(hasActivityIn = false, hasActivityOut = false)
+            )
+
+    override val carrierNetworkChangeActive =
+        callbackEvents
+            .filterIsInstance<CallbackEvent.OnCarrierNetworkChange>()
+            .map { it.active }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val resolvedNetworkType =
+        callbackEvents
+            .filterIsInstance<CallbackEvent.OnDisplayInfoChanged>()
+            .map {
+                if (it.telephonyDisplayInfo.networkType == NETWORK_TYPE_UNKNOWN) {
+                    UnknownNetworkType
+                } else if (
+                    it.telephonyDisplayInfo.overrideNetworkType == OVERRIDE_NETWORK_TYPE_NONE
+                ) {
+                    DefaultNetworkType(
+                        mobileMappingsProxy.toIconKey(it.telephonyDisplayInfo.networkType)
+                    )
+                } else {
+                    OverrideNetworkType(
+                        mobileMappingsProxy.toIconKeyOverride(
+                            it.telephonyDisplayInfo.overrideNetworkType
+                        )
+                    )
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), UnknownNetworkType)
 
     override val numberOfLevels =
         systemUiCarrierConfig.shouldInflateSignalStrength
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index f97e41c..b7da3f2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -266,6 +266,7 @@
                 val callback =
                     object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
                         override fun onLost(network: Network) {
+                            logger.logOnLost(network, isDefaultNetworkCallback = true)
                             // Send a disconnected model when lost. Maybe should create a sealed
                             // type or null here?
                             trySend(MobileConnectivityModel())
@@ -275,6 +276,11 @@
                             network: Network,
                             caps: NetworkCapabilities
                         ) {
+                            logger.logOnCapabilitiesChanged(
+                                network,
+                                caps,
+                                isDefaultNetworkCallback = true,
+                            )
                             trySend(
                                 MobileConnectivityModel(
                                     isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
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 4caf2b0..7df6764 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
@@ -34,6 +34,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
@@ -133,11 +134,9 @@
     override val isForceHidden: Flow<Boolean>,
     connectionRepository: MobileConnectionRepository,
 ) : MobileIconInteractor {
-    private val connectionInfo = connectionRepository.connectionInfo
-
     override val tableLogBuffer: TableLogBuffer = connectionRepository.tableLogBuffer
 
-    override val activity = connectionInfo.mapLatest { it.dataActivityDirection }
+    override val activity = connectionRepository.dataActivityDirection
 
     override val isConnected: Flow<Boolean> = defaultMobileConnectivity.mapLatest { it.isConnected }
 
@@ -155,11 +154,11 @@
     override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
 
     override val networkName =
-        combine(connectionInfo, connectionRepository.networkName) { connection, networkName ->
-                if (
-                    networkName is NetworkNameModel.Default && connection.operatorAlphaShort != null
-                ) {
-                    NetworkNameModel.IntentDerived(connection.operatorAlphaShort)
+        combine(connectionRepository.operatorAlphaShort, connectionRepository.networkName) {
+                operatorAlphaShort,
+                networkName ->
+                if (networkName is NetworkNameModel.Default && operatorAlphaShort != null) {
+                    NetworkNameModel.IntentDerived(operatorAlphaShort)
                 } else {
                     networkName
                 }
@@ -173,19 +172,19 @@
     /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
     override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
         combine(
-                connectionInfo,
+                connectionRepository.resolvedNetworkType,
                 defaultMobileIconMapping,
                 defaultMobileIconGroup,
                 isDefault,
-            ) { info, mapping, defaultGroup, isDefault ->
+            ) { resolvedNetworkType, mapping, defaultGroup, isDefault ->
                 if (!isDefault) {
                     return@combine NOT_DEFAULT_DATA
                 }
 
-                when (info.resolvedNetworkType) {
+                when (resolvedNetworkType) {
                     is ResolvedNetworkType.CarrierMergedNetworkType ->
-                        info.resolvedNetworkType.iconGroupOverride
-                    else -> mapping[info.resolvedNetworkType.lookupKey] ?: defaultGroup
+                        resolvedNetworkType.iconGroupOverride
+                    else -> mapping[resolvedNetworkType.lookupKey] ?: defaultGroup
                 }
             }
             .distinctUntilChanged()
@@ -200,17 +199,19 @@
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
 
-    override val isEmergencyOnly: StateFlow<Boolean> =
-        connectionInfo
-            .mapLatest { it.isEmergencyOnly }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+    override val isEmergencyOnly = connectionRepository.isEmergencyOnly
 
     override val isRoaming: StateFlow<Boolean> =
-        combine(connectionInfo, connectionRepository.cdmaRoaming) { connection, cdmaRoaming ->
-                if (connection.carrierNetworkChangeActive) {
+        combine(
+                connectionRepository.carrierNetworkChangeActive,
+                connectionRepository.isGsm,
+                connectionRepository.isRoaming,
+                connectionRepository.cdmaRoaming,
+            ) { carrierNetworkChangeActive, isGsm, isRoaming, cdmaRoaming ->
+                if (carrierNetworkChangeActive) {
                     false
-                } else if (connection.isGsm) {
-                    connection.isRoaming
+                } else if (isGsm) {
+                    isRoaming
                 } else {
                     cdmaRoaming
                 }
@@ -218,12 +219,17 @@
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     override val level: StateFlow<Int> =
-        combine(connectionInfo, alwaysUseCdmaLevel) { connection, alwaysUseCdmaLevel ->
+        combine(
+                connectionRepository.isGsm,
+                connectionRepository.primaryLevel,
+                connectionRepository.cdmaLevel,
+                alwaysUseCdmaLevel,
+            ) { isGsm, primaryLevel, cdmaLevel, alwaysUseCdmaLevel ->
                 when {
                     // GSM connections should never use the CDMA level
-                    connection.isGsm -> connection.primaryLevel
-                    alwaysUseCdmaLevel -> connection.cdmaLevel
-                    else -> connection.primaryLevel
+                    isGsm -> primaryLevel
+                    alwaysUseCdmaLevel -> cdmaLevel
+                    else -> primaryLevel
                 }
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
@@ -236,12 +242,9 @@
         )
 
     override val isDataConnected: StateFlow<Boolean> =
-        connectionInfo
-            .mapLatest { connection -> connection.dataConnectionState == Connected }
+        connectionRepository.dataConnectionState
+            .map { it == Connected }
             .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
-    override val isInService =
-        connectionRepository.connectionInfo
-            .mapLatest { it.isInService }
-            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+    override val isInService = connectionRepository.isInService
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
index 6f29e33..a96e8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/LoggerHelper.kt
@@ -38,11 +38,24 @@
                 int1 = network.getNetId()
                 str1 = networkCapabilities.toString()
             },
-            { "onCapabilitiesChanged[default=$bool1]: net=$int1 capabilities=$str1" }
+            { "on${if (bool1) "Default" else ""}CapabilitiesChanged: net=$int1 capabilities=$str1" }
         )
     }
 
-    fun logOnLost(buffer: LogBuffer, tag: String, network: Network) {
-        buffer.log(tag, LogLevel.INFO, { int1 = network.getNetId() }, { "onLost: net=$int1" })
+    fun logOnLost(
+        buffer: LogBuffer,
+        tag: String,
+        network: Network,
+        isDefaultNetworkCallback: Boolean,
+    ) {
+        buffer.log(
+            tag,
+            LogLevel.INFO,
+            {
+                int1 = network.getNetId()
+                bool1 = isDefaultNetworkCallback
+            },
+            { "on${if (bool1) "Default" else ""}Lost: net=$int1" }
+        )
     }
 }
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 ee58160..b5e7b7a 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
@@ -128,6 +128,7 @@
                         }
 
                         override fun onLost(network: Network) {
+                            logger.logOnLost(network, isDefaultNetworkCallback = true)
                             // The system no longer has a default network, so wifi is definitely not
                             // default.
                             trySend(false)
@@ -179,7 +180,7 @@
                         }
 
                         override fun onLost(network: Network) {
-                            logger.logOnLost(network)
+                            logger.logOnLost(network, isDefaultNetworkCallback = false)
 
                             wifiNetworkChangeEvents.tryEmit(Unit)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
index a32e475..bb0b166 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiInputLogger.kt
@@ -48,8 +48,8 @@
         )
     }
 
-    fun logOnLost(network: Network) {
-        LoggerHelper.logOnLost(buffer, TAG, network)
+    fun logOnLost(network: Network, isDefaultNetworkCallback: Boolean) {
+        LoggerHelper.logOnLost(buffer, TAG, network, isDefaultNetworkCallback)
     }
 
     fun logIntent(intentName: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index 853de7b..bcf3b0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -16,9 +16,9 @@
 
 package com.android.systemui.statusbar.window;
 
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
+import static android.view.WindowInsets.Type.mandatorySystemGestures;
+import static android.view.WindowInsets.Type.statusBars;
+import static android.view.WindowInsets.Type.tappableElement;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
@@ -85,6 +85,7 @@
     private final ViewGroup mLaunchAnimationContainer;
     private WindowManager.LayoutParams mLp;
     private final WindowManager.LayoutParams mLpChanged;
+    private final Binder mInsetsSourceOwner = new Binder();
 
     @Inject
     public StatusBarWindowController(
@@ -231,16 +232,16 @@
         lp.packageName = mContext.getPackageName();
         lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         final InsetsFrameProvider gestureInsetsProvider =
-                new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES);
+                new InsetsFrameProvider(mInsetsSourceOwner, 0, mandatorySystemGestures());
         final int safeTouchRegionHeight = mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.display_cutout_touchable_region_size);
         if (safeTouchRegionHeight > 0) {
-            gestureInsetsProvider.minimalInsetsSizeInDisplayCutoutSafe =
-                    Insets.of(0, safeTouchRegionHeight, 0, 0);
+            gestureInsetsProvider.setMinimalInsetsSizeInDisplayCutoutSafe(
+                    Insets.of(0, safeTouchRegionHeight, 0, 0));
         }
         lp.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(ITYPE_STATUS_BAR),
-                new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
+                new InsetsFrameProvider(mInsetsSourceOwner, 0, statusBars()),
+                new InsetsFrameProvider(mInsetsSourceOwner, 0, tappableElement()),
                 gestureInsetsProvider
         };
         return lp;
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 2ad3558..faae8df 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -29,6 +29,7 @@
 import static com.android.systemui.theme.ThemeOverlayApplier.OVERLAY_COLOR_SOURCE;
 import static com.android.systemui.theme.ThemeOverlayApplier.TIMESTAMP_FIELD;
 
+import android.app.UiModeManager;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.app.WallpaperManager.OnColorsChangedListener;
@@ -54,7 +55,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
@@ -140,8 +140,8 @@
     private boolean mNeedsOverlayCreation;
     // Dominant color extracted from wallpaper, NOT the color used on the overlay
     protected int mMainWallpaperColor = Color.TRANSPARENT;
-    // UI contrast as reported by AccessibilityManager
-    private float mUiContrast = 0;
+    // UI contrast as reported by UiModeManager
+    private float mContrast = 0;
     // Theme variant: Vibrant, Tonal, Expressive, etc
     @VisibleForTesting
     protected Style mThemeStyle = Style.TONAL_SPOT;
@@ -158,7 +158,7 @@
     private final SparseArray<WallpaperColors> mDeferredWallpaperColors = new SparseArray<>();
     private final SparseIntArray mDeferredWallpaperColorsFlags = new SparseIntArray();
     private final WakefulnessLifecycle mWakefulnessLifecycle;
-    private final AccessibilityManager mAccessibilityManager;
+    private final UiModeManager mUiModeManager;
     private DynamicScheme mDynamicSchemeDark;
     private DynamicScheme mDynamicSchemeLight;
 
@@ -392,7 +392,7 @@
             FeatureFlags featureFlags,
             @Main Resources resources,
             WakefulnessLifecycle wakefulnessLifecycle,
-            AccessibilityManager accessibilityManager) {
+            UiModeManager uiModeManager) {
         mContext = context;
         mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME);
         mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
@@ -408,7 +408,7 @@
         mUserTracker = userTracker;
         mResources = resources;
         mWakefulnessLifecycle = wakefulnessLifecycle;
-        mAccessibilityManager = accessibilityManager;
+        mUiModeManager = uiModeManager;
         dumpManager.registerDumpable(TAG, this);
     }
 
@@ -445,9 +445,9 @@
                     }
                 },
                 UserHandle.USER_ALL);
-        mUiContrast = mAccessibilityManager.getUiContrast();
-        mAccessibilityManager.addUiContrastChangeListener(mMainExecutor, uiContrast -> {
-            mUiContrast = uiContrast;
+        mContrast = mUiModeManager.getContrast();
+        mUiModeManager.addContrastChangeListener(mMainExecutor, contrast -> {
+            mContrast = contrast;
             // Force reload so that we update even when the main color has not changed
             reevaluateSystemTheme(true /* forceReload */);
         });
@@ -586,9 +586,9 @@
         mSecondaryOverlay = createAccentOverlay();
 
         mDynamicSchemeDark = dynamicSchemeFromStyle(
-                mThemeStyle, color, true /* isDark */, mUiContrast);
+                mThemeStyle, color, true /* isDark */, mContrast);
         mDynamicSchemeLight = dynamicSchemeFromStyle(
-                mThemeStyle, color, false /* isDark */, mUiContrast);
+                mThemeStyle, color, false /* isDark */, mContrast);
         mDynamicOverlay = createDynamicOverlay();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
index 433642b..94dd1b3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/UserInteractor.kt
@@ -33,6 +33,8 @@
 import android.util.Log
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.util.UserIcons
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.R
 import com.android.systemui.SystemUISecondaryUserService
 import com.android.systemui.animation.Expandable
@@ -93,6 +95,7 @@
     @Application private val applicationScope: CoroutineScope,
     telephonyInteractor: TelephonyInteractor,
     broadcastDispatcher: BroadcastDispatcher,
+    keyguardUpdateMonitor: KeyguardUpdateMonitor,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val activityManager: ActivityManager,
     private val refreshUsersScheduler: RefreshUsersScheduler,
@@ -290,6 +293,12 @@
 
     val isSimpleUserSwitcher: Boolean
         get() = repository.isSimpleUserSwitcher()
+    val keyguardUpdateMonitorCallback =
+        object : KeyguardUpdateMonitorCallback() {
+            override fun onKeyguardGoingAway() {
+                dismissDialog()
+            }
+        }
 
     init {
         refreshUsersScheduler.refreshIfNotPaused()
@@ -320,6 +329,7 @@
                 onBroadcastReceived(intent, previousSelectedUser)
             }
             .launchIn(applicationScope)
+        keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
     }
 
     fun addCallback(callback: UserCallback) {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 94ce002..95cc12a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1508,6 +1508,7 @@
         mHandler.removeMessages(H.SHOW);
         if (mIsAnimatingDismiss) {
             Log.d(TAG, "dismissH: isAnimatingDismiss");
+            Trace.endSection();
             return;
         }
         mIsAnimatingDismiss = true;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 064bc9c..38d3a3e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -521,6 +521,38 @@
     }
 
     @Test
+    public void testWillRunDismissFromKeyguardIsTrue() {
+        ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
+        when(action.willRunAnimationOnKeyguard()).thenReturn(true);
+        mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
+
+        mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
+
+        assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isTrue();
+    }
+
+    @Test
+    public void testWillRunDismissFromKeyguardIsFalse() {
+        ActivityStarter.OnDismissAction action = mock(ActivityStarter.OnDismissAction.class);
+        when(action.willRunAnimationOnKeyguard()).thenReturn(false);
+        mKeyguardSecurityContainerController.setOnDismissAction(action, null /* cancelAction */);
+
+        mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
+
+        assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
+    }
+
+    @Test
+    public void testWillRunDismissFromKeyguardIsFalseWhenNoDismissActionSet() {
+        mKeyguardSecurityContainerController.setOnDismissAction(null /* action */,
+                null /* cancelAction */);
+
+        mKeyguardSecurityContainerController.finish(false /* strongAuth */, 0 /* currentUser */);
+
+        assertThat(mKeyguardSecurityContainerController.willRunDismissFromKeyguard()).isFalse();
+    }
+
+    @Test
     public void testOnStartingToHide() {
         mKeyguardSecurityContainerController.onStartingToHide();
         verify(mInputViewController).onStartingToHide();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 531006d..565fc57 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -263,9 +263,6 @@
         assertThat(viewFlipperConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
         assertThat(userSwitcherConstraint.layout.topToTop).isEqualTo(PARENT_ID);
         assertThat(userSwitcherConstraint.layout.bottomToBottom).isEqualTo(PARENT_ID);
-        assertThat(userSwitcherConstraint.layout.bottomMargin).isEqualTo(
-                getContext().getResources().getDimensionPixelSize(
-                        R.dimen.bouncer_user_switcher_y_trans));
         assertThat(viewFlipperConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
         assertThat(userSwitcherConstraint.layout.horizontalChainStyle).isEqualTo(CHAIN_SPREAD);
         assertThat(viewFlipperConstraint.layout.mHeight).isEqualTo(MATCH_CONSTRAINT);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
index f01da2d..8a5c5b5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
@@ -61,7 +61,7 @@
         val interp = FontInterpolator()
         assertSameAxes(startFont, interp.lerp(startFont, endFont, 0f))
         assertSameAxes(endFont, interp.lerp(startFont, endFont, 1f))
-        assertSameAxes("'wght' 500, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f))
+        assertSameAxes("'wght' 496, 'ital' 0.5, 'GRAD' 450", interp.lerp(startFont, endFont, 0.5f))
     }
 
     @Test
@@ -74,7 +74,7 @@
                 .build()
 
         val interp = FontInterpolator()
-        assertSameAxes("'wght' 250, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f))
+        assertSameAxes("'wght' 249, 'ital' 0.5", interp.lerp(startFont, endFont, 0.5f))
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 28e80057..c98d537 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.FakeSelectedComponentRepository
 import com.android.systemui.controls.ui.ControlsUiController
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.settings.UserFileManager
@@ -56,7 +57,6 @@
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.anyInt
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.inOrder
@@ -66,6 +66,7 @@
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 import java.io.File
 import java.util.*
@@ -108,6 +109,8 @@
     private lateinit var listingCallbackCaptor:
             ArgumentCaptor<ControlsListingController.ControlsListingCallback>
 
+    private val preferredPanelRepository = FakeSelectedComponentRepository()
+
     private lateinit var delayableExecutor: FakeExecutor
     private lateinit var controller: ControlsControllerImpl
     private lateinit var canceller: DidRunRunnable
@@ -168,6 +171,7 @@
                 wrapper,
                 delayableExecutor,
                 uiController,
+                preferredPanelRepository,
                 bindingController,
                 listingController,
                 userFileManager,
@@ -221,6 +225,7 @@
                 mContext,
                 delayableExecutor,
                 uiController,
+                preferredPanelRepository,
                 bindingController,
                 listingController,
                 userFileManager,
@@ -240,6 +245,7 @@
                 mContext,
                 delayableExecutor,
                 uiController,
+                preferredPanelRepository,
                 bindingController,
                 listingController,
                 userFileManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
index 7ac1953..272f589 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/AuthorizedPanelsRepositoryImplTest.kt
@@ -22,6 +22,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.FakeSharedPreferences
@@ -40,6 +42,8 @@
 
     @Mock private lateinit var userTracker: UserTracker
 
+    private val featureFlags = FakeFeatureFlags()
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -48,6 +52,7 @@
             arrayOf<String>()
         )
         whenever(userTracker.userId).thenReturn(0)
+        featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
     }
 
     @Test
@@ -127,8 +132,25 @@
         assertThat(sharedPrefs.getStringSet(KEY, null)).isEmpty()
     }
 
+    @Test
+    fun testSetAuthorizedPackageAfterFeatureDisabled() {
+        mContext.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf(TEST_PACKAGE)
+        )
+        val sharedPrefs = FakeSharedPreferences()
+        val fileManager = FakeUserFileManager(mapOf(0 to sharedPrefs))
+        val repository = createRepository(fileManager)
+
+        repository.removeAuthorizedPanels(setOf(TEST_PACKAGE))
+
+        featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
+
+        assertThat(repository.getAuthorizedPanels()).isEqualTo(setOf(TEST_PACKAGE))
+    }
+
     private fun createRepository(userFileManager: UserFileManager): AuthorizedPanelsRepositoryImpl {
-        return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker)
+        return AuthorizedPanelsRepositoryImpl(mContext, userFileManager, userTracker, featureFlags)
     }
 
     private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) :
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.kt
new file mode 100644
index 0000000..a7677cc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/FakeSelectedComponentRepository.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.systemui.controls.panels
+
+class FakeSelectedComponentRepository : SelectedComponentRepository {
+
+    private var selectedComponent: SelectedComponentRepository.SelectedComponent? = null
+    private var shouldAddDefaultPanel: Boolean = true
+
+    override fun getSelectedComponent(): SelectedComponentRepository.SelectedComponent? =
+        selectedComponent
+
+    override fun setSelectedComponent(
+        selectedComponent: SelectedComponentRepository.SelectedComponent
+    ) {
+        this.selectedComponent = selectedComponent
+    }
+
+    override fun removeSelectedComponent() {
+        selectedComponent = null
+    }
+
+    override fun shouldAddDefaultComponent(): Boolean = shouldAddDefaultPanel
+
+    override fun setShouldAddDefaultComponent(shouldAdd: Boolean) {
+        shouldAddDefaultPanel = shouldAdd
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
new file mode 100644
index 0000000..0c7b9cb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/panels/SelectedComponentRepositoryTest.kt
@@ -0,0 +1,163 @@
+/*
+ * 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.controls.panels
+
+import android.content.ComponentName
+import android.content.SharedPreferences
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class SelectedComponentRepositoryTest : SysuiTestCase() {
+
+    private companion object {
+        val COMPONENT_A =
+            SelectedComponentRepository.SelectedComponent(
+                name = "a",
+                componentName = ComponentName.unflattenFromString("pkg/.cls_a"),
+                isPanel = false,
+            )
+        val COMPONENT_B =
+            SelectedComponentRepository.SelectedComponent(
+                name = "b",
+                componentName = ComponentName.unflattenFromString("pkg/.cls_b"),
+                isPanel = false,
+            )
+    }
+
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var userFileManager: UserFileManager
+
+    private val featureFlags = FakeFeatureFlags()
+    private val sharedPreferences: SharedPreferences = FakeSharedPreferences()
+
+    // under test
+    private lateinit var repository: SelectedComponentRepository
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(userFileManager.getSharedPreferences(any(), any(), any()))
+            .thenReturn(sharedPreferences)
+
+        repository = SelectedComponentRepositoryImpl(userFileManager, userTracker, featureFlags)
+    }
+
+    @Test
+    fun testUnsetIsNull() {
+        assertThat(repository.getSelectedComponent()).isNull()
+    }
+
+    @Test
+    fun testGetReturnsSet() {
+        repository.setSelectedComponent(COMPONENT_A)
+
+        assertThat(repository.getSelectedComponent()).isEqualTo(COMPONENT_A)
+    }
+
+    @Test
+    fun testSetOverrides() {
+        repository.setSelectedComponent(COMPONENT_A)
+        repository.setSelectedComponent(COMPONENT_B)
+
+        assertThat(repository.getSelectedComponent()).isEqualTo(COMPONENT_B)
+    }
+
+    @Test
+    fun testRemove() {
+        repository.setSelectedComponent(COMPONENT_A)
+
+        repository.removeSelectedComponent()
+
+        assertThat(repository.getSelectedComponent()).isNull()
+    }
+
+    @Test
+    fun testFeatureEnabled_shouldAddDefaultPanelDefaultsToTrue() {
+        featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
+
+        assertThat(repository.shouldAddDefaultComponent()).isTrue()
+    }
+
+    @Test
+    fun testFeatureDisabled_shouldAddDefaultPanelDefaultsToTrue() {
+        featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
+
+        assertThat(repository.shouldAddDefaultComponent()).isTrue()
+    }
+
+    @Test
+    fun testFeatureEnabled_shouldAddDefaultPanelChecked() {
+        featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, true)
+        repository.setShouldAddDefaultComponent(false)
+
+        assertThat(repository.shouldAddDefaultComponent()).isFalse()
+    }
+
+    @Test
+    fun testFeatureDisabled_shouldAlwaysAddDefaultPanelAlwaysTrue() {
+        featureFlags.set(Flags.APP_PANELS_REMOVE_APPS_ALLOWED, false)
+        repository.setShouldAddDefaultComponent(false)
+
+        assertThat(repository.shouldAddDefaultComponent()).isTrue()
+    }
+
+    @Test
+    fun testGetPreferredStructure_differentUserId() {
+        sharedPreferences.savePanel(COMPONENT_A)
+        whenever(
+                userFileManager.getSharedPreferences(
+                    DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+                    0,
+                    1,
+                )
+            )
+            .thenReturn(FakeSharedPreferences().also { it.savePanel(COMPONENT_B) })
+
+        val previousPreferredStructure = repository.getSelectedComponent()
+        whenever(userTracker.userId).thenReturn(1)
+        val currentPreferredStructure = repository.getSelectedComponent()
+
+        assertThat(previousPreferredStructure).isEqualTo(COMPONENT_A)
+        assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
+        assertThat(currentPreferredStructure).isEqualTo(COMPONENT_B)
+    }
+
+    private fun SharedPreferences.savePanel(panel: SelectedComponentRepository.SelectedComponent) {
+        edit()
+            .putString("controls_component", panel.componentName?.flattenToString())
+            .putString("controls_structure", panel.name)
+            .putBoolean("controls_is_panel", panel.isPanel)
+            .commit()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index 7ecaca6..9d8084d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -23,17 +23,19 @@
 import android.content.pm.ServiceInfo
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.FakeSelectedComponentRepository
 import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import java.util.Optional
 import org.junit.Before
@@ -53,16 +55,16 @@
     @Mock private lateinit var controlsController: ControlsController
     @Mock private lateinit var controlsListingController: ControlsListingController
     @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
+
+    private val preferredPanelsRepository = FakeSelectedComponentRepository()
 
     private lateinit var fakeExecutor: FakeExecutor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        context.orCreateTestableResources.addOverride(
-            R.array.config_controlsPreferredPackages,
-            arrayOf<String>()
-        )
+        whenever(authorizedPanelsRepository.getPreferredPackages()).thenReturn(setOf())
 
         fakeExecutor = FakeExecutor(FakeSystemClock())
     }
@@ -87,10 +89,8 @@
 
     @Test
     fun testPreferredPackagesNotInstalled_noNewSelection() {
-        context.orCreateTestableResources.addOverride(
-            R.array.config_controlsPreferredPackages,
-            arrayOf(TEST_PACKAGE_PANEL)
-        )
+        whenever(authorizedPanelsRepository.getPreferredPackages())
+            .thenReturn(setOf(TEST_PACKAGE_PANEL))
         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
         `when`(controlsListingController.getCurrentServices()).thenReturn(emptyList())
 
@@ -101,10 +101,8 @@
 
     @Test
     fun testPreferredPackageNotPanel_noNewSelection() {
-        context.orCreateTestableResources.addOverride(
-            R.array.config_controlsPreferredPackages,
-            arrayOf(TEST_PACKAGE_PANEL)
-        )
+        whenever(authorizedPanelsRepository.getPreferredPackages())
+            .thenReturn(setOf(TEST_PACKAGE_PANEL))
         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT, "not panel", hasPanel = false))
         `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
@@ -116,10 +114,8 @@
 
     @Test
     fun testExistingSelection_noNewSelection() {
-        context.orCreateTestableResources.addOverride(
-            R.array.config_controlsPreferredPackages,
-            arrayOf(TEST_PACKAGE_PANEL)
-        )
+        whenever(authorizedPanelsRepository.getPreferredPackages())
+            .thenReturn(setOf(TEST_PACKAGE_PANEL))
         `when`(controlsController.getPreferredSelection())
             .thenReturn(mock<SelectedItem.PanelItem>())
         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
@@ -132,10 +128,8 @@
 
     @Test
     fun testPanelAdded() {
-        context.orCreateTestableResources.addOverride(
-            R.array.config_controlsPreferredPackages,
-            arrayOf(TEST_PACKAGE_PANEL)
-        )
+        whenever(authorizedPanelsRepository.getPreferredPackages())
+            .thenReturn(setOf(TEST_PACKAGE_PANEL))
         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
         val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
         `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
@@ -147,10 +141,8 @@
 
     @Test
     fun testMultiplePreferredOnlyOnePanel_panelAdded() {
-        context.orCreateTestableResources.addOverride(
-            R.array.config_controlsPreferredPackages,
-            arrayOf("other_package", TEST_PACKAGE_PANEL)
-        )
+        whenever(authorizedPanelsRepository.getPreferredPackages())
+            .thenReturn(setOf(TEST_PACKAGE_PANEL))
         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
         val listings =
             listOf(
@@ -166,10 +158,8 @@
 
     @Test
     fun testMultiplePreferredMultiplePanels_firstPreferredAdded() {
-        context.orCreateTestableResources.addOverride(
-            R.array.config_controlsPreferredPackages,
-            arrayOf(TEST_PACKAGE_PANEL, "other_package")
-        )
+        whenever(authorizedPanelsRepository.getPreferredPackages())
+            .thenReturn(setOf(TEST_PACKAGE_PANEL))
         `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
         val listings =
             listOf(
@@ -217,6 +207,20 @@
         verify(controlsController, never()).bindComponentForPanel(any())
     }
 
+    @Test
+    fun testAlreadyAddedPanel_noNewSelection() {
+        preferredPanelsRepository.setShouldAddDefaultComponent(false)
+        whenever(authorizedPanelsRepository.getPreferredPackages())
+            .thenReturn(setOf(TEST_PACKAGE_PANEL))
+        `when`(controlsController.getPreferredSelection()).thenReturn(SelectedItem.EMPTY_SELECTION)
+        val listings = listOf(ControlsServiceInfo(TEST_COMPONENT_PANEL, "panel", hasPanel = true))
+        `when`(controlsListingController.getCurrentServices()).thenReturn(listings)
+
+        createStartable(enabled = true).start()
+
+        verify(controlsController, never()).setPreferredSelection(any())
+    }
+
     private fun createStartable(enabled: Boolean): ControlsStartable {
         val component: ControlsComponent =
             mock() {
@@ -230,7 +234,13 @@
                     `when`(getControlsListingController()).thenReturn(Optional.empty())
                 }
             }
-        return ControlsStartable(context.resources, fakeExecutor, component, userTracker)
+        return ControlsStartable(
+            fakeExecutor,
+            component,
+            userTracker,
+            authorizedPanelsRepository,
+            preferredPanelsRepository,
+        )
     }
 
     private fun ControlsServiceInfo(
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 23faa99..3f61bf7 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
@@ -42,16 +42,14 @@
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.management.ControlsProviderSelectorActivity
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
+import com.android.systemui.controls.panels.FakeSelectedComponentRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
 import com.android.systemui.controls.settings.FakeControlsSettingsRepository
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.plugins.ActivityStarter
-import com.android.systemui.settings.UserFileManager
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.shade.ShadeController
-import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
-import com.android.systemui.util.FakeSharedPreferences
 import com.android.systemui.util.FakeSystemUIDialogController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -64,20 +62,18 @@
 import com.android.wm.shell.TaskView
 import com.android.wm.shell.TaskViewFactory
 import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.function.Consumer
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyString
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
-import java.util.Optional
-import java.util.function.Consumer
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -87,11 +83,9 @@
     @Mock lateinit var controlsListingController: ControlsListingController
     @Mock lateinit var controlActionCoordinator: ControlActionCoordinator
     @Mock lateinit var activityStarter: ActivityStarter
-    @Mock lateinit var shadeController: ShadeController
     @Mock lateinit var iconCache: CustomIconCache
     @Mock lateinit var controlsMetricsLogger: ControlsMetricsLogger
     @Mock lateinit var keyguardStateController: KeyguardStateController
-    @Mock lateinit var userFileManager: UserFileManager
     @Mock lateinit var userTracker: UserTracker
     @Mock lateinit var taskViewFactory: TaskViewFactory
     @Mock lateinit var dumpManager: DumpManager
@@ -99,7 +93,7 @@
     @Mock lateinit var featureFlags: FeatureFlags
     @Mock lateinit var packageManager: PackageManager
 
-    private val sharedPreferences = FakeSharedPreferences()
+    private val preferredPanelRepository = FakeSelectedComponentRepository()
     private val fakeDialogController = FakeSystemUIDialogController()
     private val uiExecutor = FakeExecutor(FakeSystemClock())
     private val bgExecutor = FakeExecutor(FakeSystemClock())
@@ -138,94 +132,30 @@
                 iconCache,
                 controlsMetricsLogger,
                 keyguardStateController,
-                userFileManager,
                 userTracker,
                 Optional.of(taskViewFactory),
                 controlsSettingsRepository,
                 authorizedPanelsRepository,
+                preferredPanelRepository,
                 featureFlags,
                 ControlsDialogsFactory { fakeDialogController.dialog },
                 dumpManager,
             )
-        `when`(
-                userFileManager.getSharedPreferences(
-                    DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
-                    0,
-                    0
-                )
-            )
-            .thenReturn(sharedPreferences)
-        `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
-            .thenReturn(sharedPreferences)
         `when`(userTracker.userId).thenReturn(0)
         `when`(userTracker.userHandle).thenReturn(UserHandle.of(0))
     }
 
     @Test
-    fun testGetPreferredStructure() {
-        val structureInfo = mock<StructureInfo>()
-        underTest.getPreferredSelectedItem(listOf(structureInfo))
-        verify(userFileManager)
-            .getSharedPreferences(
-                fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
-                mode = 0,
-                userId = 0
-            )
-    }
-
-    @Test
-    fun testGetPreferredStructure_differentUserId() {
-        val selectedItems =
-            listOf(
-                SelectedItem.StructureItem(
-                    StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
-                ),
-                SelectedItem.StructureItem(
-                    StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList())
-                ),
-            )
-        val structures = selectedItems.map { it.structure }
-        sharedPreferences
-            .edit()
-            .putString("controls_component", selectedItems[0].componentName.flattenToString())
-            .putString("controls_structure", selectedItems[0].name.toString())
-            .commit()
-
-        val differentSharedPreferences = FakeSharedPreferences()
-        differentSharedPreferences
-            .edit()
-            .putString("controls_component", selectedItems[1].componentName.flattenToString())
-            .putString("controls_structure", selectedItems[1].name.toString())
-            .commit()
-
-        val previousPreferredStructure = underTest.getPreferredSelectedItem(structures)
-
-        `when`(
-                userFileManager.getSharedPreferences(
-                    DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
-                    0,
-                    1
-                )
-            )
-            .thenReturn(differentSharedPreferences)
-        `when`(userTracker.userId).thenReturn(1)
-
-        val currentPreferredStructure = underTest.getPreferredSelectedItem(structures)
-
-        assertThat(previousPreferredStructure).isEqualTo(selectedItems[0])
-        assertThat(currentPreferredStructure).isEqualTo(selectedItems[1])
-        assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
-    }
-
-    @Test
     fun testGetPreferredPanel() {
         val panel = SelectedItem.PanelItem("App name", ComponentName("pkg", "cls"))
-        sharedPreferences
-            .edit()
-            .putString("controls_component", panel.componentName.flattenToString())
-            .putString("controls_structure", panel.appName.toString())
-            .putBoolean("controls_is_panel", true)
-            .commit()
+
+        preferredPanelRepository.setSelectedComponent(
+            SelectedComponentRepository.SelectedComponent(
+                name = panel.appName.toString(),
+                componentName = panel.componentName,
+                isPanel = true,
+            )
+        )
 
         val selected = underTest.getPreferredSelectedItem(emptyList())
 
@@ -369,11 +299,9 @@
                     StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList())
                 ),
             )
-        sharedPreferences
-            .edit()
-            .putString("controls_component", selectedItems[0].componentName.flattenToString())
-            .putString("controls_structure", selectedItems[0].name.toString())
-            .commit()
+        preferredPanelRepository.setSelectedComponent(
+            SelectedComponentRepository.SelectedComponent(selectedItems[0])
+        )
 
         assertThat(underTest.resolveActivity())
             .isEqualTo(ControlsProviderSelectorActivity::class.java)
@@ -418,12 +346,9 @@
         val componentName = ComponentName(context, "cls")
         whenever(controlsController.removeFavorites(eq(componentName))).thenReturn(true)
         val panel = SelectedItem.PanelItem("App name", componentName)
-        sharedPreferences
-            .edit()
-            .putString("controls_component", panel.componentName.flattenToString())
-            .putString("controls_structure", panel.appName.toString())
-            .putBoolean("controls_is_panel", true)
-            .commit()
+        preferredPanelRepository.setSelectedComponent(
+                SelectedComponentRepository.SelectedComponent(panel)
+        )
         underTest.show(parent, {}, context)
         underTest.startRemovingApp(componentName, "Test App")
 
@@ -432,11 +357,8 @@
         verify(controlsController).removeFavorites(eq(componentName))
         assertThat(underTest.getPreferredSelectedItem(emptyList()))
             .isEqualTo(SelectedItem.EMPTY_SELECTION)
-        with(sharedPreferences) {
-            assertThat(contains("controls_component")).isFalse()
-            assertThat(contains("controls_structure")).isFalse()
-            assertThat(contains("controls_is_panel")).isFalse()
-        }
+        assertThat(preferredPanelRepository.shouldAddDefaultComponent()).isFalse()
+        assertThat(preferredPanelRepository.getSelectedComponent()).isNull()
     }
 
     @Test
@@ -452,12 +374,9 @@
 
     private fun setUpPanel(panel: SelectedItem.PanelItem): ControlsServiceInfo {
         val activity = ComponentName(context, "activity")
-        sharedPreferences
-            .edit()
-            .putString("controls_component", panel.componentName.flattenToString())
-            .putString("controls_structure", panel.appName.toString())
-            .putBoolean("controls_is_panel", true)
-            .commit()
+        preferredPanelRepository.setSelectedComponent(
+                SelectedComponentRepository.SelectedComponent(panel)
+        )
         return ControlsServiceInfo(panel.componentName, panel.appName, activity)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
new file mode 100644
index 0000000..0e14591
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ConditionalRestarterTest.kt
@@ -0,0 +1,140 @@
+/*
+ * 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.flags
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceUntilIdle
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class ConditionalRestarterTest : SysuiTestCase() {
+    private lateinit var restarter: ConditionalRestarter
+
+    @Mock private lateinit var systemExitRestarter: SystemExitRestarter
+
+    val restartDelayMs = 0L
+    val dispatcher = StandardTestDispatcher()
+    val testScope = TestScope(dispatcher)
+
+    val conditionA = FakeCondition()
+    val conditionB = FakeCondition()
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        restarter =
+            ConditionalRestarter(
+                systemExitRestarter,
+                setOf(conditionA, conditionB),
+                restartDelayMs,
+                testScope,
+                dispatcher
+            )
+    }
+
+    @Test
+    fun restart_ImmediatelySatisfied() =
+        testScope.runTest {
+            conditionA.canRestart = true
+            conditionB.canRestart = true
+            restarter.restartSystemUI("Restart for test")
+            advanceUntilIdle()
+            verify(systemExitRestarter).restartSystemUI(any())
+        }
+
+    @Test
+    fun restart_WaitsForConditionA() =
+        testScope.runTest {
+            conditionA.canRestart = false
+            conditionB.canRestart = true
+
+            restarter.restartSystemUI("Restart for test")
+            advanceUntilIdle()
+            // No restart occurs yet.
+            verify(systemExitRestarter, never()).restartSystemUI(any())
+
+            conditionA.canRestart = true
+            conditionA.retryFn?.invoke()
+            advanceUntilIdle()
+            verify(systemExitRestarter).restartSystemUI(any())
+        }
+
+    @Test
+    fun restart_WaitsForConditionB() =
+        testScope.runTest {
+            conditionA.canRestart = true
+            conditionB.canRestart = false
+
+            restarter.restartSystemUI("Restart for test")
+            advanceUntilIdle()
+            // No restart occurs yet.
+            verify(systemExitRestarter, never()).restartSystemUI(any())
+
+            conditionB.canRestart = true
+            conditionB.retryFn?.invoke()
+            advanceUntilIdle()
+            verify(systemExitRestarter).restartSystemUI(any())
+        }
+
+    @Test
+    fun restart_WaitsForAllConditions() =
+        testScope.runTest {
+            conditionA.canRestart = true
+            conditionB.canRestart = false
+
+            restarter.restartSystemUI("Restart for test")
+            advanceUntilIdle()
+            // No restart occurs yet.
+            verify(systemExitRestarter, never()).restartSystemUI(any())
+
+            // B becomes true, but A is now false
+            conditionA.canRestart = false
+            conditionB.canRestart = true
+            conditionB.retryFn?.invoke()
+            advanceUntilIdle()
+            // No restart occurs yet.
+            verify(systemExitRestarter, never()).restartSystemUI(any())
+
+            conditionA.canRestart = true
+            conditionA.retryFn?.invoke()
+            advanceUntilIdle()
+            verify(systemExitRestarter).restartSystemUI(any())
+        }
+
+    class FakeCondition : ConditionalRestarter.Condition {
+        var retryFn: (() -> Unit)? = null
+        var canRestart = false
+
+        override fun canRestartNow(retryFn: () -> Unit): Boolean {
+            this.retryFn = retryFn
+
+            return canRestart
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
deleted file mode 100644
index 6060afe..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseRestarterTest.kt
+++ /dev/null
@@ -1,146 +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.systemui.flags
-
-import android.test.suitebuilder.annotation.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.WakefulnessLifecycle
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
-import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
-import com.android.systemui.statusbar.policy.BatteryController
-import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.time.FakeSystemClock
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.mockito.ArgumentCaptor
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
-import org.mockito.MockitoAnnotations
-
-/**
- * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
- */
-@SmallTest
-class FeatureFlagsReleaseRestarterTest : SysuiTestCase() {
-    private lateinit var restarter: FeatureFlagsReleaseRestarter
-
-    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
-    @Mock private lateinit var batteryController: BatteryController
-    @Mock private lateinit var systemExitRestarter: SystemExitRestarter
-    private val executor = FakeExecutor(FakeSystemClock())
-
-    @Before
-    fun setup() {
-        MockitoAnnotations.initMocks(this)
-        restarter =
-            FeatureFlagsReleaseRestarter(
-                wakefulnessLifecycle,
-                batteryController,
-                executor,
-                systemExitRestarter
-            )
-    }
-
-    @Test
-    fun testRestart_ScheduledWhenReady() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        whenever(batteryController.isPluggedIn).thenReturn(true)
-
-        assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI("Restart for test")
-        assertThat(executor.numPending()).isEqualTo(1)
-    }
-
-    @Test
-    fun testRestart_RestartsWhenIdle() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        whenever(batteryController.isPluggedIn).thenReturn(true)
-
-        restarter.restartSystemUI("Restart for test")
-        verify(systemExitRestarter, never()).restartSystemUI("Restart for test")
-        executor.advanceClockToLast()
-        executor.runAllReady()
-        verify(systemExitRestarter).restartSystemUI(any())
-    }
-
-    @Test
-    fun testRestart_NotScheduledWhenAwake() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
-        whenever(batteryController.isPluggedIn).thenReturn(true)
-
-        assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI("Restart for test")
-        assertThat(executor.numPending()).isEqualTo(0)
-    }
-
-    @Test
-    fun testRestart_NotScheduledWhenNotPluggedIn() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        whenever(batteryController.isPluggedIn).thenReturn(false)
-
-        assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI("Restart for test")
-        assertThat(executor.numPending()).isEqualTo(0)
-    }
-
-    @Test
-    fun testRestart_NotDoubleSheduled() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        whenever(batteryController.isPluggedIn).thenReturn(true)
-
-        assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI("Restart for test")
-        restarter.restartSystemUI("Restart for test")
-        assertThat(executor.numPending()).isEqualTo(1)
-    }
-
-    @Test
-    fun testWakefulnessLifecycle_CanRestart() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
-        whenever(batteryController.isPluggedIn).thenReturn(true)
-        assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI("Restart for test")
-
-        val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
-        verify(wakefulnessLifecycle).addObserver(captor.capture())
-
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-
-        captor.value.onFinishedGoingToSleep()
-        assertThat(executor.numPending()).isEqualTo(1)
-    }
-
-    @Test
-    fun testBatteryController_CanRestart() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        whenever(batteryController.isPluggedIn).thenReturn(false)
-        assertThat(executor.numPending()).isEqualTo(0)
-        restarter.restartSystemUI("Restart for test")
-
-        val captor =
-            ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
-        verify(batteryController).addCallback(captor.capture())
-
-        whenever(batteryController.isPluggedIn).thenReturn(true)
-
-        captor.value.onBatteryLevelChanged(0, true, true)
-        assertThat(executor.numPending()).isEqualTo(1)
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
new file mode 100644
index 0000000..647b05a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/PluggedInConditionTest.kt
@@ -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.systemui.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.BatteryController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+/**
+ * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
+ */
+@SmallTest
+class PluggedInConditionTest : SysuiTestCase() {
+    private lateinit var condition: PluggedInCondition
+
+    @Mock private lateinit var batteryController: BatteryController
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        condition = PluggedInCondition(batteryController)
+    }
+
+    @Test
+    fun testCondition_unplugged() {
+        whenever(batteryController.isPluggedIn).thenReturn(false)
+
+        assertThat(condition.canRestartNow({})).isFalse()
+    }
+
+    @Test
+    fun testCondition_pluggedIn() {
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        assertThat(condition.canRestartNow({})).isTrue()
+    }
+
+    @Test
+    fun testCondition_invokesRetry() {
+        whenever(batteryController.isPluggedIn).thenReturn(false)
+        var retried = false
+        val retryFn = { retried = true }
+
+        // No restart yet, but we do register a listener now.
+        assertThat(condition.canRestartNow(retryFn)).isFalse()
+        val captor =
+            ArgumentCaptor.forClass(BatteryController.BatteryStateChangeCallback::class.java)
+        verify(batteryController).addCallback(captor.capture())
+
+        whenever(batteryController.isPluggedIn).thenReturn(true)
+
+        captor.value.onBatteryLevelChanged(0, true, true)
+        assertThat(retried).isTrue()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
similarity index 66%
rename from packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
index 686782f..f7a773e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugRestarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ScreenIdleConditionTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 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.
@@ -20,12 +20,11 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP
 import com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE
-import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.mockito.ArgumentCaptor
 import org.mockito.Mock
-import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
@@ -34,37 +33,45 @@
  * Be careful with the {FeatureFlagsReleaseRestarter} in this test. It has a call to System.exit()!
  */
 @SmallTest
-class FeatureFlagsDebugRestarterTest : SysuiTestCase() {
-    private lateinit var restarter: FeatureFlagsDebugRestarter
+class ScreenIdleConditionTest : SysuiTestCase() {
+    private lateinit var condition: ScreenIdleCondition
 
     @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
-    @Mock private lateinit var systemExitRestarter: SystemExitRestarter
 
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
-        restarter = FeatureFlagsDebugRestarter(wakefulnessLifecycle, systemExitRestarter)
+        condition = ScreenIdleCondition(wakefulnessLifecycle)
     }
 
     @Test
-    fun testRestart_ImmediateWhenAsleep() {
-        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
-        restarter.restartSystemUI("Restart for test")
-        verify(systemExitRestarter).restartSystemUI(any())
-    }
-
-    @Test
-    fun testRestart_WaitsForSceenOff() {
+    fun testCondition_awake() {
         whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
 
-        restarter.restartSystemUI("Restart for test")
-        verify(systemExitRestarter, never()).restartSystemUI(any())
+        assertThat(condition.canRestartNow {}).isFalse()
+    }
 
+    @Test
+    fun testCondition_asleep() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
+
+        assertThat(condition.canRestartNow {}).isTrue()
+    }
+
+    @Test
+    fun testCondition_invokesRetry() {
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_AWAKE)
+        var retried = false
+        val retryFn = { retried = true }
+
+        // No restart yet, but we do register a listener now.
+        assertThat(condition.canRestartNow(retryFn)).isFalse()
         val captor = ArgumentCaptor.forClass(WakefulnessLifecycle.Observer::class.java)
         verify(wakefulnessLifecycle).addObserver(captor.capture())
 
-        captor.value.onFinishedGoingToSleep()
+        whenever(wakefulnessLifecycle.wakefulness).thenReturn(WAKEFULNESS_ASLEEP)
 
-        verify(systemExitRestarter).restartSystemUI(any())
+        captor.value.onFinishedGoingToSleep()
+        assertThat(retried).isTrue()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index cd579db..503e002 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -284,6 +284,24 @@
         }
 
     @Test
+    fun `quickAffordance - hidden when quick settings is visible`() =
+        testScope.runTest {
+            repository.setQuickSettingsVisible(true)
+            quickAccessWallet.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon = ICON,
+                )
+            )
+
+            val collectedValue =
+                collectLastValue(
+                    underTest.quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+                )
+
+            assertThat(collectedValue()).isEqualTo(KeyguardQuickAffordanceModel.Hidden)
+        }
+
+    @Test
     fun `quickAffordance - bottom start affordance hidden while dozing`() =
         testScope.runTest {
             repository.setDozing(true)
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 fe9098f..fc3a638 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
@@ -967,6 +967,92 @@
             coroutineContext.cancelChildren()
         }
 
+    @Test
+    fun `OCCLUDED to GONE`() =
+        testScope.runTest {
+            // GIVEN a device on lockscreen
+            keyguardRepository.setKeyguardShowing(true)
+            runCurrent()
+
+            // GIVEN a prior transition has run to OCCLUDED
+            runner.startTransition(
+                testScope,
+                TransitionInfo(
+                    ownerName = "",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    animator =
+                        ValueAnimator().apply {
+                            duration = 10
+                            interpolator = Interpolators.LINEAR
+                        },
+                )
+            )
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+            reset(mockTransitionRepository)
+
+            // WHEN keyguard goes away
+            keyguardRepository.setKeyguardShowing(false)
+            // AND occlusion ends
+            keyguardRepository.setKeyguardOccluded(false)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to GONE should occur
+            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+            assertThat(info.to).isEqualTo(KeyguardState.GONE)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
+    @Test
+    fun `OCCLUDED to LOCKSCREEN`() =
+        testScope.runTest {
+            // GIVEN a device on lockscreen
+            keyguardRepository.setKeyguardShowing(true)
+            runCurrent()
+
+            // GIVEN a prior transition has run to OCCLUDED
+            runner.startTransition(
+                testScope,
+                TransitionInfo(
+                    ownerName = "",
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.OCCLUDED,
+                    animator =
+                        ValueAnimator().apply {
+                            duration = 10
+                            interpolator = Interpolators.LINEAR
+                        },
+                )
+            )
+            keyguardRepository.setKeyguardOccluded(true)
+            runCurrent()
+            reset(mockTransitionRepository)
+
+            // WHEN occlusion ends
+            keyguardRepository.setKeyguardOccluded(false)
+            runCurrent()
+
+            val info =
+                withArgCaptor<TransitionInfo> {
+                    verify(mockTransitionRepository).startTransition(capture(), anyBoolean())
+                }
+            // THEN a transition to LOCKSCREEN should occur
+            assertThat(info.ownerName).isEqualTo("FromOccludedTransitionInteractor")
+            assertThat(info.from).isEqualTo(KeyguardState.OCCLUDED)
+            assertThat(info.to).isEqualTo(KeyguardState.LOCKSCREEN)
+            assertThat(info.animator).isNotNull()
+
+            coroutineContext.cancelChildren()
+        }
+
     private fun startingToWake() =
         WakefulnessModel(
             WakefulnessState.STARTING_TO_WAKE,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 2a91799..746f668 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -21,7 +21,9 @@
 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.PrimaryBouncerInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -44,21 +46,86 @@
     private lateinit var underTest: PrimaryBouncerToGoneTransitionViewModel
     private lateinit var repository: FakeKeyguardTransitionRepository
     @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         repository = FakeKeyguardTransitionRepository()
         val interactor = KeyguardTransitionInteractor(repository)
-        underTest = PrimaryBouncerToGoneTransitionViewModel(interactor, statusBarStateController)
+        underTest =
+            PrimaryBouncerToGoneTransitionViewModel(
+                interactor,
+                statusBarStateController,
+                primaryBouncerInteractor
+            )
+
+        whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
+        whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
     }
 
     @Test
+    fun bouncerAlpha() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
+
+            val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.6f))
+
+            assertThat(values.size).isEqualTo(3)
+            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
+
+            job.cancel()
+        }
+
+    @Test
+    fun bouncerAlpha_runDimissFromKeyguard() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<Float>()
+
+            val job = underTest.bouncerAlpha.onEach { values.add(it) }.launchIn(this)
+
+            whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.6f))
+
+            assertThat(values.size).isEqualTo(3)
+            values.forEach { assertThat(it).isEqualTo(0f) }
+
+            job.cancel()
+        }
+
+    @Test
+    fun scrimAlpha_runDimissFromKeyguard() =
+        runTest(UnconfinedTestDispatcher()) {
+            val values = mutableListOf<ScrimAlpha>()
+
+            val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
+
+            whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(true)
+
+            repository.sendTransitionStep(step(0f, TransitionState.STARTED))
+            repository.sendTransitionStep(step(0.3f))
+            repository.sendTransitionStep(step(0.6f))
+            repository.sendTransitionStep(step(1f))
+
+            assertThat(values.size).isEqualTo(4)
+            values.forEach { assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f)) }
+
+            job.cancel()
+        }
+
+    @Test
     fun scrimBehindAlpha_leaveShadeOpen() =
         runTest(UnconfinedTestDispatcher()) {
-            val values = mutableListOf<Float>()
+            val values = mutableListOf<ScrimAlpha>()
 
-            val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this)
+            val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
 
             whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(true)
 
@@ -68,7 +135,9 @@
             repository.sendTransitionStep(step(1f))
 
             assertThat(values.size).isEqualTo(4)
-            values.forEach { assertThat(it).isEqualTo(1f) }
+            values.forEach {
+                assertThat(it).isEqualTo(ScrimAlpha(notificationsAlpha = 1f, behindAlpha = 1f))
+            }
 
             job.cancel()
         }
@@ -76,9 +145,9 @@
     @Test
     fun scrimBehindAlpha_doNotLeaveShadeOpen() =
         runTest(UnconfinedTestDispatcher()) {
-            val values = mutableListOf<Float>()
+            val values = mutableListOf<ScrimAlpha>()
 
-            val job = underTest.scrimBehindAlpha.onEach { values.add(it) }.launchIn(this)
+            val job = underTest.scrimAlpha.onEach { values.add(it) }.launchIn(this)
 
             whenever(statusBarStateController.leaveOpenOnKeyguardHide()).thenReturn(false)
 
@@ -88,8 +157,10 @@
             repository.sendTransitionStep(step(1f))
 
             assertThat(values.size).isEqualTo(4)
-            values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
-            assertThat(values[3]).isEqualTo(0f)
+            values.forEach { assertThat(it.notificationsAlpha).isEqualTo(0f) }
+            values.forEach { assertThat(it.frontAlpha).isEqualTo(0f) }
+            values.forEach { assertThat(it.behindAlpha).isIn(Range.closed(0f, 1f)) }
+            assertThat(values[3].behindAlpha).isEqualTo(0f)
 
             job.cancel()
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
index ab0669a..d428db7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/pipeline/MediaDataManagerTest.kt
@@ -2031,7 +2031,7 @@
     }
 
     @Test
-    fun testRetain_sessionPlayer_destroyedWhileActive_fullyRemoved() {
+    fun testRetain_sessionPlayer_destroyedWhileActive_noResume_fullyRemoved() {
         whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
         addPlaybackStateAction()
@@ -2051,6 +2051,40 @@
     }
 
     @Test
+    fun testRetain_sessionPlayer_canResume_destroyedWhileActive_setToResume() {
+        whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
+        whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
+        addPlaybackStateAction()
+
+        // When a media control using session actions and that does allow resumption is added,
+        addNotificationAndLoad()
+        val dataResumable = mediaDataCaptor.value.copy(resumeAction = Runnable {})
+        mediaDataManager.onMediaDataLoaded(KEY, null, dataResumable)
+
+        // And then the session is destroyed without timing out first
+        sessionCallbackCaptor.value.invoke(KEY)
+
+        // It is converted to a resume player
+        verify(listener)
+            .onMediaDataLoaded(
+                eq(PACKAGE_NAME),
+                eq(KEY),
+                capture(mediaDataCaptor),
+                eq(true),
+                eq(0),
+                eq(false)
+            )
+        assertThat(mediaDataCaptor.value.resumption).isTrue()
+        assertThat(mediaDataCaptor.value.active).isFalse()
+        verify(logger)
+            .logActiveConvertedToResume(
+                anyInt(),
+                eq(PACKAGE_NAME),
+                eq(mediaDataCaptor.value.instanceId)
+            )
+    }
+
+    @Test
     fun testSessionDestroyed_noNotificationKey_stillRemoved() {
         whenever(mediaFlags.isRetainingPlayersEnabled()).thenReturn(true)
         whenever(mediaFlags.areMediaSessionActionsEnabled(any(), any())).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
index 4dfa626..9ab7289 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/resume/MediaResumeListenerTest.kt
@@ -313,6 +313,25 @@
     }
 
     @Test
+    fun testOnLoadTwice_onlyChecksOnce() {
+        // When data is first loaded,
+        setUpMbsWithValidResolveInfo()
+        resumeListener.onMediaDataLoaded(KEY, null, data)
+
+        // We notify the manager to set a null action
+        verify(mediaDataManager).setResumeAction(KEY, null)
+
+        // If we then get another update from the app before the first check completes
+        assertThat(executor.numPending()).isEqualTo(1)
+        var dataWithCheck = data.copy(hasCheckedForResume = true)
+        resumeListener.onMediaDataLoaded(KEY, null, dataWithCheck)
+
+        // We do not try to start another check
+        assertThat(executor.numPending()).isEqualTo(1)
+        verify(mediaDataManager).setResumeAction(KEY, null)
+    }
+
+    @Test
     fun testOnUserUnlock_loadsTracks() {
         // Set up mock service to successfully find valid media
         val description = MediaDescription.Builder().setTitle(TITLE).build()
@@ -392,7 +411,7 @@
                 assertThat(result.size).isEqualTo(3)
                 assertThat(result[2].toLong()).isEqualTo(currentTime)
             }
-        verify(sharedPrefsEditor, times(1)).apply()
+        verify(sharedPrefsEditor).apply()
     }
 
     @Test
@@ -432,8 +451,8 @@
         resumeListener.userUnlockReceiver.onReceive(mockContext, intent)
 
         // We add its resume controls
-        verify(resumeBrowser, times(1)).findRecentMedia()
-        verify(mediaDataManager, times(1))
+        verify(resumeBrowser).findRecentMedia()
+        verify(mediaDataManager)
             .addResumptionControls(anyInt(), any(), any(), any(), any(), any(), eq(PACKAGE_NAME))
     }
 
@@ -516,7 +535,7 @@
                 assertThat(result.size).isEqualTo(3)
                 assertThat(result[2].toLong()).isEqualTo(currentTime)
             }
-        verify(sharedPrefsEditor, times(1)).apply()
+        verify(sharedPrefsEditor).apply()
     }
 
     @Test
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 4efc30f..608d809 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt
@@ -454,7 +454,7 @@
         val intentCaptor = argumentCaptor<Intent>()
         verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
         intentCaptor.value.let { intent ->
-            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+            assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
             assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
             assertThat(intent.flags).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
             assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
@@ -481,7 +481,7 @@
         val intentCaptor = argumentCaptor<Intent>()
         verify(bubbles).showOrHideAppBubble(capture(intentCaptor))
         intentCaptor.value.let { intent ->
-            assertThat(intent.action).isEqualTo(NoteTaskController.ACTION_CREATE_NOTE)
+            assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE)
             assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME)
             assertThat(intent.flags).isEqualTo(FLAG_ACTIVITY_NEW_TASK)
             assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 89606bf..0ab0e2b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -52,6 +52,8 @@
 import com.android.systemui.SysuiBaseFragmentTest;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.ui.MediaHost;
 import com.android.systemui.qs.customize.QSCustomizerController;
 import com.android.systemui.qs.dagger.QSFragmentComponent;
@@ -60,6 +62,7 @@
 import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.settings.FakeDisplayTracker;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -103,6 +106,8 @@
     @Mock private QSSquishinessController mSquishinessController;
     @Mock private FooterActionsViewModel mFooterActionsViewModel;
     @Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
+    @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+    @Mock private FeatureFlags mFeatureFlags;
     private View mQsFragmentView;
 
     public QSFragmentTest() {
@@ -148,8 +153,9 @@
     }
 
     @Test
-    public void transitionToFullShade_setsAlphaUsingShadeInterpolator() {
+    public void transitionToFullShade_smallScreen_alphaAlways1() {
         QSFragment fragment = resumeAndGetFragment();
+        setIsSmallScreen();
         setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
         boolean isTransitioningToFullShade = true;
         float transitionProgress = 0.5f;
@@ -158,6 +164,43 @@
         fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
                 squishinessFraction);
 
+        assertThat(mQsFragmentView.getAlpha()).isEqualTo(1f);
+    }
+
+    @Test
+    public void transitionToFullShade_largeScreen_flagEnabled_alphaLargeScreenShadeInterpolator() {
+        when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+                .thenReturn(true);
+        QSFragment fragment = resumeAndGetFragment();
+        setIsLargeScreen();
+        setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
+        boolean isTransitioningToFullShade = true;
+        float transitionProgress = 0.5f;
+        float squishinessFraction = 0.5f;
+        when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
+
+        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+                squishinessFraction);
+
+        assertThat(mQsFragmentView.getAlpha())
+                .isEqualTo(123f);
+    }
+
+    @Test
+    public void transitionToFullShade_largeScreen_flagDisabled_alphaStandardInterpolator() {
+        when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+                .thenReturn(false);
+        QSFragment fragment = resumeAndGetFragment();
+        setIsLargeScreen();
+        setStatusBarCurrentAndUpcomingState(StatusBarState.SHADE);
+        boolean isTransitioningToFullShade = true;
+        float transitionProgress = 0.5f;
+        float squishinessFraction = 0.5f;
+        when(mLargeScreenShadeInterpolator.getQsAlpha(transitionProgress)).thenReturn(123f);
+
+        fragment.setTransitionToFullShadeProgress(isTransitioningToFullShade, transitionProgress,
+                squishinessFraction);
+
         assertThat(mQsFragmentView.getAlpha())
                 .isEqualTo(ShadeInterpolation.getContentAlpha(transitionProgress));
     }
@@ -514,7 +557,9 @@
                 mock(DumpManager.class),
                 mock(QSLogger.class),
                 mock(FooterActionsController.class),
-                mFooterActionsViewModelFactory);
+                mFooterActionsViewModelFactory,
+                mLargeScreenShadeInterpolator,
+                mFeatureFlags);
     }
 
     private void setUpOther() {
@@ -622,4 +667,12 @@
             return null;
         }).when(view).getLocationOnScreen(any(int[].class));
     }
+
+    private void setIsLargeScreen() {
+        getFragment().setIsNotificationPanelFullWidth(false);
+    }
+
+    private void setIsSmallScreen() {
+        getFragment().setIsNotificationPanelFullWidth(true);
+    }
 }
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 4f469f7..2dfb6e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -22,8 +22,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -35,6 +33,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import android.annotation.IdRes;
 import android.content.ContentResolver;
 import android.content.res.Configuration;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt
new file mode 100644
index 0000000..8309342
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LargeScreenShadeInterpolatorImplTest.kt
@@ -0,0 +1,144 @@
+package com.android.systemui.shade.transition
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.google.common.truth.Expect
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LargeScreenShadeInterpolatorImplTest : SysuiTestCase() {
+    @get:Rule val expect: Expect = Expect.create()
+
+    private val portraitShadeInterpolator = LargeScreenPortraitShadeInterpolator()
+    private val splitShadeInterpolator = SplitShadeInterpolator()
+    private val configurationController = FakeConfigurationController()
+    private val impl =
+        LargeScreenShadeInterpolatorImpl(
+            configurationController,
+            context,
+            splitShadeInterpolator,
+            portraitShadeInterpolator
+        )
+
+    @Test
+    fun getBehindScrimAlpha_inSplitShade_usesSplitShadeValue() {
+        setSplitShadeEnabled(true)
+
+        assertInterpolation(
+            actual = { fraction -> impl.getBehindScrimAlpha(fraction) },
+            expected = { fraction -> splitShadeInterpolator.getBehindScrimAlpha(fraction) }
+        )
+    }
+
+    @Test
+    fun getBehindScrimAlpha_inPortraitShade_usesPortraitShadeValue() {
+        setSplitShadeEnabled(false)
+
+        assertInterpolation(
+            actual = { fraction -> impl.getBehindScrimAlpha(fraction) },
+            expected = { fraction -> portraitShadeInterpolator.getBehindScrimAlpha(fraction) }
+        )
+    }
+
+    @Test
+    fun getNotificationScrimAlpha_inSplitShade_usesSplitShadeValue() {
+        setSplitShadeEnabled(true)
+
+        assertInterpolation(
+            actual = { fraction -> impl.getNotificationScrimAlpha(fraction) },
+            expected = { fraction -> splitShadeInterpolator.getNotificationScrimAlpha(fraction) }
+        )
+    }
+    @Test
+    fun getNotificationScrimAlpha_inPortraitShade_usesPortraitShadeValue() {
+        setSplitShadeEnabled(false)
+
+        assertInterpolation(
+            actual = { fraction -> impl.getNotificationScrimAlpha(fraction) },
+            expected = { fraction -> portraitShadeInterpolator.getNotificationScrimAlpha(fraction) }
+        )
+    }
+
+    @Test
+    fun getNotificationContentAlpha_inSplitShade_usesSplitShadeValue() {
+        setSplitShadeEnabled(true)
+
+        assertInterpolation(
+            actual = { fraction -> impl.getNotificationContentAlpha(fraction) },
+            expected = { fraction -> splitShadeInterpolator.getNotificationContentAlpha(fraction) }
+        )
+    }
+
+    @Test
+    fun getNotificationContentAlpha_inPortraitShade_usesPortraitShadeValue() {
+        setSplitShadeEnabled(false)
+
+        assertInterpolation(
+            actual = { fraction -> impl.getNotificationContentAlpha(fraction) },
+            expected = { fraction ->
+                portraitShadeInterpolator.getNotificationContentAlpha(fraction)
+            }
+        )
+    }
+
+    @Test
+    fun getNotificationFooterAlpha_inSplitShade_usesSplitShadeValue() {
+        setSplitShadeEnabled(true)
+
+        assertInterpolation(
+            actual = { fraction -> impl.getNotificationFooterAlpha(fraction) },
+            expected = { fraction -> splitShadeInterpolator.getNotificationFooterAlpha(fraction) }
+        )
+    }
+    @Test
+    fun getNotificationFooterAlpha_inPortraitShade_usesPortraitShadeValue() {
+        setSplitShadeEnabled(false)
+
+        assertInterpolation(
+            actual = { fraction -> impl.getNotificationFooterAlpha(fraction) },
+            expected = { fraction ->
+                portraitShadeInterpolator.getNotificationFooterAlpha(fraction)
+            }
+        )
+    }
+
+    @Test
+    fun getQsAlpha_inSplitShade_usesSplitShadeValue() {
+        setSplitShadeEnabled(true)
+
+        assertInterpolation(
+            actual = { fraction -> impl.getQsAlpha(fraction) },
+            expected = { fraction -> splitShadeInterpolator.getQsAlpha(fraction) }
+        )
+    }
+    @Test
+    fun getQsAlpha_inPortraitShade_usesPortraitShadeValue() {
+        setSplitShadeEnabled(false)
+
+        assertInterpolation(
+            actual = { fraction -> impl.getQsAlpha(fraction) },
+            expected = { fraction -> portraitShadeInterpolator.getQsAlpha(fraction) }
+        )
+    }
+
+    private fun setSplitShadeEnabled(enabled: Boolean) {
+        overrideResource(R.bool.config_use_split_notification_shade, enabled)
+        configurationController.notifyConfigurationChanged()
+    }
+
+    private fun assertInterpolation(
+        actual: (fraction: Float) -> Float,
+        expected: (fraction: Float) -> Float
+    ) {
+        for (i in 0..10) {
+            val fraction = i / 10f
+            expect.that(actual(fraction)).isEqualTo(expected(fraction))
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
new file mode 100644
index 0000000..d24bcdc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/LinearLargeScreenShadeInterpolator.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.transition
+
+class LinearLargeScreenShadeInterpolator : LargeScreenShadeInterpolator {
+    override fun getBehindScrimAlpha(fraction: Float) = fraction
+    override fun getNotificationScrimAlpha(fraction: Float) = fraction
+    override fun getNotificationContentAlpha(fraction: Float) = fraction
+    override fun getNotificationFooterAlpha(fraction: Float) = fraction
+    override fun getQsAlpha(fraction: Float) = fraction
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
index 84f8656..cbf5485 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -5,6 +5,8 @@
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.shade.STATE_CLOSED
 import com.android.systemui.shade.STATE_OPEN
 import com.android.systemui.shade.STATE_OPENING
@@ -30,6 +32,7 @@
     @Mock private lateinit var dumpManager: DumpManager
     @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
     @Mock private lateinit var headsUpManager: HeadsUpManager
+    @Mock private lateinit var featureFlags: FeatureFlags
     private val configurationController = FakeConfigurationController()
 
     private lateinit var controller: ScrimShadeTransitionController
@@ -45,7 +48,8 @@
                 scrimController,
                 context.resources,
                 statusBarStateController,
-                headsUpManager)
+                headsUpManager,
+                featureFlags)
 
         controller.onPanelStateChanged(STATE_OPENING)
     }
@@ -107,6 +111,19 @@
     }
 
     @Test
+    fun onPanelExpansionChanged_inSplitShade_flagTrue_setsFractionEqualToEventFraction() {
+        whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+                .thenReturn(true)
+        whenever(statusBarStateController.currentOrUpcomingState)
+            .thenReturn(StatusBarState.SHADE)
+        setSplitShadeEnabled(true)
+
+        controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+        verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+    }
+
+    @Test
     fun onPanelExpansionChanged_inSplitShade_onKeyguard_setsFractionEqualToEventFraction() {
         whenever(statusBarStateController.currentOrUpcomingState)
             .thenReturn(StatusBarState.KEYGUARD)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
index cc45cf88..2eca78a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -57,7 +57,18 @@
         clockView.measure(50, 50)
 
         verify(mockTextAnimator).glyphFilter = any()
-        verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, false, 350L, null, 0L, null)
+        verify(mockTextAnimator)
+            .setTextStyle(
+                weight = 300,
+                textSize = -1.0f,
+                color = 200,
+                strokeWidth = -1F,
+                animate = false,
+                duration = 350L,
+                interpolator = null,
+                delay = 0L,
+                onAnimationEnd = null
+            )
         verifyNoMoreInteractions(mockTextAnimator)
     }
 
@@ -68,8 +79,30 @@
         clockView.animateAppearOnLockscreen()
 
         verify(mockTextAnimator, times(2)).glyphFilter = any()
-        verify(mockTextAnimator).setTextStyle(100, -1.0f, 200, false, 0L, null, 0L, null)
-        verify(mockTextAnimator).setTextStyle(300, -1.0f, 200, true, 350L, null, 0L, null)
+        verify(mockTextAnimator)
+            .setTextStyle(
+                weight = 100,
+                textSize = -1.0f,
+                color = 200,
+                strokeWidth = -1F,
+                animate = false,
+                duration = 0L,
+                interpolator = null,
+                delay = 0L,
+                onAnimationEnd = null
+            )
+        verify(mockTextAnimator)
+            .setTextStyle(
+                weight = 300,
+                textSize = -1.0f,
+                color = 200,
+                strokeWidth = -1F,
+                animate = true,
+                duration = 350L,
+                interpolator = null,
+                delay = 0L,
+                onAnimationEnd = null
+            )
         verifyNoMoreInteractions(mockTextAnimator)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 110926c..1cd182b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -100,6 +100,7 @@
 
         FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags();
         fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true);
+        fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false);
         mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags);
     }
 
@@ -399,17 +400,6 @@
     }
 
     @Test
-    public void testIsBlockingHelperShowing_isCorrectlyUpdated() throws Exception {
-        ExpandableNotificationRow group = mNotificationTestHelper.createGroup();
-
-        group.setBlockingHelperShowing(true);
-        assertTrue(group.isBlockingHelperShowing());
-
-        group.setBlockingHelperShowing(false);
-        assertFalse(group.isBlockingHelperShowing());
-    }
-
-    @Test
     public void testGetNumUniqueChildren_defaultChannel() throws Exception {
         ExpandableNotificationRow groupRow = mNotificationTestHelper.createGroup();
 
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 d7ac6b4..3d8a744 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
@@ -117,7 +117,6 @@
     @Mock private NotificationPresenter mPresenter;
     @Mock private NotificationActivityStarter mNotificationActivityStarter;
     @Mock private NotificationListContainer mNotificationListContainer;
-    @Mock private NotificationInfo.CheckSaveListener mCheckSaveListener;
     @Mock private OnSettingsClickListener mOnSettingsClickListener;
     @Mock private DeviceProvisionedController mDeviceProvisionedController;
     @Mock private CentralSurfaces mCentralSurfaces;
@@ -173,7 +172,6 @@
 
         // Test doesn't support animation since the guts view is not attached.
         doNothing().when(guts).openControls(
-                eq(true) /* shouldDoCircularReveal */,
                 anyInt(),
                 anyInt(),
                 anyBoolean(),
@@ -190,7 +188,6 @@
         assertEquals(View.INVISIBLE, guts.getVisibility());
         mTestableLooper.processAllMessages();
         verify(guts).openControls(
-                eq(true),
                 anyInt(),
                 anyInt(),
                 anyBoolean(),
@@ -213,7 +210,6 @@
 
         // Test doesn't support animation since the guts view is not attached.
         doNothing().when(guts).openControls(
-                eq(true) /* shouldDoCircularReveal */,
                 anyInt(),
                 anyInt(),
                 anyBoolean(),
@@ -237,7 +233,6 @@
         assertTrue(mGutsManager.openGutsInternal(row, 0, 0, menuItem));
         mTestableLooper.processAllMessages();
         verify(guts).openControls(
-                eq(true),
                 anyInt(),
                 anyInt(),
                 anyBoolean(),
@@ -379,7 +374,6 @@
     public void testInitializeNotificationInfoView_PassesAlongProvisionedState() throws Exception {
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
-        row.setBlockingHelperShowing(false);
         modifyRanking(row.getEntry())
                 .setUserSentiment(USER_SENTIMENT_NEGATIVE)
                 .build();
@@ -414,7 +408,6 @@
     public void testInitializeNotificationInfoView_withInitialAction() throws Exception {
         NotificationInfo notificationInfoView = mock(NotificationInfo.class);
         ExpandableNotificationRow row = spy(mHelper.createRow());
-        row.setBlockingHelperShowing(true);
         modifyRanking(row.getEntry())
                 .setUserSentiment(USER_SENTIMENT_NEGATIVE)
                 .build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
index e696c87..fdfb4f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt
@@ -76,7 +76,7 @@
     fun openControls() {
         guts.gutsContent = gutsContent
 
-        guts.openControls(true, 0, 0, false, null)
+        guts.openControls(0, 0, false, null)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
index 87f4c32..09382ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/AmbientStateTest.kt
@@ -20,6 +20,8 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.util.mockito.mock
@@ -39,6 +41,8 @@
     private val sectionProvider = StackScrollAlgorithm.SectionProvider { _, _ -> false }
     private val bypassController = StackScrollAlgorithm.BypassController { false }
     private val statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
+    private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
+    private val featureFlags = mock<FeatureFlags>()
 
     private lateinit var sut: AmbientState
 
@@ -51,6 +55,8 @@
                 sectionProvider,
                 bypassController,
                 statusBarKeyguardViewManager,
+                largeScreenShadeInterpolator,
+                featureFlags
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 9d759c4..b1d3daa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -7,6 +7,9 @@
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.notification.LegacySourceType
@@ -21,7 +24,9 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
 import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
 import org.mockito.Mockito.`when` as whenever
 
 /**
@@ -32,6 +37,9 @@
 @RunWithLooper
 class NotificationShelfTest : SysuiTestCase() {
 
+    @Mock private lateinit var largeScreenShadeInterpolator: LargeScreenShadeInterpolator
+    @Mock private lateinit var flags: FeatureFlags
+
     private val shelf = NotificationShelf(
             context,
             /* attrs */ null,
@@ -50,8 +58,12 @@
 
     @Before
     fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(ambientState.largeScreenShadeInterpolator).thenReturn(largeScreenShadeInterpolator)
+        whenever(ambientState.featureFlags).thenReturn(flags)
         shelf.bind(ambientState, /* hostLayoutController */ hostLayoutController)
         shelf.layout(/* left */ 0, /* top */ 0, /* right */ 30, /* bottom */5)
+        whenever(ambientState.isSmallScreen).thenReturn(true)
     }
 
     @Test
@@ -295,7 +307,35 @@
     fun updateState_expansionChanging_shelfAlphaUpdated() {
         updateState_expansionChanging_shelfAlphaUpdated(
                 expansionFraction = 0.6f,
-                expectedAlpha = ShadeInterpolation.getContentAlpha(0.6f)
+                expectedAlpha = ShadeInterpolation.getContentAlpha(0.6f),
+        )
+    }
+
+    @Test
+    fun updateState_flagTrue_largeScreen_expansionChanging_shelfAlphaUpdated_largeScreenValue() {
+        val expansionFraction = 0.6f
+        whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(true)
+        whenever(ambientState.isSmallScreen).thenReturn(false)
+        whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+            .thenReturn(0.123f)
+
+        updateState_expansionChanging_shelfAlphaUpdated(
+            expansionFraction = expansionFraction,
+            expectedAlpha = 0.123f
+        )
+    }
+
+    @Test
+    fun updateState_flagFalse_largeScreen_expansionChanging_shelfAlphaUpdated_standardValue() {
+        val expansionFraction = 0.6f
+        whenever(flags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION)).thenReturn(false)
+        whenever(ambientState.isSmallScreen).thenReturn(false)
+        whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+            .thenReturn(0.123f)
+
+        updateState_expansionChanging_shelfAlphaUpdated(
+            expansionFraction = expansionFraction,
+            expectedAlpha = ShadeInterpolation.getContentAlpha(expansionFraction)
         )
     }
 
@@ -305,7 +345,17 @@
 
         updateState_expansionChanging_shelfAlphaUpdated(
                 expansionFraction = 0.95f,
-                expectedAlpha = aboutToShowBouncerProgress(0.95f)
+                expectedAlpha = aboutToShowBouncerProgress(0.95f),
+        )
+    }
+
+    @Test
+    fun updateState_largeScreen_expansionChangingWhileBouncerInTransit_bouncerInterpolatorUsed() {
+        whenever(ambientState.isBouncerInTransit).thenReturn(true)
+
+        updateState_expansionChanging_shelfAlphaUpdated(
+                expansionFraction = 0.95f,
+                expectedAlpha = aboutToShowBouncerProgress(0.95f),
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 89c399b..485f2be 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -52,7 +52,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeController;
-import com.android.systemui.shade.transition.ShadeTransitionController;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -136,7 +135,6 @@
     @Mock private StackStateLogger mStackLogger;
     @Mock private NotificationStackScrollLogger mLogger;
     @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
-    @Mock private ShadeTransitionController mShadeTransitionController;
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private NotificationTargetsHelper mNotificationTargetsHelper;
     @Mock private SecureSettings mSecureSettings;
@@ -184,7 +182,6 @@
                 mNotifPipelineFlags,
                 mNotifCollection,
                 mLockscreenShadeTransitionController,
-                mShadeTransitionController,
                 mUiEventLogger,
                 mRemoteInputManager,
                 mVisibilityLocationProviderDelegator,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index cbf841b..7153e59 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -68,7 +68,9 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationShelfController;
@@ -129,6 +131,8 @@
     @Mock private NotificationShelf mNotificationShelf;
     @Mock private NotificationStackSizeCalculator mNotificationStackSizeCalculator;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
+    @Mock private FeatureFlags mFeatureFlags;
 
     @Before
     @UiThreadTest
@@ -142,7 +146,10 @@
                 mDumpManager,
                 mNotificationSectionsManager,
                 mBypassController,
-                mStatusBarKeyguardViewManager));
+                mStatusBarKeyguardViewManager,
+                mLargeScreenShadeInterpolator,
+                mFeatureFlags
+        ));
 
         // Inject dependencies before initializing the layout
         mDependency.injectTestDependency(SysuiStatusBarStateController.class, mBarState);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 4d9db8c..7f20f1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -8,6 +8,9 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ShadeInterpolation.getContentAlpha
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
 import com.android.systemui.statusbar.EmptyShadeView
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.StatusBarState
@@ -15,11 +18,13 @@
 import com.android.systemui.statusbar.notification.row.ExpandableView
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.mockito.Mockito.any
 import org.mockito.Mockito.eq
@@ -30,12 +35,19 @@
 @SmallTest
 class StackScrollAlgorithmTest : SysuiTestCase() {
 
+
+    @JvmField @Rule
+    var expect: Expect = Expect.create()
+
+    private val largeScreenShadeInterpolator = mock<LargeScreenShadeInterpolator>()
+
     private val hostView = FrameLayout(context)
     private val stackScrollAlgorithm = StackScrollAlgorithm(context, hostView)
     private val notificationRow = mock<ExpandableNotificationRow>()
     private val dumpManager = mock<DumpManager>()
     private val mStatusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>()
     private val notificationShelf = mock<NotificationShelf>()
+    private val featureFlags = mock<FeatureFlags>()
     private val emptyShadeView = EmptyShadeView(context, /* attrs= */ null).apply {
         layout(/* l= */ 0, /* t= */ 0, /* r= */ 100, /* b= */ 100)
     }
@@ -44,8 +56,10 @@
             dumpManager,
             /* sectionProvider */ { _, _ -> false },
             /* bypassController */ { false },
-            mStatusBarKeyguardViewManager
-    )
+            mStatusBarKeyguardViewManager,
+            largeScreenShadeInterpolator,
+            featureFlags,
+        )
 
     private val testableResources = mContext.getOrCreateTestableResources()
 
@@ -59,6 +73,7 @@
     fun setUp() {
         whenever(notificationShelf.viewState).thenReturn(ExpandableViewState())
         whenever(notificationRow.viewState).thenReturn(ExpandableViewState())
+        ambientState.isSmallScreen = true
 
         hostView.addView(notificationRow)
     }
@@ -145,11 +160,46 @@
     }
 
     @Test
-    fun resetViewStates_expansionChangingWhileBouncerInTransit_notificationAlphaUpdated() {
+    fun resetViewStates_flagTrue_largeScreen_expansionChanging_alphaUpdated_largeScreenValue() {
+        val expansionFraction = 0.6f
+        val surfaceAlpha = 123f
+        ambientState.isSmallScreen = false
+        whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+                .thenReturn(true)
+        whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
+        whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+            .thenReturn(surfaceAlpha)
+
+        resetViewStates_expansionChanging_notificationAlphaUpdated(
+            expansionFraction = expansionFraction,
+            expectedAlpha = surfaceAlpha,
+        )
+    }
+
+    @Test
+    fun resetViewStates_flagFalse_largeScreen_expansionChanging_alphaUpdated_standardValue() {
+        val expansionFraction = 0.6f
+        val surfaceAlpha = 123f
+        ambientState.isSmallScreen = false
+        whenever(featureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+                .thenReturn(false)
+        whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(false)
+        whenever(largeScreenShadeInterpolator.getNotificationContentAlpha(expansionFraction))
+            .thenReturn(surfaceAlpha)
+
+        resetViewStates_expansionChanging_notificationAlphaUpdated(
+            expansionFraction = expansionFraction,
+            expectedAlpha = getContentAlpha(expansionFraction),
+        )
+    }
+
+    @Test
+    fun expansionChanging_largeScreen_bouncerInTransit_alphaUpdated_bouncerValues() {
+        ambientState.isSmallScreen = false
         whenever(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit).thenReturn(true)
         resetViewStates_expansionChanging_notificationAlphaUpdated(
                 expansionFraction = 0.95f,
-                expectedAlpha = aboutToShowBouncerProgress(0.95f)
+                expectedAlpha = aboutToShowBouncerProgress(0.95f),
         )
     }
 
@@ -696,7 +746,7 @@
 
     private fun resetViewStates_expansionChanging_notificationAlphaUpdated(
             expansionFraction: Float,
-            expectedAlpha: Float
+            expectedAlpha: Float,
     ) {
         ambientState.isExpansionChanging = true
         ambientState.expansionFraction = expansionFraction
@@ -704,7 +754,7 @@
 
         stackScrollAlgorithm.resetViewStates(ambientState, /* speedBumpIndex= */ 0)
 
-        assertThat(notificationRow.viewState.alpha).isEqualTo(expectedAlpha)
+        expect.that(notificationRow.viewState.alpha).isEqualTo(expectedAlpha)
     }
 }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index e1fba81..7a1270f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -24,8 +24,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -41,6 +39,8 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import android.animation.Animator;
 import android.app.AlarmManager;
 import android.graphics.Color;
@@ -59,6 +59,8 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ShadeInterpolation;
 import com.android.systemui.dock.DockManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants;
@@ -67,7 +69,8 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
 import com.android.systemui.scrim.ScrimView;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator;
+import com.android.systemui.shade.transition.LinearLargeScreenShadeInterpolator;
 import com.android.systemui.statusbar.policy.FakeConfigurationController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
@@ -104,6 +107,8 @@
 
     private final FakeConfigurationController mConfigurationController =
             new FakeConfigurationController();
+    private final LargeScreenShadeInterpolator
+            mLinearLargeScreenShadeInterpolator = new LinearLargeScreenShadeInterpolator();
 
     private ScrimController mScrimController;
     private ScrimView mScrimBehind;
@@ -128,11 +133,11 @@
     @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
     @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
     @Mock private CoroutineDispatcher mMainDispatcher;
-    @Mock private SysuiStatusBarStateController mSysuiStatusBarStateController;
 
     // TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
     //   event-dispatch-on-registration pattern caused some of these unit tests to fail.)
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock private FeatureFlags mFeatureFlags;
 
     private static class AnimatorListener implements Animator.AnimatorListener {
         private int mNumStarts;
@@ -242,20 +247,28 @@
 
         when(mKeyguardTransitionInteractor.getPrimaryBouncerToGoneTransition())
                 .thenReturn(emptyFlow());
-        when(mPrimaryBouncerToGoneTransitionViewModel.getScrimBehindAlpha())
+        when(mPrimaryBouncerToGoneTransitionViewModel.getScrimAlpha())
                 .thenReturn(emptyFlow());
 
-        mScrimController = new ScrimController(mLightBarController,
-                mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
-                new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
-                mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
+        mScrimController = new ScrimController(
+                mLightBarController,
+                mDozeParameters,
+                mAlarmManager,
+                mKeyguardStateController,
+                mDelayedWakeLockBuilder,
+                new FakeHandler(mLooper.getLooper()),
+                mKeyguardUpdateMonitor,
+                mDockManager,
+                mConfigurationController,
+                new FakeExecutor(new FakeSystemClock()),
                 mScreenOffAnimationController,
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
-                mSysuiStatusBarStateController,
-                mMainDispatcher);
+                mMainDispatcher,
+                mLinearLargeScreenShadeInterpolator,
+                mFeatureFlags);
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
@@ -653,7 +666,81 @@
     }
 
     @Test
-    public void transitionToUnlocked() {
+    public void transitionToUnlocked_clippedQs() {
+        mScrimController.setClipsQsScrim(true);
+        mScrimController.setRawPanelExpansionFraction(0f);
+        mScrimController.transitionTo(ScrimState.UNLOCKED);
+        finishAnimationsImmediately();
+
+        assertScrimTinted(Map.of(
+                mNotificationsScrim, false,
+                mScrimInFront, false,
+                mScrimBehind, true
+        ));
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
+                mScrimBehind, OPAQUE));
+
+        mScrimController.setRawPanelExpansionFraction(0.25f);
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, SEMI_TRANSPARENT,
+                mScrimBehind, OPAQUE));
+
+        mScrimController.setRawPanelExpansionFraction(0.5f);
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, OPAQUE,
+                mScrimBehind, OPAQUE));
+    }
+
+    @Test
+    public void transitionToUnlocked_nonClippedQs_flagTrue_followsLargeScreensInterpolator() {
+        when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+                .thenReturn(true);
+        mScrimController.setClipsQsScrim(false);
+        mScrimController.setRawPanelExpansionFraction(0f);
+        mScrimController.transitionTo(ScrimState.UNLOCKED);
+        finishAnimationsImmediately();
+
+        assertScrimTinted(Map.of(
+                mNotificationsScrim, false,
+                mScrimInFront, false,
+                mScrimBehind, true
+        ));
+        // The large screens interpolator used in this test is a linear one, just for tests.
+        // Assertions below are based on this assumption, and that the code uses that interpolator
+        // when on a large screen (QS not clipped).
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, TRANSPARENT,
+                mScrimBehind, TRANSPARENT));
+
+        mScrimController.setRawPanelExpansionFraction(0.5f);
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, SEMI_TRANSPARENT,
+                mScrimBehind, SEMI_TRANSPARENT));
+
+        mScrimController.setRawPanelExpansionFraction(0.99f);
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, SEMI_TRANSPARENT,
+                mScrimBehind, SEMI_TRANSPARENT));
+
+        mScrimController.setRawPanelExpansionFraction(1f);
+        assertScrimAlpha(Map.of(
+                mScrimInFront, TRANSPARENT,
+                mNotificationsScrim, OPAQUE,
+                mScrimBehind, OPAQUE));
+    }
+
+
+    @Test
+    public void transitionToUnlocked_nonClippedQs_flagFalse() {
+        when(mFeatureFlags.isEnabled(Flags.LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION))
+                .thenReturn(false);
         mScrimController.setClipsQsScrim(false);
         mScrimController.setRawPanelExpansionFraction(0f);
         mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -691,7 +778,6 @@
                 mScrimBehind, OPAQUE));
     }
 
-
     @Test
     public void scrimStateCallback() {
         mScrimController.transitionTo(ScrimState.UNLOCKED);
@@ -879,17 +965,25 @@
         // GIVEN display does NOT need blanking
         when(mDozeParameters.getDisplayNeedsBlanking()).thenReturn(false);
 
-        mScrimController = new ScrimController(mLightBarController,
-                mDozeParameters, mAlarmManager, mKeyguardStateController, mDelayedWakeLockBuilder,
-                new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
-                mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
+        mScrimController = new ScrimController(
+                mLightBarController,
+                mDozeParameters,
+                mAlarmManager,
+                mKeyguardStateController,
+                mDelayedWakeLockBuilder,
+                new FakeHandler(mLooper.getLooper()),
+                mKeyguardUpdateMonitor,
+                mDockManager,
+                mConfigurationController,
+                new FakeExecutor(new FakeSystemClock()),
                 mScreenOffAnimationController,
                 mKeyguardUnlockAnimationController,
                 mStatusBarKeyguardViewManager,
                 mPrimaryBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
-                mSysuiStatusBarStateController,
-                mMainDispatcher);
+                mMainDispatcher,
+                mLinearLargeScreenShadeInterpolator,
+                mFeatureFlags);
         mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
         mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront);
         mScrimController.setAnimatorListener(mAnimatorListener);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
index 35dea60..7c9351c8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/MobileInputLoggerTest.kt
@@ -47,14 +47,14 @@
         val expectedNetId = NET_1_ID.toString()
         val expectedCaps = NET_1_CAPS.toString()
 
-        assertThat(actualString).contains("true")
+        assertThat(actualString).contains("onDefaultCapabilitiesChanged")
         assertThat(actualString).contains(expectedNetId)
         assertThat(actualString).contains(expectedCaps)
     }
 
     @Test
     fun testLogOnLost_bufferHasNetIdOfLostNetwork() {
-        logger.logOnLost(NET_1)
+        logger.logOnLost(NET_1, isDefaultNetworkCallback = false)
 
         val stringWriter = StringWriter()
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
@@ -62,6 +62,7 @@
 
         val expectedNetId = NET_1_ID.toString()
 
+        assertThat(actualString).contains("onLost")
         assertThat(actualString).contains(expectedNetId)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
deleted file mode 100644
index 45189cf..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectionModelTest.kt
+++ /dev/null
@@ -1,106 +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.statusbar.pipeline.mobile.data.model
-
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.log.table.TableRowLogger
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION_IN
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ACTIVITY_DIRECTION_OUT
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CARRIER_NETWORK_CHANGE
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CDMA_LEVEL
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_CONNECTION_STATE
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_IS_GSM
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_RESOLVED_NETWORK_TYPE
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_ROAMING
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-
-@SmallTest
-class MobileConnectionModelTest : SysuiTestCase() {
-
-    @Test
-    fun `log diff - initial log contains all columns`() {
-        val logger = TestLogger()
-        val connection = MobileConnectionModel()
-
-        connection.logFull(logger)
-
-        assertThat(logger.changes)
-            .contains(Pair(COL_EMERGENCY, connection.isEmergencyOnly.toString()))
-        assertThat(logger.changes).contains(Pair(COL_ROAMING, connection.isRoaming.toString()))
-        assertThat(logger.changes)
-            .contains(Pair(COL_OPERATOR, connection.operatorAlphaShort.toString()))
-        assertThat(logger.changes).contains(Pair(COL_IS_GSM, connection.isGsm.toString()))
-        assertThat(logger.changes).contains(Pair(COL_CDMA_LEVEL, connection.cdmaLevel.toString()))
-        assertThat(logger.changes)
-            .contains(Pair(COL_PRIMARY_LEVEL, connection.primaryLevel.toString()))
-        assertThat(logger.changes)
-            .contains(Pair(COL_CONNECTION_STATE, connection.dataConnectionState.toString()))
-        assertThat(logger.changes)
-            .contains(
-                Pair(
-                    COL_ACTIVITY_DIRECTION_IN,
-                    connection.dataActivityDirection.hasActivityIn.toString(),
-                )
-            )
-        assertThat(logger.changes)
-            .contains(
-                Pair(
-                    COL_ACTIVITY_DIRECTION_OUT,
-                    connection.dataActivityDirection.hasActivityOut.toString(),
-                )
-            )
-        assertThat(logger.changes)
-            .contains(
-                Pair(COL_CARRIER_NETWORK_CHANGE, connection.carrierNetworkChangeActive.toString())
-            )
-        assertThat(logger.changes)
-            .contains(Pair(COL_RESOLVED_NETWORK_TYPE, connection.resolvedNetworkType.toString()))
-    }
-
-    @Test
-    fun `log diff - primary level changes - only level is logged`() {
-        val logger = TestLogger()
-        val connectionOld = MobileConnectionModel(primaryLevel = 1)
-
-        val connectionNew = MobileConnectionModel(primaryLevel = 2)
-
-        connectionNew.logDiffs(connectionOld, logger)
-
-        assertThat(logger.changes).isEqualTo(listOf(Pair(COL_PRIMARY_LEVEL, "2")))
-    }
-
-    private class TestLogger : TableRowLogger {
-        val changes = mutableListOf<Pair<String, String>>()
-
-        override fun logChange(columnName: String, value: String?) {
-            changes.add(Pair(columnName, value.toString()))
-        }
-
-        override fun logChange(columnName: String, value: Int) {
-            changes.add(Pair(columnName, value.toString()))
-        }
-
-        override fun logChange(columnName: String, value: Boolean) {
-            changes.add(Pair(columnName, value.toString()))
-        }
-    }
-}
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 53cd71f1..44fbd5b 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
@@ -17,9 +17,11 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
+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
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository.Companion.DEFAULT_NUM_LEVELS
+import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
 import kotlinx.coroutines.flow.MutableStateFlow
 
 // TODO(b/261632894): remove this in favor of the real impl or DemoMobileConnectionRepository
@@ -27,8 +29,19 @@
     override val subId: Int,
     override val tableLogBuffer: TableLogBuffer,
 ) : MobileConnectionRepository {
-    private val _connectionInfo = MutableStateFlow(MobileConnectionModel())
-    override val connectionInfo = _connectionInfo
+    override val isEmergencyOnly = MutableStateFlow(false)
+    override val isRoaming = MutableStateFlow(false)
+    override val operatorAlphaShort: MutableStateFlow<String?> = MutableStateFlow(null)
+    override val isInService = MutableStateFlow(false)
+    override val isGsm = MutableStateFlow(false)
+    override val cdmaLevel = MutableStateFlow(0)
+    override val primaryLevel = MutableStateFlow(0)
+    override val dataConnectionState = MutableStateFlow(DataConnectionState.Disconnected)
+    override val dataActivityDirection =
+        MutableStateFlow(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
+    override val carrierNetworkChangeActive = MutableStateFlow(false)
+    override val resolvedNetworkType: MutableStateFlow<ResolvedNetworkType> =
+        MutableStateFlow(ResolvedNetworkType.UnknownNetworkType)
 
     override val numberOfLevels = MutableStateFlow(DEFAULT_NUM_LEVELS)
 
@@ -40,10 +53,6 @@
     override val networkName =
         MutableStateFlow<NetworkNameModel>(NetworkNameModel.Default("default"))
 
-    fun setConnectionInfo(model: MobileConnectionModel) {
-        _connectionInfo.value = model
-    }
-
     fun setDataEnabled(enabled: Boolean) {
         _dataEnabled.value = enabled
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
index b072dee..37fac34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionParameterizedTest.kt
@@ -25,7 +25,6 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
@@ -36,8 +35,11 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
@@ -123,34 +125,49 @@
             assertConnection(underTest, networkModel)
         }
 
-    private fun assertConnection(
+    private fun TestScope.startCollection(conn: DemoMobileConnectionRepository): Job {
+        val job = launch {
+            launch { conn.cdmaLevel.collect {} }
+            launch { conn.primaryLevel.collect {} }
+            launch { conn.dataActivityDirection.collect {} }
+            launch { conn.carrierNetworkChangeActive.collect {} }
+            launch { conn.isRoaming.collect {} }
+            launch { conn.networkName.collect {} }
+            launch { conn.isEmergencyOnly.collect {} }
+            launch { conn.dataConnectionState.collect {} }
+        }
+        return job
+    }
+
+    private fun TestScope.assertConnection(
         conn: DemoMobileConnectionRepository,
         model: FakeNetworkEventModel
     ) {
+        val job = startCollection(underTest)
         when (model) {
             is FakeNetworkEventModel.Mobile -> {
-                val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
                 assertThat(conn.subId).isEqualTo(model.subId)
-                assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
-                assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
-                assertThat(connectionInfo.dataActivityDirection)
+                assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
+                assertThat(conn.primaryLevel.value).isEqualTo(model.level)
+                assertThat(conn.dataActivityDirection.value)
                     .isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
-                assertThat(connectionInfo.carrierNetworkChangeActive)
+                assertThat(conn.carrierNetworkChangeActive.value)
                     .isEqualTo(model.carrierNetworkChange)
-                assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
+                assertThat(conn.isRoaming.value).isEqualTo(model.roaming)
                 assertThat(conn.networkName.value)
                     .isEqualTo(NetworkNameModel.IntentDerived(model.name))
 
                 // TODO(b/261029387): check these once we start handling them
-                assertThat(connectionInfo.isEmergencyOnly).isFalse()
-                assertThat(connectionInfo.isGsm).isFalse()
-                assertThat(connectionInfo.dataConnectionState)
-                    .isEqualTo(DataConnectionState.Connected)
+                assertThat(conn.isEmergencyOnly.value).isFalse()
+                assertThat(conn.isGsm.value).isFalse()
+                assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
             }
             // MobileDisabled isn't combinatorial in nature, and is tested in
             // DemoMobileConnectionsRepositoryTest.kt
             else -> {}
         }
+
+        job.cancel()
     }
 
     /** Matches [FakeNetworkEventModel] */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
index f60d92b..0e45d8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/demo/DemoMobileConnectionsRepositoryTest.kt
@@ -26,7 +26,6 @@
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.log.table.TableLogBufferFactory
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
@@ -40,9 +39,11 @@
 import com.google.common.truth.Truth.assertThat
 import junit.framework.Assert
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
@@ -524,47 +525,65 @@
             job.cancel()
         }
 
-    private fun assertConnection(
+    private fun TestScope.startCollection(conn: DemoMobileConnectionRepository): Job {
+        val job = launch {
+            launch { conn.cdmaLevel.collect {} }
+            launch { conn.primaryLevel.collect {} }
+            launch { conn.dataActivityDirection.collect {} }
+            launch { conn.carrierNetworkChangeActive.collect {} }
+            launch { conn.isRoaming.collect {} }
+            launch { conn.networkName.collect {} }
+            launch { conn.isEmergencyOnly.collect {} }
+            launch { conn.dataConnectionState.collect {} }
+        }
+        return job
+    }
+
+    private fun TestScope.assertConnection(
         conn: DemoMobileConnectionRepository,
-        model: FakeNetworkEventModel
+        model: FakeNetworkEventModel,
     ) {
+        val job = startCollection(conn)
+        // Assert the fields using the `MutableStateFlow` so that we don't have to start up
+        // a collector for every field for every test
         when (model) {
             is FakeNetworkEventModel.Mobile -> {
-                val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
                 assertThat(conn.subId).isEqualTo(model.subId)
-                assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
-                assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
-                assertThat(connectionInfo.dataActivityDirection)
+                assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
+                assertThat(conn.primaryLevel.value).isEqualTo(model.level)
+                assertThat(conn.dataActivityDirection.value)
                     .isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
-                assertThat(connectionInfo.carrierNetworkChangeActive)
+                assertThat(conn.carrierNetworkChangeActive.value)
                     .isEqualTo(model.carrierNetworkChange)
-                assertThat(connectionInfo.isRoaming).isEqualTo(model.roaming)
+                assertThat(conn.isRoaming.value).isEqualTo(model.roaming)
                 assertThat(conn.networkName.value)
                     .isEqualTo(NetworkNameModel.IntentDerived(model.name))
 
                 // TODO(b/261029387) check these once we start handling them
-                assertThat(connectionInfo.isEmergencyOnly).isFalse()
-                assertThat(connectionInfo.isGsm).isFalse()
-                assertThat(connectionInfo.dataConnectionState)
-                    .isEqualTo(DataConnectionState.Connected)
+                assertThat(conn.isEmergencyOnly.value).isFalse()
+                assertThat(conn.isGsm.value).isFalse()
+                assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
             }
             else -> {}
         }
+
+        job.cancel()
     }
 
-    private fun assertCarrierMergedConnection(
+    private fun TestScope.assertCarrierMergedConnection(
         conn: DemoMobileConnectionRepository,
         model: FakeWifiEventModel.CarrierMerged,
     ) {
-        val connectionInfo: MobileConnectionModel = conn.connectionInfo.value
+        val job = startCollection(conn)
         assertThat(conn.subId).isEqualTo(model.subscriptionId)
-        assertThat(connectionInfo.cdmaLevel).isEqualTo(model.level)
-        assertThat(connectionInfo.primaryLevel).isEqualTo(model.level)
-        assertThat(connectionInfo.carrierNetworkChangeActive).isEqualTo(false)
-        assertThat(connectionInfo.isRoaming).isEqualTo(false)
-        assertThat(connectionInfo.isEmergencyOnly).isFalse()
-        assertThat(connectionInfo.isGsm).isFalse()
-        assertThat(connectionInfo.dataConnectionState).isEqualTo(DataConnectionState.Connected)
+        assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
+        assertThat(conn.primaryLevel.value).isEqualTo(model.level)
+        assertThat(conn.carrierNetworkChangeActive.value).isEqualTo(false)
+        assertThat(conn.isRoaming.value).isEqualTo(false)
+        assertThat(conn.isEmergencyOnly.value).isFalse()
+        assertThat(conn.isGsm.value).isFalse()
+        assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
+        job.cancel()
     }
 }
 
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 f0f213b..441186a 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
@@ -22,7 +22,6 @@
 import com.android.systemui.SysuiTestCase
 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.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel
@@ -75,36 +74,48 @@
     }
 
     @Test
-    fun connectionInfo_inactiveWifi_isDefault() =
+    fun inactiveWifi_isDefault() =
         testScope.runTest {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latestConnState: DataConnectionState? = null
+            var latestNetType: ResolvedNetworkType? = null
+
+            val dataJob =
+                underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this)
+            val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this)
 
             wifiRepository.setWifiNetwork(WifiNetworkModel.Inactive)
 
-            assertThat(latest).isEqualTo(MobileConnectionModel())
+            assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
+            assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
 
-            job.cancel()
+            dataJob.cancel()
+            netJob.cancel()
         }
 
     @Test
-    fun connectionInfo_activeWifi_isDefault() =
+    fun activeWifi_isDefault() =
         testScope.runTest {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latestConnState: DataConnectionState? = null
+            var latestNetType: ResolvedNetworkType? = null
+
+            val dataJob =
+                underTest.dataConnectionState.onEach { latestConnState = it }.launchIn(this)
+            val netJob = underTest.resolvedNetworkType.onEach { latestNetType = it }.launchIn(this)
 
             wifiRepository.setWifiNetwork(WifiNetworkModel.Active(networkId = NET_ID, level = 1))
 
-            assertThat(latest).isEqualTo(MobileConnectionModel())
+            assertThat(latestConnState).isEqualTo(DataConnectionState.Disconnected)
+            assertThat(latestNetType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
 
-            job.cancel()
+            dataJob.cancel()
+            netJob.cancel()
         }
 
     @Test
-    fun connectionInfo_carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() =
+    fun carrierMergedWifi_isValidAndFieldsComeFromWifiNetwork() =
         testScope.runTest {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: Int? = null
+            val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
 
             wifiRepository.setIsWifiEnabled(true)
             wifiRepository.setIsWifiDefault(true)
@@ -117,34 +128,16 @@
                 )
             )
 
-            val expected =
-                MobileConnectionModel(
-                    primaryLevel = 3,
-                    cdmaLevel = 3,
-                    dataConnectionState = DataConnectionState.Connected,
-                    dataActivityDirection =
-                        DataActivityModel(
-                            hasActivityIn = false,
-                            hasActivityOut = false,
-                        ),
-                    resolvedNetworkType = ResolvedNetworkType.CarrierMergedNetworkType,
-                    isRoaming = false,
-                    isEmergencyOnly = false,
-                    operatorAlphaShort = null,
-                    isInService = true,
-                    isGsm = false,
-                    carrierNetworkChangeActive = false,
-                )
-            assertThat(latest).isEqualTo(expected)
+            assertThat(latest).isEqualTo(3)
 
             job.cancel()
         }
 
     @Test
-    fun connectionInfo_activity_comesFromWifiActivity() =
+    fun activity_comesFromWifiActivity() =
         testScope.runTest {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: DataActivityModel? = null
+            val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
 
             wifiRepository.setIsWifiEnabled(true)
             wifiRepository.setIsWifiDefault(true)
@@ -162,8 +155,8 @@
                 )
             )
 
-            assertThat(latest!!.dataActivityDirection.hasActivityIn).isTrue()
-            assertThat(latest!!.dataActivityDirection.hasActivityOut).isFalse()
+            assertThat(latest!!.hasActivityIn).isTrue()
+            assertThat(latest!!.hasActivityOut).isFalse()
 
             wifiRepository.setWifiActivity(
                 DataActivityModel(
@@ -172,17 +165,19 @@
                 )
             )
 
-            assertThat(latest!!.dataActivityDirection.hasActivityIn).isFalse()
-            assertThat(latest!!.dataActivityDirection.hasActivityOut).isTrue()
+            assertThat(latest!!.hasActivityIn).isFalse()
+            assertThat(latest!!.hasActivityOut).isTrue()
 
             job.cancel()
         }
 
     @Test
-    fun connectionInfo_carrierMergedWifi_wrongSubId_isDefault() =
+    fun carrierMergedWifi_wrongSubId_isDefault() =
         testScope.runTest {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latestLevel: Int? = null
+            var latestType: ResolvedNetworkType? = null
+            val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
+            val typeJob = underTest.resolvedNetworkType.onEach { latestType = it }.launchIn(this)
 
             wifiRepository.setWifiNetwork(
                 WifiNetworkModel.CarrierMerged(
@@ -192,20 +187,19 @@
                 )
             )
 
-            assertThat(latest).isEqualTo(MobileConnectionModel())
-            assertThat(latest!!.primaryLevel).isNotEqualTo(3)
-            assertThat(latest!!.resolvedNetworkType)
-                .isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
+            assertThat(latestLevel).isNotEqualTo(3)
+            assertThat(latestType).isNotEqualTo(ResolvedNetworkType.CarrierMergedNetworkType)
 
-            job.cancel()
+            levelJob.cancel()
+            typeJob.cancel()
         }
 
     // This scenario likely isn't possible, but write a test for it anyway
     @Test
-    fun connectionInfo_carrierMergedButNotEnabled_isDefault() =
+    fun carrierMergedButNotEnabled_isDefault() =
         testScope.runTest {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: Int? = null
+            val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
 
             wifiRepository.setWifiNetwork(
                 WifiNetworkModel.CarrierMerged(
@@ -216,17 +210,17 @@
             )
             wifiRepository.setIsWifiEnabled(false)
 
-            assertThat(latest).isEqualTo(MobileConnectionModel())
+            assertThat(latest).isNotEqualTo(3)
 
             job.cancel()
         }
 
     // This scenario likely isn't possible, but write a test for it anyway
     @Test
-    fun connectionInfo_carrierMergedButWifiNotDefault_isDefault() =
+    fun carrierMergedButWifiNotDefault_isDefault() =
         testScope.runTest {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: Int? = null
+            val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
 
             wifiRepository.setWifiNetwork(
                 WifiNetworkModel.CarrierMerged(
@@ -237,7 +231,7 @@
             )
             wifiRepository.setIsWifiDefault(false)
 
-            assertThat(latest).isEqualTo(MobileConnectionModel())
+            assertThat(latest).isNotEqualTo(3)
 
             job.cancel()
         }
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 cd4d847..db5a7d1 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
@@ -24,13 +24,12 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.log.table.TableLogBuffer
 import com.android.systemui.log.table.TableLogBufferFactory
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_EMERGENCY
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_OPERATOR
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel.Companion.COL_PRIMARY_LEVEL
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_EMERGENCY
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_OPERATOR
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.FullMobileConnectionRepository.Companion.COL_PRIMARY_LEVEL
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
@@ -94,16 +93,16 @@
     @Test
     fun startingIsCarrierMerged_usesCarrierMergedInitially() =
         testScope.runTest {
-            val carrierMergedConnectionInfo =
-                MobileConnectionModel(
-                    operatorAlphaShort = "Carrier Merged Operator",
-                )
-            carrierMergedRepo.setConnectionInfo(carrierMergedConnectionInfo)
+            val carrierMergedOperatorName = "Carrier Merged Operator"
+            val nonCarrierMergedName = "Non-carrier-merged"
+
+            carrierMergedRepo.operatorAlphaShort.value = carrierMergedOperatorName
+            mobileRepo.operatorAlphaShort.value = nonCarrierMergedName
 
             initializeRepo(startingIsCarrierMerged = true)
 
             assertThat(underTest.activeRepo.value).isEqualTo(carrierMergedRepo)
-            assertThat(underTest.connectionInfo.value).isEqualTo(carrierMergedConnectionInfo)
+            assertThat(underTest.operatorAlphaShort.value).isEqualTo(carrierMergedOperatorName)
             verify(mobileFactory, never())
                 .build(
                     SUB_ID,
@@ -116,16 +115,16 @@
     @Test
     fun startingNotCarrierMerged_usesTypicalInitially() =
         testScope.runTest {
-            val mobileConnectionInfo =
-                MobileConnectionModel(
-                    operatorAlphaShort = "Typical Operator",
-                )
-            mobileRepo.setConnectionInfo(mobileConnectionInfo)
+            val carrierMergedOperatorName = "Carrier Merged Operator"
+            val nonCarrierMergedName = "Typical Operator"
+
+            carrierMergedRepo.operatorAlphaShort.value = carrierMergedOperatorName
+            mobileRepo.operatorAlphaShort.value = nonCarrierMergedName
 
             initializeRepo(startingIsCarrierMerged = false)
 
             assertThat(underTest.activeRepo.value).isEqualTo(mobileRepo)
-            assertThat(underTest.connectionInfo.value).isEqualTo(mobileConnectionInfo)
+            assertThat(underTest.operatorAlphaShort.value).isEqualTo(nonCarrierMergedName)
             verify(carrierMergedFactory, never()).build(SUB_ID, tableLogBuffer)
         }
 
@@ -156,39 +155,40 @@
         testScope.runTest {
             initializeRepo(startingIsCarrierMerged = false)
 
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latestName: String? = null
+            var latestLevel: Int? = null
+
+            val nameJob = underTest.operatorAlphaShort.onEach { latestName = it }.launchIn(this)
+            val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
 
             underTest.setIsCarrierMerged(true)
 
-            val info1 =
-                MobileConnectionModel(
-                    operatorAlphaShort = "Carrier Merged Operator",
-                    primaryLevel = 1,
-                )
-            carrierMergedRepo.setConnectionInfo(info1)
+            val operator1 = "Carrier Merged Operator"
+            val level1 = 1
+            carrierMergedRepo.operatorAlphaShort.value = operator1
+            carrierMergedRepo.primaryLevel.value = level1
 
-            assertThat(latest).isEqualTo(info1)
+            assertThat(latestName).isEqualTo(operator1)
+            assertThat(latestLevel).isEqualTo(level1)
 
-            val info2 =
-                MobileConnectionModel(
-                    operatorAlphaShort = "Carrier Merged Operator #2",
-                    primaryLevel = 2,
-                )
-            carrierMergedRepo.setConnectionInfo(info2)
+            val operator2 = "Carrier Merged Operator #2"
+            val level2 = 2
+            carrierMergedRepo.operatorAlphaShort.value = operator2
+            carrierMergedRepo.primaryLevel.value = level2
 
-            assertThat(latest).isEqualTo(info2)
+            assertThat(latestName).isEqualTo(operator2)
+            assertThat(latestLevel).isEqualTo(level2)
 
-            val info3 =
-                MobileConnectionModel(
-                    operatorAlphaShort = "Carrier Merged Operator #3",
-                    primaryLevel = 3,
-                )
-            carrierMergedRepo.setConnectionInfo(info3)
+            val operator3 = "Carrier Merged Operator #3"
+            val level3 = 3
+            carrierMergedRepo.operatorAlphaShort.value = operator3
+            carrierMergedRepo.primaryLevel.value = level3
 
-            assertThat(latest).isEqualTo(info3)
+            assertThat(latestName).isEqualTo(operator3)
+            assertThat(latestLevel).isEqualTo(level3)
 
-            job.cancel()
+            nameJob.cancel()
+            levelJob.cancel()
         }
 
     @Test
@@ -196,39 +196,40 @@
         testScope.runTest {
             initializeRepo(startingIsCarrierMerged = false)
 
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latestName: String? = null
+            var latestLevel: Int? = null
+
+            val nameJob = underTest.operatorAlphaShort.onEach { latestName = it }.launchIn(this)
+            val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
 
             underTest.setIsCarrierMerged(false)
 
-            val info1 =
-                MobileConnectionModel(
-                    operatorAlphaShort = "Typical Merged Operator",
-                    primaryLevel = 1,
-                )
-            mobileRepo.setConnectionInfo(info1)
+            val operator1 = "Typical Merged Operator"
+            val level1 = 1
+            mobileRepo.operatorAlphaShort.value = operator1
+            mobileRepo.primaryLevel.value = level1
 
-            assertThat(latest).isEqualTo(info1)
+            assertThat(latestName).isEqualTo(operator1)
+            assertThat(latestLevel).isEqualTo(level1)
 
-            val info2 =
-                MobileConnectionModel(
-                    operatorAlphaShort = "Typical Merged Operator #2",
-                    primaryLevel = 2,
-                )
-            mobileRepo.setConnectionInfo(info2)
+            val operator2 = "Typical Merged Operator #2"
+            val level2 = 2
+            mobileRepo.operatorAlphaShort.value = operator2
+            mobileRepo.primaryLevel.value = level2
 
-            assertThat(latest).isEqualTo(info2)
+            assertThat(latestName).isEqualTo(operator2)
+            assertThat(latestLevel).isEqualTo(level2)
 
-            val info3 =
-                MobileConnectionModel(
-                    operatorAlphaShort = "Typical Merged Operator #3",
-                    primaryLevel = 3,
-                )
-            mobileRepo.setConnectionInfo(info3)
+            val operator3 = "Typical Merged Operator #3"
+            val level3 = 3
+            mobileRepo.operatorAlphaShort.value = operator3
+            mobileRepo.primaryLevel.value = level3
 
-            assertThat(latest).isEqualTo(info3)
+            assertThat(latestName).isEqualTo(operator3)
+            assertThat(latestLevel).isEqualTo(level3)
 
-            job.cancel()
+            nameJob.cancel()
+            levelJob.cancel()
         }
 
     @Test
@@ -236,57 +237,58 @@
         testScope.runTest {
             initializeRepo(startingIsCarrierMerged = false)
 
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latestName: String? = null
+            var latestLevel: Int? = null
 
-            val carrierMergedInfo =
-                MobileConnectionModel(
-                    operatorAlphaShort = "Carrier Merged Operator",
-                    primaryLevel = 4,
-                )
-            carrierMergedRepo.setConnectionInfo(carrierMergedInfo)
+            val nameJob = underTest.operatorAlphaShort.onEach { latestName = it }.launchIn(this)
+            val levelJob = underTest.primaryLevel.onEach { latestLevel = it }.launchIn(this)
 
-            val mobileInfo =
-                MobileConnectionModel(
-                    operatorAlphaShort = "Typical Operator",
-                    primaryLevel = 2,
-                )
-            mobileRepo.setConnectionInfo(mobileInfo)
+            val carrierMergedOperator = "Carrier Merged Operator"
+            val carrierMergedLevel = 4
+            carrierMergedRepo.operatorAlphaShort.value = carrierMergedOperator
+            carrierMergedRepo.primaryLevel.value = carrierMergedLevel
+
+            val mobileName = "Typical Operator"
+            val mobileLevel = 2
+            mobileRepo.operatorAlphaShort.value = mobileName
+            mobileRepo.primaryLevel.value = mobileLevel
 
             // Start with the mobile info
-            assertThat(latest).isEqualTo(mobileInfo)
+            assertThat(latestName).isEqualTo(mobileName)
+            assertThat(latestLevel).isEqualTo(mobileLevel)
 
             // WHEN isCarrierMerged is set to true
             underTest.setIsCarrierMerged(true)
 
             // THEN the carrier merged info is used
-            assertThat(latest).isEqualTo(carrierMergedInfo)
+            assertThat(latestName).isEqualTo(carrierMergedOperator)
+            assertThat(latestLevel).isEqualTo(carrierMergedLevel)
 
-            val newCarrierMergedInfo =
-                MobileConnectionModel(
-                    operatorAlphaShort = "New CM Operator",
-                    primaryLevel = 0,
-                )
-            carrierMergedRepo.setConnectionInfo(newCarrierMergedInfo)
+            val newCarrierMergedName = "New CM Operator"
+            val newCarrierMergedLevel = 0
+            carrierMergedRepo.operatorAlphaShort.value = newCarrierMergedName
+            carrierMergedRepo.primaryLevel.value = newCarrierMergedLevel
 
-            assertThat(latest).isEqualTo(newCarrierMergedInfo)
+            assertThat(latestName).isEqualTo(newCarrierMergedName)
+            assertThat(latestLevel).isEqualTo(newCarrierMergedLevel)
 
             // WHEN isCarrierMerged is set to false
             underTest.setIsCarrierMerged(false)
 
             // THEN the typical info is used
-            assertThat(latest).isEqualTo(mobileInfo)
+            assertThat(latestName).isEqualTo(mobileName)
+            assertThat(latestLevel).isEqualTo(mobileLevel)
 
-            val newMobileInfo =
-                MobileConnectionModel(
-                    operatorAlphaShort = "New Mobile Operator",
-                    primaryLevel = 3,
-                )
-            mobileRepo.setConnectionInfo(newMobileInfo)
+            val newMobileName = "New MobileOperator"
+            val newMobileLevel = 3
+            mobileRepo.operatorAlphaShort.value = newMobileName
+            mobileRepo.primaryLevel.value = newMobileLevel
 
-            assertThat(latest).isEqualTo(newMobileInfo)
+            assertThat(latestName).isEqualTo(newMobileName)
+            assertThat(latestLevel).isEqualTo(newMobileLevel)
 
-            job.cancel()
+            nameJob.cancel()
+            levelJob.cancel()
         }
 
     @Test
@@ -370,7 +372,8 @@
 
             initializeRepo(startingIsCarrierMerged = false)
 
-            val job = underTest.connectionInfo.launchIn(this)
+            val emergencyJob = underTest.isEmergencyOnly.launchIn(this)
+            val operatorJob = underTest.operatorAlphaShort.launchIn(this)
 
             // WHEN we set up some mobile connection info
             val serviceState = ServiceState()
@@ -394,7 +397,8 @@
             assertThat(dumpBuffer()).contains("$COL_OPERATOR${BUFFER_SEPARATOR}OpDiff")
             assertThat(dumpBuffer()).contains("$COL_EMERGENCY${BUFFER_SEPARATOR}true")
 
-            job.cancel()
+            emergencyJob.cancel()
+            operatorJob.cancel()
         }
 
     @Test
@@ -409,7 +413,7 @@
 
             initializeRepo(startingIsCarrierMerged = true)
 
-            val job = underTest.connectionInfo.launchIn(this)
+            val job = underTest.primaryLevel.launchIn(this)
 
             // WHEN we set up carrier merged info
             val networkId = 2
@@ -452,7 +456,7 @@
 
             initializeRepo(startingIsCarrierMerged = false)
 
-            val job = underTest.connectionInfo.launchIn(this)
+            val job = underTest.primaryLevel.launchIn(this)
 
             // WHEN we set up some mobile connection info
             val signalStrength = mock<SignalStrength>()
@@ -502,12 +506,7 @@
             assertThat(bufferAfterCarrierMerged).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}1")
 
             // WHEN the normal network is updated
-            val newMobileInfo =
-                MobileConnectionModel(
-                    operatorAlphaShort = "Mobile Operator 2",
-                    primaryLevel = 0,
-                )
-            mobileRepo.setConnectionInfo(newMobileInfo)
+            mobileRepo.primaryLevel.value = 0
 
             // THEN the new level is logged
             assertThat(dumpBuffer()).contains("$COL_PRIMARY_LEVEL${BUFFER_SEPARATOR}0")
@@ -529,7 +528,7 @@
             // WHEN isCarrierMerged = false
             initializeRepo(startingIsCarrierMerged = false)
 
-            val job = underTest.connectionInfo.launchIn(this)
+            val job = underTest.primaryLevel.launchIn(this)
 
             val signalStrength = mock<SignalStrength>()
             whenever(signalStrength.level).thenReturn(1)
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 bd5a4d7..f6e5959 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
@@ -50,13 +50,15 @@
 import android.telephony.TelephonyManager.EXTRA_SPN
 import android.telephony.TelephonyManager.EXTRA_SUBSCRIPTION_ID
 import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
+import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import androidx.test.filters.SmallTest
+import com.android.settingslib.mobile.MobileMappings
 import com.android.systemui.SysuiTestCase
 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
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
+import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.OverrideNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.UnknownNetworkType
@@ -135,235 +137,285 @@
     }
 
     @Test
-    fun testFlowForSubId_default() =
+    fun emergencyOnly() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
-
-            assertThat(latest).isEqualTo(MobileConnectionModel())
-
-            job.cancel()
-        }
-
-    @Test
-    fun testFlowForSubId_emergencyOnly() =
-        runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: Boolean? = null
+            val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
 
             val serviceState = ServiceState()
             serviceState.isEmergencyOnly = true
 
             getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
 
-            assertThat(latest?.isEmergencyOnly).isEqualTo(true)
+            assertThat(latest).isEqualTo(true)
 
             job.cancel()
         }
 
     @Test
-    fun testFlowForSubId_emergencyOnly_toggles() =
+    fun emergencyOnly_toggles() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: Boolean? = null
+            val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<ServiceStateListener>()
             val serviceState = ServiceState()
             serviceState.isEmergencyOnly = true
             callback.onServiceStateChanged(serviceState)
+            assertThat(latest).isTrue()
+
             serviceState.isEmergencyOnly = false
             callback.onServiceStateChanged(serviceState)
 
-            assertThat(latest?.isEmergencyOnly).isEqualTo(false)
+            assertThat(latest).isFalse()
 
             job.cancel()
         }
 
     @Test
-    fun testFlowForSubId_signalStrengths_levelsUpdate() =
+    fun cdmaLevelUpdates() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: Int? = null
+            val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
-            val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+            var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
             callback.onSignalStrengthsChanged(strength)
 
-            assertThat(latest?.isGsm).isEqualTo(true)
-            assertThat(latest?.primaryLevel).isEqualTo(1)
-            assertThat(latest?.cdmaLevel).isEqualTo(2)
+            assertThat(latest).isEqualTo(2)
+
+            // gsmLevel updates, no change to cdmaLevel
+            strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true)
+
+            assertThat(latest).isEqualTo(2)
 
             job.cancel()
         }
 
     @Test
-    fun testFlowForSubId_dataConnectionState_connected() =
+    fun gsmLevelUpdates() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: Int? = null
+            val job = underTest.primaryLevel.onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+            var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+            callback.onSignalStrengthsChanged(strength)
+
+            assertThat(latest).isEqualTo(1)
+
+            strength = signalStrength(gsmLevel = 3, cdmaLevel = 2, isGsm = true)
+            callback.onSignalStrengthsChanged(strength)
+
+            assertThat(latest).isEqualTo(3)
+
+            job.cancel()
+        }
+
+    @Test
+    fun isGsm() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isGsm.onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>()
+            var strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true)
+            callback.onSignalStrengthsChanged(strength)
+
+            assertThat(latest).isTrue()
+
+            strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = false)
+            callback.onSignalStrengthsChanged(strength)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun dataConnectionState_connected() =
+        runBlocking(IMMEDIATE) {
+            var latest: DataConnectionState? = null
+            val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
             callback.onDataConnectionStateChanged(DATA_CONNECTED, 200 /* unused */)
 
-            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connected)
+            assertThat(latest).isEqualTo(DataConnectionState.Connected)
 
             job.cancel()
         }
 
     @Test
-    fun testFlowForSubId_dataConnectionState_connecting() =
+    fun dataConnectionState_connecting() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: DataConnectionState? = null
+            val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
             callback.onDataConnectionStateChanged(DATA_CONNECTING, 200 /* unused */)
 
-            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connecting)
+            assertThat(latest).isEqualTo(DataConnectionState.Connecting)
 
             job.cancel()
         }
 
     @Test
-    fun testFlowForSubId_dataConnectionState_disconnected() =
+    fun dataConnectionState_disconnected() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: DataConnectionState? = null
+            val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
             callback.onDataConnectionStateChanged(DATA_DISCONNECTED, 200 /* unused */)
 
-            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnected)
+            assertThat(latest).isEqualTo(DataConnectionState.Disconnected)
 
             job.cancel()
         }
 
     @Test
-    fun testFlowForSubId_dataConnectionState_disconnecting() =
+    fun dataConnectionState_disconnecting() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: DataConnectionState? = null
+            val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
             callback.onDataConnectionStateChanged(DATA_DISCONNECTING, 200 /* unused */)
 
-            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnecting)
+            assertThat(latest).isEqualTo(DataConnectionState.Disconnecting)
 
             job.cancel()
         }
 
     @Test
-    fun testFlowForSubId_dataConnectionState_suspended() =
+    fun dataConnectionState_suspended() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: DataConnectionState? = null
+            val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
             callback.onDataConnectionStateChanged(DATA_SUSPENDED, 200 /* unused */)
 
-            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Suspended)
+            assertThat(latest).isEqualTo(DataConnectionState.Suspended)
 
             job.cancel()
         }
 
     @Test
-    fun testFlowForSubId_dataConnectionState_handoverInProgress() =
+    fun dataConnectionState_handoverInProgress() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: DataConnectionState? = null
+            val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
             callback.onDataConnectionStateChanged(DATA_HANDOVER_IN_PROGRESS, 200 /* unused */)
 
-            assertThat(latest?.dataConnectionState)
-                .isEqualTo(DataConnectionState.HandoverInProgress)
+            assertThat(latest).isEqualTo(DataConnectionState.HandoverInProgress)
 
             job.cancel()
         }
 
     @Test
-    fun testFlowForSubId_dataConnectionState_unknown() =
+    fun dataConnectionState_unknown() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: DataConnectionState? = null
+            val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
             callback.onDataConnectionStateChanged(DATA_UNKNOWN, 200 /* unused */)
 
-            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Unknown)
+            assertThat(latest).isEqualTo(DataConnectionState.Unknown)
 
             job.cancel()
         }
 
     @Test
-    fun testFlowForSubId_dataConnectionState_invalid() =
+    fun dataConnectionState_invalid() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: DataConnectionState? = null
+            val job = underTest.dataConnectionState.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
             callback.onDataConnectionStateChanged(45, 200 /* unused */)
 
-            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Invalid)
+            assertThat(latest).isEqualTo(DataConnectionState.Invalid)
 
             job.cancel()
         }
 
     @Test
-    fun testFlowForSubId_dataActivity() =
+    fun dataActivity() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: DataActivityModel? = null
+            val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<DataActivityListener>()
             callback.onDataActivity(DATA_ACTIVITY_INOUT)
 
-            assertThat(latest?.dataActivityDirection)
-                .isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel())
+            assertThat(latest).isEqualTo(DATA_ACTIVITY_INOUT.toMobileDataActivityModel())
 
             job.cancel()
         }
 
     @Test
-    fun testFlowForSubId_carrierNetworkChange() =
+    fun carrierNetworkChange() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: Boolean? = null
+            val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.CarrierNetworkListener>()
             callback.onCarrierNetworkChange(true)
 
-            assertThat(latest?.carrierNetworkChangeActive).isEqualTo(true)
+            assertThat(latest).isEqualTo(true)
 
             job.cancel()
         }
 
     @Test
-    fun subscriptionFlow_networkType_default() =
+    fun networkType_default() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: ResolvedNetworkType? = null
+            val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
             val expected = UnknownNetworkType
 
-            assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+            assertThat(latest).isEqualTo(expected)
 
             job.cancel()
         }
 
     @Test
-    fun subscriptionFlow_networkType_updatesUsingDefault() =
+    fun networkType_unknown_hasCorrectKey() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: ResolvedNetworkType? = null
+            val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
+
+            val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
+            val type = NETWORK_TYPE_UNKNOWN
+            val expected = UnknownNetworkType
+            val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
+            callback.onDisplayInfoChanged(ti)
+
+            assertThat(latest).isEqualTo(expected)
+            assertThat(latest!!.lookupKey).isEqualTo(MobileMappings.toIconKey(type))
+
+            job.cancel()
+        }
+
+    @Test
+    fun networkType_updatesUsingDefault() =
+        runBlocking(IMMEDIATE) {
+            var latest: ResolvedNetworkType? = null
+            val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
             val type = NETWORK_TYPE_LTE
@@ -371,16 +423,16 @@
             val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) }
             callback.onDisplayInfoChanged(ti)
 
-            assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+            assertThat(latest).isEqualTo(expected)
 
             job.cancel()
         }
 
     @Test
-    fun subscriptionFlow_networkType_updatesUsingOverride() =
+    fun networkType_updatesUsingOverride() =
         runBlocking(IMMEDIATE) {
-            var latest: MobileConnectionModel? = null
-            val job = underTest.connectionInfo.onEach { latest = it }.launchIn(this)
+            var latest: ResolvedNetworkType? = null
+            val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this)
 
             val callback = getTelephonyCallbackForType<TelephonyCallback.DisplayInfoListener>()
             val type = OVERRIDE_NETWORK_TYPE_LTE_CA
@@ -392,7 +444,7 @@
                 }
             callback.onDisplayInfoChanged(ti)
 
-            assertThat(latest?.resolvedNetworkType).isEqualTo(expected)
+            assertThat(latest).isEqualTo(expected)
 
             job.cancel()
         }
@@ -466,7 +518,7 @@
     fun `roaming - gsm - queries service state`() =
         runBlocking(IMMEDIATE) {
             var latest: Boolean? = null
-            val job = underTest.connectionInfo.onEach { latest = it.isRoaming }.launchIn(this)
+            val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
 
             val serviceState = ServiceState()
             serviceState.roaming = false
@@ -492,8 +544,7 @@
     fun `activity - updates from callback`() =
         runBlocking(IMMEDIATE) {
             var latest: DataActivityModel? = null
-            val job =
-                underTest.connectionInfo.onEach { latest = it.dataActivityDirection }.launchIn(this)
+            val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this)
 
             assertThat(latest)
                 .isEqualTo(DataActivityModel(hasActivityIn = false, hasActivityOut = false))
@@ -611,8 +662,7 @@
         runBlocking(IMMEDIATE) {
             var latest: String? = null
 
-            val job =
-                underTest.connectionInfo.onEach { latest = it.operatorAlphaShort }.launchIn(this)
+            val job = underTest.operatorAlphaShort.onEach { latest = it }.launchIn(this)
 
             val shortName = "short name"
             val serviceState = ServiceState()
@@ -633,7 +683,7 @@
     fun `connection model - isInService - not iwlan`() =
         runBlocking(IMMEDIATE) {
             var latest: Boolean? = null
-            val job = underTest.connectionInfo.onEach { latest = it.isInService }.launchIn(this)
+            val job = underTest.isInService.onEach { latest = it }.launchIn(this)
 
             val serviceState = ServiceState()
             serviceState.voiceRegState = STATE_IN_SERVICE
@@ -658,7 +708,7 @@
     fun `connection model - isInService - is iwlan - voice out of service - data in service`() =
         runBlocking(IMMEDIATE) {
             var latest: Boolean? = null
-            val job = underTest.connectionInfo.onEach { latest = it.isInService }.launchIn(this)
+            val job = underTest.isInService.onEach { latest = it }.launchIn(this)
 
             // Mock the service state here so we can make it specifically IWLAN
             val serviceState: ServiceState = mock()
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 fa072fc..1eb1056 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,7 +23,6 @@
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
-import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.CarrierMergedNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType.DefaultNetworkType
@@ -74,9 +73,7 @@
     @Test
     fun gsm_level_default_unknown() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(isGsm = true),
-            )
+            connectionRepository.isGsm.value = true
 
             var latest: Int? = null
             val job = underTest.level.onEach { latest = it }.launchIn(this)
@@ -89,13 +86,9 @@
     @Test
     fun gsm_usesGsmLevel() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    isGsm = true,
-                    primaryLevel = GSM_LEVEL,
-                    cdmaLevel = CDMA_LEVEL
-                ),
-            )
+            connectionRepository.isGsm.value = true
+            connectionRepository.primaryLevel.value = GSM_LEVEL
+            connectionRepository.cdmaLevel.value = CDMA_LEVEL
 
             var latest: Int? = null
             val job = underTest.level.onEach { latest = it }.launchIn(this)
@@ -108,13 +101,9 @@
     @Test
     fun gsm_alwaysShowCdmaTrue_stillUsesGsmLevel() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    isGsm = true,
-                    primaryLevel = GSM_LEVEL,
-                    cdmaLevel = CDMA_LEVEL,
-                ),
-            )
+            connectionRepository.isGsm.value = true
+            connectionRepository.primaryLevel.value = GSM_LEVEL
+            connectionRepository.cdmaLevel.value = CDMA_LEVEL
             mobileIconsInteractor.alwaysUseCdmaLevel.value = true
 
             var latest: Int? = null
@@ -128,9 +117,7 @@
     @Test
     fun notGsm_level_default_unknown() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(isGsm = false),
-            )
+            connectionRepository.isGsm.value = false
 
             var latest: Int? = null
             val job = underTest.level.onEach { latest = it }.launchIn(this)
@@ -142,13 +129,9 @@
     @Test
     fun notGsm_alwaysShowCdmaTrue_usesCdmaLevel() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    isGsm = false,
-                    primaryLevel = GSM_LEVEL,
-                    cdmaLevel = CDMA_LEVEL
-                ),
-            )
+            connectionRepository.isGsm.value = false
+            connectionRepository.primaryLevel.value = GSM_LEVEL
+            connectionRepository.cdmaLevel.value = CDMA_LEVEL
             mobileIconsInteractor.alwaysUseCdmaLevel.value = true
 
             var latest: Int? = null
@@ -162,13 +145,9 @@
     @Test
     fun notGsm_alwaysShowCdmaFalse_usesPrimaryLevel() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    isGsm = false,
-                    primaryLevel = GSM_LEVEL,
-                    cdmaLevel = CDMA_LEVEL,
-                ),
-            )
+            connectionRepository.isGsm.value = false
+            connectionRepository.primaryLevel.value = GSM_LEVEL
+            connectionRepository.cdmaLevel.value = CDMA_LEVEL
             mobileIconsInteractor.alwaysUseCdmaLevel.value = false
 
             var latest: Int? = null
@@ -197,11 +176,8 @@
     @Test
     fun iconGroup_three_g() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
-                ),
-            )
+            connectionRepository.resolvedNetworkType.value =
+                DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
 
             var latest: MobileIconGroup? = null
             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -214,23 +190,14 @@
     @Test
     fun iconGroup_updates_on_change() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
-                ),
-            )
+            connectionRepository.resolvedNetworkType.value =
+                DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
 
             var latest: MobileIconGroup? = null
             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
 
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    resolvedNetworkType =
-                        DefaultNetworkType(
-                            mobileMappingsProxy.toIconKey(FOUR_G),
-                        ),
-                ),
-            )
+            connectionRepository.resolvedNetworkType.value =
+                DefaultNetworkType(mobileMappingsProxy.toIconKey(FOUR_G))
             yield()
 
             assertThat(latest).isEqualTo(TelephonyIcons.FOUR_G)
@@ -241,12 +208,8 @@
     @Test
     fun iconGroup_5g_override_type() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    resolvedNetworkType =
-                        OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
-                ),
-            )
+            connectionRepository.resolvedNetworkType.value =
+                OverrideNetworkType(mobileMappingsProxy.toIconKeyOverride(FIVE_G_OVERRIDE))
 
             var latest: MobileIconGroup? = null
             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -259,12 +222,8 @@
     @Test
     fun iconGroup_default_if_no_lookup() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    resolvedNetworkType =
-                        DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN)),
-                ),
-            )
+            connectionRepository.resolvedNetworkType.value =
+                DefaultNetworkType(mobileMappingsProxy.toIconKey(NETWORK_TYPE_UNKNOWN))
 
             var latest: MobileIconGroup? = null
             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -277,11 +236,7 @@
     @Test
     fun iconGroup_carrierMerged_usesOverride() =
         runBlocking(IMMEDIATE) {
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    resolvedNetworkType = CarrierMergedNetworkType,
-                ),
-            )
+            connectionRepository.resolvedNetworkType.value = CarrierMergedNetworkType
 
             var latest: MobileIconGroup? = null
             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -295,11 +250,8 @@
     fun `icon group - checks default data`() =
         runBlocking(IMMEDIATE) {
             mobileIconsInteractor.defaultDataSubId.value = SUB_1_ID
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    resolvedNetworkType = DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
-                ),
-            )
+            connectionRepository.resolvedNetworkType.value =
+                DefaultNetworkType(mobileMappingsProxy.toIconKey(THREE_G))
 
             var latest: MobileIconGroup? = null
             val job = underTest.networkTypeIconGroup.onEach { latest = it }.launchIn(this)
@@ -380,9 +332,7 @@
             var latest: Boolean? = null
             val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
 
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(dataConnectionState = DataConnectionState.Connected)
-            )
+            connectionRepository.dataConnectionState.value = DataConnectionState.Connected
             yield()
 
             assertThat(latest).isTrue()
@@ -396,9 +346,7 @@
             var latest: Boolean? = null
             val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
 
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(dataConnectionState = DataConnectionState.Disconnected)
-            )
+            connectionRepository.dataConnectionState.value = DataConnectionState.Disconnected
 
             assertThat(latest).isFalse()
 
@@ -411,11 +359,11 @@
             var latest: Boolean? = null
             val job = underTest.isInService.onEach { latest = it }.launchIn(this)
 
-            connectionRepository.setConnectionInfo(MobileConnectionModel(isInService = true))
+            connectionRepository.isInService.value = true
 
             assertThat(latest).isTrue()
 
-            connectionRepository.setConnectionInfo(MobileConnectionModel(isInService = false))
+            connectionRepository.isInService.value = false
 
             assertThat(latest).isFalse()
 
@@ -429,22 +377,13 @@
             val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
 
             connectionRepository.cdmaRoaming.value = true
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    isGsm = true,
-                    isRoaming = false,
-                )
-            )
+            connectionRepository.isGsm.value = true
+            connectionRepository.isRoaming.value = false
             yield()
 
             assertThat(latest).isFalse()
 
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    isGsm = true,
-                    isRoaming = true,
-                )
-            )
+            connectionRepository.isRoaming.value = true
             yield()
 
             assertThat(latest).isTrue()
@@ -459,23 +398,15 @@
             val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
 
             connectionRepository.cdmaRoaming.value = false
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    isGsm = false,
-                    isRoaming = true,
-                )
-            )
+            connectionRepository.isGsm.value = false
+            connectionRepository.isRoaming.value = true
             yield()
 
             assertThat(latest).isFalse()
 
             connectionRepository.cdmaRoaming.value = true
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    isGsm = false,
-                    isRoaming = false,
-                )
-            )
+            connectionRepository.isGsm.value = false
+            connectionRepository.isRoaming.value = false
             yield()
 
             assertThat(latest).isTrue()
@@ -490,25 +421,15 @@
             val job = underTest.isRoaming.onEach { latest = it }.launchIn(this)
 
             connectionRepository.cdmaRoaming.value = true
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    isGsm = false,
-                    isRoaming = true,
-                    carrierNetworkChangeActive = true,
-                )
-            )
+            connectionRepository.isGsm.value = false
+            connectionRepository.isRoaming.value = true
+            connectionRepository.carrierNetworkChangeActive.value = true
             yield()
 
             assertThat(latest).isFalse()
 
             connectionRepository.cdmaRoaming.value = true
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(
-                    isGsm = true,
-                    isRoaming = true,
-                    carrierNetworkChangeActive = true,
-                )
-            )
+            connectionRepository.isGsm.value = true
             yield()
 
             assertThat(latest).isFalse()
@@ -526,24 +447,20 @@
 
             // Default network name, operator name is non-null, uses the operator name
             connectionRepository.networkName.value = DEFAULT_NAME
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(operatorAlphaShort = testOperatorName)
-            )
+            connectionRepository.operatorAlphaShort.value = testOperatorName
             yield()
 
             assertThat(latest).isEqualTo(NetworkNameModel.IntentDerived(testOperatorName))
 
             // Default network name, operator name is null, uses the default
-            connectionRepository.setConnectionInfo(MobileConnectionModel(operatorAlphaShort = null))
+            connectionRepository.operatorAlphaShort.value = null
             yield()
 
             assertThat(latest).isEqualTo(DEFAULT_NAME)
 
             // Derived network name, operator name non-null, uses the derived name
             connectionRepository.networkName.value = DERIVED_NAME
-            connectionRepository.setConnectionInfo(
-                MobileConnectionModel(operatorAlphaShort = testOperatorName)
-            )
+            connectionRepository.operatorAlphaShort.value = testOperatorName
             yield()
 
             assertThat(latest).isEqualTo(DERIVED_NAME)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index f9b5767..17b5e05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -33,6 +33,7 @@
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
+import android.app.UiModeManager;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.content.BroadcastReceiver;
@@ -47,7 +48,6 @@
 import android.os.UserManager;
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
-import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.VisibleForTesting;
 import androidx.test.filters.SmallTest;
@@ -116,7 +116,7 @@
     @Mock
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
-    private AccessibilityManager mAccessibilityManager;
+    private UiModeManager mUiModeManager;
     @Captor
     private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiver;
     @Captor
@@ -135,7 +135,7 @@
         MockitoAnnotations.initMocks(this);
         when(mFeatureFlags.isEnabled(Flags.MONET)).thenReturn(true);
         when(mWakefulnessLifecycle.getWakefulness()).thenReturn(WAKEFULNESS_AWAKE);
-        when(mAccessibilityManager.getUiContrast()).thenReturn(0.5f);
+        when(mUiModeManager.getContrast()).thenReturn(0.5f);
         when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
         when(mResources.getColor(eq(android.R.color.system_accent1_500), any()))
                 .thenReturn(Color.RED);
@@ -151,7 +151,7 @@
                 mBroadcastDispatcher, mBgHandler, mMainExecutor, mBgExecutor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
                 mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
-                mAccessibilityManager) {
+                mUiModeManager) {
             @VisibleForTesting
             protected boolean isNightMode() {
                 return false;
@@ -733,7 +733,7 @@
                 mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
                 mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
-                mAccessibilityManager) {
+                mUiModeManager) {
             @VisibleForTesting
             protected boolean isNightMode() {
                 return false;
@@ -773,7 +773,7 @@
                 mBroadcastDispatcher, mBgHandler, executor, executor, mThemeOverlayApplier,
                 mSecureSettings, mWallpaperManager, mUserManager, mDeviceProvisionedController,
                 mUserTracker, mDumpManager, mFeatureFlags, mResources, mWakefulnessLifecycle,
-                mAccessibilityManager) {
+                mUiModeManager) {
             @VisibleForTesting
             protected boolean isNightMode() {
                 return false;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index f0a53ae..8d74c82 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -29,6 +29,8 @@
 import android.provider.Settings
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.GuestResetOrExitSessionReceiver
 import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.R
@@ -63,6 +65,7 @@
 import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import junit.framework.Assert.assertNotNull
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.test.StandardTestDispatcher
@@ -73,6 +76,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
@@ -98,6 +102,7 @@
     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
     @Mock private lateinit var commandQueue: CommandQueue
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     private lateinit var underTest: UserInteractor
 
@@ -156,6 +161,7 @@
                         repository = telephonyRepository,
                     ),
                 broadcastDispatcher = fakeBroadcastDispatcher,
+                keyguardUpdateMonitor = keyguardUpdateMonitor,
                 backgroundDispatcher = testDispatcher,
                 activityManager = activityManager,
                 refreshUsersScheduler = refreshUsersScheduler,
@@ -180,6 +186,18 @@
     }
 
     @Test
+    fun `testKeyguardUpdateMonitor_onKeyguardGoingAway`() =
+        testScope.runTest {
+            val argumentCaptor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java)
+            verify(keyguardUpdateMonitor).registerCallback(argumentCaptor.capture())
+
+            argumentCaptor.value.onKeyguardGoingAway()
+
+            val lastValue = collectLastValue(underTest.dialogDismissRequests)
+            assertNotNull(lastValue)
+        }
+
+    @Test
     fun `onRecordSelected - user`() =
         testScope.runTest {
             val userInfos = createUserInfos(count = 3, includeGuest = false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
index bc0881c..9b74c1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/StatusBarUserChipViewModelTest.kt
@@ -25,6 +25,7 @@
 import android.os.UserManager
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.GuestResetOrExitSessionReceiver
 import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.SysuiTestCase
@@ -80,6 +81,7 @@
     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
     @Mock private lateinit var commandQueue: CommandQueue
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     private lateinit var underTest: StatusBarUserChipViewModel
 
@@ -263,6 +265,7 @@
                             repository = FakeTelephonyRepository(),
                         ),
                     broadcastDispatcher = fakeBroadcastDispatcher,
+                    keyguardUpdateMonitor = keyguardUpdateMonitor,
                     backgroundDispatcher = testDispatcher,
                     activityManager = activityManager,
                     refreshUsersScheduler = refreshUsersScheduler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index c8b0496..7780a43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -23,6 +23,7 @@
 import android.os.UserManager
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.GuestResetOrExitSessionReceiver
 import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.SysuiTestCase
@@ -81,6 +82,7 @@
     @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
     @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
     @Mock private lateinit var commandQueue: CommandQueue
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
 
     private lateinit var underTest: UserSwitcherViewModel
 
@@ -165,6 +167,7 @@
                                     repository = FakeTelephonyRepository(),
                                 ),
                             broadcastDispatcher = fakeBroadcastDispatcher,
+                            keyguardUpdateMonitor = keyguardUpdateMonitor,
                             backgroundDispatcher = testDispatcher,
                             activityManager = activityManager,
                             refreshUsersScheduler = refreshUsersScheduler,
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
index 69641e6..cbca3f0 100644
--- a/packages/overlays/Android.mk
+++ b/packages/overlays/Android.mk
@@ -31,6 +31,7 @@
 	NavigationBarModeGesturalOverlayNarrowBack \
 	NavigationBarModeGesturalOverlayWideBack \
 	NavigationBarModeGesturalOverlayExtraWideBack \
+	TransparentNavigationBarOverlay \
 	preinstalled-packages-platform-overlays.xml
 
 include $(BUILD_PHONY_PACKAGE)
diff --git a/packages/overlays/TransparentNavigationBarOverlay/Android.bp b/packages/overlays/TransparentNavigationBarOverlay/Android.bp
new file mode 100644
index 0000000..43dc82b
--- /dev/null
+++ b/packages/overlays/TransparentNavigationBarOverlay/Android.bp
@@ -0,0 +1,30 @@
+//
+//  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 {
+    // 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"],
+}
+
+runtime_resource_overlay {
+    name: "TransparentNavigationBarOverlay",
+    theme: "TransparentNavigationBar",
+    product_specific: true,
+}
diff --git a/packages/overlays/TransparentNavigationBarOverlay/AndroidManifest.xml b/packages/overlays/TransparentNavigationBarOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..d69abfa
--- /dev/null
+++ b/packages/overlays/TransparentNavigationBarOverlay/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.internal.systemui.navbar.transparent"
+        android:versionCode="1"
+        android:versionName="1.0">
+    <overlay android:targetPackage="android"
+        android:priority="1"/>
+
+    <application android:label="@string/transparent_navigation_bar_title" android:hasCode="false"/>
+</manifest>
\ No newline at end of file
diff --git a/packages/overlays/TransparentNavigationBarOverlay/res/values/config.xml b/packages/overlays/TransparentNavigationBarOverlay/res/values/config.xml
new file mode 100644
index 0000000..3b358ea
--- /dev/null
+++ b/packages/overlays/TransparentNavigationBarOverlay/res/values/config.xml
@@ -0,0 +1,23 @@
+<?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>
+    <!-- Controls whether the navigation bar background color provided by the app is transparent by
+         default. This should be controlled in developer options. -->
+    <bool name="config_navBarDefaultTransparent">true</bool>
+</resources>
\ No newline at end of file
diff --git a/packages/overlays/TransparentNavigationBarOverlay/res/values/strings.xml b/packages/overlays/TransparentNavigationBarOverlay/res/values/strings.xml
new file mode 100644
index 0000000..0e3462e
--- /dev/null
+++ b/packages/overlays/TransparentNavigationBarOverlay/res/values/strings.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/**
+ * Copyright (c) 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Name of overlay [CHAR LIMIT=64] -->
+    <string name="transparent_navigation_bar_title" translatable="false">Transparent navigation bar</string>
+</resources>
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7fba72b..6503595 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -26,11 +26,8 @@
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MAGNIFICATION_CONNECTION;
 import static android.accessibilityservice.AccessibilityTrace.FLAGS_WINDOW_MANAGER_INTERNAL;
 import static android.provider.Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED;
-import static android.provider.Settings.Secure.CONTRAST_LEVEL;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
-import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
 import static android.view.accessibility.AccessibilityManager.FlashNotificationReason;
 import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
@@ -1997,16 +1994,6 @@
         return false;
     }
 
-    private boolean readUiContrastLocked(AccessibilityUserState userState) {
-        float contrast = Settings.Secure.getFloatForUser(mContext.getContentResolver(),
-                CONTRAST_LEVEL, CONTRAST_DEFAULT_VALUE, userState.mUserId);
-        if (Math.abs(userState.getUiContrastLocked() - contrast) >= 1e-10) {
-            userState.setUiContrastLocked(contrast);
-            return true;
-        }
-        return false;
-    }
-
     /**
      * Performs {@link AccessibilityService}s delayed notification. The delay is configurable
      * and denotes the period after the last event before notifying the service.
@@ -2676,7 +2663,6 @@
         somethingChanged |= readMagnificationCapabilitiesLocked(userState);
         somethingChanged |= readMagnificationFollowTypingLocked(userState);
         somethingChanged |= readAlwaysOnMagnificationLocked(userState);
-        somethingChanged |= readUiContrastLocked(userState);
         return somethingChanged;
     }
 
@@ -3851,19 +3837,6 @@
         return mProxyManager.isProxyed(displayId);
     }
 
-    @Override public float getUiContrast() {
-        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) {
-            mTraceManager.logTrace(LOG_TAG + ".getUiContrast", FLAGS_ACCESSIBILITY_MANAGER);
-        }
-        synchronized (mLock) {
-            AccessibilityUserState userState = getCurrentUserStateLocked();
-            float contrast = userState.getUiContrastLocked();
-            if (contrast != CONTRAST_NOT_SET) return contrast;
-            readUiContrastLocked(userState);
-            return userState.getUiContrastLocked();
-        }
-    }
-
     @Override
     public boolean startFlashNotificationSequence(String opPkg,
             @FlashNotificationReason int reason, IBinder token) {
@@ -4350,9 +4323,6 @@
         private final Uri mAlwaysOnMagnificationUri = Settings.Secure.getUriFor(
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_ALWAYS_ON_ENABLED);
 
-        private final Uri mUiContrastUri = Settings.Secure.getUriFor(
-                CONTRAST_LEVEL);
-
         public AccessibilityContentObserver(Handler handler) {
             super(handler);
         }
@@ -4395,8 +4365,6 @@
                     mMagnificationFollowTypingUri, false, this, UserHandle.USER_ALL);
             contentResolver.registerContentObserver(
                     mAlwaysOnMagnificationUri, false, this, UserHandle.USER_ALL);
-            contentResolver.registerContentObserver(
-                    mUiContrastUri, false, this, UserHandle.USER_ALL);
         }
 
         @Override
@@ -4468,10 +4436,6 @@
                     readMagnificationFollowTypingLocked(userState);
                 } else if (mAlwaysOnMagnificationUri.equals(uri)) {
                     readAlwaysOnMagnificationLocked(userState);
-                } else if (mUiContrastUri.equals(uri)) {
-                    if (readUiContrastLocked(userState)) {
-                        updateUiContrastLocked(userState);
-                    }
                 }
             }
         }
@@ -4788,22 +4752,7 @@
                         userState.getFocusColorLocked());
             }));
         });
-    }
 
-    private void updateUiContrastLocked(AccessibilityUserState userState) {
-        if (userState.mUserId != mCurrentUserId) {
-            return;
-        }
-        if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_SERVICE_CLIENT)) {
-            mTraceManager.logTrace(LOG_TAG + ".updateUiContrastLocked",
-                    FLAGS_ACCESSIBILITY_SERVICE_CLIENT, "userState=" + userState);
-        }
-        float contrast = userState.getUiContrastLocked();
-        mMainHandler.post(() -> {
-            broadcastToClients(userState, ignoreRemoteException(client -> {
-                client.mCallback.setUiContrast(contrast);
-            }));
-        });
     }
 
     public AccessibilityTraceManager getTraceManager() {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index 1c9ce3c..3b169f8 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -26,8 +26,6 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_NONE;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
 import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
-import static android.view.accessibility.AccessibilityManager.CONTRAST_DEFAULT_VALUE;
-import static android.view.accessibility.AccessibilityManager.CONTRAST_NOT_SET;
 import static android.view.accessibility.AccessibilityManager.ShortcutType;
 
 import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
@@ -147,8 +145,6 @@
     private final int mFocusStrokeWidthDefaultValue;
     // The default value of the focus color.
     private final int mFocusColorDefaultValue;
-    /** The color contrast in [-1, 1] */
-    private float mUiContrast = CONTRAST_DEFAULT_VALUE;
 
     private Context mContext;
 
@@ -224,7 +220,6 @@
         mFocusColor = mFocusColorDefaultValue;
         mMagnificationFollowTypingEnabled = true;
         mAlwaysOnMagnificationEnabled = false;
-        mUiContrast = CONTRAST_NOT_SET;
     }
 
     void addServiceLocked(AccessibilityServiceConnection serviceConnection) {
@@ -1001,7 +996,6 @@
         return mFocusColor;
     }
 
-
     /**
      * Sets the stroke width and color of the focus rectangle.
      *
@@ -1027,20 +1021,4 @@
         }
         return false;
     }
-
-    /**
-     * Get the color contrast
-     * @return color contrast in [-1, 1]
-     */
-    public float getUiContrastLocked() {
-        return mUiContrast;
-    }
-
-    /**
-     * Set the color contrast
-     * @param contrast the new color contrast in [-1, 1]
-     */
-    public void setUiContrastLocked(float contrast) {
-        mUiContrast = contrast;
-    }
 }
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 61032dc..bea049c 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -849,7 +849,6 @@
         }
     }
 
-
     /**
      * Updates the last fill response when a view was entered.
      */
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 592045c..bcf50ad 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -356,6 +356,11 @@
                             public void onError() {
                                 onErrorCallback.run();
                             }
+
+                            @Override
+                            public void onInflate() {
+                                /* nothing */
+                            }
                         });
 
         if (inlineSuggestionsCallback.apply(inlineFillUi)) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 4a12e38..b54dbbf 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -276,6 +276,9 @@
     @GuardedBy("mLock")
     private DeathRecipient mClientVulture;
 
+    @GuardedBy("mLock")
+    private boolean mLoggedInlineDatasetShown;
+
     /**
      * Reference to the remote service.
      *
@@ -1981,6 +1984,22 @@
                 Session::removeFromService, this));
     }
 
+    // AutofillUiCallback
+    @Override
+    public void onShown(int uiType) {
+        synchronized (mLock) {
+            if (uiType == UI_TYPE_INLINE) {
+                if (mLoggedInlineDatasetShown) {
+                    // Chip inflation already logged, do not log again.
+                    // This is needed because every chip inflation will call this.
+                    return;
+                }
+                mLoggedInlineDatasetShown = true;
+            }
+            mService.logDatasetShown(this.id, mClientState, uiType);
+        }
+    }
+
     // AutoFillUiCallback
     @Override
     public void requestShowFillUi(AutofillId id, int width, int height,
@@ -3861,8 +3880,6 @@
                 synchronized (mLock) {
                     final ViewState currentView = mViewStates.get(mCurrentViewId);
                     currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
-                    mService.logDatasetShown(id, mClientState, UI_TYPE_DIALOG);
-
                     mPresentationStatsEventLogger.maybeSetCountShown(
                             response.getDatasets(), mCurrentViewId);
                     mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_DIALOG);
@@ -3892,10 +3909,6 @@
                     // back a response via callback.
                     final ViewState currentView = mViewStates.get(mCurrentViewId);
                     currentView.setState(ViewState.STATE_INLINE_SHOWN);
-                    // TODO(b/248378401): Fix it to log showed only when IME asks for inflation,
-                    // rather than here where framework sends back the response.
-                    mService.logDatasetShown(id, mClientState, UI_TYPE_INLINE);
-
                     // TODO(b/234475358): Log more accurate value of number of inline suggestions
                     // shown, inflated, and filtered.
                     mPresentationStatsEventLogger.maybeSetCountShown(
@@ -3912,7 +3925,6 @@
                 targetLabel, targetIcon, this, id, mCompatMode);
 
         synchronized (mLock) {
-            mService.logDatasetShown(id, mClientState, UI_TYPE_MENU);
             mPresentationStatsEventLogger.maybeSetCountShown(
                     response.getDatasets(), mCurrentViewId);
             mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_MENU);
@@ -4124,6 +4136,12 @@
             return false;
         }
 
+        // Set this to false - we are requesting a new inline request and haven't shown
+        // anything yet
+        synchronized (mLock) {
+            mLoggedInlineDatasetShown = false;
+        }
+
         final InlineFillUi.InlineFillUiInfo inlineFillUiInfo =
                 new InlineFillUi.InlineFillUiInfo(request, focusedId,
                         filterText, remoteRenderService, userId, id);
@@ -4153,6 +4171,11 @@
                                     InlineFillUi.emptyUi(focusedId));
                         }
                     }
+
+                    @Override
+                    public void onInflate() {
+                        Session.this.onShown(UI_TYPE_INLINE);
+                    }
                 });
         return mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
     }
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index 7db6e6f..8291610 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -98,6 +98,7 @@
         void cancelSession();
         void requestShowSoftInput(AutofillId id);
         void requestFallbackFromFillDialog();
+        void onShown(int uiType);
     }
 
     public AutoFillUI(@NonNull Context context) {
@@ -237,6 +238,13 @@
                 }
 
                 @Override
+                public void onShown() {
+                    if (mCallback != null) {
+                        mCallback.onShown(UI_TYPE_MENU);
+                    }
+                }
+
+                @Override
                 public void onDatasetPicked(Dataset dataset) {
                     log.setType(MetricsEvent.TYPE_ACTION);
                     hideFillUiUiThread(callback, true);
@@ -424,6 +432,11 @@
                         }
 
                         @Override
+                        public void onShown() {
+                            callback.onShown(UI_TYPE_DIALOG);
+                        }
+
+                        @Override
                         public void onDatasetPicked(Dataset dataset) {
                             log(MetricsEvent.TYPE_ACTION);
                             hideFillDialogUiThread(callback);
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index 72a38eb..dec0e76 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -83,6 +83,7 @@
         void onDatasetPicked(@NonNull Dataset dataset);
         void onDismissed();
         void onCanceled();
+        void onShown();
         void startIntentSender(IntentSender intentSender);
     }
 
@@ -148,7 +149,7 @@
         mDialog.setContentView(decor);
         setDialogParamsAsBottomSheet();
         mDialog.setOnCancelListener((d) -> mCallback.onCanceled());
-
+        mDialog.setOnShowListener((d) -> mCallback.onShown());
         show();
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/FillUi.java b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
index 8fbdd81..76f4505 100644
--- a/services/autofill/java/com/android/server/autofill/ui/FillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/FillUi.java
@@ -86,6 +86,7 @@
         void onDatasetPicked(@NonNull Dataset dataset);
         void onCanceled();
         void onDestroy();
+        void onShown();
         void requestShowFillUi(int width, int height,
                 IAutofillWindowPresenter windowPresenter);
         void requestHideFillUi();
@@ -706,6 +707,7 @@
                     mWm.addView(mContentView, params);
                     mOverlayControl.hideOverlays();
                     mShowing = true;
+                    mCallback.onShown();
                 } else {
                     mWm.updateViewLayout(mContentView, params);
                 }
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
index ff17590..ac8d962 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineFillUi.java
@@ -334,6 +334,17 @@
          * Callback on errors.
          */
         void onError();
+
+        /**
+         * Callback when the when the IME inflates the suggestion
+         *
+         * This goes through the following path:
+         * 1. IME Chip inflation inflate() ->
+         * 2. RemoteInlineSuggestionUi::handleInlineSuggestionUiReady() ->
+         * 3. RemoteInlineSuggestionViewConnector::onRender() ->
+         * 4. InlineSuggestionUiCallback::onInflate()
+         */
+        void onInflate();
     }
 
     /**
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index 9d4c9eb..52109ba 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -198,6 +198,11 @@
                     public void onError() {
                         Slog.w(TAG, "An error happened on the tooltip");
                     }
+
+                    @Override
+                    public void onInflate() {
+                        /* nothing */
+                    }
                 };
 
         InlinePresentation tooltipInline = new InlinePresentation(tooltipPresentation.getSlice(),
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java
index 368f717..ddb60c6 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionUi.java
@@ -221,6 +221,7 @@
         if (surfacePackage != null) {
             surfacePackage.release();
         }
+        mRemoteInlineSuggestionViewConnector.onRender();
     }
 
     private void handleOnClick() {
diff --git a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
index 46d435d..70443f9 100644
--- a/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
+++ b/services/autofill/java/com/android/server/autofill/ui/RemoteInlineSuggestionViewConnector.java
@@ -54,6 +54,8 @@
     @NonNull
     private final Runnable mOnErrorCallback;
     @NonNull
+    private final Runnable mOnInflateCallback;
+    @NonNull
     private final Consumer<IntentSender> mStartIntentSenderFromClientApp;
 
     RemoteInlineSuggestionViewConnector(
@@ -70,6 +72,7 @@
 
         mOnAutofillCallback = onAutofillCallback;
         mOnErrorCallback = uiCallback::onError;
+        mOnInflateCallback = uiCallback::onInflate;
         mStartIntentSenderFromClientApp = uiCallback::startIntentSender;
     }
 
@@ -103,6 +106,10 @@
         mOnErrorCallback.run();
     }
 
+    public void onRender() {
+        mOnInflateCallback.run();
+    }
+
     /**
      * Handles the callback for transferring the touch event on the remote view to the IME
      * process.
diff --git a/services/companion/java/com/android/server/companion/CompanionApplicationController.java b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
index 3814591..5a9c470 100644
--- a/services/companion/java/com/android/server/companion/CompanionApplicationController.java
+++ b/services/companion/java/com/android/server/companion/CompanionApplicationController.java
@@ -346,31 +346,31 @@
         // Make sure do not schedule rebind for the case ServiceConnector still gets callback after
         // app is uninstalled.
         boolean stillAssociated = false;
+        // Make sure to clean up the state for all the associations
+        // that associate with this package.
+        boolean shouldScheduleRebind = false;
 
         for (AssociationInfo ai :
                 mAssociationStore.getAssociationsForPackage(userId, packageName)) {
             final int associationId = ai.getId();
             stillAssociated = true;
-
             if (ai.isSelfManaged()) {
                 // Do not rebind if primary one is died for selfManaged application.
                 if (isPrimary
                         && mDevicePresenceMonitor.isDevicePresent(associationId)) {
                     mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId);
-                    return false;
+                    shouldScheduleRebind = false;
                 }
                 // Do not rebind if both primary and secondary services are died for
                 // selfManaged application.
-                if (!isCompanionApplicationBound(userId, packageName)) {
-                    return false;
-                }
+                shouldScheduleRebind = isCompanionApplicationBound(userId, packageName);
             } else if (ai.isNotifyOnDeviceNearby()) {
                 // Always rebind for non-selfManaged devices.
-                return true;
+                shouldScheduleRebind = true;
             }
         }
 
-        return stillAssociated;
+        return stillAssociated && shouldScheduleRebind;
     }
 
     private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
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 d7a77cd..4010be9 100644
--- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
+++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java
@@ -232,11 +232,14 @@
         }
 
         final boolean alreadyPresent = isDevicePresent(newDeviceAssociationId);
-        if (DEBUG && alreadyPresent) Log.i(TAG, "Device is already present.");
+        if (alreadyPresent) {
+            Log.i(TAG, "Device" + "id (" + newDeviceAssociationId + ") already present.");
+        }
 
         final boolean added = presentDevicesForSource.add(newDeviceAssociationId);
-        if (DEBUG && !added) {
-            Log.w(TAG, "Association with id " + newDeviceAssociationId + " is ALREADY reported as "
+        if (!added) {
+            Log.w(TAG, "Association with id "
+                    + newDeviceAssociationId + " is ALREADY reported as "
                     + "present by this source (" + sourceLoggingTag + ")");
         }
 
@@ -256,16 +259,17 @@
 
         final boolean removed = presentDevicesForSource.remove(goneDeviceAssociationId);
         if (!removed) {
-            if (DEBUG) {
-                Log.w(TAG, "Association with id " + goneDeviceAssociationId + " was NOT reported "
-                        + "as present by this source (" + sourceLoggingTag + ")");
-            }
+            Log.w(TAG, "Association with id " + goneDeviceAssociationId + " was NOT reported "
+                    + "as present by this source (" + sourceLoggingTag + ")");
+
             return;
         }
 
         final boolean stillPresent = isDevicePresent(goneDeviceAssociationId);
         if (stillPresent) {
-            if (DEBUG) Log.i(TAG, "  Device is still present.");
+            if (DEBUG) {
+                Log.i(TAG, "  Device id (" + goneDeviceAssociationId + ") is still present.");
+            }
             return;
         }
 
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 5d46de3..7c32627 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.app.UiModeManager.ContrastUtils.CONTRAST_DEFAULT_VALUE;
 import static android.app.UiModeManager.DEFAULT_PRIORITY;
 import static android.app.UiModeManager.MODE_NIGHT_AUTO;
 import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
@@ -27,8 +28,11 @@
 import static android.app.UiModeManager.PROJECTION_TYPE_AUTOMOTIVE;
 import static android.app.UiModeManager.PROJECTION_TYPE_NONE;
 import static android.os.UserHandle.USER_SYSTEM;
+import static android.provider.Settings.Secure.CONTRAST_LEVEL;
 import static android.util.TimeUtils.isTimeBetween;
 
+import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
+
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -38,6 +42,7 @@
 import android.app.AlarmManager;
 import android.app.IOnProjectionStateChangedListener;
 import android.app.IUiModeManager;
+import android.app.IUiModeManagerCallback;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -71,6 +76,7 @@
 import android.os.ShellCommand;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.service.dreams.Sandman;
 import android.service.vr.IVrManager;
@@ -193,12 +199,19 @@
     private PowerManagerInternal mLocalPowerManager;
 
     @GuardedBy("mLock")
+    private final RemoteCallbackList<IUiModeManagerCallback> mUiModeManagerCallbacks =
+            new RemoteCallbackList<IUiModeManagerCallback>();
+
+    @GuardedBy("mLock")
     @Nullable
     private SparseArray<List<ProjectionHolder>> mProjectionHolders;
     @GuardedBy("mLock")
     @Nullable
     private SparseArray<RemoteCallbackList<IOnProjectionStateChangedListener>> mProjectionListeners;
 
+    @GuardedBy("mLock")
+    private final SparseArray<Float> mContrasts = new SparseArray<>();
+
     public UiModeManagerService(Context context) {
         this(context, /* setupWizardComplete= */ false, /* tm= */ null, new Injector());
     }
@@ -352,6 +365,19 @@
         }
     };
 
+    private final ContentObserver mContrastObserver = new ContentObserver(mHandler) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            synchronized (mLock) {
+                if (updateContrastLocked()) {
+                    float contrast = getContrastLocked();
+                    mUiModeManagerCallbacks.broadcast(ignoreRemoteException(callback ->
+                            callback.notifyContrastChanged(contrast)));
+                }
+            }
+        }
+    };
+
     private void updateSystemProperties() {
         int mode = Secure.getIntForUser(getContext().getContentResolver(), Secure.UI_NIGHT_MODE,
                 mNightMode, 0);
@@ -407,6 +433,9 @@
                 context.getContentResolver()
                         .registerContentObserver(Secure.getUriFor(Secure.UI_NIGHT_MODE),
                                 false, mDarkThemeObserver, 0);
+                context.getContentResolver().registerContentObserver(
+                        Secure.getUriFor(Secure.CONTRAST_LEVEL), false,
+                        mContrastObserver, UserHandle.USER_ALL);
                 context.registerReceiver(mDockModeReceiver,
                         new IntentFilter(Intent.ACTION_DOCK_EVENT));
                 IntentFilter batteryFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
@@ -634,6 +663,13 @@
 
     private final IUiModeManager.Stub mService = new IUiModeManager.Stub() {
         @Override
+        public void addCallback(IUiModeManagerCallback callback) {
+            synchronized (mLock) {
+                mUiModeManagerCallbacks.register(callback);
+            }
+        }
+
+        @Override
         public void enableCarMode(@UiModeManager.EnableCarMode int flags,
                 @IntRange(from = 0) int priority, String callingPackage) {
             if (isUiModeLocked()) {
@@ -1132,6 +1168,13 @@
                 }
             }
         }
+
+        @Override
+        public float getContrast() {
+            synchronized (mLock) {
+                return getContrastLocked();
+            }
+        }
     };
 
     private void enforceProjectionTypePermissions(@UiModeManager.ProjectionType int p) {
@@ -1214,6 +1257,30 @@
         }
     }
 
+    /**
+     * Return the contrast for the current user. If not cached, fetch it from the settings.
+     */
+    @GuardedBy("mLock")
+    private float getContrastLocked() {
+        if (!mContrasts.contains(mCurrentUser)) updateContrastLocked();
+        return mContrasts.get(mCurrentUser);
+    }
+
+    /**
+     * Read the contrast setting for the current user and update {@link #mContrasts}
+     * if the contrast changed. Returns true if {@link #mContrasts} was updated.
+     */
+    @GuardedBy("mLock")
+    private boolean updateContrastLocked() {
+        float contrast = Settings.Secure.getFloatForUser(getContext().getContentResolver(),
+                CONTRAST_LEVEL, CONTRAST_DEFAULT_VALUE, mCurrentUser);
+        if (Math.abs(mContrasts.get(mCurrentUser, Float.MAX_VALUE) - contrast) >= 1e-10) {
+            mContrasts.put(mCurrentUser, contrast);
+            return true;
+        }
+        return false;
+    }
+
     private static class ProjectionHolder implements IBinder.DeathRecipient {
         private final String mPackageName;
         private final @UiModeManager.ProjectionType int mProjectionType;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4ba6854..37f744f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -99,6 +99,7 @@
 import static android.provider.Settings.Global.WAIT_FOR_DEBUGGER;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
+import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.util.FrameworkStatsLog.UNSAFE_INTENT_EVENT_REPORTED__EVENT_TYPE__INTERNAL_NON_EXPORTED_COMPONENT_MATCH;
@@ -9681,8 +9682,8 @@
     }
 
     private void dumpEverything(FileDescriptor fd, PrintWriter pw, String[] args, int opti,
-            boolean dumpAll, String dumpPackage, boolean dumpClient, boolean dumpNormalPriority,
-            int dumpAppId, boolean dumpProxies) {
+            boolean dumpAll, String dumpPackage, int displayIdFilter, boolean dumpClient,
+            boolean dumpNormalPriority, int dumpAppId, boolean dumpProxies) {
 
         ActiveServices.ServiceDumper sdumper;
 
@@ -9762,27 +9763,27 @@
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
             }
-            mAtmInternal.dump(
-                    DUMP_RECENTS_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+            mAtmInternal.dump(DUMP_RECENTS_CMD, fd, pw, args, opti, dumpAll, dumpClient,
+                    dumpPackage, displayIdFilter);
             pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
             }
-            mAtmInternal.dump(
-                    DUMP_LASTANR_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+            mAtmInternal.dump(DUMP_LASTANR_CMD, fd, pw, args, opti, dumpAll, dumpClient,
+                    dumpPackage, displayIdFilter);
             pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
             }
-            mAtmInternal.dump(
-                    DUMP_STARTER_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+            mAtmInternal.dump(DUMP_STARTER_CMD, fd, pw, args, opti, dumpAll, dumpClient,
+                    dumpPackage, displayIdFilter);
             if (dumpPackage == null) {
                 pw.println();
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
-                mAtmInternal.dump(
-                        DUMP_CONTAINERS_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+                mAtmInternal.dump(DUMP_CONTAINERS_CMD, fd, pw, args, opti, dumpAll, dumpClient,
+                        dumpPackage, displayIdFilter);
             }
             // Activities section is dumped as part of the Critical priority dump. Exclude the
             // section if priority is Normal.
@@ -9791,8 +9792,8 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
-                mAtmInternal.dump(
-                        DUMP_ACTIVITIES_CMD, fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+                mAtmInternal.dump(DUMP_ACTIVITIES_CMD, fd, pw, args, opti, dumpAll, dumpClient,
+                        dumpPackage, displayIdFilter);
             }
             if (mAssociations.size() > 0) {
                 pw.println();
@@ -9865,6 +9866,8 @@
         boolean dumpNormalPriority = false;
         boolean dumpVisibleStacksOnly = false;
         boolean dumpFocusedStackOnly = false;
+        boolean dumpVerbose = false;
+        int dumpDisplayId = INVALID_DISPLAY;
         String dumpPackage = null;
         int dumpUserId = UserHandle.USER_ALL;
 
@@ -9909,11 +9912,27 @@
                     pw.println("Error: --user option requires user id argument");
                     return;
                 }
+            } else if ("-d".equals(opt)) {
+                if (opti < args.length) {
+                    dumpDisplayId = Integer.parseInt(args[opti]);
+                    if (dumpDisplayId == INVALID_DISPLAY) {
+                        pw.println("Error: -d cannot be used with INVALID_DISPLAY");
+                        return;
+                    }
+                    opti++;
+                } else {
+                    pw.println("Error: -d option requires display argument");
+                    return;
+                }
+                dumpClient = true;
+            } else if ("--verbose".equals(opt)) {
+                dumpVerbose = true;
             } else if ("-h".equals(opt)) {
                 ActivityManagerShellCommand.dumpHelp(pw, true);
                 return;
             } else {
                 pw.println("Unknown argument: " + opt + "; use -h for help");
+                return;
             }
         }
 
@@ -10026,8 +10045,8 @@
                     || DUMP_RECENTS_CMD.equals(cmd) || DUMP_RECENTS_SHORT_CMD.equals(cmd)
                     || DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)
                     || DUMP_VISIBLE_ACTIVITIES.equals(cmd)) {
-                mAtmInternal.dump(
-                        cmd, fd, pw, args, opti, true /* dumpAll */, dumpClient, dumpPackage);
+                mAtmInternal.dump(cmd, fd, pw, args, opti, /* dumpAll= */ true , dumpClient,
+                        dumpPackage, dumpDisplayId);
             } else if ("binder-proxies".equals(cmd)) {
                 if (opti >= args.length) {
                     dumpBinderProxies(pw, 0 /* minToDump */);
@@ -10194,7 +10213,8 @@
             } else {
                 // Dumping a single activity?
                 if (!mAtmInternal.dumpActivity(fd, pw, cmd, args, opti, dumpAll,
-                        dumpVisibleStacksOnly, dumpFocusedStackOnly, dumpUserId)) {
+                        dumpVisibleStacksOnly, dumpFocusedStackOnly, dumpVerbose, dumpDisplayId,
+                        dumpUserId)) {
                     ActivityManagerShellCommand shell = new ActivityManagerShellCommand(this, true);
                     int res = shell.exec(this, null, fd, null, args, null,
                             new ResultReceiver(null));
@@ -10217,15 +10237,15 @@
             if (dumpClient) {
                 // dumpEverything() will take the lock when needed, and momentarily drop
                 // it for dumping client state.
-                dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpClient,
-                        dumpNormalPriority, dumpAppId, true /* dumpProxies */);
+                dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpDisplayId,
+                        dumpClient, dumpNormalPriority, dumpAppId, /* dumpProxies= */ true);
             } else {
                 // Take the lock here, so we get a consistent state for the entire dump;
                 // dumpEverything() will take the lock as well, which is fine for everything
                 // except dumping proxies, which can take a long time; exclude them.
                 synchronized(this) {
-                    dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpClient,
-                            dumpNormalPriority, dumpAppId, false /* dumpProxies */);
+                    dumpEverything(fd, pw, args, opti, dumpAll, dumpPackage, dumpDisplayId,
+                            dumpClient, dumpNormalPriority, dumpAppId, /* dumpProxies= */ false);
                 }
             }
             if (dumpAll) {
@@ -14261,7 +14281,8 @@
     // Apply permission policy around the use of specific broadcast options
     void enforceBroadcastOptionPermissionsInternal(
             @Nullable Bundle options, int callingUid) {
-        enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundle(options), callingUid);
+        enforceBroadcastOptionPermissionsInternal(BroadcastOptions.fromBundleNullable(options),
+                callingUid);
     }
 
     void enforceBroadcastOptionPermissionsInternal(
@@ -14277,7 +14298,7 @@
             }
             if (options.isInteractive()) {
                 enforceCallingPermission(
-                        android.Manifest.permission.COMPONENT_OPTION_INTERACTIVE,
+                        android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
                         "setInteractive");
             }
         }
@@ -14314,9 +14335,9 @@
         final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
                 intent, resolvedType, resultToApp, resultTo, resultCode, resultData, resultExtras,
                 requiredPermissions, excludedPermissions, excludedPackages, appOp,
-                BroadcastOptions.fromBundle(bOptions), ordered, sticky, callingPid, callingUid,
-                realCallingUid, realCallingPid, userId, backgroundStartPrivileges,
-                broadcastAllowList, filterExtrasForReceiver);
+                BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
+                callingPid, callingUid, realCallingUid, realCallingPid, userId,
+                backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver);
         BroadcastQueue.traceEnd(cookie);
         return res;
     }
@@ -18118,8 +18139,12 @@
                         | Intent.FLAG_RECEIVER_REPLACE_PENDING
                         | Intent.FLAG_RECEIVER_FOREGROUND
                         | Intent.FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS);
+                final Bundle configChangedOptions = new BroadcastOptions()
+                        .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                        .setDeferUntilActive(true)
+                        .toBundle();
                 broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
-                        null, null, OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                        null, null, OP_NONE, configChangedOptions, false, false, MY_PID, SYSTEM_UID,
                         Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
                 if ((changes & ActivityInfo.CONFIG_LOCALE) != 0) {
                     intent = new Intent(Intent.ACTION_LOCALE_CHANGED);
@@ -18178,8 +18203,14 @@
                     intent.putExtra("reason", reason);
                 }
 
+                final BroadcastOptions options = new BroadcastOptions()
+                        .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                        .setDeferUntilActive(true);
+                if (reason != null) {
+                    options.setDeliveryGroupMatchingKey(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, reason);
+                }
                 broadcastIntentLocked(null, null, null, intent, null, null, 0, null, null, null,
-                        null, null, OP_NONE, null, false, false, -1, SYSTEM_UID,
+                        null, null, OP_NONE, options.toBundle(), false, false, -1, SYSTEM_UID,
                         Binder.getCallingUid(), Binder.getCallingPid(), UserHandle.USER_ALL);
             }
         }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 523ed69..01bb549 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -3945,6 +3945,7 @@
             pw.println("    package [PACKAGE_NAME]: all state related to given package");
             pw.println("    all: dump all activities");
             pw.println("    top: dump the top activity");
+            pw.println("    users: user state");
             pw.println("  WHAT may also be a COMP_SPEC to dump activities.");
             pw.println("  COMP_SPEC may be a component name (com.foo/.myApp),");
             pw.println("    a partial substring in a component name, a");
@@ -3952,9 +3953,11 @@
             pw.println("  -a: include all available server state.");
             pw.println("  -c: include client state.");
             pw.println("  -p: limit output to given package.");
+            pw.println("  -d: limit output to given display.");
             pw.println("  --checkin: output checkin format, resetting data.");
             pw.println("  --C: output checkin format, not resetting data.");
             pw.println("  --proto: output dump in protocol buffer format.");
+            pw.println("  --verbose: dumps extra information.");
             pw.printf("  %s: dump just the DUMPABLE-related state of an activity. Use the %s "
                     + "option to list the supported DUMPABLEs\n", Activity.DUMP_ARG_DUMP_DUMPABLE,
                     Activity.DUMP_ARG_LIST_DUMPABLES);
diff --git a/services/core/java/com/android/server/am/AppProfiler.java b/services/core/java/com/android/server/am/AppProfiler.java
index ac2c725..dd6598f 100644
--- a/services/core/java/com/android/server/am/AppProfiler.java
+++ b/services/core/java/com/android/server/am/AppProfiler.java
@@ -20,6 +20,7 @@
 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
 import static android.os.Process.FIRST_APPLICATION_UID;
 import static android.util.FeatureFlagUtils.SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS;
+import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_CRITICAL;
 import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_LOW;
@@ -1604,8 +1605,8 @@
             mService.mServices.newServiceDumperLocked(null, catPw, emptyArgs, 0,
                     false, null).dumpLocked();
             catPw.println();
-            mService.mAtmInternal.dump(
-                    DUMP_ACTIVITIES_CMD, null, catPw, emptyArgs, 0, false, false, null);
+            mService.mAtmInternal.dump(DUMP_ACTIVITIES_CMD, null, catPw, emptyArgs, 0, false, false,
+                    null, INVALID_DISPLAY);
             catPw.flush();
         }
         dropBuilder.append(catSw.toString());
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 2e3e635..ddc9e91 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -96,6 +96,10 @@
         sGlobalSettingToTypeMap.put(
                 Settings.Global.ANGLE_EGL_FEATURES, String.class);
         sGlobalSettingToTypeMap.put(
+                Settings.Global.ANGLE_DEFERLIST, String.class);
+        sGlobalSettingToTypeMap.put(
+                Settings.Global.ANGLE_DEFERLIST_MODE, String.class);
+        sGlobalSettingToTypeMap.put(
                 Settings.Global.SHOW_ANGLE_IN_USE_DIALOG_BOX, String.class);
         sGlobalSettingToTypeMap.put(Settings.Global.ENABLE_GPU_DEBUG_LAYERS, int.class);
         sGlobalSettingToTypeMap.put(Settings.Global.GPU_DEBUG_APP, String.class);
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index cf81dbe..6a97243 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -366,6 +366,21 @@
             return;
         }
 
+        SoundDoseRecord[] doseRecordsArray;
+        synchronized (mCsdStateLock) {
+            mCurrentCsd = csd;
+
+            mDoseRecords.clear();
+
+            if (mCurrentCsd > 0.0f) {
+                final SoundDoseRecord record = new SoundDoseRecord();
+                record.timestamp = SystemClock.elapsedRealtime();
+                record.value = csd;
+                mDoseRecords.add(record);
+            }
+            doseRecordsArray = mDoseRecords.toArray(new SoundDoseRecord[0]);
+        }
+
         final ISoundDose soundDose = mSoundDose.get();
         if (soundDose == null) {
             Log.w(TAG, "Sound dose interface not initialized");
@@ -373,11 +388,7 @@
         }
 
         try {
-            final SoundDoseRecord record = new SoundDoseRecord();
-            record.timestamp = System.currentTimeMillis();
-            record.value = csd;
-            final SoundDoseRecord[] recordArray = new SoundDoseRecord[] { record };
-            soundDose.resetCsd(csd, recordArray);
+            soundDose.resetCsd(csd, doseRecordsArray);
         } catch (RemoteException e) {
             Log.e(TAG, "Exception while setting the CSD value", e);
         }
@@ -648,6 +659,11 @@
 
     /*package*/ void dump(PrintWriter pw) {
         pw.print("  mEnableCsd="); pw.println(mEnableCsd);
+        if (mEnableCsd) {
+            synchronized (mCsdStateLock) {
+                pw.print("  mCurrentCsd="); pw.println(mCurrentCsd);
+            }
+        }
         pw.print("  mSafeMediaVolumeState=");
         pw.println(safeMediaVolumeStateToString(mSafeMediaVolumeState));
         pw.print("  mSafeMediaVolumeIndex="); pw.println(mSafeMediaVolumeIndex);
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 592daa6..720ea99 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -50,7 +50,12 @@
 
 import java.io.PrintWriter;
 
-class AutomaticBrightnessController {
+/**
+ * Manages the associated display brightness when in auto-brightness mode. This is also
+ * responsible for managing the brightness lux-nits mapping strategies. Internally also listens to
+ * the LightSensor and adjusts the system brightness in case of changes in the surrounding lux.
+ */
+public class AutomaticBrightnessController {
     private static final String TAG = "AutomaticBrightnessController";
 
     private static final boolean DEBUG_PRETEND_LIGHT_SENSOR_ABSENT = false;
@@ -1140,7 +1145,7 @@
         if (mCurrentBrightnessMapper != null) {
             return mCurrentBrightnessMapper.convertToFloatScale(nits);
         } else {
-            return -1.0f;
+            return PowerManager.BRIGHTNESS_INVALID_FLOAT;
         }
     }
 
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index d7c1529..d047183 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -322,9 +322,10 @@
     public abstract float convertToNits(float brightness);
 
     /**
-     * Converts the provided nits value to a float value if possible.
+     * Converts the provided nit value to a float scale value if possible.
      *
-     * Returns -1.0f if there's no available mapping for the nits to float.
+     * Returns {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if there's no available mapping for
+     * the nits to float scale.
      */
     public abstract float convertToFloatScale(float nits);
 
@@ -679,7 +680,7 @@
 
         @Override
         public float convertToFloatScale(float nits) {
-            return -1.0f;
+            return PowerManager.BRIGHTNESS_INVALID_FLOAT;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java
index 4a9b562..de42370 100644
--- a/services/core/java/com/android/server/display/BrightnessSetting.java
+++ b/services/core/java/com/android/server/display/BrightnessSetting.java
@@ -121,6 +121,23 @@
         }
     }
 
+    /**
+     * @return The brightness for the default display in nits. Used when the underlying display
+     * device has changed but we want to persist the nit value.
+     */
+    public float getBrightnessNitsForDefaultDisplay() {
+        return mPersistentDataStore.getBrightnessNitsForDefaultDisplay();
+    }
+
+    /**
+     * Set brightness in nits for the default display. Used when we want to persist the nit value
+     * even if the underlying display device changes.
+     * @param nits The brightness value in nits
+     */
+    public void setBrightnessNitsForDefaultDisplay(float nits) {
+        mPersistentDataStore.setBrightnessNitsForDefaultDisplay(nits);
+    }
+
     private void notifyListeners(float brightness) {
         for (BrightnessSettingListener l : mListeners) {
             l.onBrightnessChanged(brightness);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 84fe8f2..055ca37 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -233,6 +233,10 @@
     // True if should use light sensor to automatically determine doze screen brightness.
     private final boolean mAllowAutoBrightnessWhileDozingConfig;
 
+    // True if we want to persist the brightness value in nits even if the underlying display
+    // device changes.
+    private final boolean mPersistBrightnessNitsForDefaultDisplay;
+
     // True if the brightness config has changed and the short-term model needs to be reset
     private boolean mShouldResetShortTermModel;
 
@@ -590,6 +594,9 @@
         mAllowAutoBrightnessWhileDozingConfig = resources.getBoolean(
                 com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing);
 
+        mPersistBrightnessNitsForDefaultDisplay = resources.getBoolean(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay);
+
         mDisplayDeviceConfig = logicalDisplay.getPrimaryDisplayDeviceLocked()
                 .getDisplayDeviceConfig();
 
@@ -658,7 +665,7 @@
 
         loadProximitySensor();
 
-        mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+        loadNitBasedBrightnessSetting();
         mBrightnessToFollow = PowerManager.BRIGHTNESS_INVALID_FLOAT;
         mAutoBrightnessAdjustment = getAutoBrightnessAdjustmentSetting();
         mTemporaryScreenBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
@@ -745,10 +752,10 @@
     @Override
     public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
         mHbmController.onAmbientLuxChange(ambientLux);
-        if (mAutomaticBrightnessController == null || nits < 0) {
+        if (nits < 0) {
             mBrightnessToFollow = leadDisplayBrightness;
         } else {
-            float brightness = mAutomaticBrightnessController.convertToFloatScale(nits);
+            float brightness = convertToFloatScale(nits);
             if (isValidBrightnessValue(brightness)) {
                 mBrightnessToFollow = brightness;
             } else {
@@ -895,6 +902,7 @@
                 mDisplayDeviceConfig = config;
                 mBrightnessThrottlingDataId = brightnessThrottlingDataId;
                 loadFromDisplayDeviceConfig(token, info, hbmMetadata);
+                loadNitBasedBrightnessSetting();
 
                 /// Since the underlying display-device changed, we really don't know the
                 // last command that was sent to change it's state. Lets assume it is unknown so
@@ -2563,11 +2571,34 @@
         return clampAbsoluteBrightness(brightness);
     }
 
+    private void loadNitBasedBrightnessSetting() {
+        if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
+            float brightnessNitsForDefaultDisplay =
+                    mBrightnessSetting.getBrightnessNitsForDefaultDisplay();
+            if (brightnessNitsForDefaultDisplay >= 0) {
+                float brightnessForDefaultDisplay = convertToFloatScale(
+                        brightnessNitsForDefaultDisplay);
+                if (isValidBrightnessValue(brightnessForDefaultDisplay)) {
+                    mBrightnessSetting.setBrightness(brightnessForDefaultDisplay);
+                    mCurrentScreenBrightnessSetting = brightnessForDefaultDisplay;
+                    return;
+                }
+            }
+        }
+        mCurrentScreenBrightnessSetting = getScreenBrightnessSetting();
+    }
+
     @Override
     public void setBrightness(float brightnessValue) {
         // Update the setting, which will eventually call back into DPC to have us actually update
         // the display with the new value.
         mBrightnessSetting.setBrightness(brightnessValue);
+        if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
+            float nits = convertToNits(brightnessValue);
+            if (nits >= 0) {
+                mBrightnessSetting.setBrightnessNitsForDefaultDisplay(nits);
+            }
+        }
     }
 
     @Override
@@ -2582,7 +2613,7 @@
             return;
         }
         setCurrentScreenBrightness(brightnessValue);
-        mBrightnessSetting.setBrightness(brightnessValue);
+        setBrightness(brightnessValue);
     }
 
     private void setCurrentScreenBrightness(float brightnessValue) {
@@ -2661,6 +2692,13 @@
         return mAutomaticBrightnessController.convertToNits(brightness);
     }
 
+    private float convertToFloatScale(float nits) {
+        if (mAutomaticBrightnessController == null) {
+            return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        }
+        return mAutomaticBrightnessController.convertToFloatScale(nits);
+    }
+
     @GuardedBy("mLock")
     private void updatePendingProximityRequestsLocked() {
         mWaitingForNegativeProximity |= mPendingWaitForNegativeProximityLocked;
@@ -2759,6 +2797,8 @@
         pw.println("  mUseSoftwareAutoBrightnessConfig=" + mUseSoftwareAutoBrightnessConfig);
         pw.println("  mAllowAutoBrightnessWhileDozingConfig="
                 + mAllowAutoBrightnessWhileDozingConfig);
+        pw.println("  mPersistBrightnessNitsForDefaultDisplay="
+                + mPersistBrightnessNitsForDefaultDisplay);
         pw.println("  mSkipScreenOnBrightnessRamp=" + mSkipScreenOnBrightnessRamp);
         pw.println("  mColorFadeFadesConfig=" + mColorFadeFadesConfig);
         pw.println("  mColorFadeEnabled=" + mColorFadeEnabled);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 297a6f8..3c937e8 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -861,7 +861,8 @@
         noteScreenBrightness(mPowerState.getScreenBrightness());
 
         // Initialize all of the brightness tracking state
-        final float brightness = convertToNits(mPowerState.getScreenBrightness());
+        final float brightness = mDisplayBrightnessController.convertToNits(
+                mPowerState.getScreenBrightness());
         if (mBrightnessTracker != null && brightness >= PowerManager.BRIGHTNESS_MIN) {
             mBrightnessTracker.start(brightness);
         }
@@ -1024,6 +1025,8 @@
                     mHbmController, mBrightnessThrottler, mIdleModeBrightnessMapper,
                     mDisplayDeviceConfig.getAmbientHorizonShort(),
                     mDisplayDeviceConfig.getAmbientHorizonLong(), userLux, userBrightness);
+            mDisplayBrightnessController.setAutomaticBrightnessController(
+                    mAutomaticBrightnessController);
 
             mBrightnessEventRingBuffer =
                     new RingBuffer<>(BrightnessEvent.class, RINGBUFFER_MAX);
@@ -1389,7 +1392,8 @@
                 : mAutomaticBrightnessController.getAmbientLux();
         for (int i = 0; i < displayBrightnessFollowers.size(); i++) {
             DisplayPowerControllerInterface follower = displayBrightnessFollowers.valueAt(i);
-            follower.setBrightnessToFollow(rawBrightnessState, convertToNits(rawBrightnessState),
+            follower.setBrightnessToFollow(rawBrightnessState,
+                    mDisplayBrightnessController.convertToNits(rawBrightnessState),
                     ambientLux);
         }
 
@@ -2191,10 +2195,10 @@
     @Override
     public void setBrightnessToFollow(float leadDisplayBrightness, float nits, float ambientLux) {
         mHbmController.onAmbientLuxChange(ambientLux);
-        if (mAutomaticBrightnessController == null || nits < 0) {
+        if (nits < 0) {
             mDisplayBrightnessController.setBrightnessToFollow(leadDisplayBrightness);
         } else {
-            float brightness = mAutomaticBrightnessController.convertToFloatScale(nits);
+            float brightness = mDisplayBrightnessController.convertToFloatScale(nits);
             if (BrightnessUtils.isValidBrightnessValue(brightness)) {
                 mDisplayBrightnessController.setBrightnessToFollow(brightness);
             } else {
@@ -2230,7 +2234,7 @@
 
     private void notifyBrightnessTrackerChanged(float brightness, boolean userInitiated,
             boolean wasShortTermModelActive) {
-        final float brightnessInNits = convertToNits(brightness);
+        final float brightnessInNits = mDisplayBrightnessController.convertToNits(brightness);
         if (mUseAutoBrightness && brightnessInNits >= 0.0f
                 && mAutomaticBrightnessController != null && mBrightnessTracker != null) {
             // We only want to track changes on devices that can actually map the display backlight
@@ -2247,13 +2251,6 @@
         }
     }
 
-    private float convertToNits(float brightness) {
-        if (mAutomaticBrightnessController == null) {
-            return -1f;
-        }
-        return mAutomaticBrightnessController.convertToNits(brightness);
-    }
-
     @Override
     public void addDisplayBrightnessFollower(DisplayPowerControllerInterface follower) {
         synchronized (mLock) {
@@ -2513,17 +2510,17 @@
         int appliedRbcStrength  = event.isRbcEnabled() ? event.getRbcStrength() : -1;
         float appliedHbmMaxNits =
                 event.getHbmMode() == BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
-                ? -1f : convertToNits(event.getHbmMax());
+                ? -1f : mDisplayBrightnessController.convertToNits(event.getHbmMax());
         // thermalCapNits set to -1 if not currently capping max brightness
         float appliedThermalCapNits =
                 event.getThermalMax() == PowerManager.BRIGHTNESS_MAX
-                ? -1f : convertToNits(event.getThermalMax());
+                ? -1f : mDisplayBrightnessController.convertToNits(event.getThermalMax());
         if (mLogicalDisplay.getPrimaryDisplayDeviceLocked() != null
                 && mLogicalDisplay.getPrimaryDisplayDeviceLocked()
                 .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL) {
             FrameworkStatsLog.write(FrameworkStatsLog.DISPLAY_BRIGHTNESS_CHANGED,
-                    convertToNits(event.getInitialBrightness()),
-                    convertToNits(event.getBrightness()),
+                    mDisplayBrightnessController.convertToNits(event.getInitialBrightness()),
+                    mDisplayBrightnessController.convertToNits(event.getBrightness()),
                     event.getLux(),
                     event.getPhysicalDisplayId(),
                     event.wasShortTermModelActive(),
diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java
index e7601bc..ec70c89 100644
--- a/services/core/java/com/android/server/display/PersistentDataStore.java
+++ b/services/core/java/com/android/server/display/PersistentDataStore.java
@@ -94,6 +94,7 @@
  *          &lt;/brightness-curve>
  *      &lt;/brightness-configuration>
  *  &lt;/brightness-configurations>
+ *  &lt;brightness-nits-for-default-display>600&lt;/brightness-nits-for-default-display>
  * &lt;/display-manager-state>
  * </code>
  *
@@ -130,6 +131,9 @@
     private static final String TAG_RESOLUTION_HEIGHT = "resolution-height";
     private static final String TAG_REFRESH_RATE = "refresh-rate";
 
+    private static final String TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY =
+            "brightness-nits-for-default-display";
+
     // Remembered Wifi display devices.
     private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
 
@@ -137,6 +141,8 @@
     private final HashMap<String, DisplayState> mDisplayStates =
             new HashMap<String, DisplayState>();
 
+    private float mBrightnessNitsForDefaultDisplay = -1;
+
     // Display values which should be stable across the device's lifetime.
     private final StableDeviceValues mStableDeviceValues = new StableDeviceValues();
 
@@ -312,6 +318,19 @@
         return false;
     }
 
+    public float getBrightnessNitsForDefaultDisplay() {
+        return mBrightnessNitsForDefaultDisplay;
+    }
+
+    public boolean setBrightnessNitsForDefaultDisplay(float nits) {
+        if (nits != mBrightnessNitsForDefaultDisplay) {
+            mBrightnessNitsForDefaultDisplay = nits;
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
     public boolean setUserPreferredRefreshRate(DisplayDevice displayDevice, float refreshRate) {
         final String displayDeviceUniqueId = displayDevice.getUniqueId();
         if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) {
@@ -513,6 +532,10 @@
             if (parser.getName().equals(TAG_BRIGHTNESS_CONFIGURATIONS)) {
                 mGlobalBrightnessConfigurations.loadFromXml(parser);
             }
+            if (parser.getName().equals(TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY)) {
+                String value = parser.nextText();
+                mBrightnessNitsForDefaultDisplay = Float.parseFloat(value);
+            }
         }
     }
 
@@ -592,6 +615,9 @@
         serializer.startTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
         mGlobalBrightnessConfigurations.saveToXml(serializer);
         serializer.endTag(null, TAG_BRIGHTNESS_CONFIGURATIONS);
+        serializer.startTag(null, TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY);
+        serializer.text(Float.toString(mBrightnessNitsForDefaultDisplay));
+        serializer.endTag(null, TAG_BRIGHTNESS_NITS_FOR_DEFAULT_DISPLAY);
         serializer.endTag(null, TAG_DISPLAY_MANAGER_STATE);
         serializer.endDocument();
     }
@@ -615,6 +641,7 @@
         mStableDeviceValues.dump(pw, "      ");
         pw.println("  GlobalBrightnessConfigurations:");
         mGlobalBrightnessConfigurations.dump(pw, "      ");
+        pw.println("  mBrightnessNitsForDefaultDisplay=" + mBrightnessNitsForDefaultDisplay);
     }
 
     private static final class DisplayState {
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index 68758ca..2916fef 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -16,14 +16,17 @@
 
 package com.android.server.display.brightness;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.HandlerExecutor;
 import android.os.PowerManager;
 import android.util.IndentingPrintWriter;
+import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.display.AutomaticBrightnessController;
 import com.android.server.display.BrightnessSetting;
 import com.android.server.display.DisplayBrightnessState;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
@@ -84,6 +87,15 @@
     // callback is not executed in sync and is not blocking the thread from which it is called.
     private final HandlerExecutor mBrightnessChangeExecutor;
 
+    // True if we want to persist the brightness value in nits even if the underlying display
+    // device changes.
+    private final boolean mPersistBrightnessNitsForDefaultDisplay;
+
+    // The controller for the automatic brightness level.
+    // TODO(b/265415257): Move to the automatic brightness strategy
+    @Nullable
+    private AutomaticBrightnessController mAutomaticBrightnessController;
+
     /**
      * The constructor of DisplayBrightnessController.
      */
@@ -103,6 +115,8 @@
         mDisplayBrightnessStrategySelector = injector.getDisplayBrightnessStrategySelector(context,
                 displayId);
         mBrightnessChangeExecutor = brightnessChangeExecutor;
+        mPersistBrightnessNitsForDefaultDisplay = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay);
     }
 
     /**
@@ -263,6 +277,12 @@
         // Update the setting, which will eventually call back into DPC to have us actually
         // update the display with the new value.
         mBrightnessSetting.setBrightness(brightnessValue);
+        if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
+            float nits = convertToNits(brightnessValue);
+            if (nits >= 0) {
+                mBrightnessSetting.setBrightnessNitsForDefaultDisplay(nits);
+            }
+        }
     }
 
     /**
@@ -281,6 +301,42 @@
     }
 
     /**
+     * Set the {@link AutomaticBrightnessController} which is needed to perform nit-to-float-scale
+     * conversion.
+     * @param automaticBrightnessController The ABC
+     */
+    public void setAutomaticBrightnessController(
+            AutomaticBrightnessController automaticBrightnessController) {
+        mAutomaticBrightnessController = automaticBrightnessController;
+        loadNitBasedBrightnessSetting();
+    }
+
+    /**
+     * Convert a brightness float scale value to a nit value.
+     * @param brightness The float scale value
+     * @return The nit value or -1f if no conversion is possible.
+     */
+    public float convertToNits(float brightness) {
+        if (mAutomaticBrightnessController == null) {
+            return -1f;
+        }
+        return mAutomaticBrightnessController.convertToNits(brightness);
+    }
+
+    /**
+     * Convert a brightness nit value to a float scale value.
+     * @param nits The nit value
+     * @return The float scale value or {@link PowerManager.BRIGHTNESS_INVALID_FLOAT} if no
+     * conversion is possible.
+     */
+    public float convertToFloatScale(float nits) {
+        if (mAutomaticBrightnessController == null) {
+            return PowerManager.BRIGHTNESS_INVALID_FLOAT;
+        }
+        return mAutomaticBrightnessController.convertToFloatScale(nits);
+    }
+
+    /**
      * Stops the associated listeners when the display is stopped. Invoked when the {@link
      * #mDisplayId} is being removed.
      */
@@ -300,6 +356,8 @@
         writer.println("DisplayBrightnessController:");
         writer.println("  mDisplayId=: " + mDisplayId);
         writer.println("  mScreenBrightnessDefault=" + mScreenBrightnessDefault);
+        writer.println("  mPersistBrightnessNitsForDefaultDisplay="
+                + mPersistBrightnessNitsForDefaultDisplay);
         synchronized (mLock) {
             writer.println("  mPendingScreenBrightness=" + mPendingScreenBrightness);
             writer.println("  mCurrentScreenBrightness=" + mCurrentScreenBrightness);
@@ -353,4 +411,29 @@
     private void notifyCurrentScreenBrightness() {
         mBrightnessChangeExecutor.execute(mOnBrightnessChangeRunnable);
     }
+
+    /**
+     * Loads the brightness value. If this is the default display and the config says that we should
+     * persist the nit value, the nit value for the default display will be loaded.
+     */
+    private void loadNitBasedBrightnessSetting() {
+        if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) {
+            float brightnessNitsForDefaultDisplay =
+                    mBrightnessSetting.getBrightnessNitsForDefaultDisplay();
+            if (brightnessNitsForDefaultDisplay >= 0) {
+                float brightnessForDefaultDisplay = convertToFloatScale(
+                        brightnessNitsForDefaultDisplay);
+                if (BrightnessUtils.isValidBrightnessValue(brightnessForDefaultDisplay)) {
+                    mBrightnessSetting.setBrightness(brightnessForDefaultDisplay);
+                    synchronized (mLock) {
+                        mCurrentScreenBrightness = brightnessForDefaultDisplay;
+                    }
+                    return;
+                }
+            }
+        }
+        synchronized (mLock) {
+            mCurrentScreenBrightness = getScreenBrightnessSetting();
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
index 088740e..f873a1b 100644
--- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java
+++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java
@@ -45,14 +45,17 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.view.InputDevice;
 import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.InputMethodSubtype;
 import android.widget.Toast;
 
@@ -75,6 +78,7 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Stream;
 
 /**
@@ -102,8 +106,10 @@
     private final PersistentDataStore mDataStore;
     private final Handler mHandler;
 
-    private final List<InputDevice> mKeyboardsWithMissingLayouts = new ArrayList<>();
-    private boolean mKeyboardLayoutNotificationShown = false;
+    // Connected keyboards with associated keyboard layouts (either auto-detected or manually
+    // selected layout). If the mapped value is null/empty, it means that no layout has been
+    // configured for the keyboard and user might need to manually configure it from the Settings.
+    private final SparseArray<Set<String>> mConfiguredKeyboards = new SparseArray<>();
     private Toast mSwitchedKeyboardLayoutToast;
 
     // This cache stores "best-matched" layouts so that we don't need to run the matching
@@ -158,10 +164,8 @@
 
     @Override
     public void onInputDeviceRemoved(int deviceId) {
-        if (!useNewSettingsUi()) {
-            mKeyboardsWithMissingLayouts.removeIf(device -> device.getId() == deviceId);
-            maybeUpdateNotification();
-        }
+        mConfiguredKeyboards.remove(deviceId);
+        maybeUpdateNotification();
     }
 
     @Override
@@ -178,13 +182,53 @@
                     if (layout != null) {
                         setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
                     } else {
-                        mKeyboardsWithMissingLayouts.add(inputDevice);
+                        mConfiguredKeyboards.put(inputDevice.getId(), new HashSet<>());
                     }
                 }
-                maybeUpdateNotification();
+            }
+        } else {
+            final InputDeviceIdentifier identifier = inputDevice.getIdentifier();
+            final String key = getLayoutDescriptor(identifier);
+            Set<String> selectedLayouts = new HashSet<>();
+            boolean needToShowMissingLayoutNotification = false;
+            for (ImeInfo imeInfo : getImeInfoListForLayoutMapping()) {
+                // Check if the layout has been previously configured
+                String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
+                        new ImeInfo(imeInfo.mUserId, imeInfo.mImeSubtypeHandle,
+                                imeInfo.mImeSubtype));
+                if (layout == null) {
+                    needToShowMissingLayoutNotification = true;
+                    continue;
+                }
+                selectedLayouts.add(layout);
+            }
+
+            if (needToShowMissingLayoutNotification) {
+                // If even one layout not configured properly we will show configuration
+                // notification allowing user to set the keyboard layout.
+                selectedLayouts.clear();
+            }
+
+            if (DEBUG) {
+                Slog.d(TAG,
+                        "Layouts selected for input device: " + identifier + " -> selectedLayouts: "
+                                + selectedLayouts);
+            }
+            mConfiguredKeyboards.set(inputDevice.getId(), selectedLayouts);
+
+            synchronized (mDataStore) {
+                try {
+                    if (!mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
+                        // No need to show the notification only if layout selection didn't change
+                        // from the previous configuration
+                        return;
+                    }
+                } finally {
+                    mDataStore.saveIfNeeded();
+                }
             }
         }
-        // TODO(b/259530132): Show notification for new Settings UI
+        maybeUpdateNotification();
     }
 
     private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
@@ -483,15 +527,17 @@
         key.append("vendor:").append(identifier.getVendorId()).append(",product:").append(
                 identifier.getProductId());
 
-        InputDevice inputDevice = getInputDevice(identifier);
-        Objects.requireNonNull(inputDevice, "Input device must not be null");
-        // Some keyboards can have same product ID and vendor ID but different Keyboard info like
-        // language tag and layout type.
-        if (!TextUtils.isEmpty(inputDevice.getKeyboardLanguageTag())) {
-            key.append(",languageTag:").append(inputDevice.getKeyboardLanguageTag());
-        }
-        if (!TextUtils.isEmpty(inputDevice.getKeyboardLayoutType())) {
-            key.append(",layoutType:").append(inputDevice.getKeyboardLanguageTag());
+        if (useNewSettingsUi()) {
+            InputDevice inputDevice = getInputDevice(identifier);
+            Objects.requireNonNull(inputDevice, "Input device must not be null");
+            // Some keyboards can have same product ID and vendor ID but different Keyboard info
+            // like language tag and layout type.
+            if (!TextUtils.isEmpty(inputDevice.getKeyboardLanguageTag())) {
+                key.append(",languageTag:").append(inputDevice.getKeyboardLanguageTag());
+            }
+            if (!TextUtils.isEmpty(inputDevice.getKeyboardLayoutType())) {
+                key.append(",layoutType:").append(inputDevice.getKeyboardLanguageTag());
+            }
         }
         return key.toString();
     }
@@ -669,6 +715,12 @@
     public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
         String keyboardLayoutDescriptor;
         if (useNewSettingsUi()) {
+            InputDevice inputDevice = getInputDevice(identifier);
+            if (inputDevice == null) {
+                // getKeyboardLayoutOverlay() called before input device added completely. Need
+                // to wait till the device is added which will call reloadKeyboardLayouts()
+                return null;
+            }
             if (mCurrentImeInfo == null) {
                 // Haven't received onInputMethodSubtypeChanged() callback from IMMS. Will reload
                 // keyboard layouts once we receive the callback.
@@ -991,66 +1043,140 @@
     }
 
     private void maybeUpdateNotification() {
+        if (mConfiguredKeyboards.size() == 0) {
+            hideKeyboardLayoutNotification();
+            return;
+        }
+        for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
+            // If we have a keyboard with no selected layouts, we should always show missing
+            // layout notification even if there are other keyboards that are configured properly.
+            if (mConfiguredKeyboards.valueAt(i).isEmpty()) {
+                showMissingKeyboardLayoutNotification();
+                return;
+            }
+        }
+        showConfiguredKeyboardLayoutNotification();
+    }
+
+    // Must be called on handler.
+    private void showMissingKeyboardLayoutNotification() {
+        final Resources r = mContext.getResources();
+        final String missingKeyboardLayoutNotificationContent = r.getString(
+                R.string.select_keyboard_layout_notification_message);
+
+        if (mConfiguredKeyboards.size() == 1) {
+            final InputDevice device = getInputDevice(mConfiguredKeyboards.keyAt(0));
+            if (device == null) {
+                return;
+            }
+            showKeyboardLayoutNotification(
+                    r.getString(
+                            R.string.select_keyboard_layout_notification_title,
+                            device.getName()),
+                    missingKeyboardLayoutNotificationContent,
+                    device);
+        } else {
+            showKeyboardLayoutNotification(
+                    r.getString(R.string.select_multiple_keyboards_layout_notification_title),
+                    missingKeyboardLayoutNotificationContent,
+                    null);
+        }
+    }
+
+    private void showKeyboardLayoutNotification(@NonNull String intentTitle,
+            @NonNull String intentContent, @Nullable InputDevice targetDevice) {
+        final NotificationManager notificationManager = mContext.getSystemService(
+                NotificationManager.class);
+        if (notificationManager == null) {
+            return;
+        }
+
+        final Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
+
+        if (targetDevice != null) {
+            intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, targetDevice.getIdentifier());
+        }
+
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
+                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        final PendingIntent keyboardLayoutIntent = PendingIntent.getActivityAsUser(mContext, 0,
+                intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
+
+        Notification notification =
+                new Notification.Builder(mContext, SystemNotificationChannels.PHYSICAL_KEYBOARD)
+                        .setContentTitle(intentTitle)
+                        .setContentText(intentContent)
+                        .setContentIntent(keyboardLayoutIntent)
+                        .setSmallIcon(R.drawable.ic_settings_language)
+                        .setColor(mContext.getColor(
+                                com.android.internal.R.color.system_notification_accent_color))
+                        .setAutoCancel(true)
+                        .build();
+        notificationManager.notifyAsUser(null,
+                SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
+                notification, UserHandle.ALL);
+    }
+
+    // Must be called on handler.
+    private void hideKeyboardLayoutNotification() {
         NotificationManager notificationManager = mContext.getSystemService(
                 NotificationManager.class);
         if (notificationManager == null) {
             return;
         }
-        if (!mKeyboardsWithMissingLayouts.isEmpty()) {
-            if (mKeyboardsWithMissingLayouts.size() > 1) {
-                // We have more than one keyboard missing a layout, so drop the
-                // user at the generic input methods page, so they can pick which
-                // one to set.
-                showMissingKeyboardLayoutNotification(notificationManager, null);
-            } else {
-                showMissingKeyboardLayoutNotification(notificationManager,
-                        mKeyboardsWithMissingLayouts.get(0));
-            }
-        } else if (mKeyboardLayoutNotificationShown) {
-            hideMissingKeyboardLayoutNotification(notificationManager);
-        }
+
+        notificationManager.cancelAsUser(null,
+                SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
+                UserHandle.ALL);
     }
 
-    // Must be called on handler.
-    private void showMissingKeyboardLayoutNotification(NotificationManager notificationManager,
-            InputDevice device) {
-        if (!mKeyboardLayoutNotificationShown) {
-            final Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
-            if (device != null) {
-                intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, device.getIdentifier());
-            }
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
-                    | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
-                    | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-            final PendingIntent keyboardLayoutIntent = PendingIntent.getActivityAsUser(mContext, 0,
-                    intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);
+    private void showConfiguredKeyboardLayoutNotification() {
+        final Resources r = mContext.getResources();
 
-            Resources r = mContext.getResources();
-            Notification notification =
-                    new Notification.Builder(mContext, SystemNotificationChannels.PHYSICAL_KEYBOARD)
-                            .setContentTitle(r.getString(
-                                    R.string.select_keyboard_layout_notification_title))
-                            .setContentText(r.getString(
-                                    R.string.select_keyboard_layout_notification_message))
-                            .setContentIntent(keyboardLayoutIntent)
-                            .setSmallIcon(R.drawable.ic_settings_language)
-                            .setColor(mContext.getColor(
-                                    com.android.internal.R.color.system_notification_accent_color))
-                            .build();
-            notificationManager.notifyAsUser(null,
-                    SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
-                    notification, UserHandle.ALL);
-            mKeyboardLayoutNotificationShown = true;
+        if (mConfiguredKeyboards.size() != 1) {
+            showKeyboardLayoutNotification(
+                    r.getString(R.string.keyboard_layout_notification_multiple_selected_title),
+                    r.getString(R.string.keyboard_layout_notification_multiple_selected_message),
+                    null);
+            return;
         }
+
+        final InputDevice inputDevice = getInputDevice(mConfiguredKeyboards.keyAt(0));
+        final Set<String> selectedLayouts = mConfiguredKeyboards.valueAt(0);
+        if (inputDevice == null || selectedLayouts == null || selectedLayouts.isEmpty()) {
+            return;
+        }
+
+        showKeyboardLayoutNotification(
+                r.getString(
+                        R.string.keyboard_layout_notification_selected_title,
+                        inputDevice.getName()),
+                createConfiguredNotificationText(mContext, selectedLayouts),
+                inputDevice);
     }
 
-    // Must be called on handler.
-    private void hideMissingKeyboardLayoutNotification(NotificationManager notificationManager) {
-        if (mKeyboardLayoutNotificationShown) {
-            mKeyboardLayoutNotificationShown = false;
-            notificationManager.cancelAsUser(null,
-                    SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
-                    UserHandle.ALL);
+    private String createConfiguredNotificationText(@NonNull Context context,
+            @NonNull Set<String> selectedLayouts) {
+        final Resources r = context.getResources();
+        List<String> layoutNames = new ArrayList<>();
+        selectedLayouts.forEach(
+                (layoutDesc) -> layoutNames.add(getKeyboardLayout(layoutDesc).getLabel()));
+        Collections.sort(layoutNames);
+        switch (layoutNames.size()) {
+            case 1:
+                return r.getString(R.string.keyboard_layout_notification_one_selected_message,
+                        layoutNames.get(0));
+            case 2:
+                return r.getString(R.string.keyboard_layout_notification_two_selected_message,
+                        layoutNames.get(0), layoutNames.get(1));
+            case 3:
+                return r.getString(R.string.keyboard_layout_notification_three_selected_message,
+                        layoutNames.get(0), layoutNames.get(1), layoutNames.get(2));
+            default:
+                return r.getString(
+                        R.string.keyboard_layout_notification_more_than_three_selected_message,
+                        layoutNames.get(0), layoutNames.get(1), layoutNames.get(2));
         }
     }
 
@@ -1094,6 +1220,31 @@
                 identifier.getDescriptor()) : null;
     }
 
+    private List<ImeInfo> getImeInfoListForLayoutMapping() {
+        List<ImeInfo> imeInfoList = new ArrayList<>();
+        UserManager userManager = Objects.requireNonNull(
+                mContext.getSystemService(UserManager.class));
+        InputMethodManager inputMethodManager = Objects.requireNonNull(
+                mContext.getSystemService(InputMethodManager.class));
+        for (UserHandle userHandle : userManager.getUserHandles(true /* excludeDying */)) {
+            int userId = userHandle.getIdentifier();
+            for (InputMethodInfo imeInfo : inputMethodManager.getEnabledInputMethodListAsUser(
+                    userId)) {
+                for (InputMethodSubtype imeSubtype :
+                        inputMethodManager.getEnabledInputMethodSubtypeList(
+                                imeInfo, true /* allowsImplicitlyEnabledSubtypes */)) {
+                    if (!imeSubtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
+                        continue;
+                    }
+                    imeInfoList.add(
+                            new ImeInfo(userId, InputMethodSubtypeHandle.of(imeInfo, imeSubtype),
+                                    imeSubtype));
+                }
+            }
+        }
+        return imeInfoList;
+    }
+
     private String createLayoutKey(InputDeviceIdentifier identifier, int userId,
             @NonNull InputMethodSubtypeHandle subtypeHandle) {
         Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null");
diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java
index a2b18362..bce210d 100644
--- a/services/core/java/com/android/server/input/PersistentDataStore.java
+++ b/services/core/java/com/android/server/input/PersistentDataStore.java
@@ -16,6 +16,7 @@
 
 package com.android.server.input;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.input.TouchCalibration;
 import android.util.ArrayMap;
@@ -43,6 +44,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -155,6 +157,16 @@
         return false;
     }
 
+    public boolean setSelectedKeyboardLayouts(String inputDeviceDescriptor,
+            @NonNull Set<String> selectedLayouts) {
+        InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
+        if (state.setSelectedKeyboardLayouts(selectedLayouts)) {
+            setDirty();
+            return true;
+        }
+        return false;
+    }
+
     public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
         InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
         if (state == null) {
@@ -408,6 +420,8 @@
 
         private final Map<String, String> mKeyboardLayoutMap = new ArrayMap<>();
 
+        private Set<String> mSelectedKeyboardLayouts;
+
         public TouchCalibration getTouchCalibration(int surfaceRotation) {
             try {
                 return mTouchCalibration[surfaceRotation];
@@ -439,6 +453,14 @@
             return !Objects.equals(mKeyboardLayoutMap.put(key, keyboardLayout), keyboardLayout);
         }
 
+        public boolean setSelectedKeyboardLayouts(@NonNull Set<String> selectedLayouts) {
+            if (Objects.equals(mSelectedKeyboardLayouts, selectedLayouts)) {
+                return false;
+            }
+            mSelectedKeyboardLayouts = new HashSet<>(selectedLayouts);
+            return true;
+        }
+
         @Nullable
         public String getCurrentKeyboardLayout() {
             return mCurrentKeyboardLayout;
@@ -588,6 +610,16 @@
                                 "Missing layout attribute on keyed-keyboard-layout.");
                     }
                     mKeyboardLayoutMap.put(key, layout);
+                } else if (parser.getName().equals("selected-keyboard-layout")) {
+                    String layout = parser.getAttributeValue(null, "layout");
+                    if (layout == null) {
+                        throw new XmlPullParserException(
+                                "Missing layout attribute on selected-keyboard-layout.");
+                    }
+                    if (mSelectedKeyboardLayouts == null) {
+                        mSelectedKeyboardLayouts = new HashSet<>();
+                    }
+                    mSelectedKeyboardLayouts.add(layout);
                 } else if (parser.getName().equals("light-info")) {
                     int lightId = parser.getAttributeInt(null, "light-id");
                     int lightBrightness = parser.getAttributeInt(null, "light-brightness");
@@ -668,6 +700,14 @@
                 serializer.endTag(null, "keyed-keyboard-layout");
             }
 
+            if (mSelectedKeyboardLayouts != null) {
+                for (String layout : mSelectedKeyboardLayouts) {
+                    serializer.startTag(null, "selected-keyboard-layout");
+                    serializer.attribute(null, "layout", layout);
+                    serializer.endTag(null, "selected-keyboard-layout");
+                }
+            }
+
             for (int i = 0; i < mKeyboardBacklightBrightnessMap.size(); i++) {
                 serializer.startTag(null, "light-info");
                 serializer.attributeInt(null, "light-id", mKeyboardBacklightBrightnessMap.keyAt(i));
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 94f12dd..653b718 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -1148,7 +1148,7 @@
         super.getPreloadedNanoAppIds_enforcePermission();
         Objects.requireNonNull(hubInfo, "hubInfo cannot be null");
 
-        long[] nanoappIds = mContextHubWrapper.getPreloadedNanoappIds();
+        long[] nanoappIds = mContextHubWrapper.getPreloadedNanoappIds(hubInfo.getId());
         if (nanoappIds == null) {
             return new long[0];
         }
@@ -1261,13 +1261,19 @@
             return;
         }
 
-        long[] preloadedNanoappIds = mContextHubWrapper.getPreloadedNanoappIds();
-        if (preloadedNanoappIds == null) {
-            return;
-        }
-        for (long preloadedNanoappId : preloadedNanoappIds) {
-            pw.print("ID: 0x");
-            pw.println(Long.toHexString(preloadedNanoappId));
+        for (int contextHubId: mContextHubIdToInfoMap.keySet()) {
+            long[] preloadedNanoappIds = mContextHubWrapper.getPreloadedNanoappIds(contextHubId);
+            if (preloadedNanoappIds == null) {
+                return;
+            }
+
+            pw.print("Context Hub (id=");
+            pw.print(contextHubId);
+            pw.println("):");
+            for (long preloadedNanoappId : preloadedNanoappIds) {
+                pw.print("  ID: 0x");
+                pw.println(Long.toHexString(preloadedNanoappId));
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 1e32ad6..eb1a0e2 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -363,9 +363,11 @@
      * Provides the list of preloaded nanoapp IDs on the system. The output of this API must
      * not change.
      *
-     * @return The list of preloaded nanoapp IDs
+     * @param contextHubId  The context Hub ID.
+     *
+     * @return The list of preloaded nanoapp IDs.
      */
-    public abstract long[] getPreloadedNanoappIds();
+    public abstract long[] getPreloadedNanoappIds(int contextHubId);
 
     /**
      * Registers a callback with the Context Hub.
@@ -714,14 +716,14 @@
             }
         }
 
-        public long[] getPreloadedNanoappIds() {
+        public long[] getPreloadedNanoappIds(int contextHubId) {
             android.hardware.contexthub.IContextHub hub = getHub();
             if (hub == null) {
                 return null;
             }
 
             try {
-                return hub.getPreloadedNanoappIds();
+                return hub.getPreloadedNanoappIds(contextHubId);
             } catch (RemoteException e) {
                 Log.e(TAG, "Exception while getting preloaded nanoapp IDs: " + e.getMessage());
                 return null;
@@ -924,7 +926,7 @@
                     mHub.queryApps(contextHubId));
         }
 
-        public long[] getPreloadedNanoappIds() {
+        public long[] getPreloadedNanoappIds(int contextHubId) {
             return new long[0];
         }
 
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 56f3296..92be094 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -1650,15 +1650,7 @@
                         mContext, 0, snoozeIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
 
                 final Intent viewIntent = buildViewDataUsageIntent(res, policy.template);
-                // TODO: Resolve to single code path.
-                if (UserManager.isHeadlessSystemUserMode()) {
-                    builder.setContentIntent(PendingIntent.getActivityAsUser(
-                            mContext, 0, viewIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE,
-                            /* options= */ null, UserHandle.CURRENT));
-                } else {
-                    builder.setContentIntent(PendingIntent.getActivity(
-                            mContext, 0, viewIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
-                }
+                setContentIntent(builder, viewIntent);
                 break;
             }
             case TYPE_LIMIT: {
@@ -1679,15 +1671,7 @@
                 builder.setSmallIcon(R.drawable.stat_notify_disabled_data);
 
                 final Intent intent = buildNetworkOverLimitIntent(res, policy.template);
-                // TODO: Resolve to single code path.
-                if (UserManager.isHeadlessSystemUserMode()) {
-                    builder.setContentIntent(PendingIntent.getActivityAsUser(
-                            mContext, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE,
-                            /* options= */ null, UserHandle.CURRENT));
-                } else {
-                    builder.setContentIntent(PendingIntent.getActivity(
-                            mContext, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
-                }
+                setContentIntent(builder, intent);
                 break;
             }
             case TYPE_LIMIT_SNOOZED: {
@@ -1711,15 +1695,7 @@
                 builder.setChannelId(SystemNotificationChannels.NETWORK_STATUS);
 
                 final Intent intent = buildViewDataUsageIntent(res, policy.template);
-                // TODO: Resolve to single code path.
-                if (UserManager.isHeadlessSystemUserMode()) {
-                    builder.setContentIntent(PendingIntent.getActivityAsUser(
-                            mContext, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE,
-                            /* options= */ null, UserHandle.CURRENT));
-                } else {
-                    builder.setContentIntent(PendingIntent.getActivity(
-                            mContext, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
-                }
+                setContentIntent(builder, intent);
                 break;
             }
             case TYPE_RAPID: {
@@ -1739,15 +1715,7 @@
                         mContext, 0, snoozeIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
 
                 final Intent viewIntent = buildViewDataUsageIntent(res, policy.template);
-                // TODO: Resolve to single code path.
-                if (UserManager.isHeadlessSystemUserMode()) {
-                    builder.setContentIntent(PendingIntent.getActivityAsUser(
-                            mContext, 0, viewIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE,
-                            /* options= */ null, UserHandle.CURRENT));
-                } else {
-                    builder.setContentIntent(PendingIntent.getActivity(
-                            mContext, 0, viewIntent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
-                }
+                setContentIntent(builder, viewIntent);
                 break;
             }
             default: {
@@ -1765,6 +1733,17 @@
         mActiveNotifs.add(notificationId);
     }
 
+    private void setContentIntent(Notification.Builder builder, Intent intent) {
+        if (UserManager.isHeadlessSystemUserMode()) {
+            builder.setContentIntent(PendingIntent.getActivityAsUser(
+                    mContext, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE,
+                    /* options= */ null, UserHandle.CURRENT));
+        } else {
+            builder.setContentIntent(PendingIntent.getActivity(
+                    mContext, 0, intent, FLAG_UPDATE_CURRENT | FLAG_IMMUTABLE));
+        }
+    }
+
     private void cancelNotification(NotificationId notificationId) {
         mContext.getSystemService(NotificationManager.class).cancel(notificationId.getTag(),
                 notificationId.getId());
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index de5f0c4..d3ee52c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -7218,6 +7218,7 @@
      * TODO(b/182523293): This should be removed once we finish migration of permission storage.
      */
     void writeSettingsLPrTEMP(boolean sync) {
+        snapshotComputer(false);
         mPermissionManager.writeLegacyPermissionsTEMP(mSettings.mPermissions);
         mSettings.writeLPr(mLiveComputer, sync);
     }
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 37a59da..4ec8afd 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -1247,6 +1247,11 @@
             case LONG_PRESS_POWER_SHUT_OFF:
             case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
                 mPowerKeyHandled = true;
+                // don't actually trigger the shutdown if we are running stability
+                // tests via monkey
+                if (ActivityManager.isUserAMonkey()) {
+                    break;
+                }
                 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON, false,
                         "Power - Long Press - Shut Off");
                 sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
@@ -4518,25 +4523,6 @@
      */
     private boolean isWakeKeyWhenScreenOff(int keyCode) {
         switch (keyCode) {
-            case KeyEvent.KEYCODE_VOLUME_UP:
-            case KeyEvent.KEYCODE_VOLUME_DOWN:
-            case KeyEvent.KEYCODE_VOLUME_MUTE:
-                return mDefaultDisplayPolicy.getDockMode() != Intent.EXTRA_DOCK_STATE_UNDOCKED;
-
-            case KeyEvent.KEYCODE_MUTE:
-            case KeyEvent.KEYCODE_HEADSETHOOK:
-            case KeyEvent.KEYCODE_MEDIA_PLAY:
-            case KeyEvent.KEYCODE_MEDIA_PAUSE:
-            case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
-            case KeyEvent.KEYCODE_MEDIA_STOP:
-            case KeyEvent.KEYCODE_MEDIA_NEXT:
-            case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
-            case KeyEvent.KEYCODE_MEDIA_REWIND:
-            case KeyEvent.KEYCODE_MEDIA_RECORD:
-            case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
-            case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK:
-                return false;
-
             case KeyEvent.KEYCODE_DPAD_UP:
             case KeyEvent.KEYCODE_DPAD_DOWN:
             case KeyEvent.KEYCODE_DPAD_LEFT:
diff --git a/services/core/java/com/android/server/policy/PowerAction.java b/services/core/java/com/android/server/policy/PowerAction.java
index d2de58e..deb86b5 100644
--- a/services/core/java/com/android/server/policy/PowerAction.java
+++ b/services/core/java/com/android/server/policy/PowerAction.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.policy;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.os.UserManager;
 import com.android.internal.globalactions.LongPressAction;
@@ -35,6 +36,11 @@
 
     @Override
     public boolean onLongPress() {
+        // don't actually trigger the reboot if we are running stability
+        // tests via monkey
+        if (ActivityManager.isUserAMonkey()) {
+            return false;
+        }
         UserManager um = mContext.getSystemService(UserManager.class);
         if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
             mWindowManagerFuncs.rebootSafeMode(true);
@@ -55,6 +61,11 @@
 
     @Override
     public void onPress() {
+        // don't actually trigger the shutdown if we are running stability
+        // tests via monkey
+        if (ActivityManager.isUserAMonkey()) {
+            return;
+        }
         // shutdown by making sure radio and power are handled accordingly.
         mWindowManagerFuncs.shutdown(false /* confirm */);
     }
diff --git a/services/core/java/com/android/server/policy/RestartAction.java b/services/core/java/com/android/server/policy/RestartAction.java
index 0f13da8..24c921e 100644
--- a/services/core/java/com/android/server/policy/RestartAction.java
+++ b/services/core/java/com/android/server/policy/RestartAction.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.policy;
 
+import android.app.ActivityManager;
 import android.content.Context;
 import android.os.UserManager;
 import com.android.internal.globalactions.LongPressAction;
@@ -35,6 +36,11 @@
 
     @Override
     public boolean onLongPress() {
+        // don't actually trigger the reboot if we are running stability
+        // tests via monkey
+        if (ActivityManager.isUserAMonkey()) {
+            return false;
+        }
         UserManager um = mContext.getSystemService(UserManager.class);
         if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
             mWindowManagerFuncs.rebootSafeMode(true);
@@ -55,6 +61,11 @@
 
     @Override
     public void onPress() {
+        // don't actually trigger the reboot if we are running stability
+        // tests via monkey
+        if (ActivityManager.isUserAMonkey()) {
+            return;
+        }
         mWindowManagerFuncs.reboot(false /* confirm */);
     }
 }
diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java
index 5096ad1..c2d4ac6 100644
--- a/services/core/java/com/android/server/power/ShutdownThread.java
+++ b/services/core/java/com/android/server/power/ShutdownThread.java
@@ -17,6 +17,7 @@
 
 package com.android.server.power;
 
+import android.app.ActivityManagerInternal;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.IActivityManager;
@@ -25,10 +26,12 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.IIntentReceiver;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManagerInternal;
 import android.media.AudioAttributes;
+import android.os.Bundle;
 import android.os.FileUtils;
 import android.os.Handler;
 import android.os.PowerManager;
@@ -51,7 +54,6 @@
 
 import com.android.server.LocalServices;
 import com.android.server.RescueParty;
-import com.android.server.pm.PackageManagerService;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.File;
@@ -448,13 +450,6 @@
                 new File(CHECK_POINTS_FILE_BASENAME));
         dumpCheckPointsThread.start();
 
-        BroadcastReceiver br = new BroadcastReceiver() {
-            @Override public void onReceive(Context context, Intent intent) {
-                // We don't allow apps to cancel this, so ignore the result.
-                actionDone();
-            }
-        };
-
         /*
          * Write a system property in case the system_server reboots before we
          * get to the actual hardware restart. If that happens, we'll retry at
@@ -490,8 +485,16 @@
         mActionDone = false;
         Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-        mContext.sendOrderedBroadcastAsUser(intent,
-                UserHandle.ALL, null, br, mHandler, 0, null, null);
+        final ActivityManagerInternal activityManagerInternal = LocalServices.getService(
+                ActivityManagerInternal.class);
+        activityManagerInternal.broadcastIntentWithCallback(intent,
+                new IIntentReceiver.Stub() {
+                    @Override
+                    public void performReceive(Intent intent, int resultCode, String data,
+                            Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
+                        mHandler.post(ShutdownThread.this::actionDone);
+                    }
+                }, null, UserHandle.USER_ALL, null, null, null);
 
         final long endTime = SystemClock.elapsedRealtime() + MAX_BROADCAST_TIME;
         synchronized (mActionDoneSync) {
diff --git a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
index de631bb..e7c073c 100644
--- a/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
+++ b/services/core/java/com/android/server/timedetector/NetworkTimeUpdateService.java
@@ -30,7 +30,6 @@
 import android.content.IntentFilter;
 import android.database.ContentObserver;
 import android.net.ConnectivityManager;
-import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
 import android.os.Binder;
 import android.os.Handler;
@@ -140,20 +139,12 @@
     /** Initialize the receivers and initiate the first NTP request */
     public void systemRunning() {
         // Listen for scheduled refreshes.
-        mContext.registerReceiver(
-                new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        onPollNetworkTime("scheduled refresh");
-                    }
-                },
-                new IntentFilter(ACTION_POLL),
-                /*broadcastPermission=*/ null,
-                mHandler);
+        ScheduledRefreshBroadcastReceiver receiver = new ScheduledRefreshBroadcastReceiver();
+        mContext.registerReceiver(receiver, new IntentFilter(ACTION_POLL));
 
         // Listen for network connectivity changes.
-        NetworkTimeUpdateCallback networkTimeUpdateCallback = new NetworkTimeUpdateCallback();
-        mCM.registerDefaultNetworkCallback(networkTimeUpdateCallback, mHandler);
+        NetworkConnectivityCallback networkConnectivityCallback = new NetworkConnectivityCallback();
+        mCM.registerDefaultNetworkCallback(networkConnectivityCallback, mHandler);
 
         // Listen for user settings changes.
         ContentResolver resolver = mContext.getContentResolver();
@@ -223,8 +214,25 @@
         }
     }
 
+    private class ScheduledRefreshBroadcastReceiver extends BroadcastReceiver implements Runnable {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // The BroadcastReceiver has to complete quickly or an ANR will be triggered by the
+            // platform regardless of the receiver thread used. Instead of blocking the receiver
+            // thread, the long-running / blocking work is posted to mHandler to allow onReceive()
+            // to return immediately.
+            mHandler.post(this);
+        }
+
+        @Override
+        public void run() {
+            onPollNetworkTime("scheduled refresh");
+        }
+    }
+
     // All callbacks will be invoked using mHandler because of how the callback is registered.
-    private class NetworkTimeUpdateCallback extends NetworkCallback {
+    private class NetworkConnectivityCallback extends ConnectivityManager.NetworkCallback {
         @Override
         public void onAvailable(@NonNull Network network) {
             Log.d(TAG, String.format("New default network %s; checking time.", network));
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 9aa7e56..fa3a186 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -702,8 +702,8 @@
                         + AppTransition.appTransitionOldToString(transition)
                         + " displayId: " + displayId);
             }
-            final boolean magnifying = mMagnifedViewport.isMagnifying();
-            if (magnifying) {
+            final boolean isMagnifierActivated = isForceShowingMagnifiableBounds();
+            if (isMagnifierActivated) {
                 switch (transition) {
                     case WindowManager.TRANSIT_OLD_ACTIVITY_OPEN:
                     case WindowManager.TRANSIT_OLD_TASK_FRAGMENT_OPEN:
@@ -727,8 +727,8 @@
                 Slog.i(LOG_TAG, "Window transition: " + WindowManager.transitTypeToString(type)
                         + " displayId: " + displayId);
             }
-            final boolean magnifying = mMagnifedViewport.isMagnifying();
-            if (magnifying) {
+            final boolean isMagnifierActivated = isForceShowingMagnifiableBounds();
+            if (isMagnifierActivated) {
                 // All opening/closing situations.
                 switch (type) {
                     case WindowManager.TRANSIT_OPEN:
@@ -751,12 +751,12 @@
                         + AppTransition.appTransitionOldToString(transition)
                         + " displayId: " + windowState.getDisplayId());
             }
-            final boolean magnifying = mMagnifedViewport.isMagnifying();
+            final boolean isMagnifierActivated = isForceShowingMagnifiableBounds();
             final int type = windowState.mAttrs.type;
             switch (transition) {
                 case WindowManagerPolicy.TRANSIT_ENTER:
                 case WindowManagerPolicy.TRANSIT_SHOW: {
-                    if (!magnifying) {
+                    if (!isMagnifierActivated) {
                         break;
                     }
                     switch (type) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 41fee1c..28b974c 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -573,6 +573,8 @@
 
     Drawable mEnterpriseThumbnailDrawable;
 
+    boolean mPauseSchedulePendingForPip = false;
+
     private void updateEnterpriseThumbnailDrawable(Context context) {
         DevicePolicyManager dpm = context.getSystemService(DevicePolicyManager.class);
         mEnterpriseThumbnailDrawable = dpm.getResources().getDrawable(
@@ -1502,6 +1504,12 @@
             mLastReportedMultiWindowMode = inPictureInPictureMode;
             ensureActivityConfiguration(0 /* globalChanges */, PRESERVE_WINDOWS,
                     true /* ignoreVisibility */);
+            if (inPictureInPictureMode && findMainWindow() == null) {
+                // Prevent malicious app entering PiP without valid WindowState, which can in turn
+                // result a non-touchable PiP window since the InputConsumer for PiP requires it.
+                EventLog.writeEvent(0x534e4554, "265293293", -1, "");
+                removeImmediately();
+            }
         }
     }
 
@@ -8118,9 +8126,7 @@
         // The smallest screen width is the short side of screen bounds. Because the bounds
         // and density won't be changed, smallestScreenWidthDp is also fixed.
         overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp;
-        // TODO(b/264276741): Check whether the runtime orietnation request is fixed rather than
-        // the manifest orientation which may be obsolete.
-        if (info.isFixedOrientation()) {
+        if (ActivityInfo.isFixedOrientation(getOverrideOrientation())) {
             // lock rotation too. When in size-compat, onConfigurationChanged will watch for and
             // apply runtime rotation changes.
             overrideConfig.windowConfiguration.setRotation(
@@ -8214,9 +8220,9 @@
         if (isFixedOrientationLetterboxAllowed) {
             resolveFixedOrientationConfiguration(newParentConfiguration);
         }
-
-        if (getCompatDisplayInsets() != null) {
-            resolveSizeCompatModeConfiguration(newParentConfiguration);
+        final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets();
+        if (compatDisplayInsets != null) {
+            resolveSizeCompatModeConfiguration(newParentConfiguration, compatDisplayInsets);
         } else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) {
             // We ignore activities' requested orientation in multi-window modes. They may be
             // taken into consideration in resolveFixedOrientationConfiguration call above.
@@ -8233,7 +8239,7 @@
             resolveAspectRatioRestriction(newParentConfiguration);
         }
 
-        if (isFixedOrientationLetterboxAllowed || getCompatDisplayInsets() != null
+        if (isFixedOrientationLetterboxAllowed || compatDisplayInsets != null
                 // In fullscreen, can be letterboxed for aspect ratio.
                 || !inMultiWindowMode()) {
             updateResolvedBoundsPosition(newParentConfiguration);
@@ -8241,7 +8247,7 @@
 
         boolean isIgnoreOrientationRequest = mDisplayContent != null
                 && mDisplayContent.getIgnoreOrientationRequest();
-        if (getCompatDisplayInsets() == null
+        if (compatDisplayInsets == null
                 // for size compat mode set in updateCompatDisplayInsets
                 // Fixed orientation letterboxing is possible on both large screen devices
                 // with ignoreOrientationRequest enabled and on phones in split screen even with
@@ -8288,7 +8294,7 @@
                         info.neverSandboxDisplayApis(sConstrainDisplayApisConfig),
                         info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig),
                         !matchParentBounds(),
-                        getCompatDisplayInsets() != null,
+                        compatDisplayInsets != null,
                         shouldCreateCompatDisplayInsets());
             }
             resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds);
@@ -8300,7 +8306,7 @@
     /**
      * @return The orientation to use to understand if reachability is enabled.
      */
-    @ActivityInfo.ScreenOrientation
+    @Configuration.Orientation
     int getOrientationForReachability() {
         return mLetterboxUiController.hasInheritedLetterboxBehavior()
                 ? mLetterboxUiController.getInheritedOrientation()
@@ -8704,7 +8710,8 @@
      * Resolves consistent screen configuration for orientation and rotation changes without
      * inheriting the parent bounds.
      */
-    private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration) {
+    private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration,
+            @NonNull CompatDisplayInsets compatDisplayInsets) {
         final Configuration resolvedConfig = getResolvedOverrideConfiguration();
         final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
 
@@ -8725,13 +8732,13 @@
                 ? requestedOrientation
                 // We should use the original orientation of the activity when possible to avoid
                 // forcing the activity in the opposite orientation.
-                : getCompatDisplayInsets().mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
-                        ? getCompatDisplayInsets().mOriginalRequestedOrientation
+                : compatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED
+                        ? compatDisplayInsets.mOriginalRequestedOrientation
                         : newParentConfiguration.orientation;
         int rotation = newParentConfiguration.windowConfiguration.getRotation();
         final boolean isFixedToUserRotation = mDisplayContent == null
                 || mDisplayContent.getDisplayRotation().isFixedToUserRotation();
-        if (!isFixedToUserRotation && !getCompatDisplayInsets().mIsFloating) {
+        if (!isFixedToUserRotation && !compatDisplayInsets.mIsFloating) {
             // Use parent rotation because the original display can be rotated.
             resolvedConfig.windowConfiguration.setRotation(rotation);
         } else {
@@ -8747,11 +8754,11 @@
         // rely on them to contain the original and unchanging width and height of the app.
         final Rect containingAppBounds = new Rect();
         final Rect containingBounds = mTmpBounds;
-        getCompatDisplayInsets().getContainerBounds(containingAppBounds, containingBounds, rotation,
+        compatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation,
                 orientation, orientationRequested, isFixedToUserRotation);
         resolvedBounds.set(containingBounds);
         // The size of floating task is fixed (only swap), so the aspect ratio is already correct.
-        if (!getCompatDisplayInsets().mIsFloating) {
+        if (!compatDisplayInsets.mIsFloating) {
             mIsAspectRatioApplied =
                     applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds);
         }
@@ -8760,7 +8767,7 @@
         // are calculated in compat container space. The actual position on screen will be applied
         // later, so the calculation is simpler that doesn't need to involve offset from parent.
         getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
-                getCompatDisplayInsets());
+                compatDisplayInsets);
         // Use current screen layout as source because the size of app is independent to parent.
         resolvedConfig.screenLayout = computeScreenLayout(
                 getConfiguration().screenLayout, resolvedConfig.screenWidthDp,
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 88d1086..7024886 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -172,6 +172,12 @@
     private static final int INVALID_LAUNCH_MODE = -1;
 
     /**
+     * Avoid problematical apps from occupying system resources (e.g. the amount of surface) by
+     * launching too many activities in a task.
+     */
+    private static final long MAX_TASK_WEIGHT_FOR_ADDING_ACTIVITY = 300;
+
+    /**
      * Feature flag to protect PendingIntent being abused to start background activity.
      */
     @ChangeId
@@ -1647,6 +1653,13 @@
         }
 
         if (targetTask != null) {
+            if (targetTask.getTreeWeight() > MAX_TASK_WEIGHT_FOR_ADDING_ACTIVITY) {
+                Slog.e(TAG, "Remove " + targetTask + " because it has contained too many"
+                        + " activities or windows (abort starting " + r
+                        + " from uid=" + mCallingUid);
+                targetTask.removeImmediately("bulky-task");
+                return START_ABORTED;
+            }
             mPriorAboveTask = TaskDisplayArea.getRootTaskAbove(targetTask.getRootTask());
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 2bd9052..5ea2854 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -473,9 +473,10 @@
     /** Writes current activity states to the proto stream. */
     public abstract void writeActivitiesToProto(ProtoOutputStream proto);
 
-    /** Dump the current state based on the command. */
+    /** Dump the current state based on the command and filters. */
     public abstract void dump(String cmd, FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, boolean dumpClient, String dumpPackage);
+            int opti, boolean dumpAll, boolean dumpClient, String dumpPackage,
+            int displayIdFilter);
 
     /** Dump the current state for inclusion in process dump. */
     public abstract boolean dumpForProcesses(FileDescriptor fd, PrintWriter pw, boolean dumpAll,
@@ -489,7 +490,8 @@
     /** Dump the current activities state. */
     public abstract boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name,
             String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly,
-            boolean dumpFocusedRootTaskOnly, @UserIdInt int userId);
+            boolean dumpFocusedRootTaskOnly, boolean dumpVerbose, int displayIdFilter,
+            @UserIdInt int userId);
 
     /** Dump the current state for inclusion in oom dump. */
     public abstract void dumpForOom(PrintWriter pw);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 923ca79..898b477 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -229,6 +229,7 @@
 import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
+import android.view.Display;
 import android.view.IRecentsAnimationRunner;
 import android.view.RemoteAnimationAdapter;
 import android.view.RemoteAnimationDefinition;
@@ -1495,7 +1496,7 @@
         a.persistableMode = ActivityInfo.PERSIST_NEVER;
         a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
         a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
-        a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS | ActivityInfo.FLAG_NO_HISTORY;
+        a.flags |= ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS;
         a.resizeMode = RESIZE_MODE_UNRESIZEABLE;
         a.configChanges = 0xffffffff;
 
@@ -3625,7 +3626,7 @@
                         null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
                         transition);
                 // Continue the pausing process after entering pip.
-                if (r.isState(PAUSING)) {
+                if (r.isState(PAUSING) && r.mPauseSchedulePendingForPip) {
                     r.getTask().schedulePauseActivity(r, false /* userLeaving */,
                             false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
                 }
@@ -4103,42 +4104,50 @@
         }
     }
 
-    void dumpVisibleActivitiesLocked(PrintWriter pw) {
+    void dumpVisibleActivitiesLocked(PrintWriter pw, int displayIdFilter) {
         pw.println("ACTIVITY MANAGER VISIBLE ACTIVITIES (dumpsys activity visible)");
         ArrayList<ActivityRecord> activities =
                 mRootWindowContainer.getDumpActivities("all", /* dumpVisibleRootTasksOnly */ true,
                         /* dumpFocusedRootTaskOnly */ false, UserHandle.USER_ALL);
         boolean needSeparator = false;
+        boolean printedAnything = false;
         for (int i = activities.size() - 1; i >= 0; i--) {
             ActivityRecord activity = activities.get(i);
-            if (!activity.isVisible()) {
+            if (!activity.isVisible() || (displayIdFilter != INVALID_DISPLAY
+                    && activity.getDisplayId() != displayIdFilter)) {
                 continue;
             }
             if (needSeparator) {
                 pw.println();
             }
+            printedAnything = true;
             activity.dump(pw, "", true);
             needSeparator = true;
         }
+        if (!printedAnything) {
+            pw.println("(nothing)");
+        }
     }
 
     void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) {
-        dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage,
+            int opti, boolean dumpAll, boolean dumpClient, String dumpPackage,
+            int displayIdFilter) {
+        dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage, displayIdFilter,
                 "ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)");
     }
 
     void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, boolean dumpClient, String dumpPackage, String header) {
+            int opti, boolean dumpAll, boolean dumpClient, String dumpPackage, int displayIdFilter,
+            String header) {
         pw.println(header);
 
         boolean printedAnything = mRootWindowContainer.dumpActivities(fd, pw, dumpAll, dumpClient,
-                dumpPackage);
+                dumpPackage, displayIdFilter);
         boolean needSep = printedAnything;
 
         boolean printed = ActivityTaskSupervisor.printThisActivity(pw,
-                mRootWindowContainer.getTopResumedActivity(), dumpPackage, needSep,
-                "  ResumedActivity: ", null);
+                mRootWindowContainer.getTopResumedActivity(), dumpPackage, displayIdFilter, needSep,
+                "  ResumedActivity: ", /* header= */ null);
         if (printed) {
             printedAnything = true;
             needSep = false;
@@ -4196,7 +4205,8 @@
      */
     protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args,
             int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly,
-            boolean dumpFocusedRootTaskOnly, @UserIdInt int userId) {
+            boolean dumpFocusedRootTaskOnly, boolean dumpVerbose, int displayIdFilter,
+            @UserIdInt int userId) {
         ArrayList<ActivityRecord> activities;
 
         synchronized (mGlobalLock) {
@@ -4213,6 +4223,7 @@
 
         Task lastTask = null;
         boolean needSep = false;
+        boolean printedAnything = false;
         for (int i = activities.size() - 1; i >= 0; i--) {
             ActivityRecord r = activities.get(i);
             if (needSep) {
@@ -4220,21 +4231,31 @@
             }
             needSep = true;
             synchronized (mGlobalLock) {
-                final Task task = r.getTask();
+                Task task = r.getTask();
+                int displayId = task.getDisplayId();
+                if (displayIdFilter != INVALID_DISPLAY && displayId != displayIdFilter) {
+                    continue;
+                }
                 if (lastTask != task) {
+                    printedAnything = true;
                     lastTask = task;
                     pw.print("TASK ");
                     pw.print(lastTask.affinity);
                     pw.print(" id=");
                     pw.print(lastTask.mTaskId);
                     pw.print(" userId=");
-                    pw.println(lastTask.mUserId);
+                    pw.print(lastTask.mUserId);
+                    printDisplayInfoAndNewLine(pw, r);
                     if (dumpAll) {
                         lastTask.dump(pw, "  ");
                     }
                 }
             }
-            dumpActivity("  ", fd, pw, activities.get(i), newArgs, dumpAll);
+            dumpActivity("  ", fd, pw, activities.get(i), newArgs, dumpAll, dumpVerbose);
+        }
+        if (!printedAnything) {
+            // Typically happpens when no task matches displayIdFilter
+            pw.println("(nothing)");
         }
         return true;
     }
@@ -4244,7 +4265,7 @@
      * there is a thread associated with the activity.
      */
     private void dumpActivity(String prefix, FileDescriptor fd, PrintWriter pw,
-            final ActivityRecord r, String[] args, boolean dumpAll) {
+            ActivityRecord r, String[] args, boolean dumpAll, boolean dumpVerbose) {
         String innerPrefix = prefix + "  ";
         IApplicationThread appThread = null;
         synchronized (mGlobalLock) {
@@ -4255,13 +4276,22 @@
             pw.print(Integer.toHexString(System.identityHashCode(r)));
             pw.print(" pid=");
             if (r.hasProcess()) {
-                pw.println(r.app.getPid());
+                pw.print(r.app.getPid());
                 appThread = r.app.getThread();
             } else {
-                pw.println("(not running)");
+                pw.print("(not running)");
+            }
+            if (dumpVerbose) {
+                pw.print(" userId=");
+                pw.print(r.mUserId);
+                pw.print(" uid=");
+                pw.print(r.getUid());
+                printDisplayInfoAndNewLine(pw, r);
+            } else {
+                pw.println();
             }
             if (dumpAll) {
-                r.dump(pw, innerPrefix, true /* dumpAll */);
+                r.dump(pw, innerPrefix, /* dumpAll= */ true);
             }
         }
         if (appThread != null) {
@@ -4279,6 +4309,20 @@
         }
     }
 
+    private void printDisplayInfoAndNewLine(PrintWriter pw, ActivityRecord r) {
+        pw.print(" displayId=");
+        DisplayContent displayContent = r.getDisplayContent();
+        if (displayContent == null) {
+            pw.println("N/A");
+            return;
+        }
+        Display display = displayContent.getDisplay();
+        pw.print(display.getDisplayId());
+        pw.print("(type=");
+        pw.print(Display.typeToString(display.getType()));
+        pw.println(")");
+    }
+
     private void writeSleepStateToProto(ProtoOutputStream proto, int wakeFullness,
             boolean testPssMode) {
         final long sleepToken = proto.start(ActivityManagerServiceDumpProcessesProto.SLEEP_STATUS);
@@ -5308,7 +5352,7 @@
                 + "------------");
         dumpActivitiesLocked(null /* fd */, pw, null /* args */, 0 /* opti */,
                 true /* dumpAll */, false /* dumpClient */, null /* dumpPackage */,
-                "" /* header */);
+                INVALID_DISPLAY, "" /* header */);
         pw.println();
         pw.close();
 
@@ -6359,10 +6403,11 @@
 
         @Override
         public void dump(String cmd, FileDescriptor fd, PrintWriter pw, String[] args, int opti,
-                boolean dumpAll, boolean dumpClient, String dumpPackage) {
+                boolean dumpAll, boolean dumpClient, String dumpPackage, int displayIdFilter) {
             synchronized (mGlobalLock) {
                 if (DUMP_ACTIVITIES_CMD.equals(cmd) || DUMP_ACTIVITIES_SHORT_CMD.equals(cmd)) {
-                    dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage);
+                    dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage,
+                            displayIdFilter);
                 } else if (DUMP_LASTANR_CMD.equals(cmd)) {
                     dumpLastANRLocked(pw);
                 } else if (DUMP_LASTANR_TRACES_CMD.equals(cmd)) {
@@ -6378,7 +6423,7 @@
                 } else if (DUMP_TOP_RESUMED_ACTIVITY.equals(cmd)) {
                     dumpTopResumedActivityLocked(pw);
                 } else if (DUMP_VISIBLE_ACTIVITIES.equals(cmd)) {
-                    dumpVisibleActivitiesLocked(pw);
+                    dumpVisibleActivitiesLocked(pw, displayIdFilter);
                 }
             }
         }
@@ -6573,9 +6618,11 @@
         @Override
         public boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name,
                 String[] args, int opti, boolean dumpAll, boolean dumpVisibleRootTasksOnly,
-                boolean dumpFocusedRootTaskOnly, @UserIdInt int userId) {
+                boolean dumpFocusedRootTaskOnly, boolean dumpVerbose, int displayIdFilter,
+                @UserIdInt int userId) {
             return ActivityTaskManagerService.this.dumpActivity(fd, pw, name, args, opti, dumpAll,
-                    dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, userId);
+                    dumpVisibleRootTasksOnly, dumpFocusedRootTaskOnly, dumpVerbose, displayIdFilter,
+                    userId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 710c4af..a0a2557 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2218,7 +2218,14 @@
 
     static boolean printThisActivity(PrintWriter pw, ActivityRecord activity, String dumpPackage,
             boolean needSep, String prefix, Runnable header) {
-        if (activity != null) {
+        return printThisActivity(pw, activity, dumpPackage, INVALID_DISPLAY, needSep, prefix,
+                header);
+    }
+
+    static boolean printThisActivity(PrintWriter pw, ActivityRecord activity, String dumpPackage,
+            int displayIdFilter, boolean needSep, String prefix, Runnable header) {
+        if (activity != null && (displayIdFilter == INVALID_DISPLAY
+                || displayIdFilter == activity.getDisplayId())) {
             if (dumpPackage == null || dumpPackage.equals(activity.packageName)) {
                 if (needSep) {
                     pw.println();
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 2d45dc2..f9f972c 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -21,6 +21,7 @@
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
 import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
@@ -31,6 +32,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.res.ResourceId;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -51,12 +53,14 @@
 import android.window.TaskSnapshot;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.policy.TransitionAnimation;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.LocalServices;
 import com.android.server.wm.utils.InsetUtils;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.function.Consumer;
 
 /**
@@ -80,6 +84,8 @@
     // re-parenting leashes and set launch behind, etc. Will be handled when transition finished.
     private AnimationHandler.ScheduleAnimationBuilder mPendingAnimationBuilder;
 
+    private static int sDefaultAnimationResId;
+
     /**
      * true if the back predictability feature is enabled
      */
@@ -255,9 +261,19 @@
             } else if (prevActivity != null) {
                 if (!isOccluded || prevActivity.canShowWhenLocked()) {
                     // We have another Activity in the same currentTask to go to
-                    backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
+                    final WindowContainer parent = currentActivity.getParent();
+                    final boolean isCustomize = parent != null
+                            && (parent.asTask() != null
+                            || (parent.asTaskFragment() != null
+                            && parent.canCustomizeAppTransition()))
+                            && isCustomizeExitAnimation(window);
+                    if (isCustomize) {
+                        infoBuilder.setWindowAnimations(
+                                window.mAttrs.packageName, window.mAttrs.windowAnimations);
+                    }
                     removedWindowContainer = currentActivity;
                     prevTask = prevActivity.getTask();
+                    backType = BackNavigationInfo.TYPE_CROSS_ACTIVITY;
                 } else {
                     backType = BackNavigationInfo.TYPE_CALLBACK;
                 }
@@ -370,6 +386,37 @@
         return kc.isKeyguardLocked(displayId) && kc.isDisplayOccluded(displayId);
     }
 
+    /**
+     * There are two ways to customize activity exit animation, one is to provide the
+     * windowAnimationStyle by Activity#setTheme, another one is to set resId by
+     * Window#setWindowAnimations.
+     * Not all run-time customization methods can be checked from here, such as
+     * overridePendingTransition, which the animation resource will be set just before the
+     * transition is about to happen.
+     */
+    private static boolean isCustomizeExitAnimation(WindowState window) {
+        // The default animation ResId is loaded from system package, so the result must match.
+        if (Objects.equals(window.mAttrs.packageName, "android")) {
+            return false;
+        }
+        if (window.mAttrs.windowAnimations != 0) {
+            final TransitionAnimation transitionAnimation = window.getDisplayContent()
+                    .mAppTransition.mTransitionAnimation;
+            final int attr = com.android.internal.R.styleable
+                    .WindowAnimation_activityCloseExitAnimation;
+            final int appResId = transitionAnimation.getAnimationResId(
+                    window.mAttrs, attr, TRANSIT_OLD_NONE);
+            if (ResourceId.isValid(appResId)) {
+                if (sDefaultAnimationResId == 0) {
+                    sDefaultAnimationResId = transitionAnimation.getDefaultAnimationResId(attr,
+                            TRANSIT_OLD_NONE);
+                }
+                return sDefaultAnimationResId != appResId;
+            }
+        }
+        return false;
+    }
+
     // For legacy transition.
     /**
      *  Once we find the transition targets match back animation targets, remove the target from
@@ -997,7 +1044,7 @@
 
                 return () -> {
                     try {
-                        mBackAnimationAdapter.getRunner().onAnimationStart(mType,
+                        mBackAnimationAdapter.getRunner().onAnimationStart(
                                 targets, null, null, callback);
                     } catch (RemoteException e) {
                         e.printStackTrace();
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ce3379e..389c908 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -20,11 +20,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.view.Display.TYPE_INTERNAL;
 import static android.view.InsetsFrameProvider.SOURCE_FRAME;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
@@ -198,6 +193,11 @@
 
     private boolean mCanSystemBarsBeShownByUser;
 
+    /**
+     * Let remote insets controller control system bars regardless of other settings.
+     */
+    private boolean mRemoteInsetsControllerControlsSystemBars;
+
     StatusBarManagerInternal getStatusBarManagerInternal() {
         synchronized (mServiceAcquireLock) {
             if (mStatusBarManagerInternal == null) {
@@ -781,6 +781,17 @@
         return mScreenOnListener;
     }
 
+
+    boolean isRemoteInsetsControllerControllingSystemBars() {
+        return mRemoteInsetsControllerControlsSystemBars;
+    }
+
+    @VisibleForTesting
+    void setRemoteInsetsControllerControlsSystemBars(
+            boolean remoteInsetsControllerControlsSystemBars) {
+        mRemoteInsetsControllerControlsSystemBars = remoteInsetsControllerControlsSystemBars;
+    }
+
     public void screenTurnedOn(ScreenOnListener screenOnListener) {
         synchronized (mLock) {
             mScreenOnEarly = true;
@@ -1029,7 +1040,6 @@
                         android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
                         "DisplayPolicy");
             }
-            enforceSingleInsetsTypeCorrespondingToWindowType(attrs.providedInsets);
         }
         return ADD_OKAY;
     }
@@ -1061,7 +1071,7 @@
                 final TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider =
                         getFrameProvider(win, provider, i);
                 final InsetsFrameProvider.InsetsSizeOverride[] overrides =
-                        provider.insetsSizeOverrides;
+                        provider.getInsetsSizeOverrides();
                 final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>>
                         overrideProviders;
                 if (overrides != null) {
@@ -1070,19 +1080,14 @@
                         final TriConsumer<DisplayFrames, WindowContainer, Rect>
                                 overrideFrameProvider =
                                 getOverrideFrameProvider(win, i, j);
-                        overrideProviders.put(overrides[j].windowType, overrideFrameProvider);
+                        overrideProviders.put(overrides[j].getWindowType(), overrideFrameProvider);
                     }
                 } else {
                     overrideProviders = null;
                 }
-                // TODO (b/234093736): Let InsetsFrameProvider have the following fields:
-                //                     - IBinder owner.
-                //                     - int index.
-                //                     - @InsetsType int type.
-                //                     So we can create the id by using InsetsSource#createId.
-                //                     And we won't need toPublicType anymore.
-                final int id = provider.type;
-                final @InsetsType int type = InsetsState.toPublicType(id);
+                final @InsetsType int type = provider.getType();
+                final int id = InsetsSource.createId(
+                        provider.getOwner(), provider.getIndex(), type);
                 mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(id, type)
                         .setWindowContainer(win, frameProvider, overrideProviders);
                 mInsetsSourceWindowsExceptIme.add(win);
@@ -1093,7 +1098,7 @@
     @Nullable
     private TriConsumer<DisplayFrames, WindowContainer, Rect> getFrameProvider(WindowState win,
             InsetsFrameProvider provider, int index) {
-        if (provider.insetsSize == null && provider.source == SOURCE_FRAME) {
+        if (provider.getInsetsSize() == null && provider.getSource() == SOURCE_FRAME) {
             return null;
         }
         return (displayFrames, windowContainer, inOutFrame) -> {
@@ -1101,8 +1106,8 @@
             final InsetsFrameProvider ifp = lp.providedInsets[index];
             InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted,
                     windowContainer.getBounds(), displayFrames.mDisplayCutoutSafe, inOutFrame,
-                    ifp.source, ifp.insetsSize, lp.privateFlags,
-                    ifp.minimalInsetsSizeInDisplayCutoutSafe);
+                    ifp.getSource(), ifp.getInsetsSize(), lp.privateFlags,
+                    ifp.getMinimalInsetsSizeInDisplayCutoutSafe());
         };
     }
 
@@ -1114,8 +1119,8 @@
             final InsetsFrameProvider ifp = lp.providedInsets[index];
             InsetsFrameProvider.calculateInsetsFrame(displayFrames.mUnrestricted,
                     windowContainer.getBounds(), displayFrames.mDisplayCutoutSafe, inOutFrame,
-                    ifp.source, ifp.insetsSizeOverrides[overrideIndex].insetsSize, lp.privateFlags,
-                    null);
+                    ifp.getSource(), ifp.getInsetsSizeOverrides()[overrideIndex].getInsetsSize(),
+                    lp.privateFlags, null /* displayCutoutSafeInsetsSize */);
         };
     }
 
@@ -1141,24 +1146,6 @@
         };
     }
 
-    private static void enforceSingleInsetsTypeCorrespondingToWindowType(
-            InsetsFrameProvider[] providers) {
-        int count = 0;
-        for (InsetsFrameProvider provider : providers) {
-            switch (provider.type) {
-                case ITYPE_NAVIGATION_BAR:
-                case ITYPE_STATUS_BAR:
-                case ITYPE_CLIMATE_BAR:
-                case ITYPE_EXTRA_NAVIGATION_BAR:
-                case ITYPE_CAPTION_BAR:
-                    if (++count > 1) {
-                        throw new IllegalArgumentException(
-                                "Multiple InsetsTypes corresponding to Window type");
-                    }
-            }
-        }
-    }
-
     /**
      * Called when a window is being removed from a window manager.  Must not
      * throw an exception -- clean up as much as possible.
@@ -1664,6 +1651,8 @@
         mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res);
         mNavigationBarAlwaysShowOnSideGesture =
                 res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture);
+        mRemoteInsetsControllerControlsSystemBars = res.getBoolean(
+                R.bool.config_remoteInsetsControllerControlsSystemBars);
 
         updateConfigurationAndScreenSizeDependentBehaviors();
 
@@ -2553,7 +2542,7 @@
         pw.print(mForceShowNavigationBarEnabled);
         pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
         pw.print(prefix); pw.print("mRemoteInsetsControllerControlsSystemBars=");
-        pw.println(mDisplayContent.getInsetsPolicy().getRemoteInsetsControllerControlsSystemBars());
+        pw.println(mRemoteInsetsControllerControlsSystemBars);
         pw.print(prefix); pw.println("mDecorInsetsInfo:");
         for (int rotation = 0; rotation < mDecorInsets.mInfoForRotation.length; rotation++) {
             final DecorInsets.Info info = mDecorInsets.mInfoForRotation[rotation];
@@ -2662,10 +2651,9 @@
      */
     private static boolean intersectsAnyInsets(Rect bounds, InsetsState insetsState,
             @InsetsType int insetsType) {
-        final ArraySet<Integer> internalTypes = InsetsState.toInternalType(insetsType);
-        for (int i = 0; i < internalTypes.size(); i++) {
-            final InsetsSource source = insetsState.peekSource(internalTypes.valueAt(i));
-            if (source == null || !source.isVisible()) {
+        for (int i = insetsState.sourceSize() - 1; i >= 0; i--) {
+            final InsetsSource source = insetsState.sourceAt(i);
+            if ((source.getType() & insetsType) == 0 || !source.isVisible()) {
                 continue;
             }
             if (Rect.intersects(bounds, source.getFrame())) {
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index c1f2b2b..868a15d 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -116,11 +116,6 @@
     private @InsetsType int mShowingTransientTypes;
     private boolean mAnimatingShown;
 
-    /**
-     * Let remote insets controller control system bars regardless of other settings.
-     */
-    private boolean mRemoteInsetsControllerControlsSystemBars;
-
     private final boolean mHideNavBarForKeyboard;
     private final float[] mTmpFloat9 = new float[9];
 
@@ -129,22 +124,9 @@
         mDisplayContent = displayContent;
         mPolicy = displayContent.getDisplayPolicy();
         final Resources r = mPolicy.getContext().getResources();
-        mRemoteInsetsControllerControlsSystemBars = r.getBoolean(
-                R.bool.config_remoteInsetsControllerControlsSystemBars);
         mHideNavBarForKeyboard = r.getBoolean(R.bool.config_hideNavBarForKeyboard);
     }
 
-    boolean getRemoteInsetsControllerControlsSystemBars() {
-        return mRemoteInsetsControllerControlsSystemBars;
-    }
-
-    /**
-     * Used only for testing.
-     */
-    @VisibleForTesting
-    void setRemoteInsetsControllerControlsSystemBars(boolean controlsSystemBars) {
-        mRemoteInsetsControllerControlsSystemBars = controlsSystemBars;
-    }
 
     /** Updates the target which can control system bars. */
     void updateBarControlTarget(@Nullable WindowState focusedWin) {
@@ -329,18 +311,16 @@
             state.removeSource(ID_IME);
         } else if (attrs.providedInsets != null) {
             for (InsetsFrameProvider provider : attrs.providedInsets) {
-                // TODO(b/234093736): Let InsetsFrameProvider return the public type and the ID.
-                final int sourceId = provider.type;
-                final @InsetsType int type = sourceId == ID_IME
-                        ? WindowInsets.Type.ime()
-                        : InsetsState.toPublicType(sourceId);
+                final int id = InsetsSource.createId(
+                        provider.getOwner(), provider.getIndex(), provider.getType());
+                final @InsetsType int type = provider.getType();
                 if ((type & WindowInsets.Type.systemBars()) == 0) {
                     continue;
                 }
                 if (state == originalState) {
                     state = new InsetsState(state);
                 }
-                state.removeSource(sourceId);
+                state.removeSource(id);
             }
         }
 
@@ -632,7 +612,8 @@
         if (focusedWin == null) {
             return false;
         }
-        if (!mRemoteInsetsControllerControlsSystemBars) {
+
+        if (!mPolicy.isRemoteInsetsControllerControllingSystemBars()) {
             return false;
         }
         if (mDisplayContent == null || mDisplayContent.mRemoteInsetsControlTarget == null) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 2b7a451..0953604 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -16,8 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.view.InsetsSource.ID_IME;
-
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS;
 import static com.android.server.wm.InsetsSourceProviderProto.CAPTURED_LEASH;
 import static com.android.server.wm.InsetsSourceProviderProto.CLIENT_VISIBLE;
@@ -41,7 +39,6 @@
 import android.graphics.Rect;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
-import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
 import android.view.InsetsSourceControl;
 import android.view.SurfaceControl;
@@ -514,33 +511,13 @@
     }
 
     protected void updateVisibility() {
-        mSource.setVisible(mServerVisible && (isMirroredSource() || mClientVisible));
+        mSource.setVisible(mServerVisible && mClientVisible);
         ProtoLog.d(WM_DEBUG_WINDOW_INSETS,
                 "InsetsSource updateVisibility for %s, serverVisible: %s clientVisible: %s",
                 WindowInsets.Type.toString(mSource.getType()),
                 mServerVisible, mClientVisible);
     }
 
-    private boolean isMirroredSource() {
-        if (mWindowContainer == null) {
-            return false;
-        }
-        if (mWindowContainer.asWindowState() == null) {
-            return false;
-        }
-        final InsetsFrameProvider[] providers =
-                ((WindowState) mWindowContainer).mAttrs.providedInsets;
-        if (providers == null) {
-            return false;
-        }
-        for (int i = 0; i < providers.length; i++) {
-            if (providers[i].type == ID_IME) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     InsetsSourceControl getControl(InsetsControlTarget target) {
         if (target == mControlTarget) {
             if (!mIsLeashReadyForDispatching && mControl != null) {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 0e1e63e..e4ffb8d 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -138,14 +138,6 @@
     }
 
     /**
-     * @return The provider of a source ID or null if we don't have it.
-     */
-    @Nullable
-    WindowContainerInsetsSourceProvider peekSourceProvider(int id) {
-        return mProviders.get(id);
-    }
-
-    /**
      * Called when a layout pass has occurred.
      */
     void onPostLayout() {
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 44b1cc8..7208934 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -1427,7 +1427,7 @@
      * the first opaque activity beneath.
      */
     boolean hasInheritedLetterboxBehavior() {
-        return mLetterboxConfigListener != null && !mActivityRecord.matchParentBounds();
+        return mLetterboxConfigListener != null;
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index e147219..b2a4df1 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -3472,7 +3472,7 @@
     }
 
     boolean dumpActivities(FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient,
-            String dumpPackage) {
+            String dumpPackage, int displayIdFilter) {
         boolean[] printed = {false};
         boolean[] needSep = {false};
         for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
@@ -3480,6 +3480,10 @@
             if (printed[0]) {
                 pw.println();
             }
+            if (displayIdFilter != Display.INVALID_DISPLAY
+                    && displayContent.mDisplayId != displayIdFilter) {
+                continue;
+            }
             pw.print("Display #");
             pw.print(displayContent.mDisplayId);
             pw.println(" (activities from top to bottom):");
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 7433c7e..3680e6d 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3516,7 +3516,10 @@
         info.isKeyguardOccluded =
             mAtmService.mKeyguardController.isDisplayOccluded(DEFAULT_DISPLAY);
 
-        info.startingWindowTypeParameter = activity.mStartingData.mTypeParams;
+        info.startingWindowTypeParameter = activity.mStartingData != null
+                ? activity.mStartingData.mTypeParams
+                : (StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED
+                        | StartingWindowInfo.TYPE_PARAMETER_WINDOWLESS);
         if ((info.startingWindowTypeParameter
                 & StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED) != 0) {
             final WindowState topMainWin = getWindow(w -> w.mAttrs.type == TYPE_BASE_APPLICATION);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 035859e..2ddb307 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1637,6 +1637,7 @@
 
         if (prev.attachedToProcess()) {
             if (shouldAutoPip) {
+                prev.mPauseSchedulePendingForPip = true;
                 boolean didAutoPip = mAtmService.enterPictureInPictureMode(
                         prev, prev.pictureInPictureArgs, false /* fromClient */);
                 ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "
@@ -1700,6 +1701,7 @@
             boolean pauseImmediately, boolean autoEnteringPip, String reason) {
         ProtoLog.v(WM_DEBUG_STATES, "Enqueueing pending pause: %s", prev);
         try {
+            prev.mPauseSchedulePendingForPip = false;
             EventLogTags.writeWmPauseActivity(prev.mUserId, System.identityHashCode(prev),
                     prev.shortComponentName, "userLeaving=" + userLeaving, reason);
 
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 3a30e4b..b131365 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.TaskInfo.cameraCompatControlStateToString;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
@@ -43,6 +44,7 @@
 import android.view.SurfaceControl;
 import android.window.ITaskOrganizer;
 import android.window.ITaskOrganizerController;
+import android.window.IWindowlessStartingSurfaceCallback;
 import android.window.SplashScreenView;
 import android.window.StartingWindowInfo;
 import android.window.StartingWindowRemovalInfo;
@@ -656,9 +658,10 @@
             info.splashScreenThemeResId = launchTheme;
         }
         info.taskSnapshot = taskSnapshot;
+        info.appToken = activity.token;
         // make this happen prior than prepare surface
         try {
-            lastOrganizer.addStartingWindow(info, activity.token);
+            lastOrganizer.addStartingWindow(info);
         } catch (RemoteException e) {
             Slog.e(TAG, "Exception sending onTaskStart callback", e);
             return false;
@@ -704,6 +707,55 @@
         }
     }
 
+    /**
+     * Create a starting surface which attach on a given surface.
+     * @param activity Target activity, this isn't necessary to be the top activity.
+     * @param root The root surface which the created surface will attach on.
+     * @param taskSnapshot Whether to draw snapshot.
+     * @param callback Called when surface is drawn and attached to the root surface.
+     * @return The taskId, this is a token and should be used to remove the surface, even if
+     *         the task was removed from hierarchy.
+     */
+    int addWindowlessStartingSurface(Task task, ActivityRecord activity, SurfaceControl root,
+            TaskSnapshot taskSnapshot, IWindowlessStartingSurfaceCallback callback) {
+        final Task rootTask = task.getRootTask();
+        if (rootTask == null) {
+            return INVALID_TASK_ID;
+        }
+        final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+        if (lastOrganizer == null) {
+            return INVALID_TASK_ID;
+        }
+        final StartingWindowInfo info = task.getStartingWindowInfo(activity);
+        info.taskInfo.taskDescription = activity.taskDescription;
+        info.taskSnapshot = taskSnapshot;
+        info.windowlessStartingSurfaceCallback = callback;
+        info.rootSurface = root;
+        try {
+            lastOrganizer.addStartingWindow(info);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Exception sending addWindowlessStartingSurface ", e);
+            return INVALID_TASK_ID;
+        }
+        return task.mTaskId;
+    }
+
+    void removeWindowlessStartingSurface(int taskId, boolean immediately) {
+        final ITaskOrganizer lastOrganizer = mTaskOrganizers.peekLast();
+        if (lastOrganizer == null || taskId == 0) {
+            return;
+        }
+        final StartingWindowRemovalInfo removalInfo = new StartingWindowRemovalInfo();
+        removalInfo.taskId = taskId;
+        removalInfo.windowlessSurface = true;
+        removalInfo.removeImmediately = immediately;
+        try {
+            lastOrganizer.removeStartingWindow(removalInfo);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Exception sending removeWindowlessStartingSurface ", e);
+        }
+    }
+
     boolean copySplashScreenView(Task task) {
         final Task rootTask = task.getRootTask();
         if (rootTask == null) {
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index da7400c..132f5a7 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -1473,7 +1473,7 @@
      *         {@link Configuration#ORIENTATION_PORTRAIT},
      *         {@link Configuration#ORIENTATION_UNDEFINED}).
      */
-    @ScreenOrientation
+    @Configuration.Orientation
     int getRequestedConfigurationOrientation() {
         return getRequestedConfigurationOrientation(false /* forDisplay */);
     }
@@ -1491,7 +1491,7 @@
      *         {@link Configuration#ORIENTATION_PORTRAIT},
      *         {@link Configuration#ORIENTATION_UNDEFINED}).
      */
-    @ScreenOrientation
+    @Configuration.Orientation
     int getRequestedConfigurationOrientation(boolean forDisplay) {
         int requestedOrientation = getOverrideOrientation();
         final RootDisplayArea root = getRootDisplayArea();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 98563f6..45cdacd 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2267,19 +2267,18 @@
                     if (win.mAttrs.providedInsets == null || attrs.providedInsets == null
                             || (win.mAttrs.providedInsets.length != attrs.providedInsets.length)) {
                         throw new IllegalArgumentException(
-                                "Insets types can not be changed after the window is added.");
+                                "Insets amount can not be changed after the window is added.");
                     } else {
                         final int insetsTypes = attrs.providedInsets.length;
                         for (int i = 0; i < insetsTypes; i++) {
-                            if (win.mAttrs.providedInsets[i].type != attrs.providedInsets[i].type) {
+                            if (!win.mAttrs.providedInsets[i].idEquals(attrs.providedInsets[i])) {
                                 throw new IllegalArgumentException(
-                                        "Insets types can not be changed after the window is "
-                                                + "added.");
+                                        "Insets ID can not be changed after the window is added.");
                             }
                             final InsetsFrameProvider.InsetsSizeOverride[] overrides =
-                                    win.mAttrs.providedInsets[i].insetsSizeOverrides;
+                                    win.mAttrs.providedInsets[i].getInsetsSizeOverrides();
                             final InsetsFrameProvider.InsetsSizeOverride[] newOverrides =
-                                    attrs.providedInsets[i].insetsSizeOverrides;
+                                    attrs.providedInsets[i].getInsetsSizeOverrides();
                             if (!(overrides == null && newOverrides == null)) {
                                 if (overrides == null || newOverrides == null
                                         || (overrides.length != newOverrides.length)) {
@@ -2289,7 +2288,8 @@
                                 } else {
                                     final int overrideTypes = overrides.length;
                                     for (int j = 0; j < overrideTypes; j++) {
-                                        if (overrides[j].windowType != newOverrides[j].windowType) {
+                                        if (overrides[j].getWindowType()
+                                                != newOverrides[j].getWindowType()) {
                                             throw new IllegalArgumentException(
                                                     "Insets override types can not be changed after"
                                                             + " the window is added.");
diff --git a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
index d768d23..14c49b3 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialDescriptionRegistry.java
@@ -34,7 +34,7 @@
 import java.util.concurrent.locks.ReentrantLock;
 
 /** Contains information on what CredentialProvider has what provisioned Credential. */
-public final class CredentialDescriptionRegistry {
+public class CredentialDescriptionRegistry {
 
     private static final int MAX_ALLOWED_CREDENTIAL_DESCRIPTIONS = 128;
     private static final int MAX_ALLOWED_ENTRIES_PER_PROVIDER = 16;
@@ -54,7 +54,8 @@
         final String mFlattenedRequest;
         final List<CredentialEntry> mCredentialEntries;
 
-        private FilterResult(String packageName,
+        @VisibleForTesting
+        FilterResult(String packageName,
                 String flattenedRequest,
                 List<CredentialEntry> credentialEntries) {
             mPackageName = packageName;
@@ -92,10 +93,10 @@
         }
     }
 
-    /** Clears an existing session for a given user identifier. */
+    /** Clears an existing session for a given user identifier. Used when testing only. */
     @GuardedBy("sLock")
     @VisibleForTesting
-    public static void clearAllSessions() {
+    static void clearAllSessions() {
         sLock.lock();
         try {
             sCredentialDescriptionSessionPerUser.clear();
@@ -104,6 +105,19 @@
         }
     }
 
+    /** Sets an existing session for a given user identifier. Used when testing only. */
+    @GuardedBy("sLock")
+    @VisibleForTesting
+    static void setSession(int userId, CredentialDescriptionRegistry
+            credentialDescriptionRegistry) {
+        sLock.lock();
+        try {
+            sCredentialDescriptionSessionPerUser.put(userId, credentialDescriptionRegistry);
+        } finally {
+            sLock.unlock();
+        }
+    }
+
     private Map<String, Set<CredentialDescription>> mCredentialDescriptions;
     private int mTotalDescriptionCount;
 
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 9c87005..6e998c4 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -296,6 +296,12 @@
                             mContext,
                             UserHandle.getCallingUserId(),
                             session,
+                            CredentialProviderInfoFactory.getCredentialProviderFromPackageName(
+                                    mContext, UserHandle.getCallingUserId() ,
+                                            result.second.mPackageName,
+                                            CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS,
+                                    new HashSet<>()),
+                            session.mClientAppInfo,
                             result.second.mPackageName,
                             result.first));
         }
@@ -358,6 +364,14 @@
         return providerSessions;
     }
 
+    private List<CredentialProviderInfo> getServicesForCredentialDescription(int userId) {
+        return CredentialProviderInfoFactory.getCredentialProviderServices(
+                mContext,
+                userId,
+                CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS,
+                new HashSet<>());
+    }
+
     @Override
     @GuardedBy("CredentialDescriptionRegistry.sLock")
     public void onUserStopped(@NonNull TargetUser user) {
@@ -813,14 +827,6 @@
 
             session.executeUnregisterRequest(request, callingPackage);
         }
-
-        private List<CredentialProviderInfo> getServicesForCredentialDescription(int userId) {
-            return CredentialProviderInfoFactory.getCredentialProviderServices(
-                    mContext,
-                    userId,
-                    CredentialManager.PROVIDER_FILTER_ALL_PROVIDERS,
-                    new HashSet<>());
-        }
     }
 
     private void enforceCallingPackage(String callingPackage, int callingUid) {
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index 8c6e5ce..f59b32c 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -41,7 +41,7 @@
  * Central session for a single getCredentials request. This class listens to the
  * responses from providers, and the UX app, and updates the provider(S) state.
  */
-public final class GetRequestSession extends RequestSession<GetCredentialRequest,
+public class GetRequestSession extends RequestSession<GetCredentialRequest,
         IGetCredentialCallback>
         implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
     private static final String TAG = "GetRequestSession";
diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
index 36d6b3d..457806d 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.credentials.CredentialOption;
+import android.credentials.CredentialProviderInfo;
 import android.credentials.GetCredentialException;
 import android.credentials.GetCredentialResponse;
 import android.credentials.ui.Entry;
@@ -33,6 +34,8 @@
 import android.service.credentials.CredentialProviderService;
 import android.telecom.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -52,7 +55,8 @@
         Set<CredentialDescriptionRegistry.FilterResult>> {
 
     private static final String TAG = "ProviderRegistryGetSession";
-    private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
+    @VisibleForTesting
+    static final String CREDENTIAL_ENTRY_KEY = "credential_key";
 
     /** Creates a new provider session to be used by the request session. */
     @Nullable
@@ -60,13 +64,16 @@
             @NonNull Context context,
             @UserIdInt int userId,
             @NonNull GetRequestSession getRequestSession,
+            @NonNull CredentialProviderInfo credentialProviderInfo,
+            @NonNull CallingAppInfo callingAppInfo,
             @NonNull String credentialProviderPackageName,
             @NonNull CredentialOption requestOption) {
         return new ProviderRegistryGetSession(
                 context,
                 userId,
                 getRequestSession,
-                getRequestSession.mClientAppInfo,
+                credentialProviderInfo,
+                callingAppInfo,
                 credentialProviderPackageName,
                 requestOption);
     }
@@ -81,15 +88,17 @@
     private final String mCredentialProviderPackageName;
     @NonNull
     private final String mFlattenedRequestOptionString;
-    private List<CredentialEntry> mCredentialEntries;
+    @VisibleForTesting
+    List<CredentialEntry> mCredentialEntries;
 
     protected ProviderRegistryGetSession(@NonNull Context context,
             @NonNull int userId,
             @NonNull GetRequestSession session,
+            @NonNull CredentialProviderInfo credentialProviderInfo,
             @NonNull CallingAppInfo callingAppInfo,
             @NonNull String servicePackageName,
             @NonNull CredentialOption requestOption) {
-        super(context, null, requestOption, session, userId, null);
+        super(context, credentialProviderInfo, requestOption, session, userId, null);
         mCredentialDescriptionRegistry = CredentialDescriptionRegistry.forUser(userId);
         mCallingAppInfo = callingAppInfo;
         mCredentialProviderPackageName = servicePackageName;
@@ -183,7 +192,7 @@
                             providerPendingIntentResponse.getResultData());
             if (getCredentialResponse != null) {
                 if (mCallbacks != null) {
-                    mCallbacks.onFinalResponseReceived(mComponentName,
+                    ((GetRequestSession) mCallbacks).onFinalResponseReceived(mComponentName,
                             getCredentialResponse);
                 }
                 return;
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
index a857695..a8b9bf6 100644
--- a/services/credentials/java/com/android/server/credentials/ProviderSession.java
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -114,7 +114,7 @@
                 @Nullable String message);
     }
 
-    protected ProviderSession(@NonNull Context context, @NonNull CredentialProviderInfo info,
+    protected ProviderSession(@NonNull Context context, @Nullable CredentialProviderInfo info,
             @NonNull T providerRequest,
             @Nullable ProviderInternalCallback callbacks,
             @NonNull int userId,
diff --git a/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
new file mode 100644
index 0000000..37ec8f0
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/metrics/CandidateBrowsingPhaseMetric.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials.metrics;
+
+/**
+ * A part of the Candidate Phase, but emitted alongside {@link ChosenProviderMetric}. The user is
+ * shown various entries from the provider responses, and may selectively browse through many
+ * entries. It is possible that the initial set of browsing is for a provider that is ultimately
+ * not chosen. This metric will be gathered PER browsing click, and aggregated, so that we can
+ * understand where user interaction is more cumbersome, informing us for future improvements. This
+ * can only be complete when the browsing is finished, ending in a final user choice, or possibly
+ * a cancellation. Thus, this will be collected and emitted in the final phase, though collection
+ * will begin in the candidate phase when the user begins browsing options.
+ */
+public class CandidateBrowsingPhaseMetric {
+
+    private static final String TAG = "CandidateSelectionPhaseMetric";
+    private static final int SEQUENCE_ID = 3;
+    // The session id associated with the API Call this candidate provider is a part of, default -1
+    private int mSessionId = -1;
+    // The EntryEnum that was pressed, defaults to -1 (TODO immediately, generate entry enum).
+    private int mEntryEnum = -1;
+    // The provider associated with the press, defaults to -1
+    private int mProviderUid = -1;
+
+    /* -- The session ID -- */
+
+    public void setSessionId(int sessionId) {
+        mSessionId = sessionId;
+    }
+
+    public int getSessionId() {
+        return mSessionId;
+    }
+
+    /* -- The sequence ID -- */
+
+    public int getSequenceId() {
+        return SEQUENCE_ID;
+    }
+
+    /* -- The Entry of this tap -- */
+
+    public void setEntryEnum(int entryEnum) {
+        mEntryEnum = entryEnum;
+    }
+
+    public int getEntryEnum() {
+        return mEntryEnum;
+    }
+
+    /* -- The Provider UID of this Tap -- */
+
+    public void setProviderUid(int providerUid) {
+        mProviderUid = providerUid;
+    }
+
+    public int getProviderUid() {
+        return mProviderUid;
+    }
+}
diff --git a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
index 0993295..2505abf2 100644
--- a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
+++ b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java
@@ -152,6 +152,8 @@
             }
         } catch (SQLiteException exception) {
             Slog.w("SQLite exception when querying contacts.", exception);
+        } catch (IllegalArgumentException exception) {
+            Slog.w("Illegal Argument exception when querying contacts.", exception);
         }
         if (found && lookupKey != null && hasPhoneNumber) {
             return queryPhoneNumber(lookupKey);
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 0ca4dfc..54d2c19 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -129,7 +129,6 @@
     private final List<PeopleService.ConversationsListener> mConversationsListeners =
             new ArrayList<>(1);
     private final Handler mHandler;
-
     private ContentObserver mCallLogContentObserver;
     private ContentObserver mMmsSmsContentObserver;
 
@@ -1106,6 +1105,7 @@
                 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
             mInjector.getBackgroundExecutor().execute(() -> {
                 PackageData packageData = getPackage(packageName, user.getIdentifier());
+                boolean hasCachedShortcut = false;
                 for (ShortcutInfo shortcut : shortcuts) {
                     if (ShortcutHelper.isConversationShortcut(
                             shortcut, mShortcutServiceInternal, user.getIdentifier())) {
@@ -1114,15 +1114,18 @@
                                     ? packageData.getConversationInfo(shortcut.getId()) : null;
                             if (conversationInfo == null
                                     || !conversationInfo.isShortcutCachedForNotification()) {
-                                // This is a newly cached shortcut. Clean up the existing cached
-                                // shortcuts to ensure the cache size is under the limit.
-                                cleanupCachedShortcuts(user.getIdentifier(),
-                                        MAX_CACHED_RECENT_SHORTCUTS - 1);
+                                hasCachedShortcut = true;
                             }
                         }
                         addOrUpdateConversationInfo(shortcut);
                     }
                 }
+                // Added at least one new conversation. Uncache older existing cached
+                // shortcuts to ensure the cache size is under the limit.
+                if (hasCachedShortcut) {
+                    cleanupCachedShortcuts(user.getIdentifier(),
+                            MAX_CACHED_RECENT_SHORTCUTS);
+                }
             });
         }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
index 7942e24..19aae19 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -643,6 +643,34 @@
         verify(mHolder.screenOffBrightnessSensorController).stop();
     }
 
+    @Test
+    public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
+        float brightness = 0.3f;
+        float nits = 500;
+        when(mResourcesMock.getBoolean(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
+                .thenReturn(true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+
+        mHolder.dpc.setBrightness(brightness);
+        verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+
+        float newBrightness = 0.4f;
+        when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+        when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(newBrightness);
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
+        verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
+    }
+
     private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
             String uniqueId) {
         return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
index 16bf2a22..02b6ea0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -488,17 +488,17 @@
         // We should still set screen state for the default display
         DisplayPowerRequest dpr = new DisplayPowerRequest();
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1);
+        advanceTime(1); // Run updatePowerState
         verify(mHolder.displayPowerState, times(2)).setScreenState(anyInt());
 
         mHolder = createDisplayPowerController(42, UNIQUE_ID);
 
         mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
-        advanceTime(1);
+        advanceTime(1); // Run updatePowerState
         verify(mHolder.displayPowerState, never()).setScreenState(anyInt());
 
         mHolder.dpc.onBootCompleted();
-        advanceTime(1);
+        advanceTime(1); // Run updatePowerState
         verify(mHolder.displayPowerState).setScreenState(anyInt());
     }
 
@@ -647,6 +647,34 @@
         verify(mHolder.screenOffBrightnessSensorController).stop();
     }
 
+    @Test
+    public void testBrightnessNitsPersistWhenDisplayDeviceChanges() {
+        float brightness = 0.3f;
+        float nits = 500;
+        when(mResourcesMock.getBoolean(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
+                .thenReturn(true);
+        mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
+        when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+
+        mHolder.dpc.setBrightness(brightness);
+        verify(mHolder.brightnessSetting).setBrightnessNitsForDefaultDisplay(nits);
+
+        float newBrightness = 0.4f;
+        when(mHolder.brightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+        when(mHolder.automaticBrightnessController.convertToFloatScale(nits))
+                .thenReturn(newBrightness);
+        // New display device
+        setUpDisplay(DISPLAY_ID, "new_unique_id", mHolder.display, mock(DisplayDevice.class),
+                mock(DisplayDeviceConfig.class), /* isEnabled= */ true);
+        mHolder.dpc.onDisplayChanged(mHolder.hbmMetadata, Layout.NO_LEAD_DISPLAY);
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+        // One triggered by handleBrightnessModeChange, another triggered by onDisplayChanged
+        verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat());
+    }
+
     private DisplayPowerControllerHolder createDisplayPowerController(int displayId,
             String uniqueId) {
         return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index f334a6a..3ba5d1e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -41,7 +41,7 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.JobSchedulerBackgroundThread;
+import com.android.server.AppSchedulingModuleThread;
 import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerService;
 
@@ -180,7 +180,7 @@
     }
 
     private void waitForNonDelayedMessagesProcessed() {
-        JobSchedulerBackgroundThread.getHandler().runWithScissors(() -> {}, 15_000);
+        AppSchedulingModuleThread.getHandler().runWithScissors(() -> {}, 15_000);
     }
 
     private JobInfo.Builder createBaseJobInfoBuilder(int jobId) {
diff --git a/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
new file mode 100644
index 0000000..3d52ac5
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/credentials/ProviderRegistryGetSessionTest.java
@@ -0,0 +1,341 @@
+/*
+ * 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.credentials;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.app.Activity;
+import android.app.slice.Slice;
+import android.app.slice.SliceSpec;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ServiceInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
+import android.credentials.Credential;
+import android.credentials.CredentialOption;
+import android.credentials.CredentialProviderInfo;
+import android.credentials.GetCredentialException;
+import android.credentials.GetCredentialResponse;
+import android.credentials.ui.GetCredentialProviderData;
+import android.credentials.ui.ProviderPendingIntentResponse;
+import android.net.Uri;
+import android.os.Bundle;
+import android.service.credentials.CallingAppInfo;
+import android.service.credentials.CredentialEntry;
+import android.service.credentials.CredentialProviderInfoFactory;
+import android.service.credentials.CredentialProviderService;
+import android.service.credentials.GetCredentialRequest;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+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;
+
+import java.security.cert.CertificateException;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for CredentialDescriptionRegistry.
+ *
+ * atest FrameworksServicesTests:com.android.server.credentials.ProviderRegistryGetSessionTest
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ProviderRegistryGetSessionTest {
+
+    private static final String CALLING_PACKAGE_NAME = "com.credman.app";
+    private static final int USER_ID_1 = 1;
+    private static final String FLATTENED_REQUEST = "FLATTENED_REQ";
+    private static final String CP_SERVICE_NAME = "CredentialProvider";
+    private static final ComponentName CREDENTIAL_PROVIDER_COMPONENT =
+            new ComponentName(CALLING_PACKAGE_NAME, CP_SERVICE_NAME);
+    private static final String GET_CREDENTIAL_EXCEPTION_TYPE = "TYPE";
+    private static final String CREDENTIAL_TYPE = "MDOC";
+    private static final String GET_CREDENTIAL_EXCEPTION_MESSAGE = "MESSAGE";
+
+    private ProviderRegistryGetSession mProviderRegistryGetSession;
+    @Mock private GetRequestSession mGetRequestSession;
+    private CredentialOption mGetCredentialOption;
+    @Mock private ServiceInfo mServiceInfo;
+    private CredentialProviderInfo mCredentialProviderInfo;
+    private CallingAppInfo mCallingAppInfo;
+    @Mock private CredentialDescriptionRegistry mCredentialDescriptionRegistry;
+    private Bundle mRetrievalData;
+    @Mock private CredentialEntry mEntry;
+    @Mock private CredentialEntry mEntry2;
+    private Slice mSlice;
+    private Slice mSlice2;
+    private CredentialDescriptionRegistry.FilterResult mResult;
+    private Set<CredentialDescriptionRegistry.FilterResult> mResponse;
+
+    @SuppressWarnings("GuardedBy")
+    @Before
+    public void setUp() throws CertificateException {
+        MockitoAnnotations.initMocks(this);
+        final Context context = ApplicationProvider.getApplicationContext();
+        mRetrievalData = new Bundle();
+        mRetrievalData.putString(CredentialOption.FLATTENED_REQUEST, FLATTENED_REQUEST);
+        mCallingAppInfo = createCallingAppInfo();
+        mGetCredentialOption = new CredentialOption(CREDENTIAL_TYPE, mRetrievalData,
+                new Bundle(), false);
+        when(mServiceInfo.getComponentName()).thenReturn(CREDENTIAL_PROVIDER_COMPONENT);
+        mCredentialProviderInfo = CredentialProviderInfoFactory
+                .createForTests(mServiceInfo,
+                        /* overrideLabel= */ "test",
+                        /* isSystemProvider= */ false,
+                        /* isEnabled= */ true,
+                        /* capabilities= */ Collections.EMPTY_LIST);
+        CredentialDescriptionRegistry.setSession(USER_ID_1, mCredentialDescriptionRegistry);
+        mResponse = new HashSet<>();
+        mSlice = createSlice();
+        mSlice2 = createSlice();
+        when(mEntry.getSlice()).thenReturn(mSlice);
+        when(mEntry2.getSlice()).thenReturn(mSlice2);
+        mResult = new CredentialDescriptionRegistry.FilterResult(CALLING_PACKAGE_NAME,
+                FLATTENED_REQUEST,
+                List.of(mEntry, mEntry2));
+        mResponse.add(mResult);
+        when(mCredentialDescriptionRegistry.getFilteredResultForProvider(anyString(), anyString()))
+                .thenReturn(mResponse);
+        mProviderRegistryGetSession = ProviderRegistryGetSession
+                .createNewSession(context, USER_ID_1, mGetRequestSession, mCredentialProviderInfo,
+                        mCallingAppInfo,
+                        CALLING_PACKAGE_NAME,
+                        mGetCredentialOption);
+    }
+
+    @Test
+    public void testInvokeSession_existingProvider_setsResults() {
+        final ArgumentCaptor<String> packageNameCaptor = ArgumentCaptor.forClass(String.class);
+        final ArgumentCaptor<String> flattenedRequestCaptor = ArgumentCaptor.forClass(String.class);
+        final ArgumentCaptor<ProviderSession.Status> statusCaptor =
+                ArgumentCaptor.forClass(ProviderSession.Status.class);
+        final ArgumentCaptor<ComponentName> cpComponentNameCaptor =
+                ArgumentCaptor.forClass(ComponentName.class);
+
+        mProviderRegistryGetSession.invokeSession();
+
+        verify(mCredentialDescriptionRegistry).getFilteredResultForProvider(
+                packageNameCaptor.capture(),
+                flattenedRequestCaptor.capture());
+        assertThat(packageNameCaptor.getValue()).isEqualTo(CALLING_PACKAGE_NAME);
+        assertThat(flattenedRequestCaptor.getValue()).isEqualTo(FLATTENED_REQUEST);
+        verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
+                cpComponentNameCaptor.capture());
+        assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
+        assertThat(cpComponentNameCaptor.getValue()).isEqualTo(CREDENTIAL_PROVIDER_COMPONENT);
+        assertThat(mProviderRegistryGetSession.mCredentialEntries).hasSize(2);
+        assertThat(mProviderRegistryGetSession.mCredentialEntries.get(0)).isSameInstanceAs(mEntry);
+        assertThat(mProviderRegistryGetSession.mCredentialEntries.get(1)).isSameInstanceAs(mEntry2);
+    }
+
+    @Test
+    public void testPrepareUiData_statusNonUIInvoking_throwsIllegalStateException() {
+        mProviderRegistryGetSession.setStatus(ProviderSession.Status.CREDENTIALS_RECEIVED);
+
+        assertThrows(IllegalStateException.class,
+                () -> mProviderRegistryGetSession.prepareUiData());
+    }
+
+    @Test
+    public void testPrepareUiData_statusUIInvokingNoResults_returnsNull() {
+        mProviderRegistryGetSession.setStatus(ProviderSession.Status.CANCELED);
+
+        assertThat(mProviderRegistryGetSession.prepareUiData()).isNull();
+    }
+
+    @Test
+    public void testPrepareUiData_invokeCalledSuccessfully_returnsCorrectData() {
+        mProviderRegistryGetSession.invokeSession();
+        GetCredentialProviderData providerData = (GetCredentialProviderData)
+                mProviderRegistryGetSession.prepareUiData();
+
+        assertThat(providerData).isNotNull();
+        assertThat(providerData.getCredentialEntries()).hasSize(2);
+        assertThat(providerData.getCredentialEntries().get(0).getSlice()).isSameInstanceAs(mSlice);
+        assertThat(providerData.getCredentialEntries().get(1).getSlice()).isSameInstanceAs(mSlice2);
+        Intent intent = providerData.getCredentialEntries().get(0).getFrameworkExtrasIntent();
+        GetCredentialRequest getRequest = intent.getParcelableExtra(CredentialProviderService
+                .EXTRA_GET_CREDENTIAL_REQUEST, GetCredentialRequest.class);
+        assertThat(getRequest.getCallingAppInfo()).isSameInstanceAs(mCallingAppInfo);
+        assertThat(getRequest.getCredentialOptions().get(0)).isSameInstanceAs(mGetCredentialOption);
+        Intent intent2 = providerData.getCredentialEntries().get(0).getFrameworkExtrasIntent();
+        GetCredentialRequest getRequest2 = intent2.getParcelableExtra(CredentialProviderService
+                .EXTRA_GET_CREDENTIAL_REQUEST, GetCredentialRequest.class);
+        assertThat(getRequest2.getCallingAppInfo()).isSameInstanceAs(mCallingAppInfo);
+        assertThat(getRequest2.getCredentialOptions().get(0))
+                .isSameInstanceAs(mGetCredentialOption);
+    }
+
+    @Test
+    public void testOnUiEntrySelected_wrongEntryKey_doesNothing() {
+        final Intent intent = new Intent();
+        final GetCredentialResponse response =
+                new GetCredentialResponse(new Credential(CREDENTIAL_TYPE, new Bundle()));
+        intent.putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response);
+        final ProviderPendingIntentResponse providerPendingIntentResponse = new
+                ProviderPendingIntentResponse(Activity.RESULT_OK, intent);
+
+        mProviderRegistryGetSession.onUiEntrySelected(
+                ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
+                "unsupportedKey", providerPendingIntentResponse);
+
+        verifyZeroInteractions(mGetRequestSession);
+    }
+
+    @Test
+    public void testOnUiEntrySelected_nullPendingIntentResponse_doesNothing() {
+        mProviderRegistryGetSession.onUiEntrySelected(
+                ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
+                ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY, null);
+
+        verifyZeroInteractions(mGetRequestSession);
+    }
+
+    @Test
+    public void testOnUiEntrySelected_pendingIntentWithException_callbackWithGivenException() {
+        final ArgumentCaptor<String> exceptionTypeCaptor =
+                ArgumentCaptor.forClass(String.class);
+        final ArgumentCaptor<String> exceptionMessageCaptor =
+                ArgumentCaptor.forClass(String.class);
+        final ArgumentCaptor<ComponentName> cpComponentNameCaptor =
+                ArgumentCaptor.forClass(ComponentName.class);
+        final ArgumentCaptor<ProviderSession.Status> statusCaptor =
+                ArgumentCaptor.forClass(ProviderSession.Status.class);
+        final GetCredentialException exception =
+                new GetCredentialException(GET_CREDENTIAL_EXCEPTION_TYPE,
+                        GET_CREDENTIAL_EXCEPTION_MESSAGE);
+        mProviderRegistryGetSession.invokeSession();
+        GetCredentialProviderData providerData = (GetCredentialProviderData)
+                mProviderRegistryGetSession.prepareUiData();
+        String entryKey = providerData.getCredentialEntries().get(0).getSubkey();
+        final Intent intent = new Intent();
+        intent.putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION, exception);
+        final ProviderPendingIntentResponse providerPendingIntentResponse = new
+                ProviderPendingIntentResponse(Activity.RESULT_OK, intent);
+
+        mProviderRegistryGetSession.onUiEntrySelected(
+                ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
+                entryKey, providerPendingIntentResponse);
+
+        verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
+                cpComponentNameCaptor.capture());
+        assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
+        verify(mGetRequestSession).onFinalErrorReceived(cpComponentNameCaptor.capture(),
+                exceptionTypeCaptor.capture(), exceptionMessageCaptor.capture());
+        assertThat(cpComponentNameCaptor.getValue())
+                .isSameInstanceAs(CREDENTIAL_PROVIDER_COMPONENT);
+        assertThat(exceptionTypeCaptor.getValue()).isEqualTo(GET_CREDENTIAL_EXCEPTION_TYPE);
+        assertThat(exceptionMessageCaptor.getValue()).isEqualTo(GET_CREDENTIAL_EXCEPTION_MESSAGE);
+    }
+
+    @Test
+    public void testOnUiEntrySelected_pendingIntentWithException_callbackWithCancelledException() {
+        final ArgumentCaptor<String> exceptionTypeCaptor =
+                ArgumentCaptor.forClass(String.class);
+        final ArgumentCaptor<String> exceptionMessageCaptor =
+                ArgumentCaptor.forClass(String.class);
+        final ArgumentCaptor<ComponentName> cpComponentNameCaptor =
+                ArgumentCaptor.forClass(ComponentName.class);
+        final ArgumentCaptor<ProviderSession.Status> statusCaptor =
+                ArgumentCaptor.forClass(ProviderSession.Status.class);
+
+        mProviderRegistryGetSession.invokeSession();
+        GetCredentialProviderData providerData = (GetCredentialProviderData)
+                mProviderRegistryGetSession.prepareUiData();
+        String entryKey = providerData.getCredentialEntries().get(0).getSubkey();
+        final Intent intent = new Intent();
+        final ProviderPendingIntentResponse providerPendingIntentResponse = new
+                ProviderPendingIntentResponse(Activity.RESULT_CANCELED, intent);
+
+        mProviderRegistryGetSession.onUiEntrySelected(
+                ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
+                entryKey, providerPendingIntentResponse);
+
+        verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
+                cpComponentNameCaptor.capture());
+        assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
+        verify(mGetRequestSession).onFinalErrorReceived(cpComponentNameCaptor.capture(),
+                exceptionTypeCaptor.capture(), exceptionMessageCaptor.capture());
+        assertThat(cpComponentNameCaptor.getValue())
+                .isSameInstanceAs(CREDENTIAL_PROVIDER_COMPONENT);
+        assertThat(exceptionTypeCaptor.getValue())
+                .isEqualTo(GetCredentialException.TYPE_USER_CANCELED);
+    }
+
+    @Test
+    public void testOnUiEntrySelected_correctEntryKeyPendingIntentResponseExists_succeeds() {
+        final ArgumentCaptor<GetCredentialResponse> getCredentialResponseCaptor =
+                ArgumentCaptor.forClass(GetCredentialResponse.class);
+        final ArgumentCaptor<ComponentName> cpComponentNameCaptor =
+                ArgumentCaptor.forClass(ComponentName.class);
+        final ArgumentCaptor<ProviderSession.Status> statusCaptor =
+                ArgumentCaptor.forClass(ProviderSession.Status.class);
+        mProviderRegistryGetSession.invokeSession();
+        GetCredentialProviderData providerData = (GetCredentialProviderData)
+                mProviderRegistryGetSession.prepareUiData();
+        final Intent intent = new Intent();
+        final GetCredentialResponse response =
+                new GetCredentialResponse(new Credential(CREDENTIAL_TYPE, new Bundle()));
+        intent.putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response);
+        String entryKey = providerData.getCredentialEntries().get(0).getSubkey();
+        final ProviderPendingIntentResponse providerPendingIntentResponse = new
+                ProviderPendingIntentResponse(Activity.RESULT_OK, intent);
+
+        mProviderRegistryGetSession.onUiEntrySelected(
+                ProviderRegistryGetSession.CREDENTIAL_ENTRY_KEY,
+                entryKey, providerPendingIntentResponse);
+
+        verify(mGetRequestSession).onProviderStatusChanged(statusCaptor.capture(),
+                cpComponentNameCaptor.capture());
+        assertThat(statusCaptor.getValue()).isEqualTo(ProviderSession.Status.CREDENTIALS_RECEIVED);
+        verify(mGetRequestSession).onFinalResponseReceived(cpComponentNameCaptor.capture(),
+                getCredentialResponseCaptor.capture());
+        assertThat(cpComponentNameCaptor.getValue())
+                .isSameInstanceAs(CREDENTIAL_PROVIDER_COMPONENT);
+        assertThat(getCredentialResponseCaptor.getValue()).isSameInstanceAs(response);
+    }
+
+    private static Slice createSlice() {
+        return new Slice.Builder(Uri.EMPTY, new SliceSpec("", 0)).build();
+    }
+
+    private static CallingAppInfo createCallingAppInfo() throws CertificateException {
+        return new CallingAppInfo(CALLING_PACKAGE_NAME,
+                new SigningInfo(
+                        new SigningDetails(new Signature[]{}, 0,
+                                null)));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
index 35a677e..817b245 100644
--- a/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/PersistentDataStoreTest.java
@@ -377,6 +377,33 @@
         assertTrue(Float.isNaN(mDataStore.getBrightness(testDisplayDevice)));
     }
 
+    @Test
+    public void testStoreAndRestoreBrightnessNitsForDefaultDisplay() {
+        float brightnessNitsForDefaultDisplay = 190;
+        mDataStore.loadIfNeeded();
+        mDataStore.setBrightnessNitsForDefaultDisplay(brightnessNitsForDefaultDisplay);
+
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mInjector.setWriteStream(baos);
+        mDataStore.saveIfNeeded();
+        mTestLooper.dispatchAll();
+        assertTrue(mInjector.wasWriteSuccessful());
+        TestInjector newInjector = new TestInjector();
+        PersistentDataStore newDataStore = new PersistentDataStore(newInjector);
+        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+        newInjector.setReadStream(bais);
+        newDataStore.loadIfNeeded();
+        assertEquals(brightnessNitsForDefaultDisplay,
+                mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+        assertEquals(brightnessNitsForDefaultDisplay,
+                newDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+    }
+
+    @Test
+    public void testInitialBrightnessNitsForDefaultDisplay() {
+        mDataStore.loadIfNeeded();
+        assertEquals(-1, mDataStore.getBrightnessNitsForDefaultDisplay(), 0);
+    }
 
     public class TestInjector extends PersistentDataStore.Injector {
         private InputStream mReadStream;
diff --git a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index ae05e32..cfb432a 100644
--- a/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.hardware.display.DisplayManagerInternal.DisplayPowerRequest;
 import android.os.HandlerExecutor;
 import android.os.PowerManager;
@@ -34,6 +35,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.display.AutomaticBrightnessController;
 import com.android.server.display.BrightnessSetting;
 import com.android.server.display.brightness.strategy.DisplayBrightnessStrategy;
 import com.android.server.display.brightness.strategy.TemporaryBrightnessStrategy;
@@ -47,7 +49,7 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public final class DisplayBrightnessControllerTest {
-    private static final int DISPLAY_ID = 1;
+    private static final int DISPLAY_ID = Display.DEFAULT_DISPLAY;
     private static final float DEFAULT_BRIGHTNESS = 0.15f;
 
     @Mock
@@ -55,6 +57,8 @@
     @Mock
     private Context mContext;
     @Mock
+    private Resources mResources;
+    @Mock
     private BrightnessSetting mBrightnessSetting;
     @Mock
     private Runnable mOnBrightnessChangeRunnable;
@@ -67,6 +71,7 @@
     @Before
     public void before() {
         MockitoAnnotations.initMocks(this);
+        when(mContext.getResources()).thenReturn(mResources);
         DisplayBrightnessController.Injector injector = new DisplayBrightnessController.Injector() {
             @Override
             DisplayBrightnessStrategySelector getDisplayBrightnessStrategySelector(
@@ -75,6 +80,10 @@
             }
         };
         when(mBrightnessSetting.getBrightness()).thenReturn(Float.NaN);
+        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(-1f);
+        when(mResources.getBoolean(
+                com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay))
+                .thenReturn(true);
         mDisplayBrightnessController = new DisplayBrightnessController(mContext, injector,
                 DISPLAY_ID, DEFAULT_BRIGHTNESS, mBrightnessSetting, mOnBrightnessChangeRunnable,
                 mBrightnessChangeExecutor);
@@ -82,8 +91,8 @@
 
     @Test
     public void testIfFirstScreenBrightnessIsDefault() {
-        assertEquals(mDisplayBrightnessController.getCurrentBrightness(), DEFAULT_BRIGHTNESS,
-                0.0f);
+        assertEquals(DEFAULT_BRIGHTNESS, mDisplayBrightnessController.getCurrentBrightness(),
+                /* delta= */ 0.0f);
     }
 
     @Test
@@ -123,7 +132,7 @@
         float currentScreenBrightness = 0.4f;
         mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(currentScreenBrightness);
         assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
-                currentScreenBrightness, 0.0f);
+                currentScreenBrightness, /* delta= */ 0.0f);
         verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable);
 
         // No change to the current screen brightness is same as the existing one
@@ -136,7 +145,7 @@
         float pendingScreenBrightness = 0.4f;
         mDisplayBrightnessController.setPendingScreenBrightness(pendingScreenBrightness);
         assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
-                pendingScreenBrightness, 0.0f);
+                pendingScreenBrightness, /* delta= */ 0.0f);
     }
 
     @Test
@@ -158,7 +167,7 @@
         verify(temporaryBrightnessStrategy).setTemporaryScreenBrightness(
                 PowerManager.BRIGHTNESS_INVALID_FLOAT);
         assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
-                PowerManager.BRIGHTNESS_INVALID_FLOAT, 0.0f);
+                PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
 
         // user set brightness is set as expected
         currentBrightness = 0.4f;
@@ -169,15 +178,15 @@
         mDisplayBrightnessController.setTemporaryBrightness(temporaryScreenBrightness);
         assertTrue(mDisplayBrightnessController.updateUserSetScreenBrightness());
         assertEquals(mDisplayBrightnessController.getCurrentBrightness(),
-                pendingScreenBrightness, 0.0f);
+                pendingScreenBrightness, /* delta= */ 0.0f);
         assertEquals(mDisplayBrightnessController.getLastUserSetScreenBrightness(),
-                pendingScreenBrightness, 0.0f);
+                pendingScreenBrightness, /* delta= */ 0.0f);
         verify(mBrightnessChangeExecutor, times(2))
                 .execute(mOnBrightnessChangeRunnable);
         verify(temporaryBrightnessStrategy, times(2))
                 .setTemporaryScreenBrightness(PowerManager.BRIGHTNESS_INVALID_FLOAT);
         assertEquals(mDisplayBrightnessController.getPendingScreenBrightness(),
-                PowerManager.BRIGHTNESS_INVALID_FLOAT, 0.0f);
+                PowerManager.BRIGHTNESS_INVALID_FLOAT, /* delta= */ 0.0f);
     }
 
     @Test
@@ -198,20 +207,20 @@
         float brightnessSetting = 0.2f;
         when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
         assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), brightnessSetting,
-                0.0f);
+                /* delta= */ 0.0f);
 
         // getScreenBrightnessSetting value is clamped if BrightnessSetting returns value beyond max
         brightnessSetting = 1.1f;
         when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
         assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), 1.0f,
-                0.0f);
+                /* delta= */ 0.0f);
 
         // getScreenBrightnessSetting returns default value is BrightnessSetting returns invalid
         // value.
         brightnessSetting = Float.NaN;
         when(mBrightnessSetting.getBrightness()).thenReturn(brightnessSetting);
         assertEquals(mDisplayBrightnessController.getScreenBrightnessSetting(), DEFAULT_BRIGHTNESS,
-                0.0f);
+                /* delta= */ 0.0f);
     }
 
     @Test
@@ -248,6 +257,64 @@
     }
 
     @Test
+    public void testBrightnessNitsForDefaultDisplay() {
+        float brightness = 0.3f;
+        float nits = 500;
+        AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+        when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits);
+
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+        assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(),
+                /* delta= */ 0);
+
+        float newBrightness = 0.5f;
+        float newNits = 700;
+        when(automaticBrightnessController.convertToNits(newBrightness)).thenReturn(newNits);
+        mDisplayBrightnessController.setBrightness(newBrightness);
+        verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(newNits);
+    }
+
+    @Test
+    public void testConvertToNits() {
+        float brightness = 0.5f;
+        float nits = 300;
+
+        // ABC is null
+        assertEquals(-1f, mDisplayBrightnessController.convertToNits(brightness),
+                /* delta= */ 0);
+
+        AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToNits(brightness)).thenReturn(nits);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+
+        assertEquals(nits, mDisplayBrightnessController.convertToNits(brightness), /* delta= */ 0);
+    }
+
+    @Test
+    public void testConvertToFloatScale() {
+        float brightness = 0.5f;
+        float nits = 300;
+
+        // ABC is null
+        assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT,
+                mDisplayBrightnessController.convertToFloatScale(nits), /* delta= */ 0);
+
+        AutomaticBrightnessController automaticBrightnessController =
+                mock(AutomaticBrightnessController.class);
+        when(automaticBrightnessController.convertToFloatScale(nits)).thenReturn(brightness);
+        mDisplayBrightnessController.setAutomaticBrightnessController(
+                automaticBrightnessController);
+
+        assertEquals(brightness, mDisplayBrightnessController.convertToFloatScale(nits),
+                /* delta= */ 0);
+    }
+
+    @Test
     public void stop() {
         BrightnessSetting.BrightnessSettingListener brightnessSettingListener = mock(
                 BrightnessSetting.BrightnessSettingListener.class);
diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
index c0a994b..685e8d6 100644
--- a/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/location/contexthub/ContextHubServiceTest.java
@@ -18,7 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -75,7 +76,7 @@
 
     @Test
     public void testDump_emptyPreloadedNanoappList() {
-        when(mMockContextHubWrapper.getPreloadedNanoappIds()).thenReturn(null);
+        when(mMockContextHubWrapper.getPreloadedNanoappIds(anyInt())).thenReturn(null);
         StringWriter stringWriter = new StringWriter();
 
         ContextHubService service = new ContextHubService(mContext, mMockContextHubWrapper);
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
index 299f153..16a02b6 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java
@@ -91,8 +91,16 @@
     }
 
     @Test
-    public void testQueryException_returnsFalse() {
-        contentProvider.setThrowException(true);
+    public void testQuerySQLiteException_returnsFalse() {
+        contentProvider.setThrowSQLiteException(true);
+
+        Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY);
+        assertFalse(mHelper.query(contactUri.toString()));
+    }
+
+    @Test
+    public void testQueryIllegalArgumentException_returnsFalse() {
+        contentProvider.setThrowIllegalArgumentException(true);
 
         Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY);
         assertFalse(mHelper.query(contactUri.toString()));
@@ -178,14 +186,18 @@
     private class ContactsContentProvider extends MockContentProvider {
 
         private Map<Uri, Cursor> mUriPrefixToCursorMap = new ArrayMap<>();
-        private boolean throwException = false;
+        private boolean mThrowSQLiteException = false;
+        private boolean mThrowIllegalArgumentException = false;
 
         @Override
         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                 String sortOrder) {
-            if (throwException) {
+            if (mThrowSQLiteException) {
                 throw new SQLiteException();
             }
+            if (mThrowIllegalArgumentException) {
+                throw new IllegalArgumentException();
+            }
 
             for (Uri prefixUri : mUriPrefixToCursorMap.keySet()) {
                 if (uri.isPathPrefixMatch(prefixUri)) {
@@ -195,8 +207,12 @@
             return mUriPrefixToCursorMap.get(uri);
         }
 
-        public void setThrowException(boolean throwException) {
-            this.throwException = throwException;
+        public void setThrowSQLiteException(boolean throwException) {
+            this.mThrowSQLiteException = throwException;
+        }
+
+        public void setThrowIllegalArgumentException(boolean throwException) {
+            this.mThrowIllegalArgumentException = throwException;
         }
 
         private void registerCursor(Uri uriPrefix, Cursor cursor) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 56461f0..b80c3e8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -26,6 +26,7 @@
 import static android.window.BackNavigationInfo.typeToString;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -149,6 +150,28 @@
     }
 
     @Test
+    public void backTypeCrossActivityWithCustomizeExitAnimation() {
+        CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
+        IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
+        testCase.windowFront.mAttrs.windowAnimations = 0x10;
+        spyOn(mDisplayContent.mAppTransition.mTransitionAnimation);
+        doReturn(0xffff00AB).when(mDisplayContent.mAppTransition.mTransitionAnimation)
+                .getAnimationResId(any(), anyInt(), anyInt());
+        doReturn(0xffff00CD).when(mDisplayContent.mAppTransition.mTransitionAnimation)
+                .getDefaultAnimationResId(anyInt(), anyInt());
+
+        BackNavigationInfo backNavigationInfo = startBackNavigation();
+        assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
+        assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
+        assertThat(backNavigationInfo.getCustomAnimationInfo().getWindowAnimations())
+                .isEqualTo(testCase.windowFront.mAttrs.windowAnimations);
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
+        // verify if back animation would start.
+        assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation());
+    }
+
+    @Test
     public void backTypeCrossActivityWhenBackToPreviousActivity() {
         CrossActivityTestCase testCase = createTopTaskWithTwoActivities();
         IOnBackInvokedCallback callback = withSystemCallback(testCase.task);
@@ -158,6 +181,8 @@
         assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
         assertThat(typeToString(backNavigationInfo.getType()))
                 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
+        // verify if back animation would start.
+        assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation());
 
         // reset drawing status
         testCase.recordFront.forAllWindows(w -> {
@@ -510,6 +535,8 @@
         testCase.task = task;
         testCase.recordBack = record1;
         testCase.recordFront = record2;
+        testCase.windowBack = window1;
+        testCase.windowFront = window2;
         return testCase;
     }
 
@@ -525,6 +552,8 @@
     private class CrossActivityTestCase {
         public Task task;
         public ActivityRecord recordBack;
+        public WindowState windowBack;
         public ActivityRecord recordFront;
+        public WindowState windowFront;
     }
 }
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 3379beb..d071f13 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -34,7 +34,6 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
 import static android.view.DisplayCutout.fromBoundingRect;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
@@ -1557,15 +1556,16 @@
         assertFalse(mNotificationShadeWindow.isAnimating(PARENTS, ANIMATION_TYPE_TOKEN_TRANSFORM));
 
         // If the visibility of insets state is changed, the rotated state should be updated too.
+        final int statusBarId = mStatusBarWindow.getControllableInsetProvider().getSource().getId();
         final InsetsState rotatedState = app.getFixedRotationTransformInsetsState();
         final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
-        assertEquals(state.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()),
-                rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
-        state.setSourceVisible(ITYPE_STATUS_BAR,
-                !rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+        assertEquals(state.isSourceOrDefaultVisible(statusBarId, statusBars()),
+                rotatedState.isSourceOrDefaultVisible(statusBarId, statusBars()));
+        state.setSourceVisible(statusBarId,
+                !rotatedState.isSourceOrDefaultVisible(statusBarId, statusBars()));
         mDisplayContent.getInsetsStateController().notifyInsetsChanged();
-        assertEquals(state.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()),
-                rotatedState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+        assertEquals(state.isSourceOrDefaultVisible(statusBarId, statusBars()),
+                rotatedState.isSourceOrDefaultVisible(statusBarId, statusBars()));
 
         final Rect outFrame = new Rect();
         final Rect outInsets = new Rect();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 6656f4c..695a72e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -16,37 +16,32 @@
 
 package com.android.server.wm;
 
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_GESTURES;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_90;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
 
 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 static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.spy;
-import static org.testng.Assert.expectThrows;
 
-import android.graphics.Insets;
 import android.graphics.Rect;
+import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
 import android.view.DisplayInfo;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.PrivacyIndicatorBounds;
 import android.view.RoundedCorners;
+import android.view.WindowInsets;
 
 import androidx.test.filters.SmallTest;
 
@@ -151,74 +146,23 @@
     public void addingWindow_withInsetsTypes() {
         mDisplayPolicy.removeWindowLw(mStatusBarWindow);  // Removes the existing one.
 
-        WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
+        final WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "statusBar");
+        final Binder owner = new Binder();
         win.mAttrs.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(ITYPE_STATUS_BAR),
-                new InsetsFrameProvider(ITYPE_TOP_GESTURES)
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()),
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.systemGestures())
         };
-        win.getFrame().set(0, 0, 500, 100);
-
         addWindow(win);
-        win.updateSourceFrame(win.getFrame());
-        InsetsStateController controller = mDisplayContent.getInsetsStateController();
-        controller.onPostLayout();
-
-        InsetsSourceProvider statusBarProvider = controller.peekSourceProvider(ITYPE_STATUS_BAR);
-        assertEquals(new Rect(0, 0, 500, 100), statusBarProvider.getSource().getFrame());
-        assertEquals(Insets.of(0, 100, 0, 0),
-                statusBarProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
-                        false /* ignoreVisibility */));
-
-        InsetsSourceProvider topGesturesProvider = controller.peekSourceProvider(
-                ITYPE_TOP_GESTURES);
-        assertEquals(new Rect(0, 0, 500, 100), topGesturesProvider.getSource().getFrame());
-        assertEquals(Insets.of(0, 100, 0, 0),
-                topGesturesProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
-                        false /* ignoreVisibility */));
-
-        InsetsSourceProvider navigationBarProvider = controller.peekSourceProvider(
-                ITYPE_NAVIGATION_BAR);
-        assertNotEquals(new Rect(0, 0, 500, 100), navigationBarProvider.getSource().getFrame());
-    }
-
-    @Test
-    public void addingWindow_InWindowTypeWithPredefinedInsets() {
-        mDisplayPolicy.removeWindowLw(mStatusBarWindow);  // Removes the existing one.
-        WindowState win = createWindow(null, TYPE_STATUS_BAR, "StatusBar");
-        win.mAttrs.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(ITYPE_STATUS_BAR)
-        };
         win.getFrame().set(0, 0, 500, 100);
-
-        addWindow(win);
         win.updateSourceFrame(win.getFrame());
         mDisplayContent.getInsetsStateController().onPostLayout();
 
-        InsetsSourceProvider provider =
-                mDisplayContent.getInsetsStateController().peekSourceProvider(ITYPE_STATUS_BAR);
-        // In the new flexible insets setup, the insets frame should always respect the window
-        // layout result.
-        assertEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame());
-    }
-
-    @Test
-    public void addingWindow_throwsException_WithMultipleInsetTypes() {
-        WindowState win1 = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
-        win1.mAttrs.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(ITYPE_STATUS_BAR),
-                new InsetsFrameProvider(ITYPE_NAVIGATION_BAR)
-        };
-
-        expectThrows(IllegalArgumentException.class, () -> addWindow(win1));
-
-        WindowState win2 = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
-
-        win2.mAttrs.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(ITYPE_CLIMATE_BAR),
-                new InsetsFrameProvider(ITYPE_EXTRA_NAVIGATION_BAR)
-        };
-
-        expectThrows(IllegalArgumentException.class, () -> addWindow(win2));
+        assertTrue(win.hasInsetsSourceProvider());
+        final SparseArray<InsetsSourceProvider> providers = win.getInsetsSourceProviders();
+        for (int i = providers.size() - 1; i >= 0; i--) {
+            final InsetsSourceProvider provider = providers.valueAt(i);
+            assertEquals(new Rect(0, 0, 500, 100), provider.getSource().getFrame());
+        }
     }
 
     /**
@@ -272,9 +216,10 @@
                 .rotationForActivityInDifferentOrientation(eq(mWindow.mActivityRecord));
         mWindow.mAboveInsetsState.set(
                 mDisplayContent.getInsetsStateController().getRawInsetsState());
-        final Rect frame = mWindow.getInsetsState().peekSource(ITYPE_STATUS_BAR).getFrame();
+        final int statusBarId = mStatusBarWindow.getControllableInsetProvider().getSource().getId();
+        final Rect frame = mWindow.getInsetsState().peekSource(statusBarId).getFrame();
         mDisplayContent.rotateInDifferentOrientationIfNeeded(mWindow.mActivityRecord);
-        final Rect rotatedFrame = mWindow.getInsetsState().peekSource(ITYPE_STATUS_BAR).getFrame();
+        final Rect rotatedFrame = mWindow.getInsetsState().peekSource(statusBarId).getFrame();
 
         assertEquals(DISPLAY_WIDTH, frame.width());
         assertEquals(DISPLAY_HEIGHT, rotatedFrame.width());
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index c694707..20d410c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -18,8 +18,6 @@
 
 import static android.view.DisplayCutout.NO_CUTOUT;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.RoundedCorners.NO_ROUNDED_CORNERS;
 import static android.view.Surface.ROTATION_0;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -180,7 +178,8 @@
         mDisplayContent.setLayoutNeeded();
         mDisplayContent.performLayout(true /* initial */, false /* updateImeWindows */);
 
-        final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
+        final InsetsSource navSource = new InsetsSource(
+                InsetsSource.createId(null, 0, navigationBars()), navigationBars());
         navSource.setFrame(mNavBarWindow.getFrame());
         opaqueDarkNavBar.mAboveInsetsState.addSource(navSource);
         opaqueLightNavBar.mAboveInsetsState.addSource(navSource);
@@ -250,15 +249,16 @@
 
     @Test
     public void testOverlappingWithNavBar() {
-        final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
+        final InsetsSource navSource = new InsetsSource(
+                InsetsSource.createId(null, 0, navigationBars()), navigationBars());
         navSource.setFrame(new Rect(100, 200, 200, 300));
         testOverlappingWithNavBarType(navSource);
     }
 
     @Test
     public void testOverlappingWithExtraNavBar() {
-        final InsetsSource navSource =
-                new InsetsSource(ITYPE_EXTRA_NAVIGATION_BAR, navigationBars());
+        final InsetsSource navSource = new InsetsSource(
+                InsetsSource.createId(null, 1, navigationBars()), navigationBars());
         navSource.setFrame(new Rect(100, 200, 200, 300));
         testOverlappingWithNavBarType(navSource);
     }
@@ -331,7 +331,8 @@
         displayPolicy.layoutWindowLw(mImeWindow, null, mDisplayContent.mDisplayFrames);
 
         final InsetsSource imeSource = state.peekSource(ID_IME);
-        final InsetsSource navBarSource = state.peekSource(ITYPE_NAVIGATION_BAR);
+        final InsetsSource navBarSource = state.peekSource(
+                mNavBarWindow.getControllableInsetProvider().getSource().getId());
 
         assertNotNull(imeSource);
         assertNotNull(navBarSource);
@@ -358,7 +359,8 @@
 
         displayPolicy.layoutWindowLw(mNavBarWindow, null, mDisplayContent.mDisplayFrames);
         final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
-        final InsetsSource navBarSource = state.peekSource(ITYPE_NAVIGATION_BAR);
+        final InsetsSource navBarSource = state.peekSource(
+                mNavBarWindow.getControllableInsetProvider().getSource().getId());
         assertEquals(attrs.height - 10, navBarSource.getFrame().height());
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 1a126cf..2065540 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -19,12 +19,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
@@ -51,6 +45,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.StatusBarManager;
+import android.os.Binder;
 import android.platform.test.annotations.Presubmit;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
@@ -158,7 +153,7 @@
     @Test
     public void testControlsForDispatch_remoteInsetsControllerControlsBars_appHasNoControl() {
         mDisplayContent.setRemoteInsetsController(createDisplayWindowInsetsController());
-        mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(true);
+        mDisplayContent.getDisplayPolicy().setRemoteInsetsControllerControlsSystemBars(true);
         addStatusBar();
         addNavigationBar();
 
@@ -261,11 +256,15 @@
     @Test
     public void testShowTransientBars_bothCanBeTransient_appGetsBothFakeControls() {
         final WindowState statusBar = addStatusBar();
+        final InsetsSourceProvider statusBarProvider = statusBar.getControllableInsetProvider();
+        final int statusBarId = statusBarProvider.getSource().getId();
         statusBar.setHasSurface(true);
-        statusBar.getControllableInsetProvider().setServerVisible(true);
+        statusBarProvider.setServerVisible(true);
         final WindowState navBar = addNavigationBar();
+        final InsetsSourceProvider navBarProvider = statusBar.getControllableInsetProvider();
+        final int navBarId = statusBarProvider.getSource().getId();
         navBar.setHasSurface(true);
-        navBar.getControllableInsetProvider().setServerVisible(true);
+        navBarProvider.setServerVisible(true);
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
         spyOn(policy);
         doNothing().when(policy).startAnimation(anyBoolean(), any());
@@ -276,9 +275,9 @@
         policy.updateBarControlTarget(mAppWindow);
         waitUntilWindowAnimatorIdle();
         assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
-                .isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+                .isSourceOrDefaultVisible(statusBarId, statusBars()));
         assertFalse(mDisplayContent.getInsetsStateController().getRawInsetsState()
-                .isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
+                .isSourceOrDefaultVisible(navBarId, navigationBars()));
 
         policy.showTransient(navigationBars() | statusBars(), true /* isGestureOnSystemBar */);
         waitUntilWindowAnimatorIdle();
@@ -292,9 +291,9 @@
         }
 
         assertTrue(mDisplayContent.getInsetsStateController().getRawInsetsState()
-                .isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
+                .isSourceOrDefaultVisible(statusBarId, statusBars()));
         assertTrue(mDisplayContent.getInsetsStateController().getRawInsetsState()
-                .isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
+                .isSourceOrDefaultVisible(navBarId, navigationBars()));
     }
 
     @SetupWindows(addWindows = W_ACTIVITY)
@@ -356,16 +355,16 @@
         }
 
         final InsetsState state = mAppWindow.getInsetsState();
-        state.setSourceVisible(ITYPE_STATUS_BAR, true);
-        state.setSourceVisible(ITYPE_NAVIGATION_BAR, true);
+        state.setSourceVisible(statusBarSource.getId(), true);
+        state.setSourceVisible(navBarSource.getId(), true);
 
         final InsetsState clientState = mAppWindow.getInsetsState();
         // The transient bar states for client should be invisible.
-        assertFalse(clientState.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
-        assertFalse(clientState.isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
+        assertFalse(clientState.isSourceOrDefaultVisible(statusBarSource.getId(), statusBars()));
+        assertFalse(clientState.isSourceOrDefaultVisible(navBarSource.getId(), navigationBars()));
         // The original state shouldn't be modified.
-        assertTrue(state.isSourceOrDefaultVisible(ITYPE_STATUS_BAR, statusBars()));
-        assertTrue(state.isSourceOrDefaultVisible(ITYPE_NAVIGATION_BAR, navigationBars()));
+        assertTrue(state.isSourceOrDefaultVisible(statusBarSource.getId(), statusBars()));
+        assertTrue(state.isSourceOrDefaultVisible(navBarSource.getId(), navigationBars()));
 
         mAppWindow.setRequestedVisibleTypes(
                 navigationBars() | statusBars(), navigationBars() | statusBars());
@@ -402,24 +401,26 @@
     }
 
     private WindowState addNavigationBar() {
+        final Binder owner = new Binder();
         final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
         win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
         win.mAttrs.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(ITYPE_NAVIGATION_BAR),
-                new InsetsFrameProvider(ITYPE_BOTTOM_MANDATORY_GESTURES),
-                new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT)
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars()),
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
         };
         mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
         return win;
     }
 
     private WindowState addStatusBar() {
+        final Binder owner = new Binder();
         final WindowState win = createWindow(null, TYPE_STATUS_BAR, "statusBar");
         win.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
         win.mAttrs.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(ITYPE_STATUS_BAR),
-                new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
-                new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES)
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()),
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
         };
         mDisplayContent.getDisplayPolicy().addWindowLw(win, win.mAttrs);
         return win;
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index abbd397..65c7125 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -28,9 +28,6 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
@@ -88,6 +85,7 @@
 import android.content.pm.ActivityInfo.ScreenOrientation;
 import android.content.res.Configuration;
 import android.graphics.Rect;
+import android.os.Binder;
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.provider.DeviceConfig;
@@ -95,6 +93,7 @@
 import android.view.InsetsFrameProvider;
 import android.view.InsetsSource;
 import android.view.InsetsState;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 
 import androidx.test.filters.MediumTest;
@@ -4075,14 +4074,15 @@
                 TYPE_STATUS_BAR, displayContent);
         final WindowManager.LayoutParams attrs =
                 new WindowManager.LayoutParams(TYPE_STATUS_BAR);
+        final Binder owner = new Binder();
         attrs.gravity = android.view.Gravity.TOP;
         attrs.layoutInDisplayCutoutMode =
                 WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         attrs.setFitInsetsTypes(0 /* types */);
         attrs.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(ITYPE_STATUS_BAR),
-                new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
-                new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES)
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()),
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
         };
         final TestWindowState statusBar = new TestWindowState(
                 displayContent.mWmService, mock(Session.class), new TestIWindow(), attrs, token);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
index 2bfc5ec..739737e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskLaunchParamsModifierTests.java
@@ -30,10 +30,6 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.util.DisplayMetrics.DENSITY_DEFAULT;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
-import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.window.DisplayAreaOrganizer.FEATURE_RUNTIME_TASK_CONTAINER_FIRST;
 
 import static com.android.server.wm.ActivityStarter.Request;
@@ -55,6 +51,7 @@
 import android.os.Build;
 import android.platform.test.annotations.Presubmit;
 import android.view.Gravity;
+import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.WindowInsets;
 
@@ -1916,22 +1913,24 @@
         final int st = stableFrame.top;
         final int sr = stableFrame.right;
         final int sb = stableFrame.bottom;
+        final @WindowInsets.Type.InsetsType int statusBarType = WindowInsets.Type.statusBars();
+        final @WindowInsets.Type.InsetsType int navBarType = WindowInsets.Type.navigationBars();
 
         state.setDisplayFrame(displayFrame);
         if (sl > dl) {
-            state.getOrCreateSource(ITYPE_CLIMATE_BAR, WindowInsets.Type.statusBars())
+            state.getOrCreateSource(InsetsSource.createId(null, 0, statusBarType), statusBarType)
                     .setFrame(dl, dt, sl, db);
         }
         if (st > dt) {
-            state.getOrCreateSource(ITYPE_STATUS_BAR, WindowInsets.Type.statusBars())
+            state.getOrCreateSource(InsetsSource.createId(null, 1, statusBarType), statusBarType)
                     .setFrame(dl, dt, dr, st);
         }
         if (sr < dr) {
-            state.getOrCreateSource(ITYPE_EXTRA_NAVIGATION_BAR, WindowInsets.Type.navigationBars())
+            state.getOrCreateSource(InsetsSource.createId(null, 0, navBarType), navBarType)
                     .setFrame(sr, dt, dr, db);
         }
         if (sb < db) {
-            state.getOrCreateSource(ITYPE_NAVIGATION_BAR, WindowInsets.Type.navigationBars())
+            state.getOrCreateSource(InsetsSource.createId(null, 1, navBarType), navBarType)
                     .setFrame(dl, sb, dr, db);
         }
         // Recompute config and push to children.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
index 7d13de8..ef20f2b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerInsetsSourceProviderTest.java
@@ -17,7 +17,6 @@
 package com.android.server.wm;
 
 import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -45,7 +44,8 @@
 @RunWith(WindowTestRunner.class)
 public class WindowContainerInsetsSourceProviderTest extends WindowTestsBase {
 
-    private InsetsSource mSource = new InsetsSource(ITYPE_STATUS_BAR, statusBars());
+    private InsetsSource mSource = new InsetsSource(
+            InsetsSource.createId(null, 0, statusBars()), statusBars());
     private WindowContainerInsetsSourceProvider mProvider;
     private InsetsSource mImeSource = new InsetsSource(ID_IME, ime());
     private WindowContainerInsetsSourceProvider mImeProvider;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 169586e..d7e4c55 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -1023,7 +1023,7 @@
         RunningTaskInfo mInfo;
 
         @Override
-        public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { }
+        public void addStartingWindow(StartingWindowInfo info) { }
         @Override
         public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { }
         @Override
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 33067df..b48fd7d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -21,8 +21,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.InsetsSource.ID_IME;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
@@ -425,15 +423,16 @@
         final WindowState app = mAppWindow;
         statusBar.mHasSurface = true;
         assertTrue(statusBar.isVisible());
+        final int statusBarId = InsetsSource.createId(null, 0, statusBars());
         mDisplayContent.getInsetsStateController()
-                .getOrCreateSourceProvider(ITYPE_STATUS_BAR, statusBars())
+                .getOrCreateSourceProvider(statusBarId, statusBars())
                 .setWindowContainer(statusBar, null /* frameProvider */,
                         null /* imeFrameProvider */);
         mDisplayContent.getInsetsStateController().onBarControlTargetChanged(
                 app, null /* fakeTopControlling */, app, null /* fakeNavControlling */);
         app.setRequestedVisibleTypes(0, statusBars());
         mDisplayContent.getInsetsStateController()
-                .getOrCreateSourceProvider(ITYPE_STATUS_BAR, statusBars())
+                .getOrCreateSourceProvider(statusBarId, statusBars())
                 .updateClientVisibility(app);
         waitUntilHandlersIdle();
         assertFalse(statusBar.isVisible());
@@ -1004,21 +1003,18 @@
     @SetupWindows(addWindows = { W_INPUT_METHOD, W_ACTIVITY })
     @Test
     public void testImeAlwaysReceivesVisibleNavigationBarInsets() {
-        final InsetsSource navSource = new InsetsSource(ITYPE_NAVIGATION_BAR, navigationBars());
+        final int navId = InsetsSource.createId(null, 0, navigationBars());
+        final InsetsSource navSource = new InsetsSource(navId, navigationBars());
         mImeWindow.mAboveInsetsState.addSource(navSource);
         mAppWindow.mAboveInsetsState.addSource(navSource);
 
         navSource.setVisible(false);
-        assertTrue(mImeWindow.getInsetsState().isSourceOrDefaultVisible(
-                ITYPE_NAVIGATION_BAR, navigationBars()));
-        assertFalse(mAppWindow.getInsetsState().isSourceOrDefaultVisible(
-                ITYPE_NAVIGATION_BAR, navigationBars()));
+        assertTrue(mImeWindow.getInsetsState().isSourceOrDefaultVisible(navId, navigationBars()));
+        assertFalse(mAppWindow.getInsetsState().isSourceOrDefaultVisible(navId, navigationBars()));
 
         navSource.setVisible(true);
-        assertTrue(mImeWindow.getInsetsState().isSourceOrDefaultVisible(
-                ITYPE_NAVIGATION_BAR, navigationBars()));
-        assertTrue(mAppWindow.getInsetsState().isSourceOrDefaultVisible(
-                ITYPE_NAVIGATION_BAR, navigationBars()));
+        assertTrue(mImeWindow.getInsetsState().isSourceOrDefaultVisible(navId, navigationBars()));
+        assertTrue(mAppWindow.getInsetsState().isSourceOrDefaultVisible(navId, navigationBars()));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 323894ca..ce6cd90 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -26,12 +26,6 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.os.Process.SYSTEM_UID;
-import static android.view.InsetsState.ITYPE_BOTTOM_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_BOTTOM_TAPPABLE_ELEMENT;
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
-import static android.view.InsetsState.ITYPE_TOP_MANDATORY_GESTURES;
-import static android.view.InsetsState.ITYPE_TOP_TAPPABLE_ELEMENT;
 import static android.view.View.VISIBLE;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
@@ -99,6 +93,7 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.View;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManager.DisplayImePolicy;
 import android.view.inputmethod.ImeTracker;
@@ -312,7 +307,7 @@
         beforeCreateTestDisplay();
         mDisplayContent = createNewDisplayWithImeSupport(DISPLAY_IME_POLICY_LOCAL);
         addCommonWindows(annotation.addAllCommonWindows(), annotation.addWindows());
-        mDisplayContent.getInsetsPolicy().setRemoteInsetsControllerControlsSystemBars(false);
+        mDisplayContent.getDisplayPolicy().setRemoteInsetsControllerControlsSystemBars(false);
 
         // Adding a display will cause freezing the display. Make sure to wait until it's
         // unfrozen to not run into race conditions with the tests.
@@ -338,10 +333,11 @@
             mStatusBarWindow.mAttrs.layoutInDisplayCutoutMode =
                     LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
             mStatusBarWindow.mAttrs.setFitInsetsTypes(0);
+            final IBinder owner = new Binder();
             mStatusBarWindow.mAttrs.providedInsets = new InsetsFrameProvider[] {
-                    new InsetsFrameProvider(ITYPE_STATUS_BAR),
-                    new InsetsFrameProvider(ITYPE_TOP_TAPPABLE_ELEMENT),
-                    new InsetsFrameProvider(ITYPE_TOP_MANDATORY_GESTURES)
+                    new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()),
+                    new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
+                    new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
             };
         }
         if (addAll || ArrayUtils.contains(requestedWindows, W_NOTIFICATION_SHADE)) {
@@ -358,14 +354,15 @@
                     LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
             mNavBarWindow.mAttrs.privateFlags |=
                     WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+            final IBinder owner = new Binder();
             mNavBarWindow.mAttrs.providedInsets = new InsetsFrameProvider[] {
-                    new InsetsFrameProvider(ITYPE_NAVIGATION_BAR),
-                    new InsetsFrameProvider(ITYPE_BOTTOM_MANDATORY_GESTURES),
-                    new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT)
+                    new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars()),
+                    new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
+                    new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
             };
             for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
                 mNavBarWindow.mAttrs.paramsForRotation[rot] =
-                        getNavBarLayoutParamsForRotation(rot);
+                        getNavBarLayoutParamsForRotation(rot, owner);
             }
         }
         if (addAll || ArrayUtils.contains(requestedWindows, W_DOCK_DIVIDER)) {
@@ -388,7 +385,8 @@
         }
     }
 
-    private WindowManager.LayoutParams getNavBarLayoutParamsForRotation(int rotation) {
+    private WindowManager.LayoutParams getNavBarLayoutParamsForRotation(
+            int rotation, IBinder owner) {
         int width = WindowManager.LayoutParams.MATCH_PARENT;
         int height = WindowManager.LayoutParams.MATCH_PARENT;
         int gravity = Gravity.BOTTOM;
@@ -417,9 +415,9 @@
                 WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
         lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
         lp.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(ITYPE_NAVIGATION_BAR),
-                new InsetsFrameProvider(ITYPE_BOTTOM_MANDATORY_GESTURES),
-                new InsetsFrameProvider(ITYPE_BOTTOM_TAPPABLE_ELEMENT)
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars()),
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
         };
         return lp;
     }
@@ -454,8 +452,10 @@
 
     WindowState createNavBarWithProvidedInsets(DisplayContent dc) {
         final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, dc, "navbar");
+        final Binder owner = new Binder();
         navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
-                new InsetsFrameProvider(ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, NAV_BAR_HEIGHT))
+                new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
+                        .setInsetsSize(Insets.of(0, 0, 0, NAV_BAR_HEIGHT))
         };
         dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
         return navbar;
@@ -1583,10 +1583,10 @@
         }
 
         @Override
-        public void addStartingWindow(StartingWindowInfo info, IBinder appToken) {
+        public void addStartingWindow(StartingWindowInfo info) {
             synchronized (mWMService.mGlobalLock) {
                 final ActivityRecord activity = mWMService.mRoot.getActivityRecord(
-                        appToken);
+                        info.appToken);
                 IWindow iWindow = mock(IWindow.class);
                 doReturn(mock(IBinder.class)).when(iWindow).asBinder();
                 final WindowState window = WindowTestsBase.createWindow(null,
@@ -1596,8 +1596,8 @@
                         iWindow,
                         mPowerManagerWrapper);
                 activity.mStartingWindow = window;
-                mAppWindowMap.put(appToken, window);
-                mTaskAppMap.put(info.taskInfo.taskId, appToken);
+                mAppWindowMap.put(info.appToken, window);
+                mTaskAppMap.put(info.taskInfo.taskId, info.appToken);
             }
             if (mRunnableWhenAddingSplashScreen != null) {
                 mRunnableWhenAddingSplashScreen.run();
diff --git a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
index c15374a..2954c2d 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteDatagramCallback.aidl
@@ -16,9 +16,10 @@
 
 package android.telephony.satellite;
 
-import android.telephony.satellite.ISatelliteDatagramReceiverAck;
 import android.telephony.satellite.SatelliteDatagram;
 
+import com.android.internal.telephony.ILongConsumer;
+
 /**
  * Interface for satellite datagrams callback.
  * @hide
@@ -30,10 +31,10 @@
      * @param datagramId An id that uniquely identifies incoming datagram.
      * @param datagram Datagram received from satellite.
      * @param pendingCount Number of datagrams yet to be received from satellite.
-     * @param callback This callback will be used by datagram receiver app to send ack back to
-     *                 Telephony. If the callback is not received within five minutes,
-     *                 Telephony will resend the datagrams.
+     * @param callback This callback will be used by datagram receiver app to send received
+     *                 datagramId to Telephony. If the callback is not received within five minutes,
+     *                 Telephony will resend the datagram.
      */
     void onSatelliteDatagramReceived(long datagramId, in SatelliteDatagram datagram,
-            int pendingCount, ISatelliteDatagramReceiverAck callback);
+            int pendingCount, ILongConsumer callback);
 }
diff --git a/telephony/java/android/telephony/satellite/ISatelliteDatagramReceiverAck.aidl b/telephony/java/android/telephony/satellite/ISatelliteDatagramReceiverAck.aidl
deleted file mode 100644
index eeb0ac5..0000000
--- a/telephony/java/android/telephony/satellite/ISatelliteDatagramReceiverAck.aidl
+++ /dev/null
@@ -1,41 +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 android.telephony.satellite;
-
-import android.telephony.satellite.PointingInfo;
-import android.telephony.satellite.SatelliteDatagram;
-
-/**
- * Interface for satellite datagram receiver acknowledgement.
- * @hide
- */
-oneway interface ISatelliteDatagramReceiverAck {
-     /**
-      * This callback will be used by datagram receiver app to send ack back to
-      * Telephony. If the callback is not received within five minutes,
-      * then Telephony will resend the datagram again.
-      *
-      * @param datagramId An id that uniquely identifies datagram
-      *                   received by satellite datagram receiver app.
-      *                   This should match with datagramId passed in
-      *                   {@link SatelliteDatagramCallback#onSatelliteDatagramReceived(
-      *                   long, SatelliteDatagram, int, ISatelliteDatagramReceiverAck)}.
-      *                   Upon receiving the ack, Telephony will remove the datagram from
-      *                   the persistent memory.
-      */
-    void acknowledgeSatelliteDatagramReceived(in long datagramId);
-}
diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
index 8ccc993..213b985 100644
--- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java
@@ -19,6 +19,8 @@
 import android.annotation.NonNull;
 import android.os.Binder;
 
+import com.android.internal.telephony.ILongConsumer;
+
 import java.util.concurrent.Executor;
 
 /**
@@ -38,8 +40,9 @@
         }
 
         @Override
-        public void onSatelliteDatagramReceived(long datagramId, SatelliteDatagram datagram,
-                int pendingCount, ISatelliteDatagramReceiverAck callback) {
+        public void onSatelliteDatagramReceived(long datagramId,
+                @NonNull SatelliteDatagram datagram, int pendingCount,
+                @NonNull ILongConsumer callback) {
             final long callingIdentity = Binder.clearCallingIdentity();
             try {
                 mExecutor.execute(() -> mLocalCallback.onSatelliteDatagramReceived(datagramId,
@@ -59,17 +62,18 @@
      * @param datagramId An id that uniquely identifies incoming datagram.
      * @param datagram Datagram to be received over satellite.
      * @param pendingCount Number of datagrams yet to be received by the app.
-     * @param callback This callback will be used by datagram receiver app to send ack back to
-     *                 Telephony.
+     * @param callback This callback will be used by datagram receiver app to send received
+     *                 datagramId to Telephony. If the callback is not received within five minutes,
+     *                 Telephony will resend the datagram.
      */
-    public void onSatelliteDatagramReceived(long datagramId, SatelliteDatagram datagram,
-            int pendingCount, ISatelliteDatagramReceiverAck callback) {
+    public void onSatelliteDatagramReceived(long datagramId, @NonNull SatelliteDatagram datagram,
+            int pendingCount, @NonNull ILongConsumer callback) {
         // Base Implementation
     }
 
     /** @hide */
     @NonNull
-    public final ISatelliteDatagramCallback getBinder() {
+    final ISatelliteDatagramCallback getBinder() {
         return mBinder;
     }
 
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 64454cc..248d9df 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -166,13 +166,6 @@
     public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
 
     /**
-     * Bundle key to get the respoonse from {@link
-     * #sendSatelliteDatagram(long, int, SatelliteDatagram, boolean, Executor, OutcomeReceiver)}.
-     * @hide
-     */
-    public static final String KEY_SEND_SATELLITE_DATAGRAM = "send_satellite_datagram";
-
-    /**
      * The request was successfully processed.
      */
     public static final int SATELLITE_ERROR_NONE = 0;
@@ -239,6 +232,8 @@
     public static final int SATELLITE_SERVICE_PROVISION_IN_PROGRESS = 14;
     /**
      * The ongoing request was aborted by either the satellite modem or the network.
+     * This error is also returned when framework decides to abort current send request as one
+     * of the previous send request failed.
      */
     public static final int SATELLITE_REQUEST_ABORTED = 15;
     /**
@@ -724,24 +719,24 @@
     public @interface SatelliteModemState {}
 
     /**
+     * Datagram type is unknown. This generic datagram type should be used only when the
+     * datagram type cannot be mapped to other specific datagram types.
+     */
+    public static final int DATAGRAM_TYPE_UNKNOWN = 0;
+    /**
      * Datagram type indicating that the datagram to be sent or received is of type SOS message.
      */
-    public static final int DATAGRAM_TYPE_SOS_MESSAGE = 0;
+    public static final int DATAGRAM_TYPE_SOS_MESSAGE = 1;
     /**
      * Datagram type indicating that the datagram to be sent or received is of type
      * location sharing.
      */
-    public static final int DATAGRAM_TYPE_LOCATION_SHARING = 1;
-    /**
-     * Datagram type is unknown. This generic datagram type should be used only when the
-     * datagram type cannot be mapped to other specific datagram types.
-     */
-    public static final int DATAGRAM_TYPE_UNKNOWN = -1;
+    public static final int DATAGRAM_TYPE_LOCATION_SHARING = 2;
 
     @IntDef(prefix = "DATAGRAM_TYPE_", value = {
+            DATAGRAM_TYPE_UNKNOWN,
             DATAGRAM_TYPE_SOS_MESSAGE,
-            DATAGRAM_TYPE_LOCATION_SHARING,
-            DATAGRAM_TYPE_UNKNOWN
+            DATAGRAM_TYPE_LOCATION_SHARING
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface DatagramType {}
@@ -1268,7 +1263,6 @@
      * input to this method. Datagram received here will be passed down to modem without any
      * encoding or encryption.
      *
-     * @param datagramId An id that uniquely identifies datagram requested to be sent.
      * @param datagramType datagram type indicating whether the datagram is of type
      *                     SOS_SMS or LOCATION_SHARING.
      * @param datagram encoded gateway datagram which is encrypted by the caller.
@@ -1283,51 +1277,32 @@
      *                                 user activity and the application's ability to determine the
      *                                 best possible UX experience for the user.
      * @param executor The executor on which the result listener will be called.
-     * @param callback The callback object to which the result will be returned.
-     *                 If datagram is sent successfully, then
-     *                 {@link OutcomeReceiver#onResult(Object)} will return datagramId.
-     *                 If the request is not successful, {@link OutcomeReceiver#onError(Throwable)}
-     *                 will return a {@link SatelliteException} with the {@link SatelliteError}.
+     * @param resultListener Listener for the {@link SatelliteError} result of the operation.
      *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    public void sendSatelliteDatagram(long datagramId, @DatagramType int datagramType,
+    public void sendSatelliteDatagram(@DatagramType int datagramType,
             @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI,
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<Long, SatelliteException> callback) {
+            @SatelliteError @NonNull Consumer<Integer> resultListener) {
         Objects.requireNonNull(datagram);
         Objects.requireNonNull(executor);
-        Objects.requireNonNull(callback);
+        Objects.requireNonNull(resultListener);
 
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                ResultReceiver receiver = new ResultReceiver(null) {
+                IIntegerConsumer internalCallback = new IIntegerConsumer.Stub() {
                     @Override
-                    protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == SATELLITE_ERROR_NONE) {
-                            if (resultData.containsKey(KEY_SEND_SATELLITE_DATAGRAM)) {
-                                long resultDatagramId = resultData
-                                        .getLong(KEY_SEND_SATELLITE_DATAGRAM);
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onResult(resultDatagramId)));
-                            } else {
-                                loge("KEY_SEND_SATELLITE_DATAGRAM does not exist.");
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(
-                                                new SatelliteException(SATELLITE_REQUEST_FAILED))));
-                            }
-
-                        } else {
-                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                    callback.onError(new SatelliteException(resultCode))));
-                        }
+                    public void accept(int result) {
+                        executor.execute(() -> Binder.withCleanCallingIdentity(
+                                () -> resultListener.accept(result)));
                     }
                 };
-                telephony.sendSatelliteDatagram(mSubId, datagramId, datagramType, datagram,
-                        needFullScreenPointingUI, receiver);
+                telephony.sendSatelliteDatagram(mSubId, datagramType, datagram,
+                        needFullScreenPointingUI, internalCallback);
             } else {
                 throw new IllegalStateException("telephony service is null.");
             }
diff --git a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
index e3e4171..d44a84d 100644
--- a/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatellitePositionUpdateCallback.java
@@ -93,7 +93,7 @@
 
     /**@hide*/
     @NonNull
-    public final ISatellitePositionUpdateCallback getBinder() {
+    final ISatellitePositionUpdateCallback getBinder() {
         return mBinder;
     }
 
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index cd084c9..2b6a5d9 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -65,7 +65,7 @@
 
     /**@hide*/
     @NonNull
-    public final ISatelliteProvisionStateCallback getBinder() {
+    final ISatelliteProvisionStateCallback getBinder() {
         return mBinder;
     }
 
diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
index d24bee6..17d05b7 100644
--- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java
@@ -84,7 +84,7 @@
 
     /**@hide*/
     @NonNull
-    public final ISatelliteStateCallback getBinder() {
+    final ISatelliteStateCallback getBinder() {
         return mBinder;
     }
 
diff --git a/telephony/java/com/android/internal/telephony/ILongConsumer.aidl b/telephony/java/com/android/internal/telephony/ILongConsumer.aidl
new file mode 100644
index 0000000..2f0d4a0
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ILongConsumer.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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.internal.telephony;
+
+ /**
+  * Copies consumer pattern for an operation that requires long result from another process to
+  * finish.
+  */
+ oneway interface ILongConsumer {
+    void accept(long result);
+ }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index ef9dc3e..eb537bb 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -2935,19 +2935,17 @@
     * Send datagram over satellite.
     *
     * @param subId The subId of the subscription to send satellite datagrams for.
-    * @param datagramId An id that uniquely identifies datagram requested to be sent.
     * @param datagramType Type of datagram.
     * @param datagram Datagram to send over satellite.
     * @param needFullScreenPointingUI this is used to indicate pointingUI app to open in
     *                                 full screen mode.
-    * @param receiver Result receiver to get the datagramId if datagram is sent successfully else
-    *                 error code of the request.
+    * @param callback The callback to get the error code of the request.
     */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void sendSatelliteDatagram(int subId, long datagramId, int datagramType,
+    void sendSatelliteDatagram(int subId, int datagramType,
              in SatelliteDatagram datagram, in boolean needFullScreenPointingUI,
-             in ResultReceiver receiver);
+             IIntegerConsumer callback);
 
     /**
      * Request to get whether satellite communication is allowed for the current location.
diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
index 684b385d..6046415 100644
--- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
+++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java
@@ -434,14 +434,15 @@
     /**
      * Gets the list of hotspot networks the user can select to connect to.
      *
-     * @return Returns a {@link List} of {@link HotspotNetwork} objects, empty list on failure.
+     * @return Returns a {@link List} of {@link HotspotNetwork} objects, null on failure.
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
             android.Manifest.permission.NETWORK_SETUP_WIZARD})
-    @NonNull
+    @SuppressWarnings("NullableCollection")
+    @Nullable
     public List<HotspotNetwork> getHotspotNetworks() {
         if (mService == null) {
-            return List.of();
+            return null;
         }
 
         try {
@@ -449,20 +450,21 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Exception in getHotspotNetworks", e);
         }
-        return List.of();
+        return null;
     }
 
     /**
      * Gets the list of known networks the user can select to connect to.
      *
-     * @return Returns a {@link List} of {@link KnownNetwork} objects, empty list on failure.
+     * @return Returns a {@link List} of {@link KnownNetwork} objects, null on failure.
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.NETWORK_SETTINGS,
             android.Manifest.permission.NETWORK_SETUP_WIZARD})
-    @NonNull
+    @SuppressWarnings("NullableCollection")
+    @Nullable
     public List<KnownNetwork> getKnownNetworks() {
         if (mService == null) {
-            return List.of();
+            return null;
         }
 
         try {
@@ -470,7 +472,7 @@
         } catch (RemoteException e) {
             Log.e(TAG, "Exception in getKnownNetworks", e);
         }
-        return List.of();
+        return null;
     }
 
     /**
diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
index 8c573e3..7578dfd 100644
--- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java
@@ -423,20 +423,20 @@
      * Verify getters.
      */
     @Test
-    public void getHotspotNetworks_serviceNotConnected_shouldReturnEmptyList() {
+    public void getHotspotNetworks_serviceNotConnected_shouldReturnNull() {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
 
-        assertThat(manager.getKnownNetworks()).isEmpty();
+        assertThat(manager.getHotspotNetworks()).isNull();
     }
 
     @Test
-    public void getHotspotNetworks_remoteException_shouldReturnEmptyList() throws RemoteException {
+    public void getHotspotNetworks_remoteException_shouldReturnNull() throws RemoteException {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).getHotspotNetworks();
 
-        assertThat(manager.getKnownNetworks()).isEmpty();
+        assertThat(manager.getHotspotNetworks()).isNull();
     }
 
     @Test
@@ -450,21 +450,21 @@
     }
 
     @Test
-    public void getKnownNetworks_serviceNotConnected_shouldReturnEmptyList()
+    public void getKnownNetworks_serviceNotConnected_shouldReturnNull()
             throws RemoteException {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(null);
 
-        assertThat(manager.getKnownNetworks()).isEmpty();
+        assertThat(manager.getKnownNetworks()).isNull();
     }
 
     @Test
-    public void getKnownNetworks_remoteException_shouldReturnEmptyList() throws RemoteException {
+    public void getKnownNetworks_remoteException_shouldReturnNull() throws RemoteException {
         SharedConnectivityManager manager = SharedConnectivityManager.create(mContext);
         manager.setService(mService);
         doThrow(new RemoteException()).when(mService).getKnownNetworks();
 
-        assertThat(manager.getKnownNetworks()).isEmpty();
+        assertThat(manager.getKnownNetworks()).isNull();
     }
 
     @Test