Merge changes from topic "fixSettingsCrash" into udc-dev

* changes:
  Gate unicorn APIs behind a separate flag
  Transfer policies on admin ownership transfer
  Revert "Revert "Enable unicorn APIs""
  Allow system to retrieve keyguardDisabledFeatures for an admin
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index a69af24..470c280 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -62,9 +62,7 @@
      */
     public static final int SOURCE_ARBITRARY_RECTANGLE = 3;
 
-    private final IBinder mOwner;
-    private final int mIndex;
-    private final @InsetsType int mType;
+    private final int mId;
 
     /**
      * The selection of the starting rectangle to be converted into source frame.
@@ -122,30 +120,30 @@
      * @param type the {@link InsetsType}.
      * @see InsetsSource#createId(Object, int, int)
      */
-    public InsetsFrameProvider(IBinder owner, @IntRange(from = 0, to = 2047) int index,
+    public InsetsFrameProvider(Object owner, @IntRange(from = 0, to = 2047) int index,
             @InsetsType int type) {
-        if (index < 0 || index >= 2048) {
-            throw new IllegalArgumentException();
-        }
-
-        // This throws IllegalArgumentException if the type is not valid.
-        WindowInsets.Type.indexOf(type);
-
-        mOwner = owner;
-        mIndex = index;
-        mType = type;
+        mId = InsetsSource.createId(owner, index, type);
     }
 
-    public IBinder getOwner() {
-        return mOwner;
+    /**
+     * Returns an unique integer which identifies the insets source.
+     */
+    public int getId() {
+        return mId;
     }
 
+    /**
+     * Returns the index specified in {@link #InsetsFrameProvider(IBinder, int, int)}.
+     */
     public int getIndex() {
-        return mIndex;
+        return InsetsSource.getIndex(mId);
     }
 
+    /**
+     * Returns the {@link InsetsType} specified in {@link #InsetsFrameProvider(IBinder, int, int)}.
+     */
     public int getType() {
-        return mType;
+        return InsetsSource.getType(mId);
     }
 
     public InsetsFrameProvider setSource(int source) {
@@ -211,9 +209,9 @@
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder("InsetsFrameProvider: {");
-        sb.append("owner=").append(mOwner);
-        sb.append(", index=").append(mIndex);
-        sb.append(", type=").append(WindowInsets.Type.toString(mType));
+        sb.append("id=#").append(Integer.toHexString(mId));
+        sb.append(", index=").append(getIndex());
+        sb.append(", type=").append(WindowInsets.Type.toString(getType()));
         sb.append(", source=").append(sourceToString(mSource));
         sb.append(", flags=[").append(InsetsSource.flagsToString(mFlags)).append("]");
         if (mInsetsSize != null) {
@@ -244,9 +242,7 @@
     }
 
     public InsetsFrameProvider(Parcel in) {
-        mOwner = in.readStrongBinder();
-        mIndex = in.readInt();
-        mType = in.readInt();
+        mId = in.readInt();
         mSource = in.readInt();
         mFlags = in.readInt();
         mInsetsSize = in.readTypedObject(Insets.CREATOR);
@@ -256,9 +252,7 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeStrongBinder(mOwner);
-        out.writeInt(mIndex);
-        out.writeInt(mType);
+        out.writeInt(mId);
         out.writeInt(mSource);
         out.writeInt(mFlags);
         out.writeTypedObject(mInsetsSize, flags);
@@ -267,7 +261,7 @@
     }
 
     public boolean idEquals(InsetsFrameProvider o) {
-        return Objects.equals(mOwner, o.mOwner) && mIndex == o.mIndex && mType == o.mType;
+        return mId == o.mId;
     }
 
     @Override
@@ -279,8 +273,7 @@
             return false;
         }
         final InsetsFrameProvider other = (InsetsFrameProvider) o;
-        return Objects.equals(mOwner, other.mOwner) && mIndex == other.mIndex
-                && mType == other.mType && mSource == other.mSource && mFlags == other.mFlags
+        return mId == other.mId && mSource == other.mSource && mFlags == other.mFlags
                 && Objects.equals(mInsetsSize, other.mInsetsSize)
                 && Arrays.equals(mInsetsSizeOverrides, other.mInsetsSizeOverrides)
                 && Objects.equals(mArbitraryRectangle, other.mArbitraryRectangle);
@@ -288,7 +281,7 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mOwner, mIndex, mType, mSource, mFlags, mInsetsSize,
+        return Objects.hash(mId, mSource, mFlags, mInsetsSize,
                 Arrays.hashCode(mInsetsSizeOverrides), mArbitraryRectangle);
     }
 
@@ -319,7 +312,7 @@
 
         protected InsetsSizeOverride(Parcel in) {
             mWindowType = in.readInt();
-            mInsetsSize = in.readParcelable(null, Insets.class);
+            mInsetsSize = in.readTypedObject(Insets.CREATOR);
         }
 
         public InsetsSizeOverride(int windowType, Insets insetsSize) {
@@ -354,7 +347,7 @@
         @Override
         public void writeToParcel(Parcel out, int flags) {
             out.writeInt(mWindowType);
-            out.writeParcelable(mInsetsSize, flags);
+            out.writeTypedObject(mInsetsSize, flags);
         }
 
         @Override
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index bd48771..114f4ed 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -271,7 +271,7 @@
      * @param index An owner may have multiple sources with the same type. For example, the system
      *              server might have multiple display cutout sources. This is used to identify
      *              which one is which. The value must be in a range of [0, 2047].
-     * @param type The {@link WindowInsets.Type.InsetsType type} of the source.
+     * @param type The {@link InsetsType type} of the source.
      * @return a unique integer as the identifier.
      */
     public static int createId(Object owner, @IntRange(from = 0, to = 2047) int index,
@@ -282,11 +282,29 @@
         // owner takes top 16 bits;
         // index takes 11 bits since the 6th bit;
         // type takes bottom 5 bits.
-        return (((owner != null ? owner.hashCode() : 1) % (1 << 16)) << 16)
+        return ((System.identityHashCode(owner) % (1 << 16)) << 16)
                 + (index << 5)
                 + WindowInsets.Type.indexOf(type);
     }
 
+    /**
+     * Gets the index from the ID.
+     *
+     * @see #createId(Object, int, int)
+     */
+    public static int getIndex(int id) {
+        return (id % (1 << 16)) >> 5;
+    }
+
+    /**
+     * Gets the {@link InsetsType} from the ID.
+     *
+     * @see #createId(Object, int, int)
+     */
+    public static int getType(int id) {
+        return 1 << (id % 32);
+    }
+
     public static String flagsToString(@Flags int flags) {
         final StringJoiner joiner = new StringJoiner(" ");
         if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index ad864b1..bc0af12 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -96,12 +96,12 @@
     <!-- Width of the navigation bar when it is placed vertically on the screen in car mode -->
     <dimen name="navigation_bar_width_car_mode">96dp</dimen>
     <!-- Height of notification icons in the status bar -->
-    <dimen name="status_bar_icon_size">18sp</dimen>
+    <dimen name="status_bar_icon_size">22dip</dimen>
     <!-- Desired size of system icons in status bar. -->
-    <dimen name="status_bar_system_icon_size">15sp</dimen>
+    <dimen name="status_bar_system_icon_size">15dp</dimen>
     <!-- Intrinsic size of most system icons in status bar. This is the default value that
          is used if a Drawable reports an intrinsic size of 0. -->
-    <dimen name="status_bar_system_icon_intrinsic_size">17sp</dimen>
+    <dimen name="status_bar_system_icon_intrinsic_size">17dp</dimen>
     <!-- Size of the giant number (unread count) in the notifications -->
     <dimen name="status_bar_content_number_size">48sp</dimen>
     <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. -->
@@ -330,7 +330,7 @@
     <dimen name="notification_icon_circle_start">16dp</dimen>
 
     <!-- size (width and height) of the icon in the notification header -->
-    <dimen name="notification_header_icon_size_ambient">18sp</dimen>
+    <dimen name="notification_header_icon_size_ambient">18dp</dimen>
 
     <!-- The margin before the start of the app name in the header. -->
     <dimen name="notification_header_app_name_margin_start">3dp</dimen>
diff --git a/core/tests/coretests/jni/NativeWorkSourceParcelTest.cpp b/core/tests/coretests/jni/NativeWorkSourceParcelTest.cpp
index db1f7bd..187e2c1 100644
--- a/core/tests/coretests/jni/NativeWorkSourceParcelTest.cpp
+++ b/core/tests/coretests/jni/NativeWorkSourceParcelTest.cpp
@@ -77,15 +77,18 @@
     Parcel* parcel = nativeGetParcelData(env, wsParcel);
     int32_t endMarker;
 
-    // read WorkSource and if no error read end marker
-    status_t err = ws.readFromParcel(parcel) ?: parcel->readInt32(&endMarker);
-    int32_t dataAvailable = parcel->dataAvail();
-
+    status_t err = ws.readFromParcel(parcel);
     if (err != OK) {
-        ALOGE("WorkSource readFromParcel failed %d", err);
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                StringPrintf("WorkSource readFromParcel failed: %d", err).c_str());
     }
-
+    err = parcel->readInt32(&endMarker);
+    if (err != OK) {
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                StringPrintf("Failed to read endMarker: %d", err).c_str());
+    }
     // Now we have a native WorkSource object, verify it.
+    int32_t dataAvailable = parcel->dataAvail();
     if (dataAvailable > 0) { // not all data read from the parcel
         jniThrowException(env, "java/lang/IllegalArgumentException",
                 StringPrintf("WorkSource contains more data than native read (%d)",
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index 6fa8f11..55680ab 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -227,5 +227,25 @@
         assertEquals(numTotalSources, sources.size());
     }
 
+    @Test
+    public void testGetIndex() {
+        for (int index = 0; index < 2048; index++) {
+            for (int type = FIRST; type <= LAST; type = type << 1) {
+                final int id = InsetsSource.createId(null, index, type);
+                assertEquals(index, InsetsSource.getIndex(id));
+            }
+        }
+    }
+
+    @Test
+    public void testGetType() {
+        for (int index = 0; index < 2048; index++) {
+            for (int type = FIRST; type <= LAST; type = type << 1) {
+                final int id = InsetsSource.createId(null, index, type);
+                assertEquals(type, InsetsSource.getType(id));
+            }
+        }
+    }
+
     // Parcel and equals already tested via InsetsStateTest
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
index c767376..18615f7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationAdapter.java
@@ -19,6 +19,8 @@
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
 
+import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationRunner.shouldUseSnapshotAnimationForClosingChange;
+
 import android.annotation.CallSuper;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -97,10 +99,17 @@
             final Rect startBounds = change.getStartAbsBounds();
             final Rect endBounds = change.getEndAbsBounds();
             mContentBounds.set(startBounds);
-            mContentRelOffset.set(change.getEndRelOffset());
-            mContentRelOffset.offset(
-                    startBounds.left - endBounds.left,
-                    startBounds.top - endBounds.top);
+            // Put the transition to the top left for snapshot animation.
+            if (shouldUseSnapshotAnimationForClosingChange(mChange)) {
+                // TODO(b/275034335): Fix an issue that black hole when closing the right container
+                //  in bounds change transition.
+                mContentRelOffset.set(0, 0);
+            } else {
+                mContentRelOffset.set(change.getEndRelOffset());
+                mContentRelOffset.offset(
+                        startBounds.left - endBounds.left,
+                        startBounds.top - endBounds.top);
+            }
         } else {
             mContentBounds.set(change.getEndAbsBounds());
             mContentRelOffset.set(change.getEndRelOffset());
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
index 1df6ecd..ab7c7d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationRunner.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
 import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 
+import static com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationSpec.createShowSnapshotForClosingAnimation;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.addBackgroundToTransition;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.edgeExtendWindow;
 import static com.android.wm.shell.transition.TransitionAnimationHelper.getTransitionBackgroundColorIfSet;
@@ -42,6 +43,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingAnimationAdapter.SnapshotAdapter;
 import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.util.TransitionUtil;
 
@@ -185,23 +187,23 @@
             return createChangeAnimationAdapters(info, startTransaction);
         }
         if (TransitionUtil.isClosingType(info.getType())) {
-            return createCloseAnimationAdapters(info);
+            return createCloseAnimationAdapters(info, startTransaction);
         }
-        return createOpenAnimationAdapters(info);
+        return createOpenAnimationAdapters(info, startTransaction);
     }
 
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createOpenAnimationAdapters(
-            @NonNull TransitionInfo info) {
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
         return createOpenCloseAnimationAdapters(info, true /* isOpening */,
-                mAnimationSpec::loadOpenAnimation);
+                mAnimationSpec::loadOpenAnimation, startTransaction);
     }
 
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createCloseAnimationAdapters(
-            @NonNull TransitionInfo info) {
+            @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction) {
         return createOpenCloseAnimationAdapters(info, false /* isOpening */,
-                mAnimationSpec::loadCloseAnimation);
+                mAnimationSpec::loadCloseAnimation, startTransaction);
     }
 
     /**
@@ -211,7 +213,8 @@
     @NonNull
     private List<ActivityEmbeddingAnimationAdapter> createOpenCloseAnimationAdapters(
             @NonNull TransitionInfo info, boolean isOpening,
-            @NonNull AnimationProvider animationProvider) {
+            @NonNull AnimationProvider animationProvider,
+            @NonNull SurfaceControl.Transaction startTransaction) {
         // We need to know if the change window is only a partial of the whole animation screen.
         // If so, we will need to adjust it to make the whole animation screen looks like one.
         final List<TransitionInfo.Change> openingChanges = new ArrayList<>();
@@ -224,6 +227,8 @@
                 openingWholeScreenBounds.union(change.getEndAbsBounds());
             } else {
                 closingChanges.add(change);
+                // Also union with the start bounds because the closing transition may be shrunk.
+                closingWholeScreenBounds.union(change.getStartAbsBounds());
                 closingWholeScreenBounds.union(change.getEndAbsBounds());
             }
         }
@@ -241,6 +246,17 @@
             adapters.add(adapter);
         }
         for (TransitionInfo.Change change : closingChanges) {
+            if (shouldUseSnapshotAnimationForClosingChange(change)) {
+                SurfaceControl screenshot = getOrCreateScreenshot(change, change, startTransaction);
+                if (screenshot != null) {
+                    final SnapshotAdapter snapshotAdapter = new SnapshotAdapter(
+                            createShowSnapshotForClosingAnimation(), change, screenshot);
+                    if (!isOpening) {
+                        snapshotAdapter.overrideLayer(offsetLayer++);
+                    }
+                    adapters.add(snapshotAdapter);
+                }
+            }
             final ActivityEmbeddingAnimationAdapter adapter = createOpenCloseAnimationAdapter(
                     info, change, animationProvider, closingWholeScreenBounds);
             if (!isOpening) {
@@ -251,6 +267,22 @@
         return adapters;
     }
 
+    /**
+     * Returns whether we should use snapshot animation for the closing change.
+     * It's usually because the end bounds of the closing change are shrunk, which leaves a black
+     * area in the transition.
+     */
+    static boolean shouldUseSnapshotAnimationForClosingChange(
+            @NonNull TransitionInfo.Change closingChange) {
+        // Only check closing type because we only take screenshot for closing bounds-changing
+        // changes.
+        if (!TransitionUtil.isClosingType(closingChange.getMode())) {
+            return false;
+        }
+        // Don't need to take screenshot if there's no bounds change.
+        return !closingChange.getStartAbsBounds().equals(closingChange.getEndAbsBounds());
+    }
+
     /** Sets the first frame to the {@code startTransaction} to avoid any flicker on start. */
     private void prepareForFirstFrame(@NonNull SurfaceControl.Transaction startTransaction,
             @NonNull List<ActivityEmbeddingAnimationAdapter> adapters) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
index cb8342a..19eff0e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingAnimationSpec.java
@@ -77,6 +77,15 @@
         return new AlphaAnimation(alpha, alpha);
     }
 
+    /**
+     * Animation that intended to show snapshot for closing animation because the closing end bounds
+     * are changed.
+     */
+    @NonNull
+    static Animation createShowSnapshotForClosingAnimation() {
+        return new AlphaAnimation(1f, 1f);
+    }
+
     /** Animation for window that is opening in a change transition. */
     @NonNull
     Animation createChangeBoundsOpenAnimation(@NonNull TransitionInfo.Change change,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index f79f1f7..a73ab77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -533,8 +533,12 @@
             final int layer;
             // Put all the OPEN/SHOW on top
             if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
-                // Wallpaper is always at the bottom.
-                layer = -zSplitLine;
+                // Wallpaper is always at the bottom, opening wallpaper on top of closing one.
+                if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
+                    layer = -zSplitLine + numChanges - i;
+                } else {
+                    layer = -zSplitLine - i;
+                }
             } else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
                 if (isOpening) {
                     // put on top
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
index b6d92814..b5937ae 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml
@@ -21,6 +21,9 @@
         <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
         <!-- Ensure output directory is empty at the start -->
         <option name="run-command" value="rm -rf /sdcard/flicker" />
+        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+        <option name="run-command" value="cmd window tracing size 20480" />
+        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index e73cf87..3123ee6 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1237,6 +1237,9 @@
     public static final Set<Integer> DEVICE_IN_ALL_SCO_SET;
     /** @hide */
     public static final Set<Integer> DEVICE_IN_ALL_USB_SET;
+    /** @hide */
+    public static final Set<Integer> DEVICE_IN_ALL_BLE_SET;
+
     static {
         DEVICE_IN_ALL_SET = new HashSet<>();
         DEVICE_IN_ALL_SET.add(DEVICE_IN_COMMUNICATION);
@@ -1276,6 +1279,66 @@
         DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_ACCESSORY);
         DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_DEVICE);
         DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_HEADSET);
+
+        DEVICE_IN_ALL_BLE_SET = new HashSet<>();
+        DEVICE_IN_ALL_BLE_SET.add(DEVICE_IN_BLE_HEADSET);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothDevice(int deviceType) {
+        return isBluetoothA2dpOutDevice(deviceType)
+                || isBluetoothScoDevice(deviceType)
+                || isBluetoothLeDevice(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothOutDevice(int deviceType) {
+        return isBluetoothA2dpOutDevice(deviceType)
+                || isBluetoothScoOutDevice(deviceType)
+                || isBluetoothLeOutDevice(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothInDevice(int deviceType) {
+        return isBluetoothScoInDevice(deviceType)
+                || isBluetoothLeInDevice(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothA2dpOutDevice(int deviceType) {
+        return DEVICE_OUT_ALL_A2DP_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothScoOutDevice(int deviceType) {
+        return DEVICE_OUT_ALL_SCO_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothScoInDevice(int deviceType) {
+        return DEVICE_IN_ALL_SCO_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothScoDevice(int deviceType) {
+        return isBluetoothScoOutDevice(deviceType)
+                || isBluetoothScoInDevice(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothLeOutDevice(int deviceType) {
+        return DEVICE_OUT_ALL_BLE_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothLeInDevice(int deviceType) {
+        return DEVICE_IN_ALL_BLE_SET.contains(deviceType);
+    }
+
+    /** @hide */
+    public static boolean isBluetoothLeDevice(int deviceType) {
+        return isBluetoothLeOutDevice(deviceType)
+                || isBluetoothLeInDevice(deviceType);
     }
 
     /** @hide */
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index c9e8312..6cf6825 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -50,6 +50,7 @@
 import android.util.Log;
 
 import androidx.annotation.DoNotInline;
+import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -638,6 +639,11 @@
         }
 
         @Override
+        public void onSessionReleased(@NonNull RoutingSessionInfo session) {
+            refreshDevices();
+        }
+
+        @Override
         public void onRouteListingPreferenceUpdated(
                 String packageName,
                 RouteListingPreference routeListingPreference) {
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 270fda8..0969327 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -141,6 +141,56 @@
     }
 
     @Test
+    public void onSessionReleased_shouldUpdateConnectedDevice() {
+        final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+        final RoutingSessionInfo sessionInfo1 = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(sessionInfo1);
+        final RoutingSessionInfo sessionInfo2 = mock(RoutingSessionInfo.class);
+        routingSessionInfos.add(sessionInfo2);
+
+        final List<String> selectedRoutesSession1 = new ArrayList<>();
+        selectedRoutesSession1.add(TEST_ID_1);
+        when(sessionInfo1.getSelectedRoutes()).thenReturn(selectedRoutesSession1);
+
+        final List<String> selectedRoutesSession2 = new ArrayList<>();
+        selectedRoutesSession2.add(TEST_ID_2);
+        when(sessionInfo2.getSelectedRoutes()).thenReturn(selectedRoutesSession2);
+
+        mShadowRouter2Manager.setRoutingSessions(routingSessionInfos);
+
+        final MediaRoute2Info info1 = mock(MediaRoute2Info.class);
+        when(info1.getId()).thenReturn(TEST_ID_1);
+        when(info1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        final MediaRoute2Info info2 = mock(MediaRoute2Info.class);
+        when(info2.getId()).thenReturn(TEST_ID_2);
+        when(info2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+
+        final List<MediaRoute2Info> routes = new ArrayList<>();
+        routes.add(info1);
+        routes.add(info2);
+        mShadowRouter2Manager.setAllRoutes(routes);
+        mShadowRouter2Manager.setTransferableRoutes(routes);
+
+        final MediaDevice mediaDevice1 = mInfoMediaManager.findMediaDevice(TEST_ID_1);
+        assertThat(mediaDevice1).isNull();
+        final MediaDevice mediaDevice2 = mInfoMediaManager.findMediaDevice(TEST_ID_2);
+        assertThat(mediaDevice2).isNull();
+
+        mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
+        final MediaDevice infoDevice1 = mInfoMediaManager.mMediaDevices.get(0);
+        assertThat(infoDevice1.getId()).isEqualTo(TEST_ID_1);
+        final MediaDevice infoDevice2 = mInfoMediaManager.mMediaDevices.get(1);
+        assertThat(infoDevice2.getId()).isEqualTo(TEST_ID_2);
+        // The active routing session is the last one in the list, which maps to infoDevice2.
+        assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice2);
+
+        routingSessionInfos.remove(sessionInfo2);
+        mInfoMediaManager.mMediaRouterCallback.onSessionReleased(sessionInfo2);
+        assertThat(mInfoMediaManager.getCurrentConnectedDevice()).isEqualTo(infoDevice1);
+    }
+
+    @Test
     public void onRouteAdded_buildAllRoutes_shouldAddMediaDevice() {
         final MediaRoute2Info info = mock(MediaRoute2Info.class);
         when(info.getId()).thenReturn(TEST_ID);
diff --git a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
index 934fa6f..29832a0 100644
--- a/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
+++ b/packages/SystemUI/res-keyguard/layout/status_bar_mobile_signal_group_inner.xml
@@ -30,7 +30,7 @@
 
         <FrameLayout
             android:id="@+id/inout_container"
-            android:layout_height="@*android:dimen/status_bar_system_icon_intrinsic_size"
+            android:layout_height="17dp"
             android:layout_width="wrap_content"
             android:layout_gravity="center_vertical">
             <ImageView
@@ -39,25 +39,24 @@
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_down"
                 android:visibility="gone"
-                android:paddingEnd="2sp"
+                android:paddingEnd="2dp"
                 />
             <ImageView
                 android:id="@+id/mobile_out"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_up"
-                android:paddingEnd="2sp"
+                android:paddingEnd="2dp"
                 android:visibility="gone"
                 />
         </FrameLayout>
         <ImageView
             android:id="@+id/mobile_type"
-            android:layout_height="@dimen/status_bar_mobile_signal_size"
+            android:layout_height="wrap_content"
             android:layout_width="wrap_content"
             android:layout_gravity="center_vertical"
-            android:adjustViewBounds="true"
-            android:paddingStart="2.5sp"
-            android:paddingEnd="1sp"
+            android:paddingStart="2.5dp"
+            android:paddingEnd="1dp"
             android:visibility="gone" />
         <Space
             android:id="@+id/mobile_roaming_space"
@@ -71,14 +70,14 @@
             android:layout_gravity="center_vertical">
             <com.android.systemui.statusbar.AnimatedImageView
                 android:id="@+id/mobile_signal"
-                android:layout_height="@dimen/status_bar_mobile_signal_size"
-                android:layout_width="@dimen/status_bar_mobile_signal_size"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
                 systemui:hasOverlappingRendering="false"
                 />
             <ImageView
                 android:id="@+id/mobile_roaming"
-                android:layout_width="@dimen/status_bar_mobile_signal_size"
-                android:layout_height="@dimen/status_bar_mobile_signal_size"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
                 android:src="@drawable/stat_sys_roaming"
                 android:contentDescription="@string/data_connection_roaming"
                 android:visibility="gone" />
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index e989372..441f963 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -126,7 +126,8 @@
     <com.android.systemui.battery.BatteryMeterView
         android:id="@+id/batteryRemainingIcon"
         android:layout_width="wrap_content"
-        android:layout_height="0dp"
+        android:layout_height="@dimen/large_screen_shade_header_min_height"
+        app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
         app:layout_constrainedWidth="true"
         app:textAppearance="@style/TextAppearance.QS.Status"
         app:layout_constraintStart_toEndOf="@id/statusIcons"
diff --git a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
index 473ab08..0ea0653 100644
--- a/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
+++ b/packages/SystemUI/res/layout/status_bar_wifi_group_inner.xml
@@ -24,11 +24,11 @@
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:gravity="center_vertical"
-        android:layout_marginStart="2.5sp"
+        android:layout_marginStart="2.5dp"
     >
         <FrameLayout
                 android:id="@+id/inout_container"
-                android:layout_height="@*android:dimen/status_bar_system_icon_intrinsic_size"
+                android:layout_height="17dp"
                 android:layout_width="wrap_content"
                 android:gravity="center_vertical" >
             <ImageView
@@ -37,14 +37,14 @@
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_down"
                 android:visibility="gone"
-                android:paddingEnd="2sp"
+                android:paddingEnd="2dp"
             />
             <ImageView
                 android:id="@+id/wifi_out"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
                 android:src="@drawable/ic_activity_up"
-                android:paddingEnd="2sp"
+                android:paddingEnd="2dp"
                 android:visibility="gone"
             />
         </FrameLayout>
@@ -62,7 +62,7 @@
         <View
             android:id="@+id/wifi_signal_spacer"
             android:layout_width="@dimen/status_bar_wifi_signal_spacer_width"
-            android:layout_height="4sp"
+            android:layout_height="4dp"
             android:visibility="gone" />
 
         <!-- Looks like CarStatusBar uses this... -->
@@ -75,7 +75,7 @@
         <View
             android:id="@+id/wifi_airplane_spacer"
             android:layout_width="@dimen/status_bar_airplane_spacer_width"
-            android:layout_height="4sp"
+            android:layout_height="4dp"
             android:visibility="gone"
         />
     </com.android.keyguard.AlphaOptimizedLinearLayout>
diff --git a/packages/SystemUI/res/values-sw720dp/dimens.xml b/packages/SystemUI/res/values-sw720dp/dimens.xml
index f40615e..2086459 100644
--- a/packages/SystemUI/res/values-sw720dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp/dimens.xml
@@ -17,7 +17,7 @@
 -->
 <resources>
     <!-- gap on either side of status bar notification icons -->
-    <dimen name="status_bar_icon_padding">1sp</dimen>
+    <dimen name="status_bar_icon_padding">1dp</dimen>
 
     <dimen name="controls_header_horizontal_padding">28dp</dimen>
     <dimen name="controls_content_margin_horizontal">40dp</dimen>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f5c4a4e..0aa880f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -122,26 +122,26 @@
     <dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
 
     <!-- Default horizontal drawable padding for status bar icons. -->
-    <dimen name="status_bar_horizontal_padding">2.5sp</dimen>
+    <dimen name="status_bar_horizontal_padding">2.5dp</dimen>
 
     <!-- Height of the battery icon in the status bar. -->
-    <dimen name="status_bar_battery_icon_height">13.0sp</dimen>
+    <dimen name="status_bar_battery_icon_height">13.0dp</dimen>
 
     <!-- Width of the battery icon in the status bar. The battery drawable assumes a 12x20 canvas,
-    so the width of the icon should be 13.0sp * (12.0 / 20.0) -->
-    <dimen name="status_bar_battery_icon_width">7.8sp</dimen>
+    so the width of the icon should be 13.0dp * (12.0 / 20.0) -->
+    <dimen name="status_bar_battery_icon_width">7.8dp</dimen>
 
-    <!-- The battery icon is 13sp tall, but the other system icons are 15sp tall (see
+    <!-- The battery icon is 13dp tall, but the other system icons are 15dp tall (see
          @*android:dimen/status_bar_system_icon_size) with some top and bottom padding embedded in
-         the drawables themselves. So, the battery icon may need an extra 1sp of spacing so that its
+         the drawables themselves. So, the battery icon may need an extra 1dp of spacing so that its
          bottom still aligns with the bottom of all the other system icons. See b/258672854. -->
-    <dimen name="status_bar_battery_extra_vertical_spacing">1sp</dimen>
+    <dimen name="status_bar_battery_extra_vertical_spacing">1dp</dimen>
 
     <!-- The font size for the clock in the status bar. -->
     <dimen name="status_bar_clock_size">14sp</dimen>
 
     <!-- The starting padding for the clock in the status bar. -->
-    <dimen name="status_bar_clock_starting_padding">7sp</dimen>
+    <dimen name="status_bar_clock_starting_padding">7dp</dimen>
 
     <!-- The end padding for the clock in the status bar. -->
     <dimen name="status_bar_clock_end_padding">0dp</dimen>
@@ -153,19 +153,16 @@
     <dimen name="status_bar_left_clock_end_padding">2dp</dimen>
 
     <!-- Spacing after the wifi signals that is present if there are any icons following it. -->
-    <dimen name="status_bar_wifi_signal_spacer_width">2.5sp</dimen>
+    <dimen name="status_bar_wifi_signal_spacer_width">2.5dp</dimen>
 
     <!-- Size of the view displaying the wifi signal icon in the status bar. -->
-    <dimen name="status_bar_wifi_signal_size">13sp</dimen>
-
-    <!-- Size of the view displaying the mobile signal icon in the status bar. -->
-    <dimen name="status_bar_mobile_signal_size">13sp</dimen>
+    <dimen name="status_bar_wifi_signal_size">@*android:dimen/status_bar_system_icon_size</dimen>
 
     <!-- Spacing before the airplane mode icon if there are any icons preceding it. -->
-    <dimen name="status_bar_airplane_spacer_width">4sp</dimen>
+    <dimen name="status_bar_airplane_spacer_width">4dp</dimen>
 
     <!-- Spacing between system icons. -->
-    <dimen name="status_bar_system_icon_spacing">2sp</dimen>
+    <dimen name="status_bar_system_icon_spacing">0dp</dimen>
 
     <!-- The amount to scale each of the status bar icons by. A value of 1 means no scaling. -->
     <item name="status_bar_icon_scale_factor" format="float" type="dimen">1.0</item>
@@ -313,7 +310,7 @@
     <dimen name="snooze_snackbar_min_height">56dp</dimen>
 
     <!-- size at which Notification icons will be drawn in the status bar -->
-    <dimen name="status_bar_icon_drawing_size">15sp</dimen>
+    <dimen name="status_bar_icon_drawing_size">15dp</dimen>
 
     <!-- size at which Notification icons will be drawn on Ambient Display -->
     <dimen name="status_bar_icon_drawing_size_dark">
@@ -324,22 +321,22 @@
     <item type="dimen" name="status_bar_icon_drawing_alpha">90%</item>
 
     <!-- gap on either side of status bar notification icons -->
-    <dimen name="status_bar_icon_padding">0sp</dimen>
+    <dimen name="status_bar_icon_padding">0dp</dimen>
 
     <!-- the padding on the start of the statusbar -->
-    <dimen name="status_bar_padding_start">8sp</dimen>
+    <dimen name="status_bar_padding_start">8dp</dimen>
 
     <!-- the padding on the end of the statusbar -->
-    <dimen name="status_bar_padding_end">8sp</dimen>
+    <dimen name="status_bar_padding_end">8dp</dimen>
 
     <!-- the padding on the top of the statusbar (usually 0) -->
-    <dimen name="status_bar_padding_top">0sp</dimen>
+    <dimen name="status_bar_padding_top">0dp</dimen>
 
     <!-- the radius of the overflow dot in the status bar -->
-    <dimen name="overflow_dot_radius">2sp</dimen>
+    <dimen name="overflow_dot_radius">2dp</dimen>
 
     <!-- the padding between dots in the icon overflow -->
-    <dimen name="overflow_icon_dot_padding">3sp</dimen>
+    <dimen name="overflow_icon_dot_padding">3dp</dimen>
 
     <!-- Dimensions related to screenshots -->
 
@@ -620,8 +617,8 @@
     <dimen name="qs_footer_icon_size">20dp</dimen>
     <dimen name="qs_header_row_min_height">48dp</dimen>
 
-    <dimen name="qs_header_non_clickable_element_height">24sp</dimen>
-    <dimen name="new_qs_header_non_clickable_element_height">24sp</dimen>
+    <dimen name="qs_header_non_clickable_element_height">24dp</dimen>
+    <dimen name="new_qs_header_non_clickable_element_height">24dp</dimen>
 
     <dimen name="qs_footer_padding">20dp</dimen>
     <dimen name="qs_security_footer_height">88dp</dimen>
@@ -825,7 +822,7 @@
 
     <!-- Padding between the mobile signal indicator and the start icon when the roaming icon
          is displayed in the upper left corner. -->
-    <dimen name="roaming_icon_start_padding">2sp</dimen>
+    <dimen name="roaming_icon_start_padding">2dp</dimen>
 
     <!-- Extra padding between the mobile data type icon and the strength indicator when the data
          type icon is wide for the tile in quick settings. -->
@@ -1045,13 +1042,13 @@
     <dimen name="display_cutout_margin_consumption">0px</dimen>
 
     <!-- Height of the Ongoing App Ops chip -->
-    <dimen name="ongoing_appops_chip_height">24sp</dimen>
+    <dimen name="ongoing_appops_chip_height">24dp</dimen>
     <!-- Side padding between background of Ongoing App Ops chip and content -->
     <dimen name="ongoing_appops_chip_side_padding">8dp</dimen>
     <!-- Margin between icons of Ongoing App Ops chip -->
     <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen>
     <!-- Icon size of Ongoing App Ops chip -->
-    <dimen name="ongoing_appops_chip_icon_size">16sp</dimen>
+    <dimen name="ongoing_appops_chip_icon_size">16dp</dimen>
     <!-- Radius of Ongoing App Ops chip corners -->
     <dimen name="ongoing_appops_chip_bg_corner_radius">28dp</dimen>
     <!--  One or two privacy items  -->
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 6ca409f..1fafa3d 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -62,7 +62,8 @@
     val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply")
 
     // TODO(b/279735475): Tracking Bug
-    @JvmField val NEW_LIGHT_BAR_LOGIC = unreleasedFlag(279735475, "new_light_bar_logic")
+    @JvmField
+    val NEW_LIGHT_BAR_LOGIC = unreleasedFlag(279735475, "new_light_bar_logic", teamfood = true)
 
     /**
      * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
index 166ba9f..79167f2 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt
@@ -15,7 +15,6 @@
 package com.android.systemui.privacy
 
 import android.content.Context
-import android.content.res.Configuration
 import android.util.AttributeSet
 import android.view.Gravity.CENTER_VERTICAL
 import android.view.Gravity.END
@@ -103,11 +102,6 @@
                 R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
     }
 
-    override fun onConfigurationChanged(newConfig: Configuration?) {
-        super.onConfigurationChanged(newConfig)
-        updateResources()
-    }
-
     private fun updateResources() {
         iconMargin = context.resources
                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
@@ -116,11 +110,8 @@
         iconColor =
                 Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
 
-        val height = context.resources
-                .getDimensionPixelSize(R.dimen.ongoing_appops_chip_height)
         val padding = context.resources
                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
-        iconsContainer.layoutParams.height = height
         iconsContainer.setPaddingRelative(padding, 0, padding, 0)
         iconsContainer.background = context.getDrawable(R.drawable.statusbar_privacy_chip_bg)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 129c859..7755003 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -26,7 +26,6 @@
 import android.app.ActivityManager;
 import android.app.Notification;
 import android.content.Context;
-import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.ColorStateList;
 import android.content.res.Configuration;
@@ -76,9 +75,9 @@
      */
     private static final float DARK_ALPHA_BOOST = 0.67f;
     /**
-     * Status icons are currently drawn with the intention of being 17sp tall, but we
-     * want to scale them (in a way that doesn't require an asset dump) down 2sp. So
-     * 17sp * (15 / 17) = 15sp, the new height. After the first call to {@link #reloadDimens} all
+     * Status icons are currently drawn with the intention of being 17dp tall, but we
+     * want to scale them (in a way that doesn't require an asset dump) down 2dp. So
+     * 17dp * (15 / 17) = 15dp, the new height. After the first call to {@link #reloadDimens} all
      * values will be in px.
      */
     private float mSystemIconDesiredHeight = 15f;
@@ -145,7 +144,7 @@
     private String mNumberText;
     private StatusBarNotification mNotification;
     private final boolean mBlocked;
-    private Configuration mConfiguration;
+    private int mDensity;
     private boolean mNightMode;
     private float mIconScale = 1.0f;
     private final Paint mDotPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
@@ -199,8 +198,9 @@
         mNumberPain.setAntiAlias(true);
         setNotification(sbn);
         setScaleType(ScaleType.CENTER);
-        mConfiguration = new Configuration(context.getResources().getConfiguration());
-        mNightMode = (mConfiguration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
+        mDensity = context.getResources().getDisplayMetrics().densityDpi;
+        Configuration configuration = context.getResources().getConfiguration();
+        mNightMode = (configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK)
                 == Configuration.UI_MODE_NIGHT_YES;
         initializeDecorColor();
         reloadDimens();
@@ -214,7 +214,7 @@
         mAlwaysScaleIcon = true;
         reloadDimens();
         maybeUpdateIconScaleDimens();
-        mConfiguration = new Configuration(context.getResources().getConfiguration());
+        mDensity = context.getResources().getDisplayMetrics().densityDpi;
     }
 
     /** Should always be preceded by {@link #reloadDimens()} */
@@ -231,17 +231,12 @@
     private void updateIconScaleForNotifications() {
         final float imageBounds = mIncreasedSize ?
                 mStatusBarIconDrawingSizeIncreased : mStatusBarIconDrawingSize;
-        float iconHeight = getIconHeight();
-        if (iconHeight != 0) {
-            mIconScale = imageBounds / iconHeight;
-        } else {
-            final int outerBounds = mStatusBarIconSize;
-            mIconScale = imageBounds / (float) outerBounds;
-        }
+        final int outerBounds = mStatusBarIconSize;
+        mIconScale = imageBounds / (float)outerBounds;
         updatePivot();
     }
 
-    // Makes sure that all icons are scaled to the same height (15sp). If we cannot get a height
+    // Makes sure that all icons are scaled to the same height (15dp). If we cannot get a height
     // for the icon, it uses the default SCALE (15f / 17f) which is the old behavior
     private void updateIconScaleForSystemIcons() {
         float iconHeight = getIconHeight();
@@ -272,10 +267,12 @@
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
-        final int configDiff = newConfig.diff(mConfiguration);
-        mConfiguration.setTo(newConfig);
-        if ((configDiff & (ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
-            updateIconDimens();
+        int density = newConfig.densityDpi;
+        if (density != mDensity) {
+            mDensity = density;
+            reloadDimens();
+            updateDrawable();
+            maybeUpdateIconScaleDimens();
         }
         boolean nightMode = (newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK)
                 == Configuration.UI_MODE_NIGHT_YES;
@@ -285,15 +282,6 @@
         }
     }
 
-    /**
-     * Update the icon dimens and drawable with current resources
-     */
-    public void updateIconDimens() {
-        reloadDimens();
-        updateDrawable();
-        maybeUpdateIconScaleDimens();
-    }
-
     private void reloadDimens() {
         boolean applyRadius = mDotRadius == mStaticDotRadius;
         Resources res = getResources();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index b9a12e2..006a029d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -306,7 +306,7 @@
     public void applyIconStates() {
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
-            IconState childState = mIconStates.get(child);
+            ViewState childState = mIconStates.get(child);
             if (childState != null) {
                 childState.applyToView(child);
             }
@@ -339,7 +339,6 @@
             }
         }
         if (child instanceof StatusBarIconView) {
-            ((StatusBarIconView) child).updateIconDimens();
             ((StatusBarIconView) child).setDozing(mDozing, false, 0);
         }
     }
@@ -448,14 +447,9 @@
     @VisibleForTesting
     boolean isOverflowing(boolean isLastChild, float translationX, float layoutEnd,
             float iconSize) {
-        if (isLastChild) {
-            return translationX + iconSize > layoutEnd;
-        } else {
-            // If the child is not the last child, we need to ensure that we have room for the next
-            // icon and the dot. The dot could be as large as an icon, so verify that we have room
-            // for 2 icons.
-            return translationX + iconSize * 2f > layoutEnd;
-        }
+        // Layout end, as used here, does not include padding end.
+        final float overflowX = isLastChild ? layoutEnd : layoutEnd - iconSize;
+        return translationX >= overflowX;
     }
 
     /**
@@ -495,7 +489,10 @@
             // First icon to overflow.
             if (firstOverflowIndex == -1 && isOverflowing) {
                 firstOverflowIndex = i;
-                mVisualOverflowStart = translationX;
+                mVisualOverflowStart = layoutEnd - mIconSize;
+                if (forceOverflow || mIsStaticLayout) {
+                    mVisualOverflowStart = Math.min(translationX, mVisualOverflowStart);
+                }
             }
             final float drawingScale = mOnLockScreen && view instanceof StatusBarIconView
                     ? ((StatusBarIconView) view).getIconScaleIncreased()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 678873c..a8a834f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -203,7 +203,8 @@
 
         @Override
         protected LayoutParams onCreateLayoutParams() {
-            LinearLayout.LayoutParams lp = super.onCreateLayoutParams();
+            LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                    ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
             lp.setMargins(mIconHPadding, 0, mIconHPadding, 0);
             return lp;
         }
@@ -369,7 +370,7 @@
         private final MobileIconsViewModel mMobileIconsViewModel;
 
         protected final Context mContext;
-        protected int mIconSize;
+        protected final int mIconSize;
         // Whether or not these icons show up in dumpsys
         protected boolean mShouldLog = false;
         private StatusBarIconController mController;
@@ -394,10 +395,10 @@
             mStatusBarPipelineFlags = statusBarPipelineFlags;
             mMobileContextProvider = mobileContextProvider;
             mContext = group.getContext();
+            mIconSize = mContext.getResources().getDimensionPixelSize(
+                    com.android.internal.R.dimen.status_bar_icon_size);
             mLocation = location;
 
-            reloadDimens();
-
             if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
                 // This starts the flow for the new pipeline, and will notify us of changes if
                 // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
@@ -608,9 +609,13 @@
             mGroup.removeAllViews();
         }
 
-        protected void reloadDimens() {
-            mIconSize = mContext.getResources().getDimensionPixelSize(
-                    com.android.internal.R.dimen.status_bar_icon_size);
+        protected void onDensityOrFontScaleChanged() {
+            for (int i = 0; i < mGroup.getChildCount(); i++) {
+                View child = mGroup.getChildAt(i);
+                LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT, mIconSize);
+                child.setLayoutParams(lp);
+            }
         }
 
         private void setHeightAndCenter(ImageView imageView, int height) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 80d5651..3a18423 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -109,7 +109,6 @@
         }
 
         group.setController(this);
-        group.reloadDimens();
         mIconGroups.add(group);
         List<Slot> allSlots = mStatusBarIconList.getSlots();
         for (int i = 0; i < allSlots.size(); i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index ddbfd43..26c1767 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -22,8 +22,6 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -74,16 +72,13 @@
     // Any ignored icon will never be added as a child
     private ArrayList<String> mIgnoredSlots = new ArrayList<>();
 
-    private Configuration mConfiguration;
-
     public StatusIconContainer(Context context) {
         this(context, null);
     }
 
     public StatusIconContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
-        mConfiguration = new Configuration(context.getResources().getConfiguration());
-        reloadDimens();
+        initDimens();
         setWillNotDraw(!DEBUG_OVERFLOW);
     }
 
@@ -100,7 +95,7 @@
         return mShouldRestrictIcons;
     }
 
-    private void reloadDimens() {
+    private void initDimens() {
         // This is the same value that StatusBarIconView uses
         mIconDotFrameWidth = getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.status_bar_icon_size);
@@ -216,16 +211,6 @@
         child.setTag(R.id.status_bar_view_state_tag, null);
     }
 
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        final int configDiff = newConfig.diff(mConfiguration);
-        mConfiguration.setTo(newConfig);
-        if ((configDiff & (ActivityInfo.CONFIG_DENSITY | ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
-            reloadDimens();
-        }
-    }
-
     /**
      * Add a name of an icon slot to be ignored. It will not show up nor be measured
      * @param slotName name of the icon as it exists in
@@ -363,17 +348,13 @@
         int totalVisible = mLayoutStates.size();
         int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
 
-        // Init mUnderflowStart value with the offset to let the dot be placed next to battery icon.
-        // It to prevent if the underflow happens at rightest(totalVisible - 1) child then break the
-        // for loop with mUnderflowStart staying 0(initial value), causing the dot be placed at the
-        // leftest side.
-        mUnderflowStart = (int) Math.max(contentStart, width - getPaddingEnd() - mUnderflowWidth);
+        mUnderflowStart = 0;
         int visible = 0;
         int firstUnderflowIndex = -1;
         for (int i = totalVisible - 1; i >= 0; i--) {
             StatusIconState state = mLayoutStates.get(i);
             // Allow room for underflow if we found we need it in onMeasure
-            if ((mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth)))
+            if (mNeedsUnderflow && (state.getXTranslation() < (contentStart + mUnderflowWidth))
                     || (mShouldRestrictIcons && (visible >= maxVisible))) {
                 firstUnderflowIndex = i;
                 break;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
index c282c1e..b80b825 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt
@@ -21,8 +21,6 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.StatusBarIconView
-import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
-import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertFalse
 import junit.framework.Assert.assertTrue
@@ -51,7 +49,7 @@
     fun calculateWidthFor_oneIcon_widthForOneIcon() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10)
+        iconContainer.setIconSize(10);
 
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 1f),
                 /* actual= */ 30f)
@@ -61,7 +59,7 @@
     fun calculateWidthFor_fourIcons_widthForFourIcons() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10)
+        iconContainer.setIconSize(10);
 
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 4f),
                 /* actual= */ 60f)
@@ -71,7 +69,7 @@
     fun calculateWidthFor_fiveIcons_widthForFourIcons() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10)
+        iconContainer.setIconSize(10);
         assertEquals(/* expected= */ iconContainer.calculateWidthFor(/* numIcons= */ 5f),
                 /* actual= */ 60f)
     }
@@ -80,7 +78,7 @@
     fun calculateIconXTranslations_shortShelfOneIcon_atCorrectXWithoutOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10)
+        iconContainer.setIconSize(10);
 
         val icon = mockStatusBarIcon()
         iconContainer.addView(icon)
@@ -101,7 +99,7 @@
     fun calculateIconXTranslations_shortShelfFourIcons_atCorrectXWithoutOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10)
+        iconContainer.setIconSize(10);
 
         val iconOne = mockStatusBarIcon()
         val iconTwo = mockStatusBarIcon()
@@ -130,7 +128,7 @@
     fun calculateIconXTranslations_shortShelfFiveIcons_atCorrectXWithOverflowDot() {
         iconContainer.setActualPaddingStart(10f)
         iconContainer.setActualPaddingEnd(10f)
-        iconContainer.setIconSize(10)
+        iconContainer.setIconSize(10);
 
         val iconOne = mockStatusBarIcon()
         val iconTwo = mockStatusBarIcon()
@@ -156,55 +154,6 @@
     }
 
     @Test
-    fun calculateIconXTranslations_givenWidthEnoughForThreeIcons_atCorrectXWithoutOverflowDot() {
-        iconContainer.setActualPaddingStart(0f)
-        iconContainer.setActualPaddingEnd(0f)
-        iconContainer.setActualLayoutWidth(30)
-        iconContainer.setIconSize(10)
-
-        val iconOne = mockStatusBarIcon()
-        val iconTwo = mockStatusBarIcon()
-        val iconThree = mockStatusBarIcon()
-
-        iconContainer.addView(iconOne)
-        iconContainer.addView(iconTwo)
-        iconContainer.addView(iconThree)
-        assertEquals(3, iconContainer.childCount)
-
-        iconContainer.calculateIconXTranslations()
-        assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation)
-        assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation)
-        assertEquals(20f, iconContainer.getIconState(iconThree).xTranslation)
-        assertFalse(iconContainer.areIconsOverflowing())
-    }
-
-    @Test
-    fun calculateIconXTranslations_givenWidthNotEnoughForFourIcons_atCorrectXWithOverflowDot() {
-        iconContainer.setActualPaddingStart(0f)
-        iconContainer.setActualPaddingEnd(0f)
-        iconContainer.setActualLayoutWidth(35)
-        iconContainer.setIconSize(10)
-
-        val iconOne = mockStatusBarIcon()
-        val iconTwo = mockStatusBarIcon()
-        val iconThree = mockStatusBarIcon()
-        val iconFour = mockStatusBarIcon()
-
-        iconContainer.addView(iconOne)
-        iconContainer.addView(iconTwo)
-        iconContainer.addView(iconThree)
-        iconContainer.addView(iconFour)
-        assertEquals(4, iconContainer.childCount)
-
-        iconContainer.calculateIconXTranslations()
-        assertEquals(0f, iconContainer.getIconState(iconOne).xTranslation)
-        assertEquals(10f, iconContainer.getIconState(iconTwo).xTranslation)
-        assertEquals(STATE_DOT, iconContainer.getIconState(iconThree).visibleState)
-        assertEquals(STATE_HIDDEN, iconContainer.getIconState(iconFour).visibleState)
-        assertTrue(iconContainer.areIconsOverflowing())
-    }
-
-    @Test
     fun shouldForceOverflow_appearingAboveSpeedBump_true() {
         val forceOverflow = iconContainer.shouldForceOverflow(
                 /* i= */ 1,
@@ -212,7 +161,7 @@
                 /* iconAppearAmount= */ 1f,
                 /* maxVisibleIcons= */ 5
         )
-        assertTrue(forceOverflow)
+        assertTrue(forceOverflow);
     }
 
     @Test
@@ -223,7 +172,7 @@
                 /* iconAppearAmount= */ 0f,
                 /* maxVisibleIcons= */ 5
         )
-        assertTrue(forceOverflow)
+        assertTrue(forceOverflow);
     }
 
     @Test
@@ -234,7 +183,7 @@
                 /* iconAppearAmount= */ 0f,
                 /* maxVisibleIcons= */ 5
         )
-        assertFalse(forceOverflow)
+        assertFalse(forceOverflow);
     }
 
     @Test
@@ -261,17 +210,6 @@
     }
 
     @Test
-    fun isOverflowing_lastChildXGreaterThanDotX_true() {
-        val isOverflowing = iconContainer.isOverflowing(
-                /* isLastChild= */ true,
-                /* translationX= */ 9f,
-                /* layoutEnd= */ 10f,
-                /* iconSize= */ 2f,
-        )
-        assertTrue(isOverflowing)
-    }
-
-    @Test
     fun isOverflowing_lastChildXGreaterThanLayoutEnd_true() {
         val isOverflowing = iconContainer.isOverflowing(
                 /* isLastChild= */ true,
@@ -315,7 +253,7 @@
         assertTrue(isOverflowing)
     }
 
-    private fun mockStatusBarIcon(): StatusBarIconView {
+    private fun mockStatusBarIcon() : StatusBarIconView {
         val iconView = mock(StatusBarIconView::class.java)
         whenever(iconView.width).thenReturn(10)
 
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index c879fa6..bc4e8df 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -88,14 +88,14 @@
     private final @NonNull AudioSystemAdapter mAudioSystem;
 
     /** ID for Communication strategy retrieved form audio policy manager */
-    private int mCommunicationStrategyId = -1;
+    /*package*/  int mCommunicationStrategyId = -1;
 
     /** ID for Accessibility strategy retrieved form audio policy manager */
     private int mAccessibilityStrategyId = -1;
 
 
     /** Active communication device reported by audio policy manager */
-    private AudioDeviceInfo mActiveCommunicationDevice;
+    /*package*/ AudioDeviceInfo mActiveCommunicationDevice;
     /** Last preferred device set for communication strategy */
     private AudioDeviceAttributes mPreferredCommunicationDevice;
 
@@ -755,6 +755,19 @@
             mIsLeOutput = false;
         }
 
+        BtDeviceInfo(@NonNull BtDeviceInfo src, int state) {
+            mDevice = src.mDevice;
+            mState = state;
+            mProfile = src.mProfile;
+            mSupprNoisy = src.mSupprNoisy;
+            mVolume = src.mVolume;
+            mIsLeOutput = src.mIsLeOutput;
+            mEventSource = src.mEventSource;
+            mAudioSystemDevice = src.mAudioSystemDevice;
+            mMusicDevice = src.mMusicDevice;
+            mCodec = src.mCodec;
+        }
+
         // redefine equality op so we can match messages intended for this device
         @Override
         public boolean equals(Object o) {
@@ -821,7 +834,7 @@
      * @param info struct with the (dis)connection information
      */
     /*package*/ void queueOnBluetoothActiveDeviceChanged(@NonNull BtDeviceChangedData data) {
-        if (data.mInfo.getProfile() == BluetoothProfile.A2DP && data.mPreviousDevice != null
+        if (data.mPreviousDevice != null
                 && data.mPreviousDevice.equals(data.mNewDevice)) {
             final String name = TextUtils.emptyIfNull(data.mNewDevice.getName());
             new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR
@@ -830,7 +843,8 @@
                     .set(MediaMetrics.Property.STATUS, data.mInfo.getProfile())
                     .record();
             synchronized (mDeviceStateLock) {
-                postBluetoothA2dpDeviceConfigChange(data.mNewDevice);
+                postBluetoothDeviceConfigChange(createBtDeviceInfo(data, data.mNewDevice,
+                        BluetoothProfile.STATE_CONNECTED));
             }
         } else {
             synchronized (mDeviceStateLock) {
@@ -1064,8 +1078,8 @@
                 new AudioModeInfo(mode, pid, uid));
     }
 
-    /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) {
-        sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device);
+    /*package*/ void postBluetoothDeviceConfigChange(@NonNull BtDeviceInfo info) {
+        sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info);
     }
 
     /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode,
@@ -1322,6 +1336,10 @@
         sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
     }
 
+    /*package*/ void postNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
+        sendLMsgNoDelay(MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED, SENDMSG_QUEUE, btDevice);
+    }
+
     /*package*/ static final class CommunicationDeviceInfo {
         final @NonNull IBinder mCb; // Identifies the requesting client for death handler
         final int mPid; // Requester process ID
@@ -1397,9 +1415,11 @@
         }
     }
 
-    /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect) {
+    /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes,
+                                boolean connect, @Nullable BluetoothDevice btDevice) {
         synchronized (mDeviceStateLock) {
-            return mDeviceInventory.handleDeviceConnection(attributes, connect, false /*for test*/);
+            return mDeviceInventory.handleDeviceConnection(
+                    attributes, connect, false /*for test*/, btDevice);
         }
     }
 
@@ -1640,13 +1660,10 @@
                                 (String) msg.obj, msg.arg1);
                     }
                     break;
-                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
-                    final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
+                case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
                     synchronized (mDeviceStateLock) {
-                        final int a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
-                        mDeviceInventory.onBluetoothA2dpDeviceConfigChange(
-                                new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec),
-                                        BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
+                        mDeviceInventory.onBluetoothDeviceConfigChange(
+                                (BtDeviceInfo) msg.obj, BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
                     }
                     break;
                 case MSG_BROADCAST_AUDIO_BECOMING_NOISY:
@@ -1810,6 +1827,10 @@
                 case MSG_IL_SET_LEAUDIO_SUSPENDED: {
                     setLeAudioSuspended((msg.arg1 == 1), false /*internal*/, (String) msg.obj);
                 } break;
+                case MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED: {
+                    final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
+                    BtHelper.onNotifyPreferredAudioProfileApplied(btDevice);
+                } break;
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
             }
@@ -1845,7 +1866,7 @@
     private static final int MSG_IL_BTA2DP_TIMEOUT = 10;
 
     // process change of A2DP device configuration, obj is BluetoothDevice
-    private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11;
+    private static final int MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE = 11;
 
     private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12;
     private static final int MSG_REPORT_NEW_ROUTES = 13;
@@ -1887,13 +1908,15 @@
     private static final int MSG_IL_SET_A2DP_SUSPENDED = 50;
     private static final int MSG_IL_SET_LEAUDIO_SUSPENDED = 51;
 
+    private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 52;
+
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
             case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
             case MSG_L_SET_BT_ACTIVE_DEVICE:
             case MSG_IL_BTA2DP_TIMEOUT:
             case MSG_IL_BTLEAUDIO_TIMEOUT:
-            case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+            case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
             case MSG_TOGGLE_HDMI:
             case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
             case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT:
@@ -1985,7 +2008,7 @@
                 case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
                 case MSG_IL_BTA2DP_TIMEOUT:
                 case MSG_IL_BTLEAUDIO_TIMEOUT:
-                case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
+                case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE:
                     if (sLastDeviceConnectMsgTime >= time) {
                         // add a little delay to make sure messages are ordered as expected
                         time = sLastDeviceConnectMsgTime + 30;
@@ -2005,7 +2028,7 @@
     static {
         MESSAGES_MUTE_MUSIC = new HashSet<>();
         MESSAGES_MUTE_MUSIC.add(MSG_L_SET_BT_ACTIVE_DEVICE);
-        MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONFIG_CHANGE);
+        MESSAGES_MUTE_MUSIC.add(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE);
         MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT);
         MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE);
     }
@@ -2026,7 +2049,7 @@
         // Do not mute on bluetooth event if music is playing on a wired headset.
         if ((message == MSG_L_SET_BT_ACTIVE_DEVICE
                 || message == MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT
-                || message == MSG_L_A2DP_DEVICE_CONFIG_CHANGE)
+                || message == MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE)
                 && AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0)
                 && hasIntersection(mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET,
                         mAudioService.getDeviceSetForStream(AudioSystem.STREAM_MUSIC))) {
@@ -2173,6 +2196,7 @@
                 mDeviceInventory.removePreferredDevicesForStrategy(mCommunicationStrategyId);
                 mDeviceInventory.removePreferredDevicesForStrategy(mAccessibilityStrategyId);
             }
+            mDeviceInventory.applyConnectedDevicesRoles();
         } else {
             mDeviceInventory.setPreferredDevicesForStrategy(
                     mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice));
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 94d796b..773df37 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -34,11 +34,15 @@
 import android.media.IStrategyNonDefaultDevicesDispatcher;
 import android.media.IStrategyPreferredDevicesDispatcher;
 import android.media.MediaMetrics;
+import android.media.MediaRecorder.AudioSource;
+import android.media.audiopolicy.AudioProductStrategy;
 import android.media.permission.ClearCallingIdentityContext;
 import android.media.permission.SafeCloseable;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -50,6 +54,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.utils.EventLogger;
 
+import com.google.android.collect.Sets;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -179,6 +185,8 @@
     final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers =
             new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>();
 
+    final List<AudioProductStrategy> mStrategies;
+
     /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) {
         this(broker, AudioSystemAdapter.getDefaultAdapter());
     }
@@ -193,8 +201,10 @@
                        @Nullable AudioSystemAdapter audioSystem) {
         mDeviceBroker = broker;
         mAudioSystem = audioSystem;
+        mStrategies = AudioProductStrategy.getAudioProductStrategies();
+        mBluetoothDualModeEnabled = SystemProperties.getBoolean(
+                "persist.bluetooth.enable_dual_mode_audio", false);
     }
-
     /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) {
         mDeviceBroker = broker;
     }
@@ -211,8 +221,13 @@
         int mDeviceCodecFormat;
         final UUID mSensorUuid;
 
+        /** Disabled operating modes for this device. Use a negative logic so that by default
+         * an empty list means all modes are allowed.
+         * See BluetoothAdapter.AUDIO_MODE_DUPLEX and BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY */
+        @NonNull ArraySet<String> mDisabledModes = new ArraySet(0);
+
         DeviceInfo(int deviceType, String deviceName, String deviceAddress,
-                   int deviceCodecFormat, UUID sensorUuid) {
+                   int deviceCodecFormat, @Nullable UUID sensorUuid) {
             mDeviceType = deviceType;
             mDeviceName = deviceName == null ? "" : deviceName;
             mDeviceAddress = deviceAddress == null ? "" : deviceAddress;
@@ -220,11 +235,31 @@
             mSensorUuid = sensorUuid;
         }
 
+        void setModeDisabled(String mode) {
+            mDisabledModes.add(mode);
+        }
+        void setModeEnabled(String mode) {
+            mDisabledModes.remove(mode);
+        }
+        boolean isModeEnabled(String mode) {
+            return !mDisabledModes.contains(mode);
+        }
+        boolean isOutputOnlyModeEnabled() {
+            return isModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
+        }
+        boolean isDuplexModeEnabled() {
+            return isModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+        }
+
         DeviceInfo(int deviceType, String deviceName, String deviceAddress,
                    int deviceCodecFormat) {
             this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null);
         }
 
+        DeviceInfo(int deviceType, String deviceName, String deviceAddress) {
+            this(deviceType, deviceName, deviceAddress, AudioSystem.AUDIO_FORMAT_DEFAULT);
+        }
+
         @Override
         public String toString() {
             return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType)
@@ -232,7 +267,8 @@
                     + ") name:" + mDeviceName
                     + " addr:" + mDeviceAddress
                     + " codec: " + Integer.toHexString(mDeviceCodecFormat)
-                    + " sensorUuid: " + Objects.toString(mSensorUuid) + "]";
+                    + " sensorUuid: " + Objects.toString(mSensorUuid)
+                    + " disabled modes: " + mDisabledModes + "]";
         }
 
         @NonNull String getKey() {
@@ -317,6 +353,7 @@
                         di.mDeviceCodecFormat);
             }
             mAppliedStrategyRoles.clear();
+            applyConnectedDevicesRoles_l();
         }
         synchronized (mPreferredDevices) {
             mPreferredDevices.forEach((strategy, devices) -> {
@@ -397,8 +434,7 @@
                                     btInfo.mVolume * 10, btInfo.mAudioSystemDevice,
                                     "onSetBtActiveDevice");
                         }
-                        makeA2dpDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
-                                "onSetBtActiveDevice", btInfo.mCodec);
+                        makeA2dpDeviceAvailable(btInfo, "onSetBtActiveDevice");
                     }
                     break;
                 case BluetoothProfile.HEARING_AID:
@@ -414,10 +450,7 @@
                     if (switchToUnavailable) {
                         makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice);
                     } else if (switchToAvailable) {
-                        makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
-                                streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10,
-                                btInfo.mAudioSystemDevice,
-                                "onSetBtActiveDevice");
+                        makeLeAudioDeviceAvailable(btInfo, streamType, "onSetBtActiveDevice");
                     }
                     break;
                 default: throw new IllegalArgumentException("Invalid profile "
@@ -428,30 +461,30 @@
 
 
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-        /*package*/ void onBluetoothA2dpDeviceConfigChange(
-            @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) {
+    /*package*/ void onBluetoothDeviceConfigChange(
+            @NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int event) {
         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
-                + "onBluetoothA2dpDeviceConfigChange")
-                .set(MediaMetrics.Property.EVENT, BtHelper.a2dpDeviceEventToString(event));
+                + "onBluetoothDeviceConfigChange")
+                .set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event));
 
-        final BluetoothDevice btDevice = btInfo.getBtDevice();
+        final BluetoothDevice btDevice = btInfo.mDevice;
         if (btDevice == null) {
             mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
             return;
         }
         if (AudioService.DEBUG_DEVICES) {
-            Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
+            Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice);
         }
-        int a2dpVolume = btInfo.getVolume();
-        @AudioSystem.AudioFormatNativeEnumForBtCodec final int a2dpCodec = btInfo.getCodec();
+        int volume = btInfo.mVolume;
+        @AudioSystem.AudioFormatNativeEnumForBtCodec final int audioCodec = btInfo.mCodec;
 
         String address = btDevice.getAddress();
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
         }
         AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                "onBluetoothA2dpDeviceConfigChange addr=" + address
-                    + " event=" + BtHelper.a2dpDeviceEventToString(event)));
+                "onBluetoothDeviceConfigChange addr=" + address
+                    + " event=" + BtHelper.deviceEventToString(event)));
 
         synchronized (mDevicesLock) {
             if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
@@ -466,53 +499,53 @@
                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
             final DeviceInfo di = mConnectedDevices.get(key);
             if (di == null) {
-                Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange");
+                Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange");
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
                 return;
             }
 
             mmi.set(MediaMetrics.Property.ADDRESS, address)
                     .set(MediaMetrics.Property.ENCODING,
-                            AudioSystem.audioFormatToString(a2dpCodec))
-                    .set(MediaMetrics.Property.INDEX, a2dpVolume)
+                            AudioSystem.audioFormatToString(audioCodec))
+                    .set(MediaMetrics.Property.INDEX, volume)
                     .set(MediaMetrics.Property.NAME, di.mDeviceName);
 
-            if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) {
-                // Device is connected
-                if (a2dpVolume != -1) {
-                    mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
-                            // convert index to internal representation in VolumeStreamState
-                            a2dpVolume * 10,
-                            AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                            "onBluetoothA2dpDeviceConfigChange");
-                }
-            } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
-                if (di.mDeviceCodecFormat != a2dpCodec) {
-                    di.mDeviceCodecFormat = a2dpCodec;
-                    mConnectedDevices.replace(key, di);
-                }
-            }
-            final int res = mAudioSystem.handleDeviceConfigChange(
-                    AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address,
-                    BtHelper.getName(btDevice), a2dpCodec);
 
-            if (res != AudioSystem.AUDIO_STATUS_OK) {
-                AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                        "APM handleDeviceConfigChange failed for A2DP device addr=" + address
-                                + " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
-                        .printLog(TAG));
+            if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
+                boolean a2dpCodecChange = false;
+                if (btInfo.mProfile == BluetoothProfile.A2DP) {
+                    if (di.mDeviceCodecFormat != audioCodec) {
+                        di.mDeviceCodecFormat = audioCodec;
+                        mConnectedDevices.replace(key, di);
+                        a2dpCodecChange = true;
+                    }
+                    final int res = mAudioSystem.handleDeviceConfigChange(
+                            btInfo.mAudioSystemDevice, address,
+                            BtHelper.getName(btDevice), audioCodec);
 
-                int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC);
-                // force A2DP device disconnection in case of error so that AudioService state is
-                // consistent with audio policy manager state
-                setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btDevice,
-                                BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED,
-                                musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
-            } else {
-                AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
-                        "APM handleDeviceConfigChange success for A2DP device addr=" + address
-                                + " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
-                        .printLog(TAG));
+                    if (res != AudioSystem.AUDIO_STATUS_OK) {
+                        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                                "APM handleDeviceConfigChange failed for A2DP device addr="
+                                        + address + " codec="
+                                        + AudioSystem.audioFormatToString(audioCodec))
+                                .printLog(TAG));
+
+                        // force A2DP device disconnection in case of error so that AudioService
+                        // state is consistent with audio policy manager state
+                        setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo,
+                                BluetoothProfile.STATE_DISCONNECTED));
+                    } else {
+                        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
+                                "APM handleDeviceConfigChange success for A2DP device addr="
+                                        + address
+                                        + " codec=" + AudioSystem.audioFormatToString(audioCodec))
+                                .printLog(TAG));
+
+                    }
+                }
+                if (!a2dpCodecChange) {
+                    updateBluetoothPreferredModes_l(btDevice /*connectedDevice*/);
+                }
             }
         }
         mmi.record();
@@ -595,7 +628,7 @@
             }
 
             if (!handleDeviceConnection(wdcs.mAttributes,
-                    wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest)) {
+                    wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, null)) {
                 // change of connection state failed, bailout
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
                         .record();
@@ -1134,10 +1167,11 @@
      * @param connect true if connection
      * @param isForTesting if true, not calling AudioSystem for the connection as this is
      *                    just for testing
+     * @param btDevice the corresponding Bluetooth device when relevant.
      * @return false if an error was reported by AudioSystem
      */
     /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect,
-            boolean isForTesting) {
+            boolean isForTesting, @Nullable BluetoothDevice btDevice) {
         int device = attributes.getInternalType();
         String address = attributes.getAddress();
         String deviceName = attributes.getName();
@@ -1152,6 +1186,7 @@
                 .set(MediaMetrics.Property.MODE, connect
                         ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT)
                 .set(MediaMetrics.Property.NAME, deviceName);
+        boolean status = false;
         synchronized (mDevicesLock) {
             final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
             if (AudioService.DEBUG_DEVICES) {
@@ -1179,25 +1214,31 @@
                             .record();
                     return false;
                 }
-                mConnectedDevices.put(deviceKey, new DeviceInfo(
-                        device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address));
                 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
-                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
-                return true;
+                status = true;
             } else if (!connect && isConnected) {
                 mAudioSystem.setDeviceConnectionState(attributes,
                         AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT);
                 // always remove even if disconnection failed
                 mConnectedDevices.remove(deviceKey);
-                purgeDevicesRoles_l();
-                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
-                return true;
+                status = true;
             }
-            Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
-                    + ", deviceSpec=" + di + ", connect=" + connect);
+            if (status) {
+                if (AudioSystem.isBluetoothScoDevice(device)) {
+                    updateBluetoothPreferredModes_l(connect ? btDevice : null /*connectedDevice*/);
+                    if (!connect) {
+                        purgeDevicesRoles_l();
+                    }
+                }
+                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
+            } else {
+                Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
+                        + ", deviceSpec=" + di + ", connect=" + connect);
+                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record();
+            }
         }
-        mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record();
-        return false;
+        return status;
     }
 
 
@@ -1406,15 +1447,20 @@
     // Internal utilities
 
     @GuardedBy("mDevicesLock")
-    private void makeA2dpDeviceAvailable(String address, String name, String eventSource,
-            int a2dpCodec) {
+    private void makeA2dpDeviceAvailable(AudioDeviceBroker.BtDeviceInfo btInfo,
+                                         String eventSource) {
+        final String address = btInfo.mDevice.getAddress();
+        final String name = BtHelper.getName(btInfo.mDevice);
+        final int a2dpCodec = btInfo.mCodec;
+
         // enable A2DP before notifying A2DP connection to avoid unnecessary processing in
         // audio policy manager
         mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource);
         // at this point there could be another A2DP device already connected in APM, but it
         // doesn't matter as this new one will overwrite the previous one
-        final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name),
+        AudioDeviceAttributes ada = new AudioDeviceAttributes(
+                AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name);
+        final int res = mAudioSystem.setDeviceConnectionState(ada,
                 AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec);
 
         // TODO: log in MediaMetrics once distinction between connection failure and
@@ -1436,8 +1482,7 @@
         // The convention for head tracking sensors associated with A2DP devices is to
         // use a UUID derived from the MAC address as follows:
         //   time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address
-        UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(
-                new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address));
+        UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
         final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name,
                 address, a2dpCodec, sensorUuid);
         final String diKey = di.getKey();
@@ -1448,6 +1493,208 @@
 
         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
         setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/);
+
+        updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
+    }
+
+    static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER,
+            AudioSource.VOICE_RECOGNITION, AudioSource.VOICE_COMMUNICATION,
+            AudioSource.UNPROCESSED, AudioSource.VOICE_PERFORMANCE, AudioSource.HOTWORD};
+
+    // reflects system property persist.bluetooth.enable_dual_mode_audio
+    final boolean mBluetoothDualModeEnabled;
+    /**
+     * Goes over all connected Bluetooth devices and set the audio policy device role to DISABLED
+     * or not according to their own and other devices modes.
+     * The top priority is given to LE devices, then SCO ,then A2DP.
+     */
+    @GuardedBy("mDevicesLock")
+    private void applyConnectedDevicesRoles_l() {
+        if (!mBluetoothDualModeEnabled) {
+            return;
+        }
+        DeviceInfo leOutDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_BLE_SET);
+        DeviceInfo leInDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_BLE_SET);
+        DeviceInfo a2dpDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_A2DP_SET);
+        DeviceInfo scoOutDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_SCO_SET);
+        DeviceInfo scoInDevice =
+                getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_SCO_SET);
+        boolean disableA2dp = (leOutDevice != null && leOutDevice.isOutputOnlyModeEnabled());
+        boolean disableSco = (leOutDevice != null && leOutDevice.isDuplexModeEnabled())
+                || (leInDevice != null && leInDevice.isDuplexModeEnabled());
+        AudioDeviceAttributes communicationDevice =
+                mDeviceBroker.mActiveCommunicationDevice == null
+                        ? null : ((mDeviceBroker.isInCommunication()
+                                    && mDeviceBroker.mActiveCommunicationDevice != null)
+                            ? new AudioDeviceAttributes(mDeviceBroker.mActiveCommunicationDevice)
+                            : null);
+
+        if (AudioService.DEBUG_DEVICES) {
+            Log.i(TAG, "applyConnectedDevicesRoles_l\n - leOutDevice: " + leOutDevice
+                    + "\n - leInDevice: " + leInDevice
+                    + "\n - a2dpDevice: " + a2dpDevice
+                    + "\n - scoOutDevice: " + scoOutDevice
+                    + "\n - scoInDevice: " + scoInDevice
+                    + "\n - disableA2dp: " + disableA2dp
+                    + ", disableSco: " + disableSco);
+        }
+
+        for (DeviceInfo di : mConnectedDevices.values()) {
+            if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) {
+                continue;
+            }
+            AudioDeviceAttributes ada =
+                    new AudioDeviceAttributes(di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
+            if (AudioService.DEBUG_DEVICES) {
+                Log.i(TAG, "  + checking Device: " + ada);
+            }
+            if (ada.equalTypeAddress(communicationDevice)) {
+                continue;
+            }
+
+            if (AudioSystem.isBluetoothOutDevice(di.mDeviceType)) {
+                for (AudioProductStrategy strategy : mStrategies) {
+                    boolean disable = false;
+                    if (strategy.getId() == mDeviceBroker.mCommunicationStrategyId) {
+                        if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+                            disable = disableSco || !di.isDuplexModeEnabled();
+                        } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
+                            disable = !di.isDuplexModeEnabled();
+                        }
+                    } else {
+                        if (AudioSystem.isBluetoothA2dpOutDevice(di.mDeviceType)) {
+                            disable = disableA2dp || !di.isOutputOnlyModeEnabled();
+                        } else if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+                            disable = disableSco || !di.isOutputOnlyModeEnabled();
+                        } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
+                            disable = !di.isOutputOnlyModeEnabled();
+                        }
+                    }
+                    if (AudioService.DEBUG_DEVICES) {
+                        Log.i(TAG, "     - strategy: " + strategy.getId()
+                                + ", disable: " + disable);
+                    }
+                    if (disable) {
+                        addDevicesRoleForStrategy(strategy.getId(),
+                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                    } else {
+                        removeDevicesRoleForStrategy(strategy.getId(),
+                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                    }
+                }
+            }
+            if (AudioSystem.isBluetoothInDevice(di.mDeviceType)) {
+                for (int capturePreset : CAPTURE_PRESETS) {
+                    boolean disable = false;
+                    if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) {
+                        disable = disableSco || !di.isDuplexModeEnabled();
+                    } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) {
+                        disable = !di.isDuplexModeEnabled();
+                    }
+                    if (AudioService.DEBUG_DEVICES) {
+                        Log.i(TAG, "      - capturePreset: " + capturePreset
+                                + ", disable: " + disable);
+                    }
+                    if (disable) {
+                        addDevicesRoleForCapturePreset(capturePreset,
+                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                    } else {
+                        removeDevicesRoleForCapturePreset(capturePreset,
+                                AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada));
+                    }
+                }
+            }
+        }
+    }
+
+    /* package */ void applyConnectedDevicesRoles() {
+        synchronized (mDevicesLock) {
+            applyConnectedDevicesRoles_l();
+        }
+    }
+
+    @GuardedBy("mDevicesLock")
+    int checkProfileIsConnected(int profile) {
+        switch (profile) {
+            case BluetoothProfile.HEADSET:
+                if (getFirstConnectedDeviceOfTypes(
+                        AudioSystem.DEVICE_OUT_ALL_SCO_SET) != null
+                        || getFirstConnectedDeviceOfTypes(
+                                AudioSystem.DEVICE_IN_ALL_SCO_SET) != null) {
+                    return profile;
+                }
+                break;
+            case BluetoothProfile.A2DP:
+                if (getFirstConnectedDeviceOfTypes(
+                        AudioSystem.DEVICE_OUT_ALL_A2DP_SET) != null) {
+                    return profile;
+                }
+                break;
+            case BluetoothProfile.LE_AUDIO:
+            case BluetoothProfile.LE_AUDIO_BROADCAST:
+                if (getFirstConnectedDeviceOfTypes(
+                        AudioSystem.DEVICE_OUT_ALL_BLE_SET) != null
+                        || getFirstConnectedDeviceOfTypes(
+                                AudioSystem.DEVICE_IN_ALL_BLE_SET) != null) {
+                    return profile;
+                }
+                break;
+            default:
+                break;
+        }
+        return 0;
+    }
+
+    @GuardedBy("mDevicesLock")
+    private void updateBluetoothPreferredModes_l(BluetoothDevice connectedDevice) {
+        if (!mBluetoothDualModeEnabled) {
+            return;
+        }
+        HashSet<String> processedAddresses = new HashSet<>(0);
+        for (DeviceInfo di : mConnectedDevices.values()) {
+            if (!AudioSystem.isBluetoothDevice(di.mDeviceType)
+                    || processedAddresses.contains(di.mDeviceAddress)) {
+                continue;
+            }
+            Bundle preferredProfiles = BtHelper.getPreferredAudioProfiles(di.mDeviceAddress);
+            if (AudioService.DEBUG_DEVICES) {
+                Log.i(TAG, "updateBluetoothPreferredModes_l processing device address: "
+                        + di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles);
+            }
+            for (DeviceInfo di2 : mConnectedDevices.values()) {
+                if (!AudioSystem.isBluetoothDevice(di2.mDeviceType)
+                        || !di.mDeviceAddress.equals(di2.mDeviceAddress)) {
+                    continue;
+                }
+                int profile = BtHelper.getProfileFromType(di2.mDeviceType);
+                if (profile == 0) {
+                    continue;
+                }
+                int preferredProfile = checkProfileIsConnected(
+                        preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX));
+                if (preferredProfile == profile || preferredProfile == 0) {
+                    di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+                } else {
+                    di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_DUPLEX);
+                }
+                preferredProfile = checkProfileIsConnected(
+                        preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY));
+                if (preferredProfile == profile || preferredProfile == 0) {
+                    di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
+                } else {
+                    di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY);
+                }
+            }
+            processedAddresses.add(di.mDeviceAddress);
+        }
+        applyConnectedDevicesRoles_l();
+        if (connectedDevice != null) {
+            mDeviceBroker.postNotifyPreferredAudioProfileApplied(connectedDevice);
+        }
     }
 
     @GuardedBy("mDevicesLock")
@@ -1495,6 +1742,7 @@
         // Remove A2DP routes as well
         setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/);
         mmi.record();
+        updateBluetoothPreferredModes_l(null /*connectedDevice*/);
         purgeDevicesRoles_l();
     }
 
@@ -1525,8 +1773,7 @@
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.put(
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address),
-                new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "",
-                        address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", address));
     }
 
     @GuardedBy("mDevicesLock")
@@ -1552,8 +1799,7 @@
                 AudioSystem.AUDIO_FORMAT_DEFAULT);
         mConnectedDevices.put(
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address),
-                new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name,
-                        address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, address));
         mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID);
         mDeviceBroker.postApplyVolumeOnDevice(streamType,
                 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
@@ -1591,29 +1837,56 @@
      * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise.
      */
     boolean isHearingAidConnected() {
+        return getFirstConnectedDeviceOfTypes(
+                Sets.newHashSet(AudioSystem.DEVICE_OUT_HEARING_AID)) != null;
+    }
+
+    /**
+     * Returns a DeviceInfo for the first connected device matching one of the supplied types
+     */
+    private DeviceInfo getFirstConnectedDeviceOfTypes(Set<Integer> internalTypes) {
+        List<DeviceInfo> devices = getConnectedDevicesOfTypes(internalTypes);
+        return devices.isEmpty() ? null : devices.get(0);
+    }
+
+    /**
+     * Returns a list of connected devices matching one of the supplied types
+     */
+    private List<DeviceInfo> getConnectedDevicesOfTypes(Set<Integer> internalTypes) {
+        ArrayList<DeviceInfo> devices = new ArrayList<>();
         synchronized (mDevicesLock) {
             for (DeviceInfo di : mConnectedDevices.values()) {
-                if (di.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) {
-                    return true;
+                if (internalTypes.contains(di.mDeviceType)) {
+                    devices.add(di);
                 }
             }
-            return false;
         }
+        return devices;
+    }
+
+    /* package */ AudioDeviceAttributes getDeviceOfType(int type) {
+        DeviceInfo di = getFirstConnectedDeviceOfTypes(Sets.newHashSet(type));
+        return di == null ? null : new AudioDeviceAttributes(
+                    di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
     }
 
     @GuardedBy("mDevicesLock")
-    private void makeLeAudioDeviceAvailable(String address, String name, int streamType,
-            int volumeIndex, int device, String eventSource) {
+    private void makeLeAudioDeviceAvailable(
+            AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) {
+        final String address = btInfo.mDevice.getAddress();
+        final String name = BtHelper.getName(btInfo.mDevice);
+        final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10;
+        final int device = btInfo.mAudioSystemDevice;
+
         if (device != AudioSystem.DEVICE_NONE) {
             /* Audio Policy sees Le Audio similar to A2DP. Let's make sure
              * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set
              */
             mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource);
 
-            final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes(
-                    device, address, name),
-                    AudioSystem.DEVICE_STATE_AVAILABLE,
-                    AudioSystem.AUDIO_FORMAT_DEFAULT);
+            AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name);
+            final int res = AudioSystem.setDeviceConnectionState(ada,
+                    AudioSystem.DEVICE_STATE_AVAILABLE,  AudioSystem.AUDIO_FORMAT_DEFAULT);
             if (res != AudioSystem.AUDIO_STATUS_OK) {
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "APM failed to make available LE Audio device addr=" + address
@@ -1624,12 +1897,13 @@
                 AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "LE Audio device addr=" + address + " now available").printLog(TAG));
             }
-
             // Reset LEA suspend state each time a new sink is connected
             mDeviceBroker.clearLeAudioSuspended();
 
+            UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada);
             mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address),
-                    new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
+                    new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT,
+                            sensorUuid));
             mDeviceBroker.postAccessoryPlugMediaUnmute(device);
             setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false);
         }
@@ -1645,6 +1919,8 @@
         final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType);
         mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType);
         mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable");
+
+        updateBluetoothPreferredModes_l(btInfo.mDevice /*connectedDevice*/);
     }
 
     @GuardedBy("mDevicesLock")
@@ -1669,6 +1945,7 @@
         }
 
         setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/);
+        updateBluetoothPreferredModes_l(null /*connectedDevice*/);
         purgeDevicesRoles_l();
     }
 
@@ -2005,18 +2282,6 @@
         }
     }
 
-    /* package */ AudioDeviceAttributes getDeviceOfType(int type) {
-        synchronized (mDevicesLock) {
-            for (DeviceInfo di : mConnectedDevices.values()) {
-                if (di.mDeviceType == type) {
-                    return new AudioDeviceAttributes(
-                            di.mDeviceType, di.mDeviceAddress, di.mDeviceName);
-                }
-            }
-        }
-        return null;
-    }
-
     //----------------------------------------------------------
     // For tests only
 
@@ -2027,10 +2292,12 @@
      */
     @VisibleForTesting
     public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) {
-        final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                device.getAddress());
-        synchronized (mDevicesLock) {
-            return (mConnectedDevices.get(key) != null);
+        for (DeviceInfo di : getConnectedDevicesOfTypes(
+                Sets.newHashSet(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
+            if (di.mDeviceAddress.equals(device.getAddress())) {
+                return true;
+            }
         }
+        return false;
     }
 }
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 8c27c3e..e46c3cc 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -33,6 +33,7 @@
 import android.media.AudioSystem;
 import android.media.BluetoothProfileConnectionInfo;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.TextUtils;
@@ -150,60 +151,12 @@
         }
     }
 
-    //----------------------------------------------------------------------
-    /*package*/ static class BluetoothA2dpDeviceInfo {
-        private final @NonNull BluetoothDevice mBtDevice;
-        private final int mVolume;
-        private final @AudioSystem.AudioFormatNativeEnumForBtCodec int mCodec;
-
-        BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) {
-            this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT);
-        }
-
-        BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) {
-            mBtDevice = btDevice;
-            mVolume = volume;
-            mCodec = codec;
-        }
-
-        public @NonNull BluetoothDevice getBtDevice() {
-            return mBtDevice;
-        }
-
-        public int getVolume() {
-            return mVolume;
-        }
-
-        public @AudioSystem.AudioFormatNativeEnumForBtCodec int getCodec() {
-            return mCodec;
-        }
-
-        // redefine equality op so we can match messages intended for this device
-        @Override
-        public boolean equals(Object o) {
-            if (o == null) {
-                return false;
-            }
-            if (this == o) {
-                return true;
-            }
-            if (o instanceof BluetoothA2dpDeviceInfo) {
-                return mBtDevice.equals(((BluetoothA2dpDeviceInfo) o).getBtDevice());
-            }
-            return false;
-        }
-
-
-    }
-
     // A2DP device events
     /*package*/ static final int EVENT_DEVICE_CONFIG_CHANGE = 0;
-    /*package*/ static final int EVENT_ACTIVE_DEVICE_CHANGE = 1;
 
-    /*package*/ static String a2dpDeviceEventToString(int event) {
+    /*package*/ static String deviceEventToString(int event) {
         switch (event) {
             case EVENT_DEVICE_CONFIG_CHANGE: return "DEVICE_CONFIG_CHANGE";
-            case EVENT_ACTIVE_DEVICE_CHANGE: return "ACTIVE_DEVICE_CHANGE";
             default:
                 return new String("invalid event:" + event);
         }
@@ -620,11 +573,12 @@
         return btHeadsetDeviceToAudioDevice(mBluetoothHeadsetDevice);
     }
 
-    private AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) {
+    private static AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) {
         if (btDevice == null) {
             return new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
         }
         String address = btDevice.getAddress();
+        String name = getName(btDevice);
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
         }
@@ -646,7 +600,7 @@
                     + " btClass: " + (btClass == null ? "Unknown" : btClass)
                     + " nativeType: " + nativeType + " address: " + address);
         }
-        return new AudioDeviceAttributes(nativeType, address);
+        return new AudioDeviceAttributes(nativeType, address, name);
     }
 
     private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) {
@@ -655,12 +609,9 @@
         }
         int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET;
         AudioDeviceAttributes audioDevice =  btHeadsetDeviceToAudioDevice(btDevice);
-        String btDeviceName =  getName(btDevice);
         boolean result = false;
         if (isActive) {
-            result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
-                    audioDevice.getInternalType(), audioDevice.getAddress(), btDeviceName),
-                    isActive);
+            result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice);
         } else {
             int[] outDeviceTypes = {
                     AudioSystem.DEVICE_OUT_BLUETOOTH_SCO,
@@ -669,14 +620,14 @@
             };
             for (int outDeviceType : outDeviceTypes) {
                 result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
-                        outDeviceType, audioDevice.getAddress(), btDeviceName),
-                        isActive);
+                        outDeviceType, audioDevice.getAddress(), audioDevice.getName()),
+                        isActive, btDevice);
             }
         }
         // handleDeviceConnection() && result to make sure the method get executed
         result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes(
-                        inDevice, audioDevice.getAddress(), btDeviceName),
-                isActive) && result;
+                        inDevice, audioDevice.getAddress(), audioDevice.getName()),
+                isActive, btDevice) && result;
         return result;
     }
 
@@ -973,6 +924,30 @@
         }
     }
 
+    /*package */ static int getProfileFromType(int deviceType) {
+        if (AudioSystem.isBluetoothA2dpOutDevice(deviceType)) {
+            return BluetoothProfile.A2DP;
+        } else if (AudioSystem.isBluetoothScoDevice(deviceType)) {
+            return BluetoothProfile.HEADSET;
+        } else if (AudioSystem.isBluetoothLeDevice(deviceType)) {
+            return BluetoothProfile.LE_AUDIO;
+        }
+        return 0; // 0 is not a valid profile
+    }
+
+    /*package */ static Bundle getPreferredAudioProfiles(String address) {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        return adapter.getPreferredAudioProfiles(adapter.getRemoteDevice(address));
+    }
+
+    /**
+     * Notifies Bluetooth framework that new preferred audio profiles for Bluetooth devices
+     * have been applied.
+     */
+    public static void onNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) {
+        BluetoothAdapter.getDefaultAdapter().notifyActiveDeviceChangeApplied(btDevice);
+    }
+
     /**
      * Returns the string equivalent for the btDeviceClass class.
      */
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index d2341448..c678a92 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -456,22 +456,22 @@
 
     private static class CarrierConfigInfo {
         public final String mccMnc;
-        public final int keepaliveDelayMs;
+        public final int keepaliveDelaySec;
         public final int encapType;
         public final int ipVersion;
 
-        CarrierConfigInfo(String mccMnc, int keepaliveDelayMs,
+        CarrierConfigInfo(String mccMnc, int keepaliveDelaySec,
                 int encapType,
                 int ipVersion) {
             this.mccMnc = mccMnc;
-            this.keepaliveDelayMs = keepaliveDelayMs;
+            this.keepaliveDelaySec = keepaliveDelaySec;
             this.encapType = encapType;
             this.ipVersion = ipVersion;
         }
 
         @Override
         public String toString() {
-            return "CarrierConfigInfo(" + mccMnc + ") [keepaliveDelayMs=" + keepaliveDelayMs
+            return "CarrierConfigInfo(" + mccMnc + ") [keepaliveDelaySec=" + keepaliveDelaySec
                     + ", encapType=" + encapType + ", ipVersion=" + ipVersion + "]";
         }
     }
@@ -3603,7 +3603,7 @@
             }
             final CarrierConfigInfo carrierconfig = getCarrierConfigForUnderlyingNetwork();
             final int nattKeepaliveSec = (carrierconfig != null)
-                    ? carrierconfig.keepaliveDelayMs : AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
+                    ? carrierconfig.keepaliveDelaySec : AUTOMATIC_KEEPALIVE_DELAY_SECONDS;
             if (carrierconfig != null) {
                 Log.d(TAG, "Get customized keepalive (" + nattKeepaliveSec + "s) on SIM (mccmnc="
                         + carrierconfig.mccMnc + ")");
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 9020cb3..45c7c9a 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -2445,12 +2445,36 @@
 
     /**
      * TODO(multi-display) Extends this method with specific display.
-     * Propagate ambient state to wallpaper engine.
+     * Propagate ambient state to wallpaper engine(s).
      *
      * @param inAmbientMode {@code true} when in ambient mode, {@code false} otherwise.
      * @param animationDuration Duration of the animation, or 0 when immediate.
      */
     public void setInAmbientMode(boolean inAmbientMode, long animationDuration) {
+        if (mIsLockscreenLiveWallpaperEnabled) {
+            List<IWallpaperEngine> engines = new ArrayList<>();
+            synchronized (mLock) {
+                mInAmbientMode = inAmbientMode;
+                for (WallpaperData data : getActiveWallpapers()) {
+                    if (data.connection.mInfo == null
+                            || data.connection.mInfo.supportsAmbientMode()) {
+                        // TODO(multi-display) Extends this method with specific display.
+                        IWallpaperEngine engine = data.connection
+                                .getDisplayConnectorOrCreate(DEFAULT_DISPLAY).mEngine;
+                        if (engine != null) engines.add(engine);
+                    }
+                }
+            }
+            for (IWallpaperEngine engine : engines) {
+                try {
+                    engine.setInAmbientMode(inAmbientMode, animationDuration);
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to set ambient mode", e);
+                }
+            }
+            return;
+        }
+
         final IWallpaperEngine engine;
         synchronized (mLock) {
             mInAmbientMode = inAmbientMode;
@@ -2475,10 +2499,25 @@
     }
 
     /**
-     * Propagate a wake event to the wallpaper engine.
+     * Propagate a wake event to the wallpaper engine(s).
      */
     public void notifyWakingUp(int x, int y, @NonNull Bundle extras) {
         synchronized (mLock) {
+            if (mIsLockscreenLiveWallpaperEnabled) {
+                for (WallpaperData data : getActiveWallpapers()) {
+                    data.connection.forEachDisplayConnector(displayConnector -> {
+                        if (displayConnector.mEngine != null) {
+                            try {
+                                displayConnector.mEngine.dispatchWallpaperCommand(
+                                        WallpaperManager.COMMAND_WAKING_UP, x, y, -1, extras);
+                            } catch (RemoteException e) {
+                                Slog.w(TAG, "Failed to dispatch COMMAND_WAKING_UP", e);
+                            }
+                        }
+                    });
+                }
+                return;
+            }
             final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
             if (data != null && data.connection != null) {
                 data.connection.forEachDisplayConnector(
@@ -2497,10 +2536,26 @@
     }
 
     /**
-     * Propagate a sleep event to the wallpaper engine.
+     * Propagate a sleep event to the wallpaper engine(s).
      */
     public void notifyGoingToSleep(int x, int y, @NonNull Bundle extras) {
         synchronized (mLock) {
+            if (mIsLockscreenLiveWallpaperEnabled) {
+                for (WallpaperData data : getActiveWallpapers()) {
+                    data.connection.forEachDisplayConnector(displayConnector -> {
+                        if (displayConnector.mEngine != null) {
+                            try {
+                                displayConnector.mEngine.dispatchWallpaperCommand(
+                                        WallpaperManager.COMMAND_GOING_TO_SLEEP, x, y, -1,
+                                        extras);
+                            } catch (RemoteException e) {
+                                Slog.w(TAG, "Failed to dispatch COMMAND_GOING_TO_SLEEP", e);
+                            }
+                        }
+                    });
+                }
+                return;
+            }
             final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
             if (data != null && data.connection != null) {
                 data.connection.forEachDisplayConnector(
@@ -2520,11 +2575,27 @@
     }
 
     /**
-     * Propagates screen turned on event to wallpaper engine.
+     * Propagates screen turned on event to wallpaper engine(s).
      */
     @Override
     public void notifyScreenTurnedOn(int displayId) {
         synchronized (mLock) {
+            if (mIsLockscreenLiveWallpaperEnabled) {
+                for (WallpaperData data : getActiveWallpapers()) {
+                    if (data.connection.containsDisplay(displayId)) {
+                        final IWallpaperEngine engine = data.connection
+                                .getDisplayConnectorOrCreate(displayId).mEngine;
+                        if (engine != null) {
+                            try {
+                                engine.onScreenTurnedOn();
+                            } catch (RemoteException e) {
+                                Slog.w(TAG, "Failed to notify that the screen turned on", e);
+                            }
+                        }
+                    }
+                }
+                return;
+            }
             final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
             if (data != null
                     && data.connection != null
@@ -2545,11 +2616,27 @@
 
 
     /**
-     * Propagate screen turning on event to wallpaper engine.
+     * Propagate screen turning on event to wallpaper engine(s).
      */
     @Override
     public void notifyScreenTurningOn(int displayId) {
         synchronized (mLock) {
+            if (mIsLockscreenLiveWallpaperEnabled) {
+                for (WallpaperData data : getActiveWallpapers()) {
+                    if (data.connection.containsDisplay(displayId)) {
+                        final IWallpaperEngine engine = data.connection
+                                .getDisplayConnectorOrCreate(displayId).mEngine;
+                        if (engine != null) {
+                            try {
+                                engine.onScreenTurningOn();
+                            } catch (RemoteException e) {
+                                Slog.w(TAG, "Failed to notify that the screen is turning on", e);
+                            }
+                        }
+                    }
+                }
+                return;
+            }
             final WallpaperData data = mWallpaperMap.get(mCurrentUserId);
             if (data != null
                     && data.connection != null
@@ -2576,6 +2663,17 @@
         return true;
     }
 
+    private WallpaperData[] getActiveWallpapers() {
+        WallpaperData systemWallpaper = mWallpaperMap.get(mCurrentUserId);
+        WallpaperData lockWallpaper = mLockWallpaperMap.get(mCurrentUserId);
+        boolean systemValid = systemWallpaper != null && systemWallpaper.connection != null;
+        boolean lockValid = lockWallpaper != null && lockWallpaper.connection != null;
+        return systemValid && lockValid ? new WallpaperData[]{systemWallpaper, lockWallpaper}
+                : systemValid ? new WallpaperData[]{systemWallpaper}
+                : lockValid ? new WallpaperData[]{lockWallpaper}
+                : new WallpaperData[0];
+    }
+
     private IWallpaperEngine getEngine(int which, int userId, int displayId) {
         WallpaperData wallpaperData = findWallpaperAtDisplay(userId, displayId);
         if (wallpaperData == null) return null;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index ce43628..be52e5a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -1095,11 +1095,9 @@
                 } else {
                     overrideProviders = null;
                 }
-                final @InsetsType int type = provider.getType();
-                final int id = InsetsSource.createId(
-                        provider.getOwner(), provider.getIndex(), type);
-                mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(id, type)
-                        .setWindowContainer(win, frameProvider, overrideProviders);
+                mDisplayContent.getInsetsStateController().getOrCreateSourceProvider(
+                        provider.getId(), provider.getType()).setWindowContainer(
+                                win, frameProvider, overrideProviders);
                 mInsetsSourceWindowsExceptIme.add(win);
             }
         }
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index fe13b87..ddf96c5 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -311,16 +311,13 @@
             state.removeSource(ID_IME);
         } else if (attrs.providedInsets != null) {
             for (InsetsFrameProvider provider : attrs.providedInsets) {
-                final int id = InsetsSource.createId(
-                        provider.getOwner(), provider.getIndex(), provider.getType());
-                final @InsetsType int type = provider.getType();
-                if ((type & WindowInsets.Type.systemBars()) == 0) {
+                if ((provider.getType() & WindowInsets.Type.systemBars()) == 0) {
                     continue;
                 }
                 if (state == originalState) {
                     state = new InsetsState(state);
                 }
-                state.removeSource(id);
+                state.removeSource(provider.getId());
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 08a6358..5f6d660 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -269,6 +269,8 @@
                     TRANSIT_TO_BACK, transitFlags, null /* trigger */, dc);
             updateKeyguardSleepToken();
 
+            // Make the home wallpaper visible
+            dc.mWallpaperController.showHomeWallpaperInTransition();
             // Some stack visibility might change (e.g. docked stack)
             mRootWindowContainer.resumeFocusedTasksTopActivities();
             mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 5caf663..80f918b 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1325,6 +1325,7 @@
         // ActivityRecord#canShowWindows() may reject to show its window. The visibility also
         // needs to be updated for STATE_ABORT.
         commitVisibleActivities(transaction);
+        commitVisibleWallpapers();
 
         // Fall-back to the default display if there isn't one participating.
         final DisplayContent primaryDisplay = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0)
@@ -1357,6 +1358,7 @@
         }
         // Resolve the animating targets from the participants.
         mTargets = calculateTargets(mParticipants, mChanges);
+
         // Check whether the participants were animated from back navigation.
         mController.mAtm.mBackNavigationController.onTransactionReady(this, mTargets);
         final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, transaction);
@@ -1633,6 +1635,30 @@
         }
     }
 
+    /**
+     * Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones
+     */
+    private void commitVisibleWallpapers() {
+        boolean showWallpaper = shouldWallpaperBeVisible();
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
+            if (wallpaper != null) {
+                wallpaper.waitingToShow = false;
+                if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
+                    wallpaper.commitVisibility(showWallpaper);
+                }
+            }
+        }
+    }
+
+    private boolean shouldWallpaperBeVisible() {
+        for (int i = mParticipants.size() - 1; i >= 0; --i) {
+            WindowContainer participant = mParticipants.valueAt(i);
+            if (participant.showWallpaper()) return true;
+        }
+        return false;
+    }
+
     // TODO(b/188595497): Remove after migrating to shell.
     /** @see RecentsAnimationController#attachNavigationBarToApp */
     private void handleLegacyRecentsStartBehavior(DisplayContent dc, TransitionInfo info) {
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index f416316..edafe06 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -339,6 +339,31 @@
         }
     }
 
+    /**
+     * Change the visibility if wallpaper is home screen only.
+     * This is called during the keyguard unlocking transition
+     * (see {@link KeyguardController#keyguardGoingAway(int, int)}) and thus assumes that if the
+     * system wallpaper is shared with lock, then it needs no animation.
+     */
+    public void showHomeWallpaperInTransition() {
+        updateWallpaperWindowsTarget(mFindResults);
+
+        if (!mFindResults.hasTopShowWhenLockedWallpaper()) {
+            Slog.w(TAG, "There is no wallpaper for the lock screen");
+            return;
+        }
+        WindowState hideWhenLocked = mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper;
+        WindowState showWhenLocked = mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper;
+        if (!mFindResults.hasTopHideWhenLockedWallpaper()) {
+            // Shared wallpaper, ensure its visibility
+            showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindows(true);
+        } else {
+            // Separate lock and home wallpapers: show home wallpaper and hide lock
+            hideWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(true);
+            showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(false);
+        }
+    }
+
     void hideDeferredWallpapersIfNeededLegacy() {
         for (int i = mWallpaperTokens.size() - 1; i >= 0; i--) {
             final WallpaperWindowToken token = mWallpaperTokens.get(i);
@@ -815,7 +840,6 @@
             }
         }
 
-        // Keep both wallpapers visible unless the keyguard is locked (then hide private wp)
         if (!mDisplayContent.isKeyguardGoingAway() || !mIsLockscreenLiveWallpaperEnabled) {
             // When keyguard goes away, KeyguardController handles the visibility
             updateWallpaperTokens(visible, mDisplayContent.isKeyguardLocked());
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 1ffee05..5ea8f65 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -128,6 +128,20 @@
         }
     }
 
+    /**
+     * Update the visibility of the token to {@param visible}. If a transition will collect the
+     * wallpaper, then the visibility will be committed during the execution of the transition.
+     *
+     * waitingToShow is reset at the beginning of the transition:
+     * {@link Transition#onTransactionReady(int, SurfaceControl.Transaction)}
+     */
+    void updateWallpaperWindowsInTransition(boolean visible) {
+        if (mTransitionController.isCollecting() && mVisibleRequested != visible) {
+            waitingToShow = true;
+        }
+        updateWallpaperWindows(visible);
+    }
+
     void updateWallpaperWindows(boolean visible) {
         if (mVisibleRequested != visible) {
             ProtoLog.d(WM_DEBUG_WALLPAPER, "Wallpaper token %s visible=%b",
@@ -199,11 +213,11 @@
     }
 
     /**
-     * Commits the visibility of this token. This will directly update the visibility without
-     * regard for other state (like being in a transition).
+     * Commits the visibility of this token. This will directly update the visibility unless the
+     * wallpaper is in a transition.
      */
     void commitVisibility(boolean visible) {
-        if (visible == isVisible()) return;
+        if (visible == isVisible() || waitingToShow) return;
 
         ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                 "commitVisibility: %s: visible=%b mVisibleRequested=%b", this,
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index a5cdd0b..3ccf183 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -435,8 +435,7 @@
         if (mLocalInsetsSources == null) {
             mLocalInsetsSources = new SparseArray<>();
         }
-        final int id = InsetsSource.createId(
-                provider.getOwner(), provider.getIndex(), provider.getType());
+        final int id = provider.getId();
         if (mLocalInsetsSources.get(id) != null) {
             if (DEBUG) {
                 Slog.d(TAG, "The local insets source for this " + provider
@@ -457,8 +456,7 @@
             return;
         }
 
-        final int id = InsetsSource.createId(
-                provider.getOwner(), provider.getIndex(), provider.getType());
+        final int id = provider.getId();
         if (mLocalInsetsSources.get(id) == null) {
             if (DEBUG) {
                 Slog.d(TAG, "Given " + provider + " doesn't have a local insets source.");
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index ed4b475..5360ab9 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -10760,7 +10760,9 @@
     @VisibleForTesting
     boolean hasDeviceIdAccessUnchecked(String packageName, int uid) {
         final int userId = UserHandle.getUserId(uid);
-        if (isPermissionCheckFlagEnabled()) {
+        // TODO(b/280048070): Introduce a permission to handle device ID access
+        if (isPermissionCheckFlagEnabled()
+                && !(isUidProfileOwnerLocked(uid) || isUidDeviceOwnerLocked(uid))) {
             return hasPermission(MANAGE_DEVICE_POLICY_CERTIFICATES, packageName, userId);
         } else {
             ComponentName deviceOwner = getDeviceOwnerComponent(true);
@@ -22942,6 +22944,7 @@
                     MANAGE_DEVICE_POLICY_LOCATION,
                     MANAGE_DEVICE_POLICY_LOCK,
                     MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
+                    MANAGE_DEVICE_POLICY_CERTIFICATES,
                     MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
                     MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
                     MANAGE_DEVICE_POLICY_PACKAGE_STATE,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index ad606cb..2d8ddfa 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -1443,10 +1443,8 @@
         final InsetsFrameProvider provider2 =
                 new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect2);
-        final int sourceId1 = InsetsSource.createId(
-                provider1.getOwner(), provider1.getIndex(), provider1.getType());
-        final int sourceId2 = InsetsSource.createId(
-                provider2.getOwner(), provider2.getIndex(), provider2.getType());
+        final int sourceId1 = provider1.getId();
+        final int sourceId2 = provider2.getId();
 
         rootTask.addLocalInsetsFrameProvider(provider1);
         container.addLocalInsetsFrameProvider(provider2);
@@ -1504,10 +1502,8 @@
         final InsetsFrameProvider provider2 =
                 new InsetsFrameProvider(null, 1, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(genericOverlayInsetsRect2);
-        final int sourceId1 = InsetsSource.createId(
-                provider1.getOwner(), provider1.getIndex(), provider1.getType());
-        final int sourceId2 = InsetsSource.createId(
-                provider2.getOwner(), provider2.getIndex(), provider2.getType());
+        final int sourceId1 = provider1.getId();
+        final int sourceId2 = provider2.getId();
 
         rootTask.addLocalInsetsFrameProvider(provider1);
         activity0.forAllWindows(window -> {
@@ -1566,10 +1562,8 @@
         final InsetsFrameProvider provider2 =
                 new InsetsFrameProvider(null, 2, WindowInsets.Type.systemOverlays())
                         .setArbitraryRectangle(navigationBarInsetsRect2);
-        final int sourceId1 = InsetsSource.createId(
-                provider1.getOwner(), provider1.getIndex(), provider1.getType());
-        final int sourceId2 = InsetsSource.createId(
-                provider2.getOwner(), provider2.getIndex(), provider2.getType());
+        final int sourceId1 = provider1.getId();
+        final int sourceId2 = provider2.getId();
 
         rootTask.addLocalInsetsFrameProvider(provider1);
         container.addLocalInsetsFrameProvider(provider2);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index a63807d..a4cad5e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -359,9 +359,12 @@
                     new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
                     new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
             };
-            for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
-                mNavBarWindow.mAttrs.paramsForRotation[rot] =
-                        getNavBarLayoutParamsForRotation(rot, owner);
+            // If the navigation bar cannot move then it is always at the bottom.
+            if (mDisplayContent.getDisplayPolicy().navigationBarCanMove()) {
+                for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
+                    mNavBarWindow.mAttrs.paramsForRotation[rot] =
+                            getNavBarLayoutParamsForRotation(rot, owner);
+                }
             }
         }
         if (addAll || ArrayUtils.contains(requestedWindows, W_DOCK_DIVIDER)) {
diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml
index 7272abb..32ff243 100644
--- a/tests/FlickerTests/AndroidTest.xml
+++ b/tests/FlickerTests/AndroidTest.xml
@@ -21,6 +21,9 @@
         <option name="run-command" value="setprop ro.test_harness 1 ; am force-stop com.google.android.apps.nexuslauncher" />
         <!-- Ensure output directory is empty at the start -->
         <option name="run-command" value="rm -rf /sdcard/flicker" />
+        <!-- Increase trace size: 20mb for WM and 80mb for SF -->
+        <option name="run-command" value="cmd window tracing size 20480" />
+        <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />