Merge "[Autofill PCC]: Allow Providers to set both field and type Allow providers to set both field and type while creating a dataset. Also, fix NPE, and issue a request if no detection results available. Test: atest android.autofillservice.cts.unittests.DatasetTest BUG: 270423491" 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 3748052..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();
@@ -12733,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";
@@ -26277,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 {
@@ -41110,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();
@@ -41127,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);
   }
 
@@ -54161,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();
@@ -54180,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
@@ -54203,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 5dbed34..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 {
@@ -10141,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 03bdb15..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();
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/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/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/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/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/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 f58cf8f..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 -->
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/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/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/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/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/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/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/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/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/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/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/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/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/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 095e090..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>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index ccdd71c..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] -->
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/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 2f937a9..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() {
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/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/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 131ad55..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,7 +388,7 @@
     @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")
@@ -582,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")
@@ -656,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/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/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt
index d092337..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
@@ -372,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/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/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 5a8dec1..4a7dd97 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2956,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
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/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/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/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/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/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/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/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/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/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/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/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/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 fef4c6d..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;
 
@@ -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/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/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/MetricUtilities.java b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
index 91470f6..255b2f8 100644
--- a/services/credentials/java/com/android/server/credentials/MetricUtilities.java
+++ b/services/credentials/java/com/android/server/credentials/MetricUtilities.java
@@ -90,33 +90,37 @@
     protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus,
             Map<String, ProviderSession> providers, int callingUid,
             ChosenProviderMetric chosenProviderMetric) {
-        var providerSessions = providers.values();
-        int providerSize = providerSessions.size();
-        int[] candidateUidList = new int[providerSize];
-        int[] candidateQueryRoundTripTimeList = new int[providerSize];
-        int[] candidateStatusList = new int[providerSize];
-        int index = 0;
-        for (var session : providerSessions) {
-            CandidateProviderMetric metric = session.mCandidateProviderMetric;
-            candidateUidList[index] = metric.getCandidateUid();
-            candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds();
-            candidateStatusList[index] = metric.getProviderQueryStatus();
-            index++;
+        try {
+            var providerSessions = providers.values();
+            int providerSize = providerSessions.size();
+            int[] candidateUidList = new int[providerSize];
+            int[] candidateQueryRoundTripTimeList = new int[providerSize];
+            int[] candidateStatusList = new int[providerSize];
+            int index = 0;
+            for (var session : providerSessions) {
+                CandidateProviderMetric metric = session.mCandidateProviderMetric;
+                candidateUidList[index] = metric.getCandidateUid();
+                candidateQueryRoundTripTimeList[index] = metric.getQueryLatencyMicroseconds();
+                candidateStatusList[index] = metric.getProviderQueryStatus();
+                index++;
+            }
+            FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED,
+                    /* api_name */apiName.getMetricCode(),
+                    /* caller_uid */ callingUid,
+                    /* api_status */ apiStatus.getMetricCode(),
+                    /* repeated_candidate_provider_uid */ candidateUidList,
+                    /* repeated_candidate_provider_round_trip_time_query_microseconds */
+                    candidateQueryRoundTripTimeList,
+                    /* repeated_candidate_provider_status */ candidateStatusList,
+                    /* chosen_provider_uid */ chosenProviderMetric.getChosenUid(),
+                    /* chosen_provider_round_trip_time_overall_microseconds */
+                    chosenProviderMetric.getEntireProviderLatencyMicroseconds(),
+                    /* chosen_provider_final_phase_microseconds (backwards compat only) */
+                    DEFAULT_INT_32,
+                    /* chosen_provider_status */ chosenProviderMetric.getChosenProviderStatus());
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
         }
-        FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED,
-                /* api_name */apiName.getMetricCode(),
-                /* caller_uid */ callingUid,
-                /* api_status */ apiStatus.getMetricCode(),
-                /* repeated_candidate_provider_uid */ candidateUidList,
-                /* repeated_candidate_provider_round_trip_time_query_microseconds */
-                candidateQueryRoundTripTimeList,
-                /* repeated_candidate_provider_status */ candidateStatusList,
-                /* chosen_provider_uid */ chosenProviderMetric.getChosenUid(),
-                /* chosen_provider_round_trip_time_overall_microseconds */
-                chosenProviderMetric.getEntireProviderLatencyMicroseconds(),
-                /* chosen_provider_final_phase_microseconds (backwards compat only) */
-                DEFAULT_INT_32,
-                /* chosen_provider_status */ chosenProviderMetric.getChosenProviderStatus());
     }
 
     /**
@@ -131,20 +135,24 @@
      */
     protected static void logApiCalled(ApiName apiName, ApiStatus apiStatus,
             int callingUid) {
-        FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED,
-                /* api_name */apiName.getMetricCode(),
-                /* caller_uid */ callingUid,
-                /* api_status */ apiStatus.getMetricCode(),
-                /* repeated_candidate_provider_uid */  DEFAULT_REPEATED_INT_32,
-                /* repeated_candidate_provider_round_trip_time_query_microseconds */
-                DEFAULT_REPEATED_INT_32,
-                /* repeated_candidate_provider_status */ DEFAULT_REPEATED_INT_32,
-                /* chosen_provider_uid */ DEFAULT_INT_32,
-                /* chosen_provider_round_trip_time_overall_microseconds */
-                DEFAULT_INT_32,
-                /* chosen_provider_final_phase_microseconds */
-                DEFAULT_INT_32,
-                /* chosen_provider_status */ DEFAULT_INT_32);
+        try {
+            FrameworkStatsLog.write(FrameworkStatsLog.CREDENTIAL_MANAGER_API_CALLED,
+                    /* api_name */apiName.getMetricCode(),
+                    /* caller_uid */ callingUid,
+                    /* api_status */ apiStatus.getMetricCode(),
+                    /* repeated_candidate_provider_uid */  DEFAULT_REPEATED_INT_32,
+                    /* repeated_candidate_provider_round_trip_time_query_microseconds */
+                    DEFAULT_REPEATED_INT_32,
+                    /* repeated_candidate_provider_status */ DEFAULT_REPEATED_INT_32,
+                    /* chosen_provider_uid */ DEFAULT_INT_32,
+                    /* chosen_provider_round_trip_time_overall_microseconds */
+                    DEFAULT_INT_32,
+                    /* chosen_provider_final_phase_microseconds */
+                    DEFAULT_INT_32,
+                    /* chosen_provider_status */ DEFAULT_INT_32);
+        } catch (Exception e) {
+            Log.w(TAG, "Unexpected error during metric logging: " + e);
+        }
     }
 
 }
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/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/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