Merge "Add VQDS service base files"
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 8defa16..4e52ed3 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -4303,14 +4303,13 @@
 
     int getStorageSeq() {
         synchronized (mLock) {
-            return mStorageController != null ? mStorageController.getTracker().getSeq() : -1;
+            return mStorageController.getTracker().getSeq();
         }
     }
 
     boolean getStorageNotLow() {
         synchronized (mLock) {
-            return mStorageController != null
-                    ? mStorageController.getTracker().isStorageNotLow() : false;
+            return mStorageController.getTracker().isStorageNotLow();
         }
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 88270494..53c56e7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -832,8 +832,8 @@
         private void writeConstraintsToXml(TypedXmlSerializer out, JobStatus jobStatus)
                 throws IOException {
             out.startTag(null, XML_TAG_PARAMS_CONSTRAINTS);
+            final JobInfo job = jobStatus.getJob();
             if (jobStatus.hasConnectivityConstraint()) {
-                final JobInfo job = jobStatus.getJob();
                 final NetworkRequest network = jobStatus.getJob().getRequiredNetwork();
                 out.attribute(null, "net-capabilities-csv", intArrayToString(
                         network.getCapabilities()));
@@ -854,16 +854,16 @@
                             job.getMinimumNetworkChunkBytes());
                 }
             }
-            if (jobStatus.hasIdleConstraint()) {
+            if (job.isRequireDeviceIdle()) {
                 out.attribute(null, "idle", Boolean.toString(true));
             }
-            if (jobStatus.hasChargingConstraint()) {
+            if (job.isRequireCharging()) {
                 out.attribute(null, "charging", Boolean.toString(true));
             }
-            if (jobStatus.hasBatteryNotLowConstraint()) {
+            if (job.isRequireBatteryNotLow()) {
                 out.attribute(null, "battery-not-low", Boolean.toString(true));
             }
-            if (jobStatus.hasStorageNotLowConstraint()) {
+            if (job.isRequireStorageNotLow()) {
                 out.attribute(null, "storage-not-low", Boolean.toString(true));
             }
             out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
index 36a26f0..0a305a2 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/PendingJobQueue.java
@@ -273,6 +273,13 @@
                 return Integer.compare(job2.overrideState, job1.overrideState);
             }
 
+            final boolean job1UI = job1.getJob().isUserInitiated();
+            final boolean job2UI = job2.getJob().isUserInitiated();
+            if (job1UI != job2UI) {
+                // Attempt to run user-initiated jobs ahead of all other jobs.
+                return job1UI ? -1 : 1;
+            }
+
             final boolean job1EJ = job1.isRequestedExpeditedJob();
             final boolean job2EJ = job2.isRequestedExpeditedJob();
             if (job1EJ != job2EJ) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index b0b6a01..0b875cc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -96,10 +96,16 @@
     public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
     public static final long NO_EARLIEST_RUNTIME = 0L;
 
-    static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
-    static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;  // 1 << 2
-    static final int CONSTRAINT_BATTERY_NOT_LOW = JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
-    static final int CONSTRAINT_STORAGE_NOT_LOW = JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static final int CONSTRAINT_CHARGING = JobInfo.CONSTRAINT_FLAG_CHARGING; // 1 < 0
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static final int CONSTRAINT_IDLE = JobInfo.CONSTRAINT_FLAG_DEVICE_IDLE;  // 1 << 2
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static final int CONSTRAINT_BATTERY_NOT_LOW =
+            JobInfo.CONSTRAINT_FLAG_BATTERY_NOT_LOW; // 1 << 1
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static final int CONSTRAINT_STORAGE_NOT_LOW =
+            JobInfo.CONSTRAINT_FLAG_STORAGE_NOT_LOW; // 1 << 3
     public static final int CONSTRAINT_TIMING_DELAY = 1 << 31;
     public static final int CONSTRAINT_DEADLINE = 1 << 30;
     public static final int CONSTRAINT_CONNECTIVITY = 1 << 28;
@@ -1820,7 +1826,8 @@
      * separately from the job's explicitly requested constraints and MUST be satisfied before
      * the job can run if the app doesn't have quota.
      */
-    private void addDynamicConstraints(int constraints) {
+    @VisibleForTesting
+    public void addDynamicConstraints(int constraints) {
         if ((constraints & CONSTRAINT_WITHIN_QUOTA) != 0) {
             // Quota should never be used as a dynamic constraint.
             Slog.wtf(TAG, "Tried to set quota as a dynamic constraint");
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index b84c8a4..2b59209 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -164,8 +164,9 @@
     }
 
     @GuardedBy("mLock")
-    private boolean isAffordableLocked(long balance, long price, long ctp) {
-        return balance >= price && mScribe.getRemainingConsumableCakesLocked() >= ctp;
+    private boolean isAffordableLocked(long balance, long price, long stockLimitHonoringCtp) {
+        return balance >= price
+                && mScribe.getRemainingConsumableCakesLocked() >= stockLimitHonoringCtp;
     }
 
     @GuardedBy("mLock")
@@ -303,7 +304,8 @@
                         note.recalculateCosts(economicPolicy, userId, pkgName);
                         final boolean isAffordable = isVip
                                 || isAffordableLocked(newBalance,
-                                        note.getCachedModifiedPrice(), note.getCtp());
+                                        note.getCachedModifiedPrice(),
+                                        note.getStockLimitHonoringCtp());
                         if (note.isCurrentlyAffordable() != isAffordable) {
                             note.setNewAffordability(isAffordable);
                             mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -339,7 +341,7 @@
                 note.recalculateCosts(economicPolicy, userId, pkgName);
                 final boolean isAffordable = isVip
                         || isAffordableLocked(newBalance,
-                        note.getCachedModifiedPrice(), note.getCtp());
+                        note.getCachedModifiedPrice(), note.getStockLimitHonoringCtp());
                 if (note.isCurrentlyAffordable() != isAffordable) {
                     note.setNewAffordability(isAffordable);
                     mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -403,7 +405,8 @@
                         note.recalculateCosts(economicPolicy, userId, pkgName);
                         final boolean isAffordable = isVip
                                 || isAffordableLocked(newBalance,
-                                        note.getCachedModifiedPrice(), note.getCtp());
+                                        note.getCachedModifiedPrice(),
+                                        note.getStockLimitHonoringCtp());
                         if (note.isCurrentlyAffordable() != isAffordable) {
                             note.setNewAffordability(isAffordable);
                             mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -541,7 +544,7 @@
                     final ActionAffordabilityNote note = actionAffordabilityNotes.valueAt(i);
                     final boolean isAffordable = isVip
                             || isAffordableLocked(newBalance,
-                                    note.getCachedModifiedPrice(), note.getCtp());
+                                    note.getCachedModifiedPrice(), note.getStockLimitHonoringCtp());
                     if (note.isCurrentlyAffordable() != isAffordable) {
                         note.setNewAffordability(isAffordable);
                         mIrs.postAffordabilityChanged(userId, pkgName, note);
@@ -882,7 +885,7 @@
                         mUpperThreshold = (mUpperThreshold == Long.MIN_VALUE)
                                 ? price : Math.min(mUpperThreshold, price);
                     }
-                    final long ctp = note.getCtp();
+                    final long ctp = note.getStockLimitHonoringCtp();
                     if (ctp <= mRemainingConsumableCredits) {
                         mCtpThreshold = Math.max(mCtpThreshold, ctp);
                     }
@@ -1119,7 +1122,7 @@
             note.recalculateCosts(economicPolicy, userId, pkgName);
             note.setNewAffordability(isVip
                     || isAffordableLocked(getBalanceLocked(userId, pkgName),
-                            note.getCachedModifiedPrice(), note.getCtp()));
+                            note.getCachedModifiedPrice(), note.getStockLimitHonoringCtp()));
             mIrs.postAffordabilityChanged(userId, pkgName, note);
             // Update ongoing alarm
             scheduleBalanceCheckLocked(userId, pkgName);
@@ -1146,7 +1149,7 @@
     static final class ActionAffordabilityNote {
         private final EconomyManagerInternal.ActionBill mActionBill;
         private final EconomyManagerInternal.AffordabilityChangeListener mListener;
-        private long mCtp;
+        private long mStockLimitHonoringCtp;
         private long mModifiedPrice;
         private boolean mIsAffordable;
 
@@ -1185,29 +1188,34 @@
             return mModifiedPrice;
         }
 
-        private long getCtp() {
-            return mCtp;
+        /** Returns the cumulative CTP of actions in this note that respect the stock limit. */
+        private long getStockLimitHonoringCtp() {
+            return mStockLimitHonoringCtp;
         }
 
         @VisibleForTesting
         void recalculateCosts(@NonNull EconomicPolicy economicPolicy,
                 int userId, @NonNull String pkgName) {
             long modifiedPrice = 0;
-            long ctp = 0;
+            long stockLimitHonoringCtp = 0;
             final List<EconomyManagerInternal.AnticipatedAction> anticipatedActions =
                     mActionBill.getAnticipatedActions();
             for (int i = 0; i < anticipatedActions.size(); ++i) {
                 final EconomyManagerInternal.AnticipatedAction aa = anticipatedActions.get(i);
+                final EconomicPolicy.Action action = economicPolicy.getAction(aa.actionId);
 
                 final EconomicPolicy.Cost actionCost =
                         economicPolicy.getCostOfAction(aa.actionId, userId, pkgName);
                 modifiedPrice += actionCost.price * aa.numInstantaneousCalls
                         + actionCost.price * (aa.ongoingDurationMs / 1000);
-                ctp += actionCost.costToProduce * aa.numInstantaneousCalls
-                        + actionCost.costToProduce * (aa.ongoingDurationMs / 1000);
+                if (action.respectsStockLimit) {
+                    stockLimitHonoringCtp +=
+                            actionCost.costToProduce * aa.numInstantaneousCalls
+                                    + actionCost.costToProduce * (aa.ongoingDurationMs / 1000);
+                }
             }
             mModifiedPrice = modifiedPrice;
-            mCtp = ctp;
+            mStockLimitHonoringCtp = stockLimitHonoringCtp;
         }
 
         boolean isCurrentlyAffordable() {
@@ -1267,7 +1275,8 @@
                                 final ActionAffordabilityNote note =
                                         actionAffordabilityNotes.valueAt(i);
                                 final boolean isAffordable = isVip || isAffordableLocked(
-                                        newBalance, note.getCachedModifiedPrice(), note.getCtp());
+                                        newBalance, note.getCachedModifiedPrice(),
+                                        note.getStockLimitHonoringCtp());
                                 if (note.isCurrentlyAffordable() != isAffordable) {
                                     note.setNewAffordability(isAffordable);
                                     mIrs.postAffordabilityChanged(userId, pkgName, note);
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index 46338fa..a6d064c 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -262,12 +262,17 @@
                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE,
                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE_CAKES);
 
+        // Apps must hold the SCHEDULE_EXACT_ALARM or USE_EXACT_ALARMS permission in order to use
+        // exact alarms. Since the user has the option of granting/revoking the permission, we can
+        // be a little lenient on the action cost checks and only stop the action if the app has
+        // run out of credits, and not when the system has run out of stock.
         mActions.put(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE,
                 new Action(ACTION_ALARM_WAKEUP_EXACT_ALLOW_WHILE_IDLE,
                         getConstantAsCake(mParser, properties,
                                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_CTP_CAKES),
-                        exactAllowWhileIdleWakeupBasePrice));
+                        exactAllowWhileIdleWakeupBasePrice,
+                        /* respectsStockLimit */ false));
         mActions.put(ACTION_ALARM_WAKEUP_EXACT,
                 new Action(ACTION_ALARM_WAKEUP_EXACT,
                         getConstantAsCake(mParser, properties,
@@ -275,7 +280,8 @@
                                 DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_CTP_CAKES),
                         getConstantAsCake(mParser, properties,
                                 KEY_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE,
-                                DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES)));
+                                DEFAULT_AM_ACTION_ALARM_EXACT_WAKEUP_BASE_PRICE_CAKES),
+                        /* respectsStockLimit */ false));
 
         final long inexactAllowWhileIdleWakeupBasePrice =
                 getConstantAsCake(mParser, properties,
@@ -287,7 +293,8 @@
                         getConstantAsCake(mParser, properties,
                                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_WAKEUP_CTP_CAKES),
-                        inexactAllowWhileIdleWakeupBasePrice));
+                        inexactAllowWhileIdleWakeupBasePrice,
+                        /* respectsStockLimit */ false));
         mActions.put(ACTION_ALARM_WAKEUP_INEXACT,
                 new Action(ACTION_ALARM_WAKEUP_INEXACT,
                         getConstantAsCake(mParser, properties,
@@ -295,7 +302,8 @@
                                 DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES),
                         getConstantAsCake(mParser, properties,
                                 KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE,
-                                DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES)));
+                                DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES),
+                        /* respectsStockLimit */ false));
 
         final long exactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties,
                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_BASE_PRICE,
@@ -305,7 +313,8 @@
                         getConstantAsCake(mParser, properties,
                                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP,
                                 DEFAULT_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_NONWAKEUP_CTP_CAKES),
-                        exactAllowWhileIdleNonWakeupBasePrice));
+                        exactAllowWhileIdleNonWakeupBasePrice,
+                        /* respectsStockLimit */ false));
 
         mActions.put(ACTION_ALARM_NONWAKEUP_EXACT,
                 new Action(ACTION_ALARM_NONWAKEUP_EXACT,
@@ -314,7 +323,8 @@
                                 DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_CTP_CAKES),
                         getConstantAsCake(mParser, properties,
                                 KEY_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE,
-                                DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES)));
+                                DEFAULT_AM_ACTION_ALARM_EXACT_NONWAKEUP_BASE_PRICE_CAKES),
+                        /* respectsStockLimit */ false));
 
         final long inexactAllowWhileIdleNonWakeupBasePrice = getConstantAsCake(mParser, properties,
                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_INEXACT_NONWAKEUP_BASE_PRICE,
@@ -342,7 +352,8 @@
                                 DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_CTP_CAKES),
                         getConstantAsCake(mParser, properties,
                                 KEY_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE,
-                                DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES)));
+                                DEFAULT_AM_ACTION_ALARM_ALARMCLOCK_BASE_PRICE_CAKES),
+                        /* respectsStockLimit */ false));
 
         mRewards.put(REWARD_TOP_ACTIVITY, new Reward(REWARD_TOP_ACTIVITY,
                 getConstantAsCake(mParser, properties,
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index b52f6f1..a4043dd 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -149,11 +149,22 @@
          * the action unless a modifier lowers the cost to produce.
          */
         public final long basePrice;
+        /**
+         * Whether the remaining stock limit affects an app's ability to perform this action.
+         * If {@code false}, then the action can be performed, even if the cost is higher
+         * than the remaining stock. This does not affect checking against an app's balance.
+         */
+        public final boolean respectsStockLimit;
 
         Action(int id, long costToProduce, long basePrice) {
+            this(id, costToProduce, basePrice, true);
+        }
+
+        Action(int id, long costToProduce, long basePrice, boolean respectsStockLimit) {
             this.id = id;
             this.costToProduce = costToProduce;
             this.basePrice = basePrice;
+            this.respectsStockLimit = respectsStockLimit;
         }
     }
 
diff --git a/core/api/current.txt b/core/api/current.txt
index 1b0c5c2..326a8e7 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -40318,12 +40318,14 @@
 
   public final class Tile implements android.os.Parcelable {
     method public int describeContents();
+    method @Nullable public android.app.PendingIntent getActivityLaunchForClick();
     method public CharSequence getContentDescription();
     method public android.graphics.drawable.Icon getIcon();
     method public CharSequence getLabel();
     method public int getState();
     method @Nullable public CharSequence getStateDescription();
     method @Nullable public CharSequence getSubtitle();
+    method public void setActivityLaunchForClick(@Nullable android.app.PendingIntent);
     method public void setContentDescription(CharSequence);
     method public void setIcon(android.graphics.drawable.Icon);
     method public void setLabel(CharSequence);
@@ -40352,6 +40354,7 @@
     method public static final void requestListeningState(android.content.Context, android.content.ComponentName);
     method public final void showDialog(android.app.Dialog);
     method public final void startActivityAndCollapse(android.content.Intent);
+    method public final void startActivityAndCollapse(@NonNull android.app.PendingIntent);
     method public final void unlockAndRun(Runnable);
     field public static final String ACTION_QS_TILE = "android.service.quicksettings.action.QS_TILE";
     field public static final String ACTION_QS_TILE_PREFERENCES = "android.service.quicksettings.action.QS_TILE_PREFERENCES";
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index bc15d3e..b4594e0 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -393,6 +393,8 @@
   }
 
   public static final class R.bool {
+    field public static final int config_enableDefaultNotes;
+    field public static final int config_enableDefaultNotesForWorkProfile;
     field public static final int config_enableQrCodeScannerOnLockScreen = 17891336; // 0x1110008
     field public static final int config_safetyProtectionEnabled;
     field public static final int config_sendPackageName = 17891328; // 0x1110000
@@ -429,6 +431,7 @@
     field public static final int config_defaultCallRedirection = 17039397; // 0x1040025
     field public static final int config_defaultCallScreening = 17039398; // 0x1040026
     field public static final int config_defaultDialer = 17039395; // 0x1040023
+    field public static final int config_defaultNotes;
     field public static final int config_defaultSms = 17039396; // 0x1040024
     field public static final int config_devicePolicyManagement = 17039421; // 0x104003d
     field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f
@@ -6604,6 +6607,7 @@
   public class AudioManager {
     method @Deprecated public int abandonAudioFocus(android.media.AudioManager.OnAudioFocusChangeListener, android.media.AudioAttributes);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addAssistantServicesUids(@NonNull int[]);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public void addOnDevicesForAttributesChangedListener(@NonNull android.media.AudioAttributes, @NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnDevicesForAttributesChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnNonDefaultDevicesForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnNonDefaultDevicesForStrategyChangedListener) throws java.lang.SecurityException;
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDeviceForStrategyChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener) throws java.lang.SecurityException;
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void addOnPreferredDevicesForCapturePresetChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener) throws java.lang.SecurityException;
@@ -6644,6 +6648,7 @@
     method public void registerVolumeGroupCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.AudioManager.VolumeGroupCallback);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeAssistantServicesUids(@NonNull int[]);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean removeDeviceAsNonDefaultForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public void removeOnDevicesForAttributesChangedListener(@NonNull android.media.AudioManager.OnDevicesForAttributesChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnNonDefaultDevicesForStrategyChangedListener(@NonNull android.media.AudioManager.OnNonDefaultDevicesForStrategyChangedListener);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDeviceForStrategyChangedListener(@NonNull android.media.AudioManager.OnPreferredDeviceForStrategyChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void removeOnPreferredDevicesForCapturePresetChangedListener(@NonNull android.media.AudioManager.OnPreferredDevicesForCapturePresetChangedListener);
@@ -6701,6 +6706,10 @@
     field public static final int EVENT_TIMEOUT = 2; // 0x2
   }
 
+  public static interface AudioManager.OnDevicesForAttributesChangedListener {
+    method public void onDevicesForAttributesChanged(@NonNull android.media.AudioAttributes, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
+  }
+
   public static interface AudioManager.OnNonDefaultDevicesForStrategyChangedListener {
     method public void onNonDefaultDevicesForStrategyChanged(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List<android.media.AudioDeviceAttributes>);
   }
diff --git a/core/java/android/service/quicksettings/Tile.java b/core/java/android/service/quicksettings/Tile.java
index 289b0e0..910fc44 100644
--- a/core/java/android/service/quicksettings/Tile.java
+++ b/core/java/android/service/quicksettings/Tile.java
@@ -227,7 +227,6 @@
 
     /**
      * Gets the Activity {@link PendingIntent} to be launched when the tile is clicked.
-     * @hide
      */
     @Nullable
     public PendingIntent getActivityLaunchForClick() {
@@ -243,7 +242,6 @@
      * (This is the default behavior if this method is never called.)
      * @param pendingIntent a PendingIntent for an activity to be launched onclick, or {@code null}
      *                      to handle the clicks in the `TileService`.
-     * @hide
      */
     public void setActivityLaunchForClick(@Nullable PendingIntent pendingIntent) {
         if (pendingIntent != null && !pendingIntent.isActivity()) {
diff --git a/core/java/android/service/quicksettings/TileService.java b/core/java/android/service/quicksettings/TileService.java
index 506b3b8..7b6ff97 100644
--- a/core/java/android/service/quicksettings/TileService.java
+++ b/core/java/android/service/quicksettings/TileService.java
@@ -15,6 +15,7 @@
  */
 package android.service.quicksettings;
 
+import android.annotation.NonNull;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.SystemApi;
@@ -41,6 +42,8 @@
 
 import com.android.internal.R;
 
+import java.util.Objects;
+
 /**
  * A TileService provides the user a tile that can be added to Quick Settings.
  * Quick Settings is a space provided that allows the user to change settings and
@@ -341,9 +344,9 @@
      * Will collapse Quick Settings after launching.
      *
      * @param pendingIntent A PendingIntent for an Activity to be launched immediately.
-     * @hide
      */
-    public void startActivityAndCollapse(PendingIntent pendingIntent) {
+    public final void startActivityAndCollapse(@NonNull PendingIntent pendingIntent) {
+        Objects.requireNonNull(pendingIntent);
         try {
             mService.startActivity(mTileToken, pendingIntent);
         } catch (RemoteException e) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index e031e5f..a7c48f3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -5977,7 +5977,7 @@
     <permission android:name="android.permission.START_VIEW_PERMISSION_USAGE"
         android:label="@string/permlab_startViewPermissionUsage"
         android:description="@string/permdesc_startViewPermissionUsage"
-        android:protectionLevel="signature|installer" />
+        android:protectionLevel="signature|installer|module" />
 
     <!--
         @SystemApi
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 184f869..2310367 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2135,6 +2135,12 @@
     <string name="config_defaultAutomotiveNavigation" translatable="false"></string>
     <!-- The name of the package that will hold the system wear health service role. -->
     <string name="config_systemWearHealthService" translatable="false"></string>
+    <!-- The name of the package that will hold the default notes role. -->
+    <string name="config_defaultNotes" translatable="false"></string>
+    <!-- Whether the default notes role should be enabled. -->
+    <bool name="config_enableDefaultNotes">false</bool>
+    <!-- Whether the default notes role for work profile should be enabled. -->
+    <bool name="config_enableDefaultNotesForWorkProfile">false</bool>
 
     <!-- The name of the package that will handle updating the device management role. -->
     <string name="config_devicePolicyManagementUpdater" translatable="false"></string>
diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml
index 97feaac..dfd4d9a 100644
--- a/core/res/res/values/public-staging.xml
+++ b/core/res/res/values/public-staging.xml
@@ -139,6 +139,8 @@
   <staging-public-group type="string" first-id="0x01cb0000">
     <!-- @hide @SystemApi -->
     <public name="config_systemWearHealthService" />
+    <!-- @hide @SystemApi -->
+    <public name="config_defaultNotes" />
   </staging-public-group>
 
   <staging-public-group type="dimen" first-id="0x01ca0000">
@@ -182,6 +184,10 @@
   <staging-public-group type="bool" first-id="0x01be0000">
     <!-- @hide @SystemApi -->
     <public name="config_safetyProtectionEnabled" />
+    <!-- @hide @SystemApi -->
+    <public name="config_enableDefaultNotes" />
+    <!-- @hide @SystemApi -->
+    <public name="config_enableDefaultNotesForWorkProfile" />
   </staging-public-group>
 
   <staging-public-group type="fraction" first-id="0x01bd0000">
diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
index 9947d34..c55a781 100644
--- a/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
+++ b/keystore/java/android/security/keystore2/AndroidKeyStoreProvider.java
@@ -38,6 +38,7 @@
 import java.security.Security;
 import java.security.Signature;
 import java.security.UnrecoverableKeyException;
+import java.security.cert.X509Certificate;
 import java.security.interfaces.ECPublicKey;
 import java.security.interfaces.RSAPublicKey;
 
@@ -221,7 +222,14 @@
         }
         final byte[] x509PublicCert = metadata.certificate;
 
-        PublicKey publicKey = AndroidKeyStoreSpi.toCertificate(x509PublicCert).getPublicKey();
+        final X509Certificate parsedX509Certificate =
+                AndroidKeyStoreSpi.toCertificate(x509PublicCert);
+        if (parsedX509Certificate == null) {
+            throw new UnrecoverableKeyException("Failed to parse the X.509 certificate containing"
+                   + " the public key. This likely indicates a hardware problem.");
+        }
+
+        PublicKey publicKey = parsedX509Certificate.getPublicKey();
 
         String jcaKeyAlgorithm = publicKey.getAlgorithm();
 
diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp
index a5b192c..dc4b563 100644
--- a/libs/WindowManager/Jetpack/Android.bp
+++ b/libs/WindowManager/Jetpack/Android.bp
@@ -63,12 +63,6 @@
     sdk_version: "current",
 }
 
-android_library_import {
-    name: "window-extensions-core",
-    aars: ["window-extensions-core-release.aar"],
-    sdk_version: "current",
-}
-
 java_library {
     name: "androidx.window.extensions",
     srcs: [
@@ -76,10 +70,7 @@
         "src/androidx/window/util/**/*.java",
         "src/androidx/window/common/**/*.java",
     ],
-    static_libs: [
-        "window-extensions",
-        "window-extensions-core",
-    ],
+    static_libs: ["window-extensions"],
     installable: true,
     sdk_version: "core_platform",
     system_ext_specific: true,
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 54474ae..ce7d695 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -75,10 +75,7 @@
 import androidx.annotation.Nullable;
 import androidx.window.common.CommonFoldingFeature;
 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
-import androidx.window.extensions.WindowExtensionsImpl;
 import androidx.window.extensions.WindowExtensionsProvider;
-import androidx.window.extensions.core.util.function.Consumer;
-import androidx.window.extensions.core.util.function.Function;
 import androidx.window.extensions.embedding.TransactionManager.TransactionRecord;
 import androidx.window.extensions.layout.WindowLayoutComponentImpl;
 
@@ -89,6 +86,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 
 /**
  * Main controller class that manages split states and presentation.
@@ -114,7 +112,7 @@
     /**
      * A developer-defined {@link SplitAttributes} calculator to compute the current
      * {@link SplitAttributes} with the current device and window states.
-     * It is registered via {@link #setSplitAttributesCalculator(Function)}
+     * It is registered via {@link #setSplitAttributesCalculator(SplitAttributesCalculator)}
      * and unregistered via {@link #clearSplitAttributesCalculator()}.
      * This is called when:
      * <ul>
@@ -127,7 +125,7 @@
      */
     @GuardedBy("mLock")
     @Nullable
-    private Function<SplitAttributesCalculatorParams, SplitAttributes> mSplitAttributesCalculator;
+    private SplitAttributesCalculator mSplitAttributesCalculator;
 
     /**
      * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
@@ -140,7 +138,6 @@
     final SparseArray<TaskContainer> mTaskContainers = new SparseArray<>();
 
     /** Callback to Jetpack to notify about changes to split states. */
-    @GuardedBy("mLock")
     @Nullable
     private Consumer<List<SplitInfo>> mEmbeddingCallback;
     private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
@@ -174,8 +171,7 @@
         mWindowLayoutComponent.addFoldingStateChangedCallback(new FoldingFeatureListener());
     }
 
-    private class FoldingFeatureListener
-            implements java.util.function.Consumer<List<CommonFoldingFeature>> {
+    private class FoldingFeatureListener implements Consumer<List<CommonFoldingFeature>> {
         @Override
         public void accept(List<CommonFoldingFeature> foldingFeatures) {
             synchronized (mLock) {
@@ -216,8 +212,7 @@
     }
 
     @Override
-    public void setSplitAttributesCalculator(
-            @NonNull Function<SplitAttributesCalculatorParams, SplitAttributes> calculator) {
+    public void setSplitAttributesCalculator(@NonNull SplitAttributesCalculator calculator) {
         synchronized (mLock) {
             mSplitAttributesCalculator = calculator;
         }
@@ -232,7 +227,7 @@
 
     @GuardedBy("mLock")
     @Nullable
-    Function<SplitAttributesCalculatorParams, SplitAttributes> getSplitAttributesCalculator() {
+    SplitAttributesCalculator getSplitAttributesCalculator() {
         return mSplitAttributesCalculator;
     }
 
@@ -245,22 +240,9 @@
 
     /**
      * Registers the split organizer callback to notify about changes to active splits.
-     * @deprecated Use {@link #setSplitInfoCallback(Consumer)} starting with
-     * {@link WindowExtensionsImpl#getVendorApiLevel()} 2.
      */
-    @Deprecated
     @Override
-    public void setSplitInfoCallback(
-            @NonNull java.util.function.Consumer<List<SplitInfo>> callback) {
-        Consumer<List<SplitInfo>> oemConsumer = callback::accept;
-        setSplitInfoCallback(oemConsumer);
-    }
-
-    /**
-     * Registers the split organizer callback to notify about changes to active splits.
-     * @since {@link WindowExtensionsImpl#getVendorApiLevel()} 2
-     */
-    public void setSplitInfoCallback(Consumer<List<SplitInfo>> callback) {
+    public void setSplitInfoCallback(@NonNull Consumer<List<SplitInfo>> callback) {
         synchronized (mLock) {
             mEmbeddingCallback = callback;
             updateCallbackIfNecessary();
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 41580a0..9db9f87 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -42,11 +42,11 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.window.extensions.core.util.function.Function;
 import androidx.window.extensions.embedding.SplitAttributes.SplitType;
 import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
 import androidx.window.extensions.embedding.SplitAttributes.SplitType.HingeSplitType;
 import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
+import androidx.window.extensions.embedding.SplitAttributesCalculator.SplitAttributesCalculatorParams;
 import androidx.window.extensions.embedding.TaskContainer.TaskProperties;
 import androidx.window.extensions.layout.DisplayFeature;
 import androidx.window.extensions.layout.FoldingFeature;
@@ -522,8 +522,7 @@
             @NonNull SplitRule rule, @Nullable Pair<Size, Size> minDimensionsPair) {
         final Configuration taskConfiguration = taskProperties.getConfiguration();
         final WindowMetrics taskWindowMetrics = getTaskWindowMetrics(taskConfiguration);
-        final Function<SplitAttributesCalculatorParams, SplitAttributes> calculator =
-                mController.getSplitAttributesCalculator();
+        final SplitAttributesCalculator calculator = mController.getSplitAttributesCalculator();
         final SplitAttributes defaultSplitAttributes = rule.getDefaultSplitAttributes();
         final boolean isDefaultMinSizeSatisfied = rule.checkParentMetrics(taskWindowMetrics);
         if (calculator == null) {
@@ -539,7 +538,7 @@
         final SplitAttributesCalculatorParams params = new SplitAttributesCalculatorParams(
                 taskWindowMetrics, taskConfiguration, defaultSplitAttributes,
                 isDefaultMinSizeSatisfied, windowLayoutInfo, rule.getTag());
-        final SplitAttributes splitAttributes = calculator.apply(params);
+        final SplitAttributes splitAttributes = calculator.computeSplitAttributesForParams(params);
         return sanitizeSplitAttributes(taskProperties, splitAttributes, minDimensionsPair);
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index 32b915c..84b2bfc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -44,7 +44,6 @@
 import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
 import androidx.window.common.EmptyLifecycleCallbacksAdapter;
 import androidx.window.common.RawFoldingFeatureProducer;
-import androidx.window.extensions.core.util.function.Consumer;
 import androidx.window.util.DataProducer;
 
 import java.util.ArrayList;
@@ -52,6 +51,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
 
 /**
  * Reference implementation of androidx.window.extensions.layout OEM interface for use with
@@ -80,10 +80,6 @@
     private final Map<IBinder, ConfigurationChangeListener> mConfigurationChangeListeners =
             new ArrayMap<>();
 
-    @GuardedBy("mLock")
-    private final Map<java.util.function.Consumer<WindowLayoutInfo>, Consumer<WindowLayoutInfo>>
-            mJavaToExtConsumers = new ArrayMap<>();
-
     public WindowLayoutComponentImpl(@NonNull Context context) {
         ((Application) context.getApplicationContext())
                 .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
@@ -94,8 +90,7 @@
     }
 
     /** Registers to listen to {@link CommonFoldingFeature} changes */
-    public void addFoldingStateChangedCallback(
-            java.util.function.Consumer<List<CommonFoldingFeature>> consumer) {
+    public void addFoldingStateChangedCallback(Consumer<List<CommonFoldingFeature>> consumer) {
         synchronized (mLock) {
             mFoldingFeatureProducer.addDataChangedCallback(consumer);
         }
@@ -109,17 +104,13 @@
      */
     @Override
     public void addWindowLayoutInfoListener(@NonNull Activity activity,
-            @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) {
-        final Consumer<WindowLayoutInfo> extConsumer = consumer::accept;
-        synchronized (mLock) {
-            mJavaToExtConsumers.put(consumer, extConsumer);
-        }
-        addWindowLayoutInfoListener(activity, extConsumer);
+            @NonNull Consumer<WindowLayoutInfo> consumer) {
+        addWindowLayoutInfoListener((Context) activity, consumer);
     }
 
     /**
-     * Similar to {@link #addWindowLayoutInfoListener(Activity, java.util.function.Consumer)}, but
-     * takes a UI Context as a parameter.
+     * Similar to {@link #addWindowLayoutInfoListener(Activity, Consumer)}, but takes a UI Context
+     * as a parameter.
      *
      * Jetpack {@link androidx.window.layout.ExtensionWindowLayoutInfoBackend} makes sure all
      * consumers related to the same {@link Context} gets updated {@link WindowLayoutInfo}
@@ -160,18 +151,6 @@
         }
     }
 
-    @Override
-    public void removeWindowLayoutInfoListener(
-            @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) {
-        final Consumer<WindowLayoutInfo> extConsumer;
-        synchronized (mLock) {
-            extConsumer = mJavaToExtConsumers.remove(consumer);
-        }
-        if (extConsumer != null) {
-            removeWindowLayoutInfoListener(extConsumer);
-        }
-    }
-
     /**
      * Removes a listener no longer interested in receiving updates.
      *
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 13a2c78..d189ae2 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -22,6 +22,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
+import androidx.window.extensions.embedding.SplitAttributes;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -53,4 +54,15 @@
     public void testGetActivityEmbeddingComponent() {
         assertThat(mExtensions.getActivityEmbeddingComponent()).isNotNull();
     }
+
+    @Test
+    public void testSplitAttributes_default() {
+        // Make sure the default value in the extensions aar.
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder().build();
+        assertThat(splitAttributes.getLayoutDirection())
+                .isEqualTo(SplitAttributes.LayoutDirection.LOCALE);
+        assertThat(splitAttributes.getSplitType())
+                .isEqualTo(new SplitAttributes.SplitType.RatioSplitType(0.5f));
+        assertThat(splitAttributes.getAnimationBackgroundColor()).isEqualTo(0);
+    }
 }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index 459ec9f..2f92a57 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -34,11 +34,9 @@
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.util.Pair;
-import android.view.WindowMetrics;
 import android.window.TaskFragmentInfo;
 import android.window.WindowContainerToken;
 
-import androidx.window.extensions.core.util.function.Predicate;
 import androidx.window.extensions.embedding.SplitAttributes.SplitType;
 import androidx.window.extensions.layout.DisplayFeature;
 import androidx.window.extensions.layout.FoldingFeature;
@@ -109,7 +107,7 @@
     static SplitRule createSplitRule(@NonNull Activity primaryActivity,
             @NonNull Intent secondaryIntent, boolean clearTop) {
         final Pair<Activity, Intent> targetPair = new Pair<>(primaryActivity, secondaryIntent);
-        return createSplitPairRuleBuilder(
+        return new SplitPairRule.Builder(
                 activityPair -> false,
                 targetPair::equals,
                 w -> true)
@@ -146,7 +144,7 @@
             @NonNull Activity secondaryActivity, int finishPrimaryWithSecondary,
             int finishSecondaryWithPrimary, boolean clearTop) {
         final Pair<Activity, Activity> targetPair = new Pair<>(primaryActivity, secondaryActivity);
-        return createSplitPairRuleBuilder(
+        return new SplitPairRule.Builder(
                 targetPair::equals,
                 activityIntentPair -> false,
                 w -> true)
@@ -225,26 +223,4 @@
         displayFeatures.add(foldingFeature);
         return new WindowLayoutInfo(displayFeatures);
     }
-
-    static ActivityRule.Builder createActivityBuilder(
-            @NonNull Predicate<Activity> activityPredicate,
-            @NonNull Predicate<Intent> intentPredicate) {
-        return new ActivityRule.Builder(activityPredicate, intentPredicate);
-    }
-
-    static SplitPairRule.Builder createSplitPairRuleBuilder(
-            @NonNull Predicate<Pair<Activity, Activity>> activitiesPairPredicate,
-            @NonNull Predicate<Pair<Activity, Intent>> activityIntentPairPredicate,
-            @NonNull Predicate<WindowMetrics> windowMetricsPredicate) {
-        return new SplitPairRule.Builder(activitiesPairPredicate, activityIntentPairPredicate,
-                windowMetricsPredicate);
-    }
-
-    static SplitPlaceholderRule.Builder createSplitPlaceholderRuleBuilder(
-            @NonNull Intent placeholderIntent, @NonNull Predicate<Activity> activityPredicate,
-            @NonNull Predicate<Intent> intentPredicate,
-            @NonNull Predicate<WindowMetrics> windowMetricsPredicate) {
-        return new SplitPlaceholderRule.Builder(placeholderIntent, activityPredicate,
-                intentPredicate, windowMetricsPredicate);
-    }
 }
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index f9cbb5c..3cc31f9 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -34,11 +34,8 @@
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TEST_TAG;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityBuilder;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPlaceholderRuleBuilder;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createTestTaskContainer;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
@@ -432,7 +429,7 @@
     @Test
     public void testResolveStartActivityIntent_withoutLaunchingActivity() {
         final Intent intent = new Intent();
-        final ActivityRule expandRule = createActivityBuilder(r -> false, i -> i == intent)
+        final ActivityRule expandRule = new ActivityRule.Builder(r -> false, i -> i == intent)
                 .setShouldAlwaysExpand(true)
                 .build();
         mSplitController.setEmbeddingRules(Collections.singleton(expandRule));
@@ -1170,7 +1167,7 @@
 
     @Test
     public void testHasSamePresentation() {
-        SplitPairRule splitRule1 = createSplitPairRuleBuilder(
+        SplitPairRule splitRule1 = new SplitPairRule.Builder(
                 activityPair -> true,
                 activityIntentPair -> true,
                 windowMetrics -> true)
@@ -1178,7 +1175,7 @@
                 .setFinishPrimaryWithSecondary(DEFAULT_FINISH_PRIMARY_WITH_SECONDARY)
                 .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
                 .build();
-        SplitPairRule splitRule2 = createSplitPairRuleBuilder(
+        SplitPairRule splitRule2 = new SplitPairRule.Builder(
                 activityPair -> true,
                 activityIntentPair -> true,
                 windowMetrics -> true)
@@ -1191,7 +1188,7 @@
                 SplitController.haveSamePresentation(splitRule1, splitRule2,
                         new WindowMetrics(TASK_BOUNDS, WindowInsets.CONSUMED)));
 
-        splitRule2 = createSplitPairRuleBuilder(
+        splitRule2 = new SplitPairRule.Builder(
                 activityPair -> true,
                 activityIntentPair -> true,
                 windowMetrics -> true)
@@ -1355,7 +1352,7 @@
 
     /** Setups a rule to always expand the given intent. */
     private void setupExpandRule(@NonNull Intent expandIntent) {
-        final ActivityRule expandRule = createActivityBuilder(r -> false, expandIntent::equals)
+        final ActivityRule expandRule = new ActivityRule.Builder(r -> false, expandIntent::equals)
                 .setShouldAlwaysExpand(true)
                 .build();
         mSplitController.setEmbeddingRules(Collections.singleton(expandRule));
@@ -1363,7 +1360,7 @@
 
     /** Setups a rule to always expand the given activity. */
     private void setupExpandRule(@NonNull Activity expandActivity) {
-        final ActivityRule expandRule = createActivityBuilder(expandActivity::equals, i -> false)
+        final ActivityRule expandRule = new ActivityRule.Builder(expandActivity::equals, i -> false)
                 .setShouldAlwaysExpand(true)
                 .build();
         mSplitController.setEmbeddingRules(Collections.singleton(expandRule));
@@ -1371,7 +1368,7 @@
 
     /** Setups a rule to launch placeholder for the given activity. */
     private void setupPlaceholderRule(@NonNull Activity primaryActivity) {
-        final SplitRule placeholderRule = createSplitPlaceholderRuleBuilder(PLACEHOLDER_INTENT,
+        final SplitRule placeholderRule = new SplitPlaceholderRule.Builder(PLACEHOLDER_INTENT,
                 primaryActivity::equals, i -> false, w -> true)
                 .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
                 .build();
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index cbb6e31..6dae0a1 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -27,7 +27,6 @@
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createActivityInfoWithMinDimensions;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
-import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitRule;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createWindowLayoutInfo;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.getSplitBounds;
@@ -474,7 +473,7 @@
         final Activity secondaryActivity = createMockActivity();
         final TaskFragmentContainer bottomTf = mController.newContainer(secondaryActivity, TASK_ID);
         final TaskFragmentContainer primaryTf = mController.newContainer(mActivity, TASK_ID);
-        final SplitPairRule rule = createSplitPairRuleBuilder(pair ->
+        final SplitPairRule rule = new SplitPairRule.Builder(pair ->
                 pair.first == mActivity && pair.second == secondaryActivity, pair -> false,
                 metrics -> true)
                 .setDefaultSplitAttributes(SPLIT_ATTRIBUTES)
@@ -492,7 +491,7 @@
 
     @Test
     public void testComputeSplitAttributes() {
-        final SplitPairRule splitPairRule = createSplitPairRuleBuilder(
+        final SplitPairRule splitPairRule = new SplitPairRule.Builder(
                 activityPair -> true,
                 activityIntentPair -> true,
                 windowMetrics -> windowMetrics.getBounds().equals(TASK_BOUNDS))
diff --git a/libs/WindowManager/Jetpack/window-extensions-core-release.aar b/libs/WindowManager/Jetpack/window-extensions-core-release.aar
deleted file mode 100644
index 4c08074..0000000
--- a/libs/WindowManager/Jetpack/window-extensions-core-release.aar
+++ /dev/null
Binary files differ
diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar
index 6fd8d29..84ab448 100644
--- a/libs/WindowManager/Jetpack/window-extensions-release.aar
+++ b/libs/WindowManager/Jetpack/window-extensions-release.aar
Binary files differ
diff --git a/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml b/libs/WindowManager/Shell/res/color/decor_title_color.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/color/decor_caption_title_color.xml
rename to libs/WindowManager/Shell/res/color/decor_title_color.xml
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/drawable/decor_caption_menu_background.xml
rename to libs/WindowManager/Shell/res/drawable/desktop_mode_decor_menu_background.xml
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
similarity index 100%
rename from libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
rename to libs/WindowManager/Shell/res/drawable/desktop_mode_decor_title.xml
diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
similarity index 96%
rename from libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
rename to libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
index 582a11c..8b4792a 100644
--- a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_decor_handle_menu.xml
@@ -20,7 +20,7 @@
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:gravity="center_horizontal"
-android:background="@drawable/decor_caption_menu_background">
+android:background="@drawable/desktop_mode_decor_menu_background">
     <Button
         style="@style/CaptionButtonStyle"
         android:id="@+id/fullscreen_button"
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
similarity index 93%
rename from libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
rename to libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
index 51e634c..2a4cc02 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor.xml
@@ -16,10 +16,10 @@
   -->
 <com.android.wm.shell.windowdecor.WindowDecorLinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/caption"
+    android:id="@+id/desktop_mode_caption"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:background="@drawable/decor_caption_title">
+    android:background="@drawable/desktop_mode_decor_title">
     <Button
         style="@style/CaptionButtonStyle"
         android:id="@+id/back_button"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index b500f5f..b430157 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -282,7 +282,7 @@
         public boolean onTouch(View v, MotionEvent e) {
             boolean isDrag = false;
             int id = v.getId();
-            if (id != R.id.caption_handle && id != R.id.caption) {
+            if (id != R.id.caption_handle && id != R.id.desktop_mode_caption) {
                 return false;
             }
             if (id == R.id.caption_handle) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 467f374..9c2beb9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -132,7 +132,7 @@
 
         mRelayoutParams.reset();
         mRelayoutParams.mRunningTaskInfo = taskInfo;
-        mRelayoutParams.mLayoutResId = R.layout.caption_window_decoration;
+        mRelayoutParams.mLayoutResId = R.layout.desktop_mode_window_decor;
         mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height;
         mRelayoutParams.mCaptionWidthId = R.dimen.freeform_decor_caption_width;
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
@@ -212,7 +212,7 @@
      * Sets up listeners when a new root view is created.
      */
     private void setupRootView() {
-        View caption = mResult.mRootView.findViewById(R.id.caption);
+        View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
         caption.setOnTouchListener(mOnCaptionTouchListener);
         View close = caption.findViewById(R.id.close_window);
         close.setOnClickListener(mOnCaptionButtonClickListener);
@@ -243,7 +243,7 @@
      */
     private void setCaptionVisibility(boolean visible) {
         int v = visible ? View.VISIBLE : View.GONE;
-        View captionView = mResult.mRootView.findViewById(R.id.caption);
+        View captionView = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
         captionView.setVisibility(v);
         if (!visible) closeHandleMenu();
     }
@@ -265,7 +265,7 @@
      */
     void setButtonVisibility(boolean visible) {
         int visibility = visible ? View.VISIBLE : View.GONE;
-        View caption = mResult.mRootView.findViewById(R.id.caption);
+        View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
         View back = caption.findViewById(R.id.back_button);
         View close = caption.findViewById(R.id.close_window);
         back.setVisibility(visibility);
@@ -304,7 +304,7 @@
         int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
         int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
         String namePrefix = "Caption Menu";
-        mHandleMenu = addWindow(R.layout.caption_handle_menu, namePrefix, t,
+        mHandleMenu = addWindow(R.layout.desktop_mode_decor_handle_menu, namePrefix, t,
                 x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
                 width, height);
         mSyncQueue.runInSync(transaction -> {
@@ -336,7 +336,7 @@
      */
     void closeHandleMenuIfNeeded(MotionEvent ev) {
         if (isHandleMenuActive()) {
-            if (!checkEventInCaptionView(ev, R.id.caption)) {
+            if (!checkEventInCaptionView(ev, R.id.desktop_mode_caption)) {
                 closeHandleMenu();
             }
         }
@@ -389,7 +389,7 @@
      */
     void checkClickEvent(MotionEvent ev) {
         if (mResult.mRootView == null) return;
-        View caption = mResult.mRootView.findViewById(R.id.caption);
+        View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
         PointF inputPoint = offsetCaptionLocation(ev);
         if (!isHandleMenuActive()) {
             View handle = caption.findViewById(R.id.caption_handle);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index dd9ab98..d4746ee 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -560,7 +560,8 @@
             int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
             String name = "Test Window";
             WindowDecoration.AdditionalWindow additionalWindow =
-                    addWindow(R.layout.caption_handle_menu, name, mMockSurfaceControlAddWindowT,
+                    addWindow(R.layout.desktop_mode_decor_handle_menu, name,
+                            mMockSurfaceControlAddWindowT,
                             x - mRelayoutResult.mDecorContainerOffsetX,
                             y - mRelayoutResult.mDecorContainerOffsetY,
                             width, height);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index a84c42af..761edf6 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -5855,6 +5855,117 @@
         }
     }
 
+    // Each listener corresponds to a unique callback stub because each listener can subscribe to
+    // different AudioAttributes.
+    private final ConcurrentHashMap<OnDevicesForAttributesChangedListener,
+            IDevicesForAttributesCallbackStub> mDevicesForAttributesListenerToStub =
+                    new ConcurrentHashMap<>();
+
+    private static final class IDevicesForAttributesCallbackStub
+            extends IDevicesForAttributesCallback.Stub {
+        ListenerInfo<OnDevicesForAttributesChangedListener> mInfo;
+
+        IDevicesForAttributesCallbackStub(@NonNull OnDevicesForAttributesChangedListener listener,
+                @NonNull Executor executor) {
+            mInfo = new ListenerInfo<>(listener, executor);
+        }
+
+        public void register(boolean register, AudioAttributes attributes) {
+            try {
+                if (register) {
+                    getService().addOnDevicesForAttributesChangedListener(attributes, this);
+                } else {
+                    getService().removeOnDevicesForAttributesChangedListener(this);
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        @Override
+        public void onDevicesForAttributesChanged(AudioAttributes attributes, boolean forVolume,
+                List<AudioDeviceAttributes> devices) {
+            // forVolume is ignored. The case where it is `true` is not handled.
+            mInfo.mExecutor.execute(() ->
+                    mInfo.mListener.onDevicesForAttributesChanged(
+                            attributes, devices));
+        }
+    }
+
+    /**
+     * @hide
+     * Interface to be notified of when routing changes for the registered audio attributes.
+     */
+    @SystemApi
+    public interface OnDevicesForAttributesChangedListener {
+        /**
+         * Called on the listener to indicate that the audio devices for the given audio
+         * attributes have changed.
+         * @param attributes the {@link AudioAttributes} whose routing changed
+         * @param devices a list of newly routed audio devices
+         */
+        void onDevicesForAttributesChanged(@NonNull AudioAttributes attributes,
+                @NonNull List<AudioDeviceAttributes> devices);
+    }
+
+    /**
+     * @hide
+     * Adds a listener for being notified of routing changes for the given {@link AudioAttributes}.
+     * @param attributes the {@link AudioAttributes} to listen for routing changes
+     * @param executor
+     * @param listener
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+            android.Manifest.permission.QUERY_AUDIO_STATE
+    })
+    public void addOnDevicesForAttributesChangedListener(@NonNull AudioAttributes attributes,
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OnDevicesForAttributesChangedListener listener) {
+        Objects.requireNonNull(attributes);
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(listener);
+
+        synchronized (mDevicesForAttributesListenerToStub) {
+            IDevicesForAttributesCallbackStub callbackStub =
+                    mDevicesForAttributesListenerToStub.get(listener);
+
+            if (callbackStub == null) {
+                callbackStub = new IDevicesForAttributesCallbackStub(listener, executor);
+                mDevicesForAttributesListenerToStub.put(listener, callbackStub);
+            }
+
+            callbackStub.register(true, attributes);
+        }
+    }
+
+    /**
+     * @hide
+     * Removes a previously registered listener for being notified of routing changes for the given
+     * {@link AudioAttributes}.
+     * @param listener
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            android.Manifest.permission.MODIFY_AUDIO_ROUTING,
+            android.Manifest.permission.QUERY_AUDIO_STATE
+    })
+    public void removeOnDevicesForAttributesChangedListener(
+            @NonNull OnDevicesForAttributesChangedListener listener) {
+        Objects.requireNonNull(listener);
+
+        synchronized (mDevicesForAttributesListenerToStub) {
+            IDevicesForAttributesCallbackStub callbackStub =
+                    mDevicesForAttributesListenerToStub.get(listener);
+            if (callbackStub != null) {
+                callbackStub.register(false, null /* attributes */);
+            }
+
+            mDevicesForAttributesListenerToStub.remove(listener);
+        }
+    }
+
     /**
      * Get the audio devices that would be used for the routing of the given audio attributes.
      * These are the devices anticipated to play sound from an {@link AudioTrack} created with
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index 0de367d..5ee32d6 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -36,6 +36,7 @@
 import android.media.ICapturePresetDevicesRoleDispatcher;
 import android.media.ICommunicationDeviceDispatcher;
 import android.media.IDeviceVolumeBehaviorDispatcher;
+import android.media.IDevicesForAttributesCallback;
 import android.media.IMuteAwaitConnectionCallback;
 import android.media.IPlaybackConfigDispatcher;
 import android.media.IPreferredMixerAttributesDispatcher;
@@ -356,6 +357,12 @@
 
     List<AudioDeviceAttributes> getDevicesForAttributesUnprotected(in AudioAttributes attributes);
 
+    void addOnDevicesForAttributesChangedListener(in AudioAttributes attributes,
+            in IDevicesForAttributesCallback callback);
+
+    oneway void removeOnDevicesForAttributesChangedListener(
+            in IDevicesForAttributesCallback callback);
+
     int setAllowedCapturePolicy(in int capturePolicy);
 
     int getAllowedCapturePolicy();
diff --git a/media/java/android/media/IDevicesForAttributesCallback.aidl b/media/java/android/media/IDevicesForAttributesCallback.aidl
new file mode 100644
index 0000000..489ecf6
--- /dev/null
+++ b/media/java/android/media/IDevicesForAttributesCallback.aidl
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+
+/**
+ * AIDL for AudioService to signal updates of audio devices routing for attributes.
+ *
+ * {@hide}
+ */
+oneway interface IDevicesForAttributesCallback {
+
+    void onDevicesForAttributesChanged(in AudioAttributes attributes, boolean forVolume,
+            in List<AudioDeviceAttributes> devices);
+
+}
+
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 58078cf..aacea3d 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -609,6 +609,7 @@
     jobject obj = env->NewObject(eventClazz, eventInit, tableId, version, sectionNum, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
+    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getMediaEvent(jobjectArray &arr, const int size,
@@ -639,6 +640,7 @@
 
         audioDescriptor = env->NewObject(adClazz, adInit, adFade, adPan, versionTextTag,
                                          adGainCenter, adGainFront, adGainSurround);
+        env->DeleteLocalRef(adClazz);
     }
 
     jlong dataLength = mediaEvent.dataLength;
@@ -685,6 +687,7 @@
         env->DeleteLocalRef(audioDescriptor);
     }
     env->DeleteLocalRef(obj);
+    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getPesEvent(jobjectArray &arr, const int size,
@@ -701,6 +704,7 @@
     jobject obj = env->NewObject(eventClazz, eventInit, streamId, dataLength, mpuSequenceNumber);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
+    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getTsRecordEvent(jobjectArray &arr, const int size,
@@ -741,6 +745,7 @@
             env->NewObject(eventClazz, eventInit, jpid, ts, sc, byteNumber, pts, firstMbInSlice);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
+    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getMmtpRecordEvent(jobjectArray &arr, const int size,
@@ -762,6 +767,7 @@
                                  mpuSequenceNumber, pts, firstMbInSlice, tsIndexMask);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
+    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getDownloadEvent(jobjectArray &arr, const int size,
@@ -782,6 +788,7 @@
                                  itemFragmentIndex, lastItemFragmentIndex, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
+    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getIpPayloadEvent(jobjectArray &arr, const int size,
@@ -795,6 +802,7 @@
     jobject obj = env->NewObject(eventClazz, eventInit, dataLength);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
+    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getTemiEvent(jobjectArray &arr, const int size,
@@ -815,6 +823,7 @@
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(array);
     env->DeleteLocalRef(obj);
+    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getScramblingStatusEvent(jobjectArray &arr, const int size,
@@ -829,6 +838,7 @@
     jobject obj = env->NewObject(eventClazz, eventInit, scramblingStatus);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
+    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getIpCidChangeEvent(jobjectArray &arr, const int size,
@@ -842,6 +852,7 @@
     jobject obj = env->NewObject(eventClazz, eventInit, cid);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
+    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::getRestartEvent(jobjectArray &arr, const int size,
@@ -854,6 +865,7 @@
     jobject obj = env->NewObject(eventClazz, eventInit, startId);
     env->SetObjectArrayElement(arr, size, obj);
     env->DeleteLocalRef(obj);
+    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::onFilterEvent(const vector<DemuxFilterEvent> &events) {
@@ -952,6 +964,7 @@
               "Filter object has been freed. Ignoring callback.");
     }
     env->DeleteLocalRef(array);
+    env->DeleteLocalRef(eventClazz);
 }
 
 void FilterClientCallbackImpl::onFilterStatus(const DemuxFilterStatus status) {
@@ -1058,6 +1071,7 @@
         executeOnScanMessage(env, clazz, frontend, type, message);
         env->DeleteLocalRef(frontend);
     }
+    env->DeleteLocalRef(clazz);
 }
 
 void FrontendClientCallbackImpl::executeOnScanMessage(
@@ -1184,6 +1198,7 @@
                                                  "Atsc3PlpInfo;)V"),
                                 array);
             env->DeleteLocalRef(array);
+            env->DeleteLocalRef(plpClazz);
             break;
         }
         case FrontendScanMessageType::MODULATION: {
@@ -2195,6 +2210,7 @@
                                        static_cast<long>(s.get<FrontendStatus::Tag::innerFec>()));
                 env->SetObjectField(statusObj, field, newLongObj);
                 env->DeleteLocalRef(newLongObj);
+                env->DeleteLocalRef(longClazz);
                 break;
             }
             case FrontendStatus::Tag::modulationStatus: {
@@ -2360,6 +2376,7 @@
 
                 env->SetObjectField(statusObj, field, valObj);
                 env->DeleteLocalRef(valObj);
+                env->DeleteLocalRef(plpClazz);
                 break;
             }
             case FrontendStatus::Tag::modulations: {
@@ -2770,6 +2787,7 @@
 
                 env->SetObjectField(statusObj, field, valObj);
                 env->DeleteLocalRef(valObj);
+                env->DeleteLocalRef(plpClazz);
                 break;
             }
         }
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
index 244b367..51fc7ed 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/AndroidManifest.xml
@@ -18,6 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.settingslib.widget">
 
-    <uses-sdk android:minSdkVersion="29" />
+    <uses-sdk android:minSdkVersion="21" />
 
 </manifest>
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
index c799b99..02f69f6 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout/collapsing_toolbar_base_layout.xml
@@ -29,6 +29,12 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:theme="?android:attr/actionBarTheme" />
+    <androidx.appcompat.widget.Toolbar
+        android:id="@+id/support_action_bar"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="?android:attr/actionBarTheme"
+        android:visibility="gone" />
     <FrameLayout
         android:id="@+id/content_frame"
         android:layout_width="match_parent"
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
new file mode 100644
index 0000000..dcc6e5a
--- /dev/null
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarAppCompatActivity.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.collapsingtoolbar;
+
+import android.app.ActionBar;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toolbar;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.android.settingslib.utils.BuildCompatUtils;
+import com.android.settingslib.widget.R;
+
+import com.google.android.material.appbar.AppBarLayout;
+import com.google.android.material.appbar.CollapsingToolbarLayout;
+import com.google.android.material.color.DynamicColors;
+
+/**
+ * A base Activity that has a collapsing toolbar layout is used for the activities intending to
+ * enable the collapsing toolbar function.
+ */
+public class CollapsingToolbarAppCompatActivity extends AppCompatActivity {
+
+    private class DelegateCallback implements CollapsingToolbarDelegate.HostCallback {
+        @Nullable
+        @Override
+        public ActionBar setActionBar(Toolbar toolbar) {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public androidx.appcompat.app.ActionBar setActionBar(
+                androidx.appcompat.widget.Toolbar toolbar) {
+            CollapsingToolbarAppCompatActivity.super.setSupportActionBar(toolbar);
+            return CollapsingToolbarAppCompatActivity.super.getSupportActionBar();
+        }
+
+        @Override
+        public void setOuterTitle(CharSequence title) {
+            CollapsingToolbarAppCompatActivity.super.setTitle(title);
+        }
+    }
+
+    private CollapsingToolbarDelegate mToolbardelegate;
+
+    private int mCustomizeLayoutResId = 0;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (BuildCompatUtils.isAtLeastS()) {
+            DynamicColors.applyToActivityIfAvailable(this);
+        }
+        setTheme(R.style.Theme_SubSettingsBase);
+
+        if (mCustomizeLayoutResId > 0 && !BuildCompatUtils.isAtLeastS()) {
+            super.setContentView(mCustomizeLayoutResId);
+            return;
+        }
+
+        View view = getToolbarDelegate().onCreateView(getLayoutInflater(), null, this);
+        super.setContentView(view);
+    }
+
+    @Override
+    public void setContentView(int layoutResID) {
+        final ViewGroup parent = (mToolbardelegate == null) ? findViewById(R.id.content_frame)
+                : mToolbardelegate.getContentFrameLayout();
+        if (parent != null) {
+            parent.removeAllViews();
+        }
+        LayoutInflater.from(this).inflate(layoutResID, parent);
+    }
+
+    @Override
+    public void setContentView(View view) {
+        final ViewGroup parent = (mToolbardelegate == null) ? findViewById(R.id.content_frame)
+                : mToolbardelegate.getContentFrameLayout();
+        if (parent != null) {
+            parent.addView(view);
+        }
+    }
+
+    @Override
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+        final ViewGroup parent = (mToolbardelegate == null) ? findViewById(R.id.content_frame)
+                : mToolbardelegate.getContentFrameLayout();
+        if (parent != null) {
+            parent.addView(view, params);
+        }
+    }
+
+    /**
+     * This method allows an activity to replace the default layout with a customize layout. Notice
+     * that it will no longer apply the features being provided by this class when this method
+     * gets called.
+     */
+    protected void setCustomizeContentView(int layoutResId) {
+        mCustomizeLayoutResId = layoutResId;
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        getToolbarDelegate().setTitle(title);
+    }
+
+    @Override
+    public void setTitle(int titleId) {
+        setTitle(getText(titleId));
+    }
+
+    @Override
+    public boolean onSupportNavigateUp() {
+        if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
+            getSupportFragmentManager().popBackStackImmediate();
+        }
+
+        // Closes the activity if there is no fragment inside the stack. Otherwise the activity will
+        // has a blank screen since there is no any fragment.
+        if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+            finishAfterTransition();
+        }
+        return true;
+    }
+
+    @Override
+    public void onBackPressed() {
+        super.onBackPressed();
+
+        // Closes the activity if there is no fragment inside the stack. Otherwise the activity will
+        // has a blank screen since there is no any fragment. onBackPressed() in Activity.java only
+        // handles popBackStackImmediate(). This will close activity to avoid a blank screen.
+        if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+            finishAfterTransition();
+        }
+    }
+
+    /**
+     * Returns an instance of collapsing toolbar.
+     */
+    @Nullable
+    public CollapsingToolbarLayout getCollapsingToolbarLayout() {
+        return getToolbarDelegate().getCollapsingToolbarLayout();
+    }
+
+    /**
+     * Return an instance of app bar.
+     */
+    @Nullable
+    public AppBarLayout getAppBarLayout() {
+        return getToolbarDelegate().getAppBarLayout();
+    }
+
+    private CollapsingToolbarDelegate getToolbarDelegate() {
+        if (mToolbardelegate == null) {
+            mToolbardelegate = new CollapsingToolbarDelegate(new DelegateCallback());
+        }
+        return mToolbardelegate;
+    }
+}
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
index 8c8b478..01f92c4 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarBaseActivity.java
@@ -117,12 +117,30 @@
 
     @Override
     public boolean onNavigateUp() {
-        if (!super.onNavigateUp()) {
+        if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
+            getSupportFragmentManager().popBackStackImmediate();
+        }
+
+        // Closes the activity if there is no fragment inside the stack. Otherwise the activity will
+        // has a blank screen since there is no any fragment.
+        if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
             finishAfterTransition();
         }
         return true;
     }
 
+    @Override
+    public void onBackPressed() {
+        super.onBackPressed();
+
+        // Closes the activity if there is no fragment inside the stack. Otherwise the activity will
+        // has a blank screen since there is no any fragment. onBackPressed() in Activity.java only
+        // handles popBackStackImmediate(). This will close activity to avoid a blank screen.
+        if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+            finishAfterTransition();
+        }
+    }
+
     /**
      * Returns an instance of collapsing toolbar.
      */
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
index 01698b7..1c2288a 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/CollapsingToolbarDelegate.java
@@ -19,9 +19,11 @@
 import static android.text.Layout.HYPHENATION_FREQUENCY_NORMAL_FAST;
 
 import android.app.ActionBar;
+import android.app.Activity;
 import android.content.res.Configuration;
 import android.graphics.text.LineBreakConfig;
 import android.os.Build;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -30,6 +32,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
 import androidx.coordinatorlayout.widget.CoordinatorLayout;
 
 import com.android.settingslib.widget.R;
@@ -42,7 +45,7 @@
  * extend from {@link CollapsingToolbarBaseActivity} or from {@link CollapsingToolbarBaseFragment}.
  */
 public class CollapsingToolbarDelegate {
-
+    private static final String TAG = "CTBdelegate";
     /** Interface to be implemented by the host of the Collapsing Toolbar. */
     public interface HostCallback {
         /**
@@ -53,6 +56,13 @@
         @Nullable
         ActionBar setActionBar(Toolbar toolbar);
 
+        /** Sets support tool bar and return support action bar, this is for AppCompatActivity. */
+        @Nullable
+        default androidx.appcompat.app.ActionBar setActionBar(
+                androidx.appcompat.widget.Toolbar toolbar) {
+            return null;
+        }
+
         /** Sets a title on the host. */
         void setOuterTitle(CharSequence title);
     }
@@ -79,6 +89,13 @@
     /** Method to call that creates the root view of the collapsing toolbar. */
     @SuppressWarnings("RestrictTo")
     public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) {
+        return onCreateView(inflater, container, null);
+    }
+
+    /** Method to call that creates the root view of the collapsing toolbar. */
+    @SuppressWarnings("RestrictTo")
+    View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            Activity activity) {
         final View view =
                 inflater.inflate(R.layout.collapsing_toolbar_base_layout, container, false);
         if (view instanceof CoordinatorLayout) {
@@ -99,17 +116,57 @@
             }
         }
         autoSetCollapsingToolbarLayoutScrolling();
-        mToolbar = view.findViewById(R.id.action_bar);
         mContentFrameLayout = view.findViewById(R.id.content_frame);
-        final ActionBar actionBar = mHostCallback.setActionBar(mToolbar);
+        if (activity instanceof AppCompatActivity) {
+            Log.d(TAG, "onCreateView: from AppCompatActivity and sub-class.");
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+                initSupportActionBar(inflater);
+            } else {
+                initRSupportActionBar(view);
+            }
+        } else {
+            Log.d(TAG, "onCreateView: from NonAppCompatActivity.");
+            mToolbar = view.findViewById(R.id.action_bar);
+            final ActionBar actionBar = mHostCallback.setActionBar(mToolbar);
+            // Enable title and home button by default
+            if (actionBar != null) {
+                actionBar.setDisplayHomeAsUpEnabled(true);
+                actionBar.setHomeButtonEnabled(true);
+                actionBar.setDisplayShowTitleEnabled(true);
+            }
+        }
+        return view;
+    }
 
-        // Enable title and home button by default
+    private void initSupportActionBar(@NonNull LayoutInflater inflater) {
+        if (mCollapsingToolbarLayout == null) {
+            return;
+        }
+        mCollapsingToolbarLayout.removeAllViews();
+        inflater.inflate(R.layout.support_toolbar, mCollapsingToolbarLayout);
+        final androidx.appcompat.widget.Toolbar supportToolbar =
+                mCollapsingToolbarLayout.findViewById(R.id.support_action_bar);
+        final androidx.appcompat.app.ActionBar actionBar =
+                mHostCallback.setActionBar(supportToolbar);
         if (actionBar != null) {
             actionBar.setDisplayHomeAsUpEnabled(true);
             actionBar.setHomeButtonEnabled(true);
             actionBar.setDisplayShowTitleEnabled(true);
         }
-        return view;
+    }
+
+    private void initRSupportActionBar(View view) {
+        view.findViewById(R.id.action_bar).setVisibility(View.GONE);
+        final androidx.appcompat.widget.Toolbar supportToolbar =
+                view.findViewById(R.id.support_action_bar);
+        supportToolbar.setVisibility(View.VISIBLE);
+        final androidx.appcompat.app.ActionBar actionBar =
+                mHostCallback.setActionBar(supportToolbar);
+        if (actionBar != null) {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+            actionBar.setHomeButtonEnabled(true);
+            actionBar.setDisplayShowTitleEnabled(true);
+        }
     }
 
     /** Return an instance of CoordinatorLayout. */
@@ -160,9 +217,13 @@
                 new AppBarLayout.Behavior.DragCallback() {
                     @Override
                     public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
-                        // Header can be scrolling while device in landscape mode.
-                        return appBarLayout.getResources().getConfiguration().orientation
-                                == Configuration.ORIENTATION_LANDSCAPE;
+                        // Header can be scrolling while device in landscape mode and SDK > 33
+                        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
+                            return false;
+                        } else {
+                            return appBarLayout.getResources().getConfiguration().orientation
+                                    == Configuration.ORIENTATION_LANDSCAPE;
+                        }
                     }
                 });
         params.setBehavior(behavior);
diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
index d67ac3b..e4e34f8 100644
--- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
+++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java
@@ -49,7 +49,7 @@
  */
 @RequiresApi(Build.VERSION_CODES.S)
 public class CollapsingCoordinatorLayout extends CoordinatorLayout {
-    private static final String TAG = "CollapsingCoordinatorLayout";
+    private static final String TAG = "CollapsingCoordinator";
     private static final float TOOLBAR_LINE_SPACING_MULTIPLIER = 1.1f;
 
     private CharSequence mToolbarTitle;
@@ -255,9 +255,13 @@
                 new AppBarLayout.Behavior.DragCallback() {
                     @Override
                     public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
-                        // Header can be scrolling while device in landscape mode.
-                        return appBarLayout.getResources().getConfiguration().orientation
-                                == Configuration.ORIENTATION_LANDSCAPE;
+                        // Header can be scrolling while device in landscape mode and SDK > 33
+                        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
+                            return false;
+                        } else {
+                            return appBarLayout.getResources().getConfiguration().orientation
+                                    == Configuration.ORIENTATION_LANDSCAPE;
+                        }
                     }
                 });
         params.setBehavior(behavior);
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
index 59ae122..b39d09f 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml
@@ -18,7 +18,11 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_height="wrap_content"
-    android:layout_width="match_parent">
+    android:layout_width="match_parent"
+    android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
+    android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+    android:paddingRight="?android:attr/listPreferredItemPaddingRight"
+    android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
 
     <TextView
         android:id="@+id/switch_text"
@@ -50,7 +54,6 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_gravity="center_vertical"
-        android:layout_marginEnd="@dimen/settingslib_switchbar_subsettings_margin_end"
         android:focusable="false"
         android:clickable="false"
         android:theme="@style/SwitchBar.Switch.Settingslib"/>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-sw600dp/dmiens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-sw600dp/dmiens.xml
deleted file mode 100644
index 55a2589..0000000
--- a/packages/SettingsLib/MainSwitchPreference/res/values-sw600dp/dmiens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2021 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<resources>
-
-    <!-- SwitchBar sub settings margin start / end -->
-    <dimen name="settingslib_switchbar_subsettings_margin_start">80dp</dimen>
-</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp-land/dmiens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp-land/dmiens.xml
deleted file mode 100644
index 53995bc..0000000
--- a/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp-land/dmiens.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2021 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<resources>
-
-    <!-- SwitchBar sub settings margin start / end -->
-    <dimen name="settingslib_switchbar_subsettings_margin_start">128dp</dimen>
-    <dimen name="settingslib_switchbar_subsettings_margin_end">128dp</dimen>
-</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp/dmiens.xml b/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp/dmiens.xml
deleted file mode 100644
index 9015c58..0000000
--- a/packages/SettingsLib/MainSwitchPreference/res/values-sw720dp/dmiens.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2021 The Android Open Source Project
-
-  Licensed under the Apache License, Version 2.0 (the "License");
-  you may not use this file except in compliance with the License.
-  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
-  -->
-
-<resources>
-
-    <!-- SwitchBar sub settings margin start / end -->
-    <dimen name="settingslib_switchbar_subsettings_margin_start">80dp</dimen>
-    <dimen name="settingslib_switchbar_subsettings_margin_end">80dp</dimen>
-</resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
index 157a54e..88b2c87 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/values/dimens.xml
@@ -27,6 +27,6 @@
     <dimen name="settingslib_switch_title_margin">24dp</dimen>
 
     <!-- SwitchBar sub settings margin start / end -->
-    <dimen name="settingslib_switchbar_subsettings_margin_start">72dp</dimen>
+    <dimen name="settingslib_switchbar_subsettings_margin_start">56dp</dimen>
     <dimen name="settingslib_switchbar_subsettings_margin_end">16dp</dimen>
 </resources>
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
index 86fec50..864a8bb 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchBar.java
@@ -161,6 +161,19 @@
     }
 
     /**
+     * Set icon space reserved for title
+     */
+    public void setIconSpaceReserved(boolean iconSpaceReserved) {
+        if (mTextView != null && !BuildCompatUtils.isAtLeastS()) {
+            LayoutParams params = (LayoutParams) mTextView.getLayoutParams();
+            int iconSpace = getContext().getResources().getDimensionPixelSize(
+                    R.dimen.settingslib_switchbar_subsettings_margin_start);
+            params.setMarginStart(iconSpaceReserved ? iconSpace : 0);
+            mTextView.setLayoutParams(params);
+        }
+    }
+
+    /**
      * Show the MainSwitchBar
      */
     public void show() {
diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
index fc0e05f..53cc268 100644
--- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
+++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java
@@ -37,7 +37,6 @@
     private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
 
     private MainSwitchBar mMainSwitchBar;
-    private CharSequence mTitle;
 
     public MainSwitchPreference(Context context) {
         super(context);
@@ -68,6 +67,10 @@
         holder.setDividerAllowedBelow(false);
 
         mMainSwitchBar = (MainSwitchBar) holder.findViewById(R.id.settingslib_main_switch_bar);
+        // To support onPreferenceChange callback, it needs to call callChangeListener() when
+        // MainSwitchBar is clicked.
+        mMainSwitchBar.setOnClickListener((view) -> callChangeListener(isChecked()));
+        setIconSpaceReserved(isIconSpaceReserved());
         updateStatus(isChecked());
         registerListenerToSwitchBar();
     }
@@ -82,6 +85,10 @@
             final CharSequence title = a.getText(
                     androidx.preference.R.styleable.Preference_android_title);
             setTitle(title);
+
+            final boolean bIconSpaceReserved = a.getBoolean(
+                    androidx.preference.R.styleable.Preference_android_iconSpaceReserved, true);
+            setIconSpaceReserved(bIconSpaceReserved);
             a.recycle();
         }
     }
@@ -96,9 +103,17 @@
 
     @Override
     public void setTitle(CharSequence title) {
-        mTitle = title;
+        super.setTitle(title);
         if (mMainSwitchBar != null) {
-            mMainSwitchBar.setTitle(mTitle);
+            mMainSwitchBar.setTitle(title);
+        }
+    }
+
+    @Override
+    public void setIconSpaceReserved(boolean iconSpaceReserved) {
+        super.setIconSpaceReserved(iconSpaceReserved);
+        if (mMainSwitchBar != null) {
+            mMainSwitchBar.setIconSpaceReserved(iconSpaceReserved);
         }
     }
 
@@ -113,7 +128,7 @@
     public void updateStatus(boolean checked) {
         setChecked(checked);
         if (mMainSwitchBar != null) {
-            mMainSwitchBar.setTitle(mTitle);
+            mMainSwitchBar.setTitle(getTitle());
             mMainSwitchBar.show();
         }
     }
@@ -125,6 +140,7 @@
         if (!mSwitchChangeListeners.contains(listener)) {
             mSwitchChangeListeners.add(listener);
         }
+
         if (mMainSwitchBar != null) {
             mMainSwitchBar.addOnSwitchChangeListener(listener);
         }
diff --git a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
index bda478e..f1e028b 100644
--- a/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values-v31/style_preference.xml
@@ -17,6 +17,7 @@
 <resources>
     <style name="PreferenceTheme.SettingsLib" parent="@style/PreferenceThemeOverlay">
         <item name="preferenceCategoryTitleTextAppearance">@style/TextAppearance.CategoryTitle.SettingsLib</item>
+        <item name="preferenceScreenStyle">@style/SettingsPreferenceScreen.SettingsLib</item>
         <item name="preferenceCategoryStyle">@style/SettingsCategoryPreference.SettingsLib</item>
         <item name="preferenceStyle">@style/SettingsPreference.SettingsLib</item>
         <item name="checkBoxPreferenceStyle">@style/SettingsCheckBoxPreference.SettingsLib</item>
@@ -28,6 +29,11 @@
         <item name="footerPreferenceStyle">@style/Preference.Material</item>
     </style>
 
+    <style name="SettingsPreferenceScreen.SettingsLib" parent="@style/Preference.PreferenceScreen.Material">
+        <item name="layout">@layout/settingslib_preference</item>
+        <item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
+    </style>
+
     <style name="SettingsCategoryPreference.SettingsLib" parent="@style/Preference.Category.Material">
         <item name="iconSpaceReserved">@bool/settingslib_config_icon_space_reserved</item>
         <item name="allowDividerAbove">@bool/settingslib_config_allow_divider</item>
diff --git a/packages/SettingsLib/SettingsTheme/res/values/styles.xml b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
index 328ab46..af3fc48 100644
--- a/packages/SettingsLib/SettingsTheme/res/values/styles.xml
+++ b/packages/SettingsLib/SettingsTheme/res/values/styles.xml
@@ -28,19 +28,19 @@
     </style>
 
     <style name="TextAppearance.TopIntroText"
-        parent="@android:style/TextAppearance.DeviceDefault">
+           parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:textSize">14sp</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
     </style>
 
     <style name="TextAppearance.EntityHeaderTitle"
-        parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
+           parent="@android:style/TextAppearance.DeviceDefault.WindowTitle">
         <item name="android:textColor">?android:attr/textColorPrimary</item>
         <item name="android:textSize">20sp</item>
     </style>
 
     <style name="TextAppearance.EntityHeaderSummary"
-        parent="@android:style/TextAppearance.DeviceDefault">
+           parent="@android:style/TextAppearance.DeviceDefault">
         <item name="android:textAlignment">viewStart</item>
         <item name="android:textColor">?android:attr/textColorSecondary</item>
         <item name="android:singleLine">true</item>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 2845916..8508878 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1368,7 +1368,7 @@
     <string name="user_add_user_message_long">You can share this device with other people by creating additional users. Each user has their own space, which they can customize with apps, wallpaper, and so on. Users can also adjust device settings like Wi\u2011Fi that affect everyone.\n\nWhen you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. Accessibility settings and services may not transfer to the new user.</string>
     <!-- Message for add user confirmation dialog - short version. [CHAR LIMIT=none] -->
     <string name="user_add_user_message_short">When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. </string>
-    <!-- Title for grant user admin privileges dialog [CHAR LIMIT=30] -->
+    <!-- Title for grant user admin privileges dialog [CHAR LIMIT=65] -->
     <string name="user_grant_admin_title">Give this user admin privileges?</string>
     <!-- Message for grant admin privileges dialog. [CHAR LIMIT=none] -->
     <string name="user_grant_admin_message">As an admin, they will be able to manage other users, modify device settings and factory reset the device.</string>
diff --git a/packages/SystemUI/docs/modern-architecture.png b/packages/SystemUI/docs/modern-architecture.png
new file mode 100644
index 0000000..2636362
--- /dev/null
+++ b/packages/SystemUI/docs/modern-architecture.png
Binary files differ
diff --git a/packages/SystemUI/docs/status-bar-data-pipeline.md b/packages/SystemUI/docs/status-bar-data-pipeline.md
new file mode 100644
index 0000000..9fcdce1
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar-data-pipeline.md
@@ -0,0 +1,261 @@
+# Status Bar Data Pipeline
+
+## Background
+
+The status bar is the UI shown at the top of the user's screen that gives them
+information about the time, notifications, and system status like mobile
+conectivity and battery level. This document is about the implementation of the
+wifi and mobile system icons on the right side:
+
+![image of status bar](status-bar.png)
+
+In Android U, the data pipeline that determines what mobile and wifi icons to
+show in the status bar has been re-written with a new architecture. This format
+generally follows Android best practices to
+[app architecture](https://developer.android.com/topic/architecture#recommended-app-arch).
+This document serves as a guide for the new architecture, and as a guide for how
+OEMs can add customizations to the new architecture.
+
+## Architecture
+
+In the new architecture, there is a separate pipeline for each type of icon. For
+Android U, **only the wifi icon and mobile icons have been implemented in the
+new architecture**.
+
+As shown in the Android best practices guide, each new pipeline has a data
+layer, a domain layer, and a UI layer:
+
+![diagram of UI, domain, and data layers](modern-architecture.png)
+
+The classes in the data layer are `repository` instances. The classes in the
+domain layer are `interactor` instances. The classes in the UI layer are
+`viewmodel` instances and `viewbinder` instances. In this document, "repository"
+and "data layer" will be used interchangably (and the same goes for the other
+layers).
+
+The wifi logic is in `statusbar/pipeline/wifi` and the mobile logic is in
+`statusbar/pipeline/mobile`.
+
+#### Repository (data layer)
+
+System callbacks, broadcast receivers, configuration values are all defined
+here, and exposed through the appropriate interface. Where appropriate, we
+define `Model` objects at this layer so that clients do not have to rely on
+system-defined interfaces.
+
+#### Interactor (domain layer)
+
+Here is where we define the business logic and transform the data layer objects
+into something consumable by the ViewModel classes. For example,
+`MobileIconsInteractor` defines the CBRS filtering logic by exposing a
+`filteredSubscriptions` list.
+
+#### ViewModel (UI layer)
+
+View models should define the final piece of business logic mapping to UI logic.
+For example, the mobile view model checks the `IconInteractor.isRoaming` flow to
+decide whether or not to show the roaming indicator.
+
+#### ViewBinder
+
+These have already been implemented and configured. ViewBinders replace the old
+`applyMobileState` mechanism that existed in the `IconManager` classes of the
+old pipeline. A view binder associates a ViewModel with a View, and keeps the
+view up-to-date with the most recent information from the model.
+
+Any new fields added to the ViewModel classes need to be equivalently bound to
+the view here.
+
+### Putting it all together
+
+Putting that altogether, we have this overall architecture diagram for the
+icons:
+
+![diagram of wifi and mobile pipelines](status-bar-pipeline.png)
+
+### Mobile icons architecture
+
+Because there can be multiple mobile connections at the same time, the mobile
+pipeline is split up hierarchically. At each level (data, domain, and UI), there
+is a singleton parent class that manages information relevant to **all** mobile
+connections, and multiple instances of child classes that manage information for
+a **single** mobile connection.
+
+For example, `MobileConnectionsRepository` is a singleton at the data layer that
+stores information relevant to **all** mobile connections, and it also manages a
+list of child `MobileConnectionRepository` classes. `MobileConnectionRepository`
+is **not** a singleton, and each individual `MobileConnectionRepository`
+instance fully qualifies the state of a **single** connection. This pattern is
+repeated at the `Interactor` and `ViewModel` layers for mobile.
+
+![diagram of mobile parent child relationship](status-bar-mobile-pipeline.png)
+
+Note: Since there is at most one wifi connection, the wifi pipeline is not split
+up in the same way.
+
+## Customizations
+
+The new pipeline completely replaces these classes:
+
+*   `WifiStatusTracker`
+*   `MobileStatusTracker`
+*   `NetworkSignalController` and `NetworkSignalControllerImpl`
+*   `MobileSignalController`
+*   `WifiSignalController`
+*   `StatusBarSignalPolicy` (including `SignalIconState`, `MobileIconState`, and
+    `WifiIconState`)
+
+Any customizations in any of these classes will need to be migrated to the new
+pipeline. As a general rule, any change that would have gone into
+`NetworkControllerImpl` would be done in `MobileConnectionsRepository`, and any
+change for `MobileSignalController` can be done in `MobileConnectionRepository`
+(see above on the relationship between those repositories).
+
+### Sample customization: New service
+
+Some customizations require listening to additional services to get additional
+data. This new architecture makes it easy to add additional services to the
+status bar data pipeline to get icon customizations.
+
+Below is a general guide to how a new service should be added. However, there
+may be exceptions to this guide for specific use cases.
+
+1.  In the data layer (`repository` classes), add a new `StateFlow` that listens
+    to the service:
+
+    ```kotlin
+    class MobileConnectionsRepositoryImpl {
+      ...
+      val fooVal: StateFlow<Int> =
+        conflatedCallbackFlow {
+          val callback = object : FooServiceCallback(), FooListener {
+            override fun onFooChanged(foo: Int) {
+              trySend(foo)
+            }
+          }
+
+          fooService.registerCallback(callback)
+
+          awaitClose { fooService.unregisterCallback(callback) }
+        }
+          .stateIn(scope, started = SharingStarted.WhileSubscribed(), FOO_DEFAULT_VAL)
+    }
+    ```
+
+1.  In the domain layer (`interactor` classes), either use this new flow to
+    process values, or just expose the flow as-is for the UI layer.
+
+    For example, if `bar` should only be true when `foo` is positive:
+
+    ```kotlin
+    class MobileIconsInteractor {
+      ...
+      val bar: StateFlow<Boolean> =
+        mobileConnectionsRepo
+          .mapLatest { foo -> foo > 0 }
+          .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = false)
+    }
+    ```
+
+1.  In the UI layer (`viewmodel` classes), update the existing flows to process
+    the new value from the interactor.
+
+    For example, if the icon should be hidden when `bar` is true:
+
+    ```kotlin
+    class MobileIconViewModel {
+      ...
+      iconId: Flow<Int> = combine(
+        iconInteractor.level,
+        iconInteractor.numberOfLevels,
+        iconInteractor.bar,
+    ) { level, numberOfLevels, bar ->
+      if (bar) {
+        null
+      } else {
+        calcIcon(level, numberOfLevels)
+      }
+    }
+    ```
+
+## Demo mode
+
+SystemUI demo mode is a first-class citizen in the new pipeline. It is
+implemented as an entirely separate repository,
+`DemoMobileConnectionsRepository`. When the system moves into demo mode, the
+implementation of the data layer is switched to the demo repository via the
+`MobileRepositorySwitcher` class.
+
+Because the demo mode repositories implement the same interfaces as the
+production classes, any changes made above will have to be implemented for demo
+mode as well.
+
+1.  Following from above, if `fooVal` is added to the
+    `MobileConnectionsRepository` interface:
+
+    ```kotlin
+    class DemoMobileConnectionsRepository {
+      private val _fooVal = MutableStateFlow(FOO_DEFAULT_VALUE)
+      override val fooVal: StateFlow<Int> = _fooVal.asStateFlow()
+
+      // Process the state. **See below on how to add the command to the CLI**
+      fun processEnabledMobileState(state: Mobile) {
+        ...
+        _fooVal.value = state.fooVal
+      }
+    }
+    ```
+
+1.  (Optional) If you want to enable the command line interface for setting and
+    testing this value in demo mode, you can add parsing logic to
+    `DemoModeMobileConnectionDataSource` and `FakeNetworkEventModel`:
+
+    ```kotlin
+    sealed interface FakeNetworkEventModel {
+      data class Mobile(
+      ...
+      // Add new fields here
+      val fooVal: Int?
+      )
+    }
+    ```
+
+    ```kotlin
+    class DemoModeMobileConnectionDataSource {
+      // Currently, the demo commands are implemented as an extension function on Bundle
+      private fun Bundle.activeMobileEvent(): Mobile {
+        ...
+        val fooVal = getString("fooVal")?.toInt()
+        return Mobile(
+          ...
+          fooVal = fooVal,
+        )
+      }
+    }
+    ```
+
+If step 2 is implemented, then you will be able to pass demo commands via the
+command line:
+
+```
+adb shell am broadcast -a com.android.systemui.demo -e command network -e mobile show -e fooVal <test value>
+```
+
+## Migration plan
+
+For Android U, the new pipeline will be enabled and default. However, the old
+pipeline code will still be around just in case the new pipeline doesn’t do well
+in the testing phase.
+
+For Android V, the old pipeline will be completely removed and the new pipeline
+will be the one source of truth.
+
+Our ask for OEMs is to default to using the new pipeline in Android U. If there
+are customizations that seem difficult to migrate over to the new pipeline,
+please file a bug with us and we’d be more than happy to consult on the best
+solution. The new pipeline was designed with customizability in mind, so our
+hope is that working the new pipeline will be easier and faster.
+
+Note: The new pipeline currently only supports the wifi and mobile icons. The
+other system status bar icons may be migrated to a similar architecture in the
+future.
diff --git a/packages/SystemUI/docs/status-bar-mobile-pipeline.png b/packages/SystemUI/docs/status-bar-mobile-pipeline.png
new file mode 100644
index 0000000..620563d
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar-mobile-pipeline.png
Binary files differ
diff --git a/packages/SystemUI/docs/status-bar-pipeline.png b/packages/SystemUI/docs/status-bar-pipeline.png
new file mode 100644
index 0000000..1c568c9
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar-pipeline.png
Binary files differ
diff --git a/packages/SystemUI/docs/status-bar.png b/packages/SystemUI/docs/status-bar.png
new file mode 100644
index 0000000..3a5af0e
--- /dev/null
+++ b/packages/SystemUI/docs/status-bar.png
Binary files differ
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 76c1158..96bfa43 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -643,6 +643,7 @@
 
     @Provides
     @Singleton
+    @Nullable
     static BluetoothAdapter provideBluetoothAdapter(BluetoothManager bluetoothManager) {
         return bluetoothManager.getAdapter();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
index 3e111e6..302d6a9 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusManager.kt
@@ -38,7 +38,7 @@
 @Inject
 constructor(
     private val inputManager: InputManager,
-    private val bluetoothAdapter: BluetoothAdapter,
+    private val bluetoothAdapter: BluetoothAdapter?,
     @Background private val handler: Handler,
     @Background private val executor: Executor,
 ) : InputManager.InputDeviceListener, BluetoothAdapter.OnMetadataChangedListener {
@@ -141,7 +141,7 @@
     }
 
     private fun onStylusBluetoothConnected(btAddress: String) {
-        val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
+        val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
         try {
             bluetoothAdapter.addOnMetadataChangedListener(device, executor, this)
         } catch (e: IllegalArgumentException) {
@@ -150,7 +150,7 @@
     }
 
     private fun onStylusBluetoothDisconnected(btAddress: String) {
-        val device: BluetoothDevice = bluetoothAdapter.getRemoteDevice(btAddress) ?: return
+        val device: BluetoothDevice = bluetoothAdapter?.getRemoteDevice(btAddress) ?: return
         try {
             bluetoothAdapter.removeOnMetadataChangedListener(device, this)
         } catch (e: IllegalArgumentException) {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index b3f8af5..81fbd78 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -4281,6 +4281,7 @@
         if (mContexts == null) {
             mContexts = new ArrayList<>(1);
         }
+        mContexts.add(new FillContext(requestId, new AssistStructure(), mCurrentViewId));
 
         if (inlineSuggestionsRequest != null && !inlineSuggestionsRequest.isClientSupported()) {
             inlineSuggestionsRequest = null;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0f7a725..78bff95 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -106,6 +106,7 @@
 import android.media.ICapturePresetDevicesRoleDispatcher;
 import android.media.ICommunicationDeviceDispatcher;
 import android.media.IDeviceVolumeBehaviorDispatcher;
+import android.media.IDevicesForAttributesCallback;
 import android.media.IMuteAwaitConnectionCallback;
 import android.media.IPlaybackConfigDispatcher;
 import android.media.IPreferredMixerAttributesDispatcher;
@@ -3124,6 +3125,25 @@
         return mAudioSystem.getDevicesForAttributes(attributes, forVolume);
     }
 
+    /**
+     * @see AudioManager#addOnDevicesForAttributesChangedListener(
+     *      AudioAttributes, Executor, OnDevicesForAttributesChangedListener)
+     */
+    public void addOnDevicesForAttributesChangedListener(AudioAttributes attributes,
+            IDevicesForAttributesCallback callback) {
+        mAudioSystem.addOnDevicesForAttributesChangedListener(
+                attributes, false /* forVolume */, callback);
+    }
+
+    /**
+     * @see AudioManager#removeOnDevicesForAttributesChangedListener(
+     *      OnDevicesForAttributesChangedListener)
+     */
+    public void removeOnDevicesForAttributesChangedListener(
+            IDevicesForAttributesCallback callback) {
+        mAudioSystem.removeOnDevicesForAttributesChangedListener(callback);
+    }
+
     // pre-condition: event.getKeyCode() is one of KeyEvent.KEYCODE_VOLUME_UP,
     //                                   KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE
     public void handleVolumeKey(@NonNull KeyEvent event, boolean isOnTv,
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7fefc55..7839ada 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -22,10 +22,15 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioMixerAttributes;
 import android.media.AudioSystem;
+import android.media.IDevicesForAttributesCallback;
 import android.media.ISoundDose;
 import android.media.ISoundDoseCallback;
 import android.media.audiopolicy.AudioMix;
+import android.os.IBinder;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
 import android.os.SystemClock;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Pair;
 
@@ -64,8 +69,21 @@
 
     private static final boolean USE_CACHE_FOR_GETDEVICES = true;
     private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
+            mLastDevicesForAttr = new ConcurrentHashMap<>();
+    private ConcurrentHashMap<Pair<AudioAttributes, Boolean>, ArrayList<AudioDeviceAttributes>>
             mDevicesForAttrCache;
+    private final Object mDeviceCacheLock = new Object();
     private int[] mMethodCacheHit;
+    /**
+     * Map that stores all attributes + forVolume pairs that are registered for
+     * routing change callback. The key is the {@link IBinder} that corresponds
+     * to the remote callback.
+     */
+    private final ArrayMap<IBinder, List<Pair<AudioAttributes, Boolean>>> mRegisteredAttributesMap =
+            new ArrayMap<>();
+    private final RemoteCallbackList<IDevicesForAttributesCallback>
+            mDevicesForAttributesCallbacks = new RemoteCallbackList<>();
+
     private static final Object sRoutingListenerLock = new Object();
     @GuardedBy("sRoutingListenerLock")
     private static @Nullable OnRoutingUpdatedListener sRoutingListener;
@@ -96,6 +114,34 @@
         if (listener != null) {
             listener.onRoutingUpdatedFromNative();
         }
+
+        synchronized (mRegisteredAttributesMap) {
+            final int nbCallbacks = mDevicesForAttributesCallbacks.beginBroadcast();
+
+            for (int i = 0; i < nbCallbacks; i++) {
+                IDevicesForAttributesCallback cb =
+                        mDevicesForAttributesCallbacks.getBroadcastItem(i);
+                List<Pair<AudioAttributes, Boolean>> attrList =
+                        mRegisteredAttributesMap.get(cb.asBinder());
+
+                if (attrList == null) {
+                    throw new IllegalStateException("Attribute list must not be null");
+                }
+
+                for (Pair<AudioAttributes, Boolean> attr : attrList) {
+                    ArrayList<AudioDeviceAttributes> devices =
+                            getDevicesForAttributes(attr.first, attr.second);
+                    if (!mLastDevicesForAttr.containsKey(attr)
+                            || !sameDeviceList(devices, mLastDevicesForAttr.get(attr))) {
+                        try {
+                            cb.onDevicesForAttributesChanged(
+                                    attr.first, attr.second, devices);
+                        } catch (RemoteException e) { }
+                    }
+                }
+            }
+            mDevicesForAttributesCallbacks.finishBroadcast();
+        }
     }
 
     interface OnRoutingUpdatedListener {
@@ -109,6 +155,66 @@
     }
 
     /**
+     * @see AudioManager#addOnDevicesForAttributesChangedListener(
+     *      AudioAttributes, Executor, OnDevicesForAttributesChangedListener)
+     */
+    public void addOnDevicesForAttributesChangedListener(AudioAttributes attributes,
+            boolean forVolume, @NonNull IDevicesForAttributesCallback listener) {
+        List<Pair<AudioAttributes, Boolean>> res;
+        final Pair<AudioAttributes, Boolean> attr = new Pair(attributes, forVolume);
+        synchronized (mRegisteredAttributesMap) {
+            res = mRegisteredAttributesMap.get(listener.asBinder());
+            if (res == null) {
+                res = new ArrayList<>();
+                mRegisteredAttributesMap.put(listener.asBinder(), res);
+                mDevicesForAttributesCallbacks.register(listener);
+            }
+
+            if (!res.contains(attr)) {
+                res.add(attr);
+            }
+        }
+
+        // Make query on registration to populate cache
+        getDevicesForAttributes(attributes, forVolume);
+    }
+
+    /**
+     * @see AudioManager#removeOnDevicesForAttributesChangedListener(
+     *      OnDevicesForAttributesChangedListener)
+     */
+    public void removeOnDevicesForAttributesChangedListener(
+            @NonNull IDevicesForAttributesCallback listener) {
+        synchronized (mRegisteredAttributesMap) {
+            if (!mRegisteredAttributesMap.containsKey(listener.asBinder())) {
+                Log.w(TAG, "listener to be removed is not found.");
+                return;
+            }
+            mRegisteredAttributesMap.remove(listener.asBinder());
+            mDevicesForAttributesCallbacks.unregister(listener);
+        }
+    }
+
+    /**
+     * Helper function to compare lists of {@link AudioDeviceAttributes}
+     * @return true if the two lists contains the same devices, false otherwise.
+     */
+    private boolean sameDeviceList(@NonNull List<AudioDeviceAttributes> a,
+            @NonNull List<AudioDeviceAttributes> b) {
+        for (AudioDeviceAttributes device : a) {
+            if (!b.contains(device)) {
+                return false;
+            }
+        }
+        for (AudioDeviceAttributes device : b) {
+            if (!a.contains(device)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
      * Implementation of AudioSystem.VolumeRangeInitRequestCallback
      */
     @Override
@@ -159,8 +265,11 @@
         if (DEBUG_CACHE) {
             Log.d(TAG, "---- clearing cache ----------");
         }
-        if (mDevicesForAttrCache != null) {
-            synchronized (mDevicesForAttrCache) {
+        synchronized (mDeviceCacheLock) {
+            if (mDevicesForAttrCache != null) {
+                // Save latest cache to determine routing updates
+                mLastDevicesForAttr.putAll(mDevicesForAttrCache);
+
                 mDevicesForAttrCache.clear();
             }
         }
@@ -189,7 +298,7 @@
         if (USE_CACHE_FOR_GETDEVICES) {
             ArrayList<AudioDeviceAttributes> res;
             final Pair<AudioAttributes, Boolean> key = new Pair(attributes, forVolume);
-            synchronized (mDevicesForAttrCache) {
+            synchronized (mDeviceCacheLock) {
                 res = mDevicesForAttrCache.get(key);
                 if (res == null) {
                     res = AudioSystem.getDevicesForAttributes(attributes, forVolume);
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index b813995..3ae699b 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -684,6 +684,16 @@
      */
     @GuardedBy("mLock")
     protected List<S> updateCachedServiceListLocked(@UserIdInt int userId, boolean disabled) {
+        if (mServiceNameResolver != null
+                && mServiceNameResolver.isConfiguredInMultipleMode()) {
+            // In multiple mode, we have multiple instances of AbstractPerUserSystemService, per
+            // user where each instance holds information needed to connect to a backend. An
+            // update operation in this mode needs to account for addition, deletion, change
+            // of backends and cannot be executed in the scope of a given
+            // AbstractPerUserSystemService.
+            return updateCachedServiceListMultiModeLocked(userId, disabled);
+        }
+        // isConfiguredInMultipleMode is false
         final List<S> services = getServiceListForUserLocked(userId);
         if (services == null) {
             return null;
@@ -704,6 +714,19 @@
         return services;
     }
 
+    @GuardedBy("mLock")
+    private List<S> updateCachedServiceListMultiModeLocked(int userId, boolean disabled) {
+        final int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, false, null,
+                null);
+        List<S> services = new ArrayList<>();
+        synchronized (mLock) {
+            removeCachedServiceListLocked(resolvedUserId);
+            services = getServiceListForUserLocked(userId);
+        }
+        return services;
+    }
+
     /**
      * Gets the Settings property that defines the name of the component name used to bind this
      * service to an external service, or {@code null} when the service is not defined by such
diff --git a/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java b/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java
index ddb19f0..af025c0 100644
--- a/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractPerUserSystemService.java
@@ -154,7 +154,14 @@
 
         if (mMaster.mServiceNameResolver != null
                 && mMaster.mServiceNameResolver.isConfiguredInMultipleMode()) {
-            updateServiceInfoListLocked();
+            // Update of multi configured mode should always happen in AbstractMasterSystemService
+            // as this class is not aware of the complete list of multiple backends. Since we
+            // should never end up in this state, it is safe to not do anything if we end up here
+            // through a different code path.
+            if (mMaster.debug) {
+                Slog.d(mTag, "Should not end up in updateLocked when "
+                        + "isConfiguredInMultipleMode is true");
+            }
         } else {
             updateServiceInfoLocked();
         }
diff --git a/services/core/java/com/android/server/power/ShutdownCheckPoints.java b/services/core/java/com/android/server/power/ShutdownCheckPoints.java
index 1a9eae6..32f1bcf 100644
--- a/services/core/java/com/android/server/power/ShutdownCheckPoints.java
+++ b/services/core/java/com/android/server/power/ShutdownCheckPoints.java
@@ -54,6 +54,7 @@
     private static final int MAX_DUMP_FILES = 20;
     private static final SimpleDateFormat DATE_FORMAT =
             new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z");
+    private static final File[] EMPTY_FILE_ARRAY = {};
 
     private final ArrayList<CheckPoint> mCheckPoints;
     private final Injector mInjector;
@@ -381,6 +382,9 @@
                     return true;
                 }
             });
+            if (files == null) {
+                return EMPTY_FILE_ARRAY;
+            }
             Arrays.sort(files);
             return files;
         }
diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
index 1c44da1..5560b41 100644
--- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java
@@ -6,6 +6,7 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThrows;
@@ -208,6 +209,34 @@
         assertEquals("Incorrect # of persisted tasks.", 0, jobStatusSet.size());
     }
 
+    /**
+     * Test that dynamic constraints aren't written to disk.
+     */
+    @Test
+    public void testDynamicConstraintsNotPersisted() throws Exception {
+        JobInfo.Builder b = new Builder(42, mComponent).setPersisted(true);
+        JobStatus js = JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null);
+        js.addDynamicConstraints(JobStatus.CONSTRAINT_BATTERY_NOT_LOW
+                | JobStatus.CONSTRAINT_CHARGING
+                | JobStatus.CONSTRAINT_IDLE
+                | JobStatus.CONSTRAINT_STORAGE_NOT_LOW);
+        assertTrue(js.hasBatteryNotLowConstraint());
+        assertTrue(js.hasChargingConstraint());
+        assertTrue(js.hasIdleConstraint());
+        assertTrue(js.hasStorageNotLowConstraint());
+        mTaskStoreUnderTest.add(js);
+        waitForPendingIo();
+
+        final JobSet jobStatusSet = new JobSet();
+        mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true);
+        assertEquals("Job count is incorrect.", 1, jobStatusSet.size());
+        JobStatus loaded = jobStatusSet.getAllJobs().iterator().next();
+        assertFalse(loaded.hasBatteryNotLowConstraint());
+        assertFalse(loaded.hasChargingConstraint());
+        assertFalse(loaded.hasIdleConstraint());
+        assertFalse(loaded.hasStorageNotLowConstraint());
+    }
+
     @Test
     public void testExtractUidFromJobFileName() {
         File file = new File(mTestContext.getFilesDir(), "randomName");
diff --git a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
index 3268df2..8c3838b 100644
--- a/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/PendingJobQueueTest.java
@@ -496,8 +496,10 @@
         Random random = new Random(1); // Always use the same series of pseudo random values.
 
         for (int i = 0; i < 5000; ++i) {
+            final boolean ui = random.nextBoolean();
+            final boolean ej = !ui && random.nextBoolean();
             JobStatus job = createJobStatus("testPendingJobSorting_Random",
-                    createJobInfo(i).setExpedited(random.nextBoolean()), random.nextInt(250));
+                    createJobInfo(i).setExpedited(ej).setUserInitiated(ui), random.nextInt(250));
             job.enqueueTime = random.nextInt(1_000_000);
             jobQueue.add(job);
         }
@@ -531,8 +533,10 @@
             jobQueue.clear();
 
             for (int i = 0; i < 300; ++i) {
+                final boolean ui = random.nextBoolean();
+                final boolean ej = !ui && random.nextBoolean();
                 JobStatus job = createJobStatus("testPendingJobSortingTransitivity",
-                        createJobInfo(i).setExpedited(random.nextBoolean()), random.nextInt(50));
+                        createJobInfo(i).setExpedited(ej).setUserInitiated(ui), random.nextInt(50));
                 job.enqueueTime = random.nextInt(1_000_000);
                 job.overrideState = random.nextInt(4);
                 jobQueue.add(job);
@@ -553,8 +557,10 @@
             jobQueue.clear();
 
             for (int i = 0; i < 300; ++i) {
+                final boolean ui = random.nextFloat() < .02;
+                final boolean ej = !ui && random.nextFloat() < .03;
                 JobStatus job = createJobStatus("testPendingJobSortingTransitivity_Concentrated",
-                        createJobInfo(i).setExpedited(random.nextFloat() < .03),
+                        createJobInfo(i).setExpedited(ej).setUserInitiated(ui),
                         random.nextInt(20));
                 job.enqueueTime = random.nextInt(250);
                 job.overrideState = random.nextFloat() < .01
@@ -653,10 +659,11 @@
     }
 
     private void checkPendingJobInvariants(PendingJobQueue jobQueue) {
+        final SparseBooleanArray eJobSeen = new SparseBooleanArray();
         final SparseBooleanArray regJobSeen = new SparseBooleanArray();
         // Latest priority enqueue times seen for each priority+namespace for each app.
         final SparseArrayMap<String, SparseLongArray> latestPriorityRegEnqueueTimesPerUid =
-                new SparseArrayMap();
+                new SparseArrayMap<>();
         final SparseArrayMap<String, SparseLongArray> latestPriorityEjEnqueueTimesPerUid =
                 new SparseArrayMap<>();
         final int noEntry = -1;
@@ -672,7 +679,8 @@
             // Invariant #1: All jobs are sorted by override state
             // Invariant #2: All jobs (for a UID) are sorted by priority order
             // Invariant #3: Jobs (for a UID) with the same priority are sorted by enqueue time.
-            // Invariant #4: EJs (for a UID) should be before regular jobs
+            // Invariant #4: User-initiated jobs (for a UID) should be before all other jobs.
+            // Invariant #5: EJs (for a UID) should be before regular jobs
 
             // Invariant 1
             if (prevOverrideState != job.overrideState) {
@@ -683,6 +691,7 @@
                 // to avoid confusion in the other checks.
                 latestPriorityEjEnqueueTimesPerUid.clear();
                 latestPriorityRegEnqueueTimesPerUid.clear();
+                eJobSeen.clear();
                 regJobSeen.clear();
                 prevOverrideState = job.overrideState;
             }
@@ -718,10 +727,24 @@
             }
             latestPriorityEnqueueTimes.put(priority, job.enqueueTime);
 
-            // Invariant 4
-            if (!job.isRequestedExpeditedJob()) {
+            if (job.isRequestedExpeditedJob()) {
+                eJobSeen.put(uid, true);
+            } else if (!job.getJob().isUserInitiated()) {
                 regJobSeen.put(uid, true);
-            } else if (regJobSeen.get(uid)) {
+            }
+
+            // Invariant 4
+            if (job.getJob().isUserInitiated()) {
+                if (eJobSeen.get(uid)) {
+                    fail("UID " + uid + " had a UIJ ordered after an EJ");
+                }
+                if (regJobSeen.get(uid)) {
+                    fail("UID " + uid + " had a UIJ ordered after a regular job");
+                }
+            }
+
+            // Invariant 5
+            if (job.isRequestedExpeditedJob() && regJobSeen.get(uid)) {
                 fail("UID " + uid + " had an EJ ordered after a regular job");
             }
         }
@@ -733,6 +756,9 @@
                 + "/o" + job.overrideState
                 + "/p" + job.getEffectivePriority()
                 + "/b" + job.lastEvaluatedBias
-                + "/" + job.isRequestedExpeditedJob() + "@" + job.enqueueTime;
+                + "/"
+                + (job.isRequestedExpeditedJob()
+                        ? "e" : (job.getJob().isUserInitiated() ? "u" : "r"))
+                + "@" + job.enqueueTime;
     }
 }
diff --git a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
index 2e93c87..3a520bc 100644
--- a/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
+++ b/tests/TouchLatency/app/src/main/java/com/prefabulated/touchlatency/TouchLatencyActivity.java
@@ -37,7 +37,7 @@
 
 public class TouchLatencyActivity extends AppCompatActivity {
     private static final int REFRESH_RATE_SLIDER_MIN = 20;
-    private static final int REFRESH_RATE_SLIDER_STEP = 5;
+    private static final int REFRESH_RATE_SLIDER_STEP = 1;
 
     private Menu mMenu;
     private Mode[] mDisplayModes;