Merge "A2DP switch device refactor"
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 9af6c1b..3a35f24 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -113,6 +113,7 @@
   public class AudioManager {
     method public void adjustStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
     method public void adjustSuggestedStreamVolumeForUid(int, int, int, @NonNull String, int, int, int);
+    method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void handleBluetoothActiveDeviceChanged(@Nullable android.bluetooth.BluetoothDevice, @Nullable android.bluetooth.BluetoothDevice, @NonNull android.media.BtProfileConnectionInfo);
     method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setA2dpSuspended(boolean);
     method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setBluetoothHeadsetProperties(@NonNull String, boolean, boolean);
     method @RequiresPermission("android.permission.BLUETOOTH_STACK") public void setHfpEnabled(boolean);
@@ -122,6 +123,19 @@
     field public static final int FLAG_FROM_KEY = 4096; // 0x1000
   }
 
+  public final class BtProfileConnectionInfo implements android.os.Parcelable {
+    method @NonNull public static android.media.BtProfileConnectionInfo a2dpInfo(boolean, int);
+    method public int describeContents();
+    method public boolean getIsLeOutput();
+    method public int getProfile();
+    method public boolean getSuppressNoisyIntent();
+    method public int getVolume();
+    method @NonNull public static android.media.BtProfileConnectionInfo hearingAidInfo(boolean);
+    method @NonNull public static android.media.BtProfileConnectionInfo leAudio(boolean, boolean);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.BtProfileConnectionInfo> CREATOR;
+  }
+
   public class MediaMetadataRetriever implements java.lang.AutoCloseable {
     field public static final int METADATA_KEY_VIDEO_CODEC_MIME_TYPE = 40; // 0x28
   }
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index cf7039b..c7f5696 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -32,7 +32,6 @@
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothCodecConfig;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Context;
@@ -5796,112 +5795,25 @@
         }
     }
 
-     /**
-     * Indicate Hearing Aid connection state change and eventually suppress
-     * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent.
-     * This operation is asynchronous but its execution will still be sequentially scheduled
-     * relative to calls to {@link #setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-     * * BluetoothDevice, int, int, boolean, int)} and
-     * and {@link #handleBluetoothA2dpDeviceConfigChange(BluetoothDevice)}.
-     * @param device Bluetooth device connected/disconnected
-     * @param state new connection state (BluetoothProfile.STATE_xxx)
-     * @param musicDevice Default get system volume for the connecting device.
-     * (either {@link android.bluetooth.BluetoothProfile.hearingaid} or
-     * {@link android.bluetooth.BluetoothProfile.HEARING_AID})
-     * @param suppressNoisyIntent if true the
-     * {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be sent.
-     * {@hide}
-     */
-    public void setBluetoothHearingAidDeviceConnectionState(
-                BluetoothDevice device, int state, boolean suppressNoisyIntent,
-                int musicDevice) {
-        final IAudioService service = getService();
-        try {
-            service.setBluetoothHearingAidDeviceConnectionState(device,
-                state, suppressNoisyIntent, musicDevice);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
     /**
-    * Indicate Le Audio output device connection state change and eventually suppress
-    * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent.
-    * @param device Bluetooth device connected/disconnected
-    * @param state new connection state (BluetoothProfile.STATE_xxx)
-    * @param suppressNoisyIntent if true the
-    * {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be sent.
-    * {@hide}
-    */
-    public void setBluetoothLeAudioOutDeviceConnectionState(BluetoothDevice device, int state,
-            boolean suppressNoisyIntent) {
-        final IAudioService service = getService();
-        try {
-            service.setBluetoothLeAudioOutDeviceConnectionState(device, state, suppressNoisyIntent);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-    * Indicate Le Audio input connection state change.
-    * @param device Bluetooth device connected/disconnected
-    * @param state new connection state (BluetoothProfile.STATE_xxx)
-    * {@hide}
-    */
-    public void setBluetoothLeAudioInDeviceConnectionState(BluetoothDevice device, int state) {
-        final IAudioService service = getService();
-        try {
-            service.setBluetoothLeAudioInDeviceConnectionState(device, state);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-     /**
-     * Indicate A2DP source or sink connection state change and eventually suppress
-     * the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent.
-     * This operation is asynchronous but its execution will still be sequentially scheduled
-     * relative to calls to {@link #setBluetoothHearingAidDeviceConnectionState(BluetoothDevice,
-     * int, boolean, int)} and
-     * {@link #handleBluetoothA2dpDeviceConfigChange(BluetoothDevice)}.
-     * @param device Bluetooth device connected/disconnected
-     * @param state  new connection state, {@link BluetoothProfile#STATE_CONNECTED}
-     *     or {@link BluetoothProfile#STATE_DISCONNECTED}
-     * @param profile profile for the A2DP device
-     * @param a2dpVolume New volume for the connecting device. Does nothing if disconnecting.
-     * (either {@link android.bluetooth.BluetoothProfile.A2DP} or
-     * {@link android.bluetooth.BluetoothProfile.A2DP_SINK})
-     * @param suppressNoisyIntent if true the
-     * {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be sent.
+     * Indicate Bluetooth profile connection state change.
+     * Configuration changes for A2DP are indicated by having the same <code>newDevice</code> and
+     * <code>previousDevice</code>
+     * This operation is asynchronous.
+     *
+     * @param newDevice Bluetooth device connected or null if there is no new devices
+     * @param previousDevice Bluetooth device disconnected or null if there is no disconnected
+     * devices
+     * @param info contain all info related to the device. {@link BtProfileConnectionInfo}
      * {@hide}
      */
-    public void setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-            BluetoothDevice device, int state,
-            int profile, boolean suppressNoisyIntent, int a2dpVolume) {
+    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_STACK)
+    public void handleBluetoothActiveDeviceChanged(@Nullable BluetoothDevice newDevice,
+            @Nullable BluetoothDevice previousDevice, @NonNull BtProfileConnectionInfo info) {
         final IAudioService service = getService();
         try {
-            service.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(device,
-                state, profile, suppressNoisyIntent, a2dpVolume);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-     /**
-     * Indicate A2DP device configuration has changed.
-     * This operation is asynchronous but its execution will still be sequentially scheduled
-     * relative to calls to
-     * {@link #setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(BluetoothDevice, int, int,
-     * boolean, int)} and
-     * {@link #setBluetoothHearingAidDeviceConnectionState(BluetoothDevice, int, boolean, int)}
-     * @param device Bluetooth device whose configuration has changed.
-     * {@hide}
-     */
-    public void handleBluetoothA2dpDeviceConfigChange(BluetoothDevice device) {
-        final IAudioService service = getService();
-        try {
-            service.handleBluetoothA2dpDeviceConfigChange(device);
+            service.handleBluetoothActiveDeviceChanged(newDevice, previousDevice, info);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/BtProfileConnectionInfo.aidl b/media/java/android/media/BtProfileConnectionInfo.aidl
new file mode 100644
index 0000000..047f06b
--- /dev/null
+++ b/media/java/android/media/BtProfileConnectionInfo.aidl
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+parcelable BtProfileConnectionInfo;
+
diff --git a/media/java/android/media/BtProfileConnectionInfo.java b/media/java/android/media/BtProfileConnectionInfo.java
new file mode 100644
index 0000000..19ea2de
--- /dev/null
+++ b/media/java/android/media/BtProfileConnectionInfo.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.bluetooth.BluetoothProfile;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Contains information about Bluetooth profile connection state changed
+ * {@hide}
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class BtProfileConnectionInfo implements Parcelable {
+    /** @hide */
+    @IntDef({
+            BluetoothProfile.A2DP,
+            BluetoothProfile.A2DP_SINK, // Can only be set by BtHelper
+            BluetoothProfile.HEADSET, // Can only be set by BtHelper
+            BluetoothProfile.HEARING_AID,
+            BluetoothProfile.LE_AUDIO,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BtProfile {}
+
+    private final @BtProfile int mProfile;
+    private final boolean mSupprNoisy;
+    private final int mVolume;
+    private final boolean mIsLeOutput;
+
+    private BtProfileConnectionInfo(@BtProfile int profile, boolean suppressNoisyIntent, int volume,
+            boolean isLeOutput) {
+        mProfile = profile;
+        mSupprNoisy = suppressNoisyIntent;
+        mVolume = volume;
+        mIsLeOutput = isLeOutput;
+    }
+
+    /**
+     * Constructor used by BtHelper when a profile is connected
+     * {@hide}
+     */
+    public BtProfileConnectionInfo(@BtProfile int profile) {
+        this(profile, false, -1, false);
+    }
+
+    public static final @NonNull Parcelable.Creator<BtProfileConnectionInfo> CREATOR =
+            new Parcelable.Creator<BtProfileConnectionInfo>() {
+                @Override
+                public BtProfileConnectionInfo createFromParcel(Parcel source) {
+                    return new BtProfileConnectionInfo(source.readInt(), source.readBoolean(),
+                            source.readInt(), source.readBoolean());
+                }
+
+                @Override
+                public BtProfileConnectionInfo[] newArray(int size) {
+                    return new BtProfileConnectionInfo[size];
+                }
+            };
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, @WriteFlags int flags) {
+        dest.writeInt(mProfile);
+        dest.writeBoolean(mSupprNoisy);
+        dest.writeInt(mVolume);
+        dest.writeBoolean(mIsLeOutput);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Constructor for A2dp info
+     *
+     * @param suppressNoisyIntent if true the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY}
+     * intent will not be sent.
+     *
+     * @param volume of device -1 to ignore value
+     */
+    public static @NonNull BtProfileConnectionInfo a2dpInfo(boolean suppressNoisyIntent,
+            int volume) {
+        return new BtProfileConnectionInfo(BluetoothProfile.A2DP, suppressNoisyIntent, volume,
+            false);
+    }
+
+    /**
+     * Constructor for hearing aid info
+     *
+     * @param suppressNoisyIntent if true the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY}
+     * intent will not be sent.
+     */
+    public static @NonNull BtProfileConnectionInfo hearingAidInfo(boolean suppressNoisyIntent) {
+        return new BtProfileConnectionInfo(BluetoothProfile.HEARING_AID, suppressNoisyIntent, -1,
+            false);
+    }
+
+    /**
+     * constructor for le audio info
+     *
+     * @param suppressNoisyIntent if true the {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY}
+     * intent will not be sent.
+     *
+     * @param isLeOutput if true mean the device is an output device, if false it's an input device
+     */
+    public static @NonNull BtProfileConnectionInfo leAudio(boolean suppressNoisyIntent,
+            boolean isLeOutput) {
+        return new BtProfileConnectionInfo(BluetoothProfile.LE_AUDIO, suppressNoisyIntent, -1,
+            isLeOutput);
+    }
+
+    /**
+     * @return The profile connection
+     */
+    public @BtProfile int getProfile() {
+        return mProfile;
+    }
+
+    /**
+     * @return {@code true} if {@link AudioManager.ACTION_AUDIO_BECOMING_NOISY} intent will not be
+     * sent
+     */
+    public boolean getSuppressNoisyIntent() {
+        return mSupprNoisy;
+    }
+
+    /**
+     * Only for {@link BluetoothProfile.A2DP} profile
+     * @return the volume of the connection or -1 if the value is ignored
+     */
+    public int getVolume() {
+        return mVolume;
+    }
+
+    /**
+     * Only for {@link BluetoothProfile.LE_AUDIO} profile
+     * @return {@code true} is the LE device is an output device, {@code false} if it's an input
+     * device
+     */
+    public boolean getIsLeOutput() {
+        return mIsLeOutput;
+    }
+}
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index dd44fdf..5ff56f9 100755
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -24,6 +24,7 @@
 import android.media.AudioPlaybackConfiguration;
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
+import android.media.BtProfileConnectionInfo;
 import android.media.IAudioFocusDispatcher;
 import android.media.IAudioModeDispatcher;
 import android.media.IAudioRoutesObserver;
@@ -207,8 +208,6 @@
     void setWiredDeviceConnectionState(int type, int state, String address, String name,
             String caller);
 
-    void handleBluetoothA2dpDeviceConfigChange(in BluetoothDevice device);
-
     @UnsupportedAppUsage
     AudioRoutesInfo startWatchingRoutes(in IAudioRoutesObserver observer);
 
@@ -268,16 +267,8 @@
 
     oneway void playerHasOpPlayAudio(in int piid, in boolean hasOpPlayAudio);
 
-    void setBluetoothHearingAidDeviceConnectionState(in BluetoothDevice device,
-            int state, boolean suppressNoisyIntent, int musicDevice);
-
-    void setBluetoothLeAudioOutDeviceConnectionState(in BluetoothDevice device, int state,
-            boolean suppressNoisyIntent);
-
-    void setBluetoothLeAudioInDeviceConnectionState(in BluetoothDevice device, int state);
-
-    void setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(in BluetoothDevice device,
-            int state, int profile, boolean suppressNoisyIntent, int a2dpVolume);
+    void handleBluetoothActiveDeviceChanged(in BluetoothDevice newDevice,
+            in BluetoothDevice previousDevice, in BtProfileConnectionInfo info);
 
     oneway void setFocusRequestResultFromExtPolicy(in AudioFocusInfo afi, int requestResult,
             in IAudioPolicyCallback pcb);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index c383f51..0b2311b 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -17,12 +17,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
-import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothLeAudio;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -32,6 +29,7 @@
 import android.media.AudioManager;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
+import android.media.BtProfileConnectionInfo;
 import android.media.IAudioRoutesObserver;
 import android.media.ICapturePresetDevicesRoleDispatcher;
 import android.media.ICommunicationDeviceDispatcher;
@@ -516,29 +514,82 @@
         }
     };
 
-    /*package*/ static final class BtDeviceConnectionInfo {
-        final @NonNull BluetoothDevice mDevice;
-        final @AudioService.BtProfileConnectionState int mState;
-        final int mProfile;
-        final boolean mSupprNoisy;
-        final int mVolume;
+    /*package*/ static final class BtDeviceChangedData {
+        final @Nullable BluetoothDevice mNewDevice;
+        final @Nullable BluetoothDevice mPreviousDevice;
+        final @NonNull BtProfileConnectionInfo mInfo;
+        final @NonNull String mEventSource;
 
-        BtDeviceConnectionInfo(@NonNull BluetoothDevice device,
-                @AudioService.BtProfileConnectionState int state,
-                int profile, boolean suppressNoisyIntent, int vol) {
-            mDevice = device;
-            mState = state;
-            mProfile = profile;
-            mSupprNoisy = suppressNoisyIntent;
-            mVolume = vol;
+        BtDeviceChangedData(@Nullable BluetoothDevice newDevice,
+                @Nullable BluetoothDevice previousDevice,
+                @NonNull BtProfileConnectionInfo info, @NonNull String eventSource) {
+            mNewDevice = newDevice;
+            mPreviousDevice = previousDevice;
+            mInfo = info;
+            mEventSource = eventSource;
         }
 
-        BtDeviceConnectionInfo(@NonNull BtDeviceConnectionInfo info) {
-            mDevice = info.mDevice;
-            mState = info.mState;
-            mProfile = info.mProfile;
-            mSupprNoisy = info.mSupprNoisy;
-            mVolume = info.mVolume;
+        @Override
+        public String toString() {
+            return "BtDeviceChangedData profile=" + BluetoothProfile.getProfileName(
+                    mInfo.getProfile())
+                + ", switch device: [" + mPreviousDevice + "] -> [" + mNewDevice + "]";
+        }
+    }
+
+    /*package*/ static final class BtDeviceInfo {
+        final @NonNull BluetoothDevice mDevice;
+        final @AudioService.BtProfileConnectionState int mState;
+        final @AudioService.BtProfile int mProfile;
+        final boolean mSupprNoisy;
+        final int mVolume;
+        final boolean mIsLeOutput;
+        final @NonNull String mEventSource;
+        final @AudioSystem.AudioFormatNativeEnumForBtCodec int mCodec;
+        final int mAudioSystemDevice;
+        final int mMusicDevice;
+
+        BtDeviceInfo(@NonNull BtDeviceChangedData d, @NonNull BluetoothDevice device, int state,
+                    int audioDevice, @AudioSystem.AudioFormatNativeEnumForBtCodec int codec) {
+            mDevice = device;
+            mState = state;
+            mProfile = d.mInfo.getProfile();
+            mSupprNoisy = d.mInfo.getSuppressNoisyIntent();
+            mVolume = d.mInfo.getVolume();
+            mIsLeOutput = d.mInfo.getIsLeOutput();
+            mEventSource = d.mEventSource;
+            mAudioSystemDevice = audioDevice;
+            mMusicDevice = AudioSystem.DEVICE_NONE;
+            mCodec = codec;
+        }
+
+        // constructor used by AudioDeviceBroker to search similar message
+        BtDeviceInfo(@NonNull BluetoothDevice device, int profile) {
+            mDevice = device;
+            mProfile = profile;
+            mEventSource = "";
+            mMusicDevice = AudioSystem.DEVICE_NONE;
+            mCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
+            mAudioSystemDevice = 0;
+            mState = 0;
+            mSupprNoisy = false;
+            mVolume = -1;
+            mIsLeOutput = false;
+        }
+
+        // constructor used by AudioDeviceInventory when config change failed
+        BtDeviceInfo(@NonNull BluetoothDevice device, int profile, int state, int musicDevice,
+                    int audioSystemDevice) {
+            mDevice = device;
+            mProfile = profile;
+            mEventSource = "";
+            mMusicDevice = musicDevice;
+            mCodec = AudioSystem.AUDIO_FORMAT_DEFAULT;
+            mAudioSystemDevice = audioSystemDevice;
+            mState = state;
+            mSupprNoisy = false;
+            mVolume = -1;
+            mIsLeOutput = false;
         }
 
         // redefine equality op so we can match messages intended for this device
@@ -550,16 +601,52 @@
             if (this == o) {
                 return true;
             }
-            if (o instanceof BtDeviceConnectionInfo) {
-                return mDevice.equals(((BtDeviceConnectionInfo) o).mDevice);
+            if (o instanceof BtDeviceInfo) {
+                return mProfile == ((BtDeviceInfo) o).mProfile
+                    && mDevice.equals(((BtDeviceInfo) o).mDevice);
             }
             return false;
         }
+    }
 
-        @Override
-        public String toString() {
-            return "BtDeviceConnectionInfo dev=" + mDevice.toString();
+    BtDeviceInfo createBtDeviceInfo(@NonNull BtDeviceChangedData d, @NonNull BluetoothDevice device,
+                int state) {
+        int audioDevice;
+        int codec = AudioSystem.AUDIO_FORMAT_DEFAULT;
+        switch (d.mInfo.getProfile()) {
+            case BluetoothProfile.A2DP_SINK:
+                audioDevice = AudioSystem.DEVICE_IN_BLUETOOTH_A2DP;
+                break;
+            case BluetoothProfile.A2DP:
+                audioDevice = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
+                synchronized (mDeviceStateLock) {
+                    codec = mBtHelper.getA2dpCodec(device);
+                }
+                break;
+            case BluetoothProfile.HEARING_AID:
+                audioDevice = AudioSystem.DEVICE_OUT_HEARING_AID;
+                break;
+            case BluetoothProfile.LE_AUDIO:
+                if (d.mInfo.getIsLeOutput()) {
+                    audioDevice = AudioSystem.DEVICE_OUT_BLE_HEADSET;
+                } else {
+                    audioDevice = AudioSystem.DEVICE_IN_BLE_HEADSET;
+                }
+                break;
+            default: throw new IllegalArgumentException("Invalid profile " + d.mInfo.getProfile());
         }
+        return new BtDeviceInfo(d, device, state, audioDevice, codec);
+    }
+
+    private void btMediaMetricRecord(@NonNull BluetoothDevice device, String state,
+            @NonNull BtDeviceChangedData data) {
+        final String name = TextUtils.emptyIfNull(device.getName());
+        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR
+                + "queueOnBluetoothActiveDeviceChanged")
+            .set(MediaMetrics.Property.STATE, state)
+            .set(MediaMetrics.Property.STATUS, data.mInfo.getProfile())
+            .set(MediaMetrics.Property.NAME, name)
+            .record();
     }
 
     /**
@@ -567,116 +654,37 @@
      * not just a simple message post
      * @param info struct with the (dis)connection information
      */
-    /*package*/ void queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-            @NonNull BtDeviceConnectionInfo info) {
-        final String name = TextUtils.emptyIfNull(info.mDevice.getName());
-        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR
-                + "postBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent")
-                .set(MediaMetrics.Property.STATE, info.mState == BluetoothProfile.STATE_CONNECTED
-                        ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
-                .set(MediaMetrics.Property.INDEX, info.mVolume)
-                .set(MediaMetrics.Property.NAME, name)
-                .record();
-
-        // operations of removing and posting messages related to A2DP device state change must be
-        // mutually exclusive
-        synchronized (mDeviceStateLock) {
-            // when receiving a request to change the connection state of a device, this last
-            // request is the source of truth, so cancel all previous requests that are already in
-            // the handler
-            removeScheduledA2dpEvents(info.mDevice);
-
-            sendLMsgNoDelay(
-                    info.mState == BluetoothProfile.STATE_CONNECTED
-                            ? MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION
-                            : MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
-                    SENDMSG_QUEUE, info);
+    /*package*/ void queueOnBluetoothActiveDeviceChanged(@NonNull BtDeviceChangedData data) {
+        if (data.mInfo.getProfile() == BluetoothProfile.A2DP && 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
+                    + "queueOnBluetoothActiveDeviceChanged_update")
+                    .set(MediaMetrics.Property.NAME, name)
+                    .set(MediaMetrics.Property.STATUS, data.mInfo.getProfile())
+                    .record();
+            synchronized (mDeviceStateLock) {
+                postBluetoothA2dpDeviceConfigChange(data.mNewDevice);
+            }
+        } else {
+            synchronized (mDeviceStateLock) {
+                if (data.mPreviousDevice != null) {
+                    btMediaMetricRecord(data.mPreviousDevice, MediaMetrics.Value.DISCONNECTED,
+                            data);
+                    sendLMsgNoDelay(MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT, SENDMSG_QUEUE,
+                            createBtDeviceInfo(data, data.mPreviousDevice,
+                                    BluetoothProfile.STATE_DISCONNECTED));
+                }
+                if (data.mNewDevice != null) {
+                    btMediaMetricRecord(data.mNewDevice, MediaMetrics.Value.CONNECTED, data);
+                    sendLMsgNoDelay(MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT, SENDMSG_QUEUE,
+                            createBtDeviceInfo(data, data.mNewDevice,
+                                    BluetoothProfile.STATE_CONNECTED));
+                }
+            }
         }
     }
 
-    /** remove all previously scheduled connection and state change events for the given device */
-    @GuardedBy("mDeviceStateLock")
-    private void removeScheduledA2dpEvents(@NonNull BluetoothDevice device) {
-        mBrokerHandler.removeEqualMessages(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, device);
-
-        final BtDeviceConnectionInfo connectionInfoToRemove = new BtDeviceConnectionInfo(device,
-                // the next parameters of the constructor will be ignored when finding the message
-                // to remove as the equality of the message's object is tested on the device itself
-                // (see BtDeviceConnectionInfo.equals() method override)
-                BluetoothProfile.STATE_CONNECTED, 0, false, -1);
-        mBrokerHandler.removeEqualMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION,
-                connectionInfoToRemove);
-        mBrokerHandler.removeEqualMessages(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION,
-                connectionInfoToRemove);
-
-        final BtHelper.BluetoothA2dpDeviceInfo devInfoToRemove =
-                new BtHelper.BluetoothA2dpDeviceInfo(device);
-        mBrokerHandler.removeEqualMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED,
-                devInfoToRemove);
-        mBrokerHandler.removeEqualMessages(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
-                devInfoToRemove);
-        mBrokerHandler.removeEqualMessages(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE,
-                devInfoToRemove);
-    }
-
-    private static final class HearingAidDeviceConnectionInfo {
-        final @NonNull BluetoothDevice mDevice;
-        final @AudioService.BtProfileConnectionState int mState;
-        final boolean mSupprNoisy;
-        final int mMusicDevice;
-        final @NonNull String mEventSource;
-
-        HearingAidDeviceConnectionInfo(@NonNull BluetoothDevice device,
-                @AudioService.BtProfileConnectionState int state,
-                boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) {
-            mDevice = device;
-            mState = state;
-            mSupprNoisy = suppressNoisyIntent;
-            mMusicDevice = musicDevice;
-            mEventSource = eventSource;
-        }
-    }
-
-    /*package*/ void postBluetoothHearingAidDeviceConnectionState(
-            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
-            boolean suppressNoisyIntent, int musicDevice, @NonNull String eventSource) {
-        final HearingAidDeviceConnectionInfo info = new HearingAidDeviceConnectionInfo(
-                device, state, suppressNoisyIntent, musicDevice, eventSource);
-        sendLMsgNoDelay(MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
-    }
-
-    private static final class LeAudioDeviceConnectionInfo {
-        final @NonNull BluetoothDevice mDevice;
-        final @AudioService.BtProfileConnectionState int mState;
-        final boolean mSupprNoisy;
-        final @NonNull String mEventSource;
-
-        LeAudioDeviceConnectionInfo(@NonNull BluetoothDevice device,
-                @AudioService.BtProfileConnectionState int state,
-                boolean suppressNoisyIntent, @NonNull String eventSource) {
-            mDevice = device;
-            mState = state;
-            mSupprNoisy = suppressNoisyIntent;
-            mEventSource = eventSource;
-        }
-    }
-
-    /*package*/ void postBluetoothLeAudioOutDeviceConnectionState(
-            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
-            boolean suppressNoisyIntent, @NonNull String eventSource) {
-        final LeAudioDeviceConnectionInfo info = new LeAudioDeviceConnectionInfo(
-                device, state, suppressNoisyIntent, eventSource);
-        sendLMsgNoDelay(MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
-    }
-
-    /*package*/ void postBluetoothLeAudioInDeviceConnectionState(
-            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
-            @NonNull String eventSource) {
-        final LeAudioDeviceConnectionInfo info = new LeAudioDeviceConnectionInfo(
-                device, state, false, eventSource);
-        sendLMsgNoDelay(MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT, SENDMSG_QUEUE, info);
-    }
-
     /**
      * Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn().
      */
@@ -926,19 +934,8 @@
     }
 
     @GuardedBy("mDeviceStateLock")
-    /*package*/ void postA2dpSinkConnection(@AudioService.BtProfileConnectionState int state,
-            @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
-        sendILMsg(state == BluetoothA2dp.STATE_CONNECTED
-                        ? MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED
-                        : MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED,
-                SENDMSG_QUEUE,
-                state, btDeviceInfo, delay);
-    }
-
-    /*package*/ void postA2dpSourceConnection(@AudioService.BtProfileConnectionState int state,
-            @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo, int delay) {
-        sendILMsg(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE,
-                state, btDeviceInfo, delay);
+    /*package*/ void postBluetoothActiveDevice(BtDeviceInfo info, int delay) {
+        sendLMsg(MSG_L_SET_BT_ACTIVE_DEVICE, SENDMSG_QUEUE, info, delay);
     }
 
     /*package*/ void postSetWiredDeviceConnectionState(
@@ -946,72 +943,12 @@
         sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE, SENDMSG_QUEUE, connectionState, delay);
     }
 
-    /*package*/ void postSetHearingAidConnectionState(
-            @AudioService.BtProfileConnectionState int state,
-            @NonNull BluetoothDevice device, int delay) {
-        sendILMsg(MSG_IL_SET_HEARING_AID_CONNECTION_STATE, SENDMSG_QUEUE,
-                state,
-                device,
-                delay);
+    /*package*/ void postBtProfileDisconnected(int profile) {
+        sendIMsgNoDelay(MSG_I_BT_SERVICE_DISCONNECTED_PROFILE, SENDMSG_QUEUE, profile);
     }
 
-    /*package*/ void postSetLeAudioOutConnectionState(
-            @AudioService.BtProfileConnectionState int state,
-            @NonNull BluetoothDevice device, int delay) {
-        sendILMsg(MSG_IL_SET_LE_AUDIO_OUT_CONNECTION_STATE, SENDMSG_QUEUE,
-                state,
-                device,
-                delay);
-    }
-
-    /*package*/ void postSetLeAudioInConnectionState(
-            @AudioService.BtProfileConnectionState int state,
-            @NonNull BluetoothDevice device) {
-        sendILMsgNoDelay(MSG_IL_SET_LE_AUDIO_IN_CONNECTION_STATE, SENDMSG_QUEUE,
-                state,
-                device);
-    }
-
-    /*package*/ void postDisconnectA2dp() {
-        sendMsgNoDelay(MSG_DISCONNECT_A2DP, SENDMSG_QUEUE);
-    }
-
-    /*package*/ void postDisconnectA2dpSink() {
-        sendMsgNoDelay(MSG_DISCONNECT_A2DP_SINK, SENDMSG_QUEUE);
-    }
-
-    /*package*/ void postDisconnectHearingAid() {
-        sendMsgNoDelay(MSG_DISCONNECT_BT_HEARING_AID, SENDMSG_QUEUE);
-    }
-
-    /*package*/ void postDisconnectLeAudio() {
-        sendMsgNoDelay(MSG_DISCONNECT_BT_LE_AUDIO, SENDMSG_QUEUE);
-    }
-
-    /*package*/ void postDisconnectHeadset() {
-        sendMsgNoDelay(MSG_DISCONNECT_BT_HEADSET, SENDMSG_QUEUE);
-    }
-
-    /*package*/ void postBtA2dpProfileConnected(BluetoothA2dp a2dpProfile) {
-        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP, SENDMSG_QUEUE, a2dpProfile);
-    }
-
-    /*package*/ void postBtA2dpSinkProfileConnected(BluetoothProfile profile) {
-        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK, SENDMSG_QUEUE, profile);
-    }
-
-    /*package*/ void postBtHeasetProfileConnected(BluetoothHeadset headsetProfile) {
-        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET, SENDMSG_QUEUE, headsetProfile);
-    }
-
-    /*package*/ void postBtHearingAidProfileConnected(BluetoothHearingAid hearingAidProfile) {
-        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID, SENDMSG_QUEUE,
-                hearingAidProfile);
-    }
-
-    /*package*/ void postBtLeAudioProfileConnected(BluetoothLeAudio leAudioProfile) {
-        sendLMsgNoDelay(MSG_L_BT_SERVICE_CONNECTED_PROFILE_LE_AUDIO, SENDMSG_QUEUE,
-                leAudioProfile);
+    /*package*/ void postBtProfileConnected(int profile, BluetoothProfile proxy) {
+        sendILMsgNoDelay(MSG_IL_BT_SERVICE_CONNECTED_PROFILE, SENDMSG_QUEUE, profile, proxy);
     }
 
     /*package*/ void postCommunicationRouteClientDied(CommunicationRouteClient client) {
@@ -1069,13 +1006,6 @@
         }
     }
 
-    /*package*/ void postSetA2dpSourceConnectionState(@BluetoothProfile.BtProfileState int state,
-            @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
-        final int intState = (state == BluetoothA2dp.STATE_CONNECTED) ? 1 : 0;
-        sendILMsgNoDelay(MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE, SENDMSG_QUEUE, state,
-                btDeviceInfo);
-    }
-
     /*package*/ void handleFailureToConnectToBtHeadsetService(int delay) {
         sendMsg(MSG_BT_HEADSET_CNCT_FAILED, SENDMSG_REPLACE, delay);
     }
@@ -1088,19 +1018,10 @@
         sendMsgNoDelay(fromA2dp ? MSG_REPORT_NEW_ROUTES_A2DP : MSG_REPORT_NEW_ROUTES, SENDMSG_NOOP);
     }
 
-    /*package*/ void postA2dpActiveDeviceChange(
-                    @NonNull BtHelper.BluetoothA2dpDeviceInfo btDeviceInfo) {
-        sendLMsgNoDelay(MSG_L_A2DP_ACTIVE_DEVICE_CHANGE, SENDMSG_QUEUE, btDeviceInfo);
-    }
-
     // must be called synchronized on mConnectedDevices
-    /*package*/ boolean hasScheduledA2dpSinkConnectionState(BluetoothDevice btDevice) {
-        final BtHelper.BluetoothA2dpDeviceInfo devInfoToCheck =
-                new BtHelper.BluetoothA2dpDeviceInfo(btDevice);
-        return (mBrokerHandler.hasEqualMessages(
-                    MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED, devInfoToCheck)
-            || mBrokerHandler.hasEqualMessages(
-                    MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED, devInfoToCheck));
+    /*package*/ boolean hasScheduledA2dpConnection(BluetoothDevice btDevice) {
+        final BtDeviceInfo devInfoToCheck = new BtDeviceInfo(btDevice, BluetoothProfile.A2DP);
+        return mBrokerHandler.hasEqualMessages(MSG_L_SET_BT_ACTIVE_DEVICE, devInfoToCheck);
     }
 
     /*package*/ void setA2dpTimeout(String address, int a2dpCodec, int delayMs) {
@@ -1124,12 +1045,6 @@
         }
     }
 
-    /*package*/ int getA2dpCodec(@NonNull BluetoothDevice device) {
-        synchronized (mDeviceStateLock) {
-            return mBtHelper.getA2dpCodec(device);
-        }
-    }
-
     /*package*/ void broadcastStickyIntentToCurrentProfileGroup(Intent intent) {
         mSystemServer.broadcastStickyIntentToCurrentProfileGroup(intent);
     }
@@ -1285,39 +1200,11 @@
                         mDeviceInventory.onReportNewRoutes();
                     }
                     break;
-                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
-                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
+                case MSG_L_SET_BT_ACTIVE_DEVICE:
                     synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onSetA2dpSinkConnectionState(
-                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
-                    }
-                    break;
-                case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onSetA2dpSourceConnectionState(
-                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj, msg.arg1);
-                    }
-                    break;
-                case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onSetHearingAidConnectionState(
-                                (BluetoothDevice) msg.obj, msg.arg1,
+                        mDeviceInventory.onSetBtActiveDevice((BtDeviceInfo) msg.obj,
                                 mAudioService.getBluetoothContextualVolumeStream());
                     }
-                    break;
-                case MSG_IL_SET_LE_AUDIO_OUT_CONNECTION_STATE:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onSetLeAudioOutConnectionState(
-                                (BluetoothDevice) msg.obj, msg.arg1,
-                                mAudioService.getBluetoothContextualVolumeStream());
-                    }
-                    break;
-                case MSG_IL_SET_LE_AUDIO_IN_CONNECTION_STATE:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onSetLeAudioInConnectionState(
-                                (BluetoothDevice) msg.obj, msg.arg1);
-                    }
-                    break;
                 case MSG_BT_HEADSET_CNCT_FAILED:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
@@ -1332,14 +1219,10 @@
                     }
                     break;
                 case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
-                    final int a2dpCodec;
                     final BluetoothDevice btDevice = (BluetoothDevice) msg.obj;
                     synchronized (mDeviceStateLock) {
-                        a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
-                        // TODO: name of method being called on AudioDeviceInventory is currently
-                        //       misleading (config change vs active device change), to be
-                        //       reconciliated once the BT side has been updated.
-                        mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
+                        final int a2dpCodec = mBtHelper.getA2dpCodec(btDevice);
+                        mDeviceInventory.onBluetoothA2dpDeviceConfigChange(
                                 new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec),
                                         BtHelper.EVENT_DEVICE_CONFIG_CHANGE);
                     }
@@ -1392,96 +1275,47 @@
                         mDeviceInventory.onToggleHdmi();
                     }
                     break;
-                case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.onBluetoothA2dpActiveDeviceChange(
-                                (BtHelper.BluetoothA2dpDeviceInfo) msg.obj,
-                                 BtHelper.EVENT_ACTIVE_DEVICE_CHANGE);
-                    }
-                    break;
-                case MSG_DISCONNECT_A2DP:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.disconnectA2dp();
-                    }
-                    break;
-                case MSG_DISCONNECT_A2DP_SINK:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.disconnectA2dpSink();
-                    }
-                    break;
-                case MSG_DISCONNECT_BT_HEARING_AID:
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.disconnectHearingAid();
-                    }
-                    break;
-                case MSG_DISCONNECT_BT_HEADSET:
-                    synchronized (mSetModeLock) {
+                case MSG_I_BT_SERVICE_DISCONNECTED_PROFILE:
+                    if (msg.arg1 != BluetoothProfile.HEADSET) {
                         synchronized (mDeviceStateLock) {
-                            mBtHelper.disconnectHeadset();
+                            mDeviceInventory.onBtProfileDisconnected(msg.arg1);
+                        }
+                    } else {
+                        synchronized (mSetModeLock) {
+                            synchronized (mDeviceStateLock) {
+                                mBtHelper.disconnectHeadset();
+                            }
                         }
                     }
                     break;
-                case MSG_DISCONNECT_BT_LE_AUDIO:
-                    synchronized(mDeviceStateLock) {
-                        mDeviceInventory.disconnectLeAudio();
-                    }
-                    break;
-                case MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP:
-                    synchronized (mDeviceStateLock) {
-                        mBtHelper.onA2dpProfileConnected((BluetoothA2dp) msg.obj);
-                    }
-                    break;
-                case MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK:
-                    synchronized (mDeviceStateLock) {
-                        mBtHelper.onA2dpSinkProfileConnected((BluetoothProfile) msg.obj);
-                    }
-                    break;
-                case MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID:
-                    synchronized (mDeviceStateLock) {
-                        mBtHelper.onHearingAidProfileConnected((BluetoothHearingAid) msg.obj);
-                    }
-                    break;
-
-                case MSG_L_BT_SERVICE_CONNECTED_PROFILE_LE_AUDIO:
-                    synchronized(mDeviceStateLock) {
-                        mBtHelper.onLeAudioProfileConnected((BluetoothLeAudio) msg.obj);
-                    }
-                    break;
-                case MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET:
-                    synchronized (mSetModeLock) {
+                case MSG_IL_BT_SERVICE_CONNECTED_PROFILE:
+                    if (msg.arg1 != BluetoothProfile.HEADSET) {
                         synchronized (mDeviceStateLock) {
-                            mBtHelper.onHeadsetProfileConnected((BluetoothHeadset) msg.obj);
+                            mBtHelper.onBtProfileConnected(msg.arg1, (BluetoothProfile) msg.obj);
+                        }
+                    } else {
+                        synchronized (mSetModeLock) {
+                            synchronized (mDeviceStateLock) {
+                                mBtHelper.onHeadsetProfileConnected((BluetoothHeadset) msg.obj);
+                            }
                         }
                     }
                     break;
-                case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION:
-                case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION: {
-                    final BtDeviceConnectionInfo info = (BtDeviceConnectionInfo) msg.obj;
+                case MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT: {
+                    final BtDeviceInfo info = (BtDeviceInfo) msg.obj;
+                    if (info.mDevice == null) break;
                     AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
-                            "msg: setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent "
+                            "msg: onBluetoothActiveDeviceChange "
                                     + " state=" + info.mState
                                     // only querying address as this is the only readily available
                                     // field on the device
                                     + " addr=" + info.mDevice.getAddress()
-                                    + " prof=" + info.mProfile + " supprNoisy=" + info.mSupprNoisy
-                                    + " vol=" + info.mVolume)).printLog(TAG));
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.setBluetoothA2dpDeviceConnectionState(
-                                info.mDevice, info.mState, info.mProfile, info.mSupprNoisy,
-                                AudioSystem.DEVICE_NONE, info.mVolume);
-                    }
-                } break;
-                case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: {
-                    final HearingAidDeviceConnectionInfo info =
-                            (HearingAidDeviceConnectionInfo) msg.obj;
-                    AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
-                            "msg: setHearingAidDeviceConnectionState state=" + info.mState
-                                    + " addr=" + info.mDevice.getAddress()
+                                    + " prof=" + info.mProfile
                                     + " supprNoisy=" + info.mSupprNoisy
-                                    + " src=" + info.mEventSource)).printLog(TAG));
+                                    + " src=" + info.mEventSource
+                                    )).printLog(TAG));
                     synchronized (mDeviceStateLock) {
-                        mDeviceInventory.setBluetoothHearingAidDeviceConnectionState(
-                                info.mDevice, info.mState, info.mSupprNoisy, info.mMusicDevice);
+                        mDeviceInventory.setBluetoothActiveDevice(info);
                     }
                 } break;
                 case MSG_IL_SAVE_PREF_DEVICES_FOR_STRATEGY: {
@@ -1524,31 +1358,6 @@
                     final int capturePreset = msg.arg1;
                     mDeviceInventory.onSaveClearPreferredDevicesForCapturePreset(capturePreset);
                 } break;
-                case MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT: {
-                    final LeAudioDeviceConnectionInfo info =
-                            (LeAudioDeviceConnectionInfo) msg.obj;
-                    AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
-                            "setLeAudioDeviceOutConnectionState state=" + info.mState
-                                    + " addr=" + info.mDevice.getAddress()
-                                    + " supprNoisy=" + info.mSupprNoisy
-                                    + " src=" + info.mEventSource)).printLog(TAG));
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.setBluetoothLeAudioOutDeviceConnectionState(
-                                info.mDevice, info.mState, info.mSupprNoisy);
-                    }
-                } break;
-                case MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT: {
-                    final LeAudioDeviceConnectionInfo info =
-                            (LeAudioDeviceConnectionInfo) msg.obj;
-                    AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
-                            "setLeAudioDeviceInConnectionState state=" + info.mState
-                                    + " addr=" + info.mDevice.getAddress()
-                                    + " src=" + info.mEventSource)).printLog(TAG));
-                    synchronized (mDeviceStateLock) {
-                        mDeviceInventory.setBluetoothLeAudioInDeviceConnectionState(info.mDevice,
-                                info.mState);
-                    }
-                } break;
                 default:
                     Log.wtf(TAG, "Invalid message " + msg.what);
             }
@@ -1579,8 +1388,7 @@
     private static final int MSG_IIL_SET_FORCE_USE = 4;
     private static final int MSG_IIL_SET_FORCE_BT_A2DP_USE = 5;
     private static final int MSG_TOGGLE_HDMI = 6;
-    private static final int MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE = 7;
-    private static final int MSG_IL_SET_HEARING_AID_CONNECTION_STATE = 8;
+    private static final int MSG_L_SET_BT_ACTIVE_DEVICE = 7;
     private static final int MSG_BT_HEADSET_CNCT_FAILED = 9;
     private static final int MSG_IL_BTA2DP_TIMEOUT = 10;
 
@@ -1593,25 +1401,11 @@
     private static final int MSG_I_SET_AVRCP_ABSOLUTE_VOLUME = 15;
     private static final int MSG_I_SET_MODE_OWNER_PID = 16;
 
-    // process active A2DP device change, obj is BtHelper.BluetoothA2dpDeviceInfo
-    private static final int MSG_L_A2DP_ACTIVE_DEVICE_CHANGE = 18;
-
-    private static final int MSG_DISCONNECT_A2DP = 19;
-    private static final int MSG_DISCONNECT_A2DP_SINK = 20;
-    private static final int MSG_DISCONNECT_BT_HEARING_AID = 21;
-    private static final int MSG_DISCONNECT_BT_HEADSET = 22;
-    private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP = 23;
-    private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_A2DP_SINK = 24;
-    private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEARING_AID = 25;
-    private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_HEADSET = 26;
-
-    // process change of state, obj is BtHelper.BluetoothA2dpDeviceInfo
-    private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED = 27;
-    private static final int MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED = 28;
+    private static final int MSG_I_BT_SERVICE_DISCONNECTED_PROFILE = 22;
+    private static final int MSG_IL_BT_SERVICE_CONNECTED_PROFILE = 23;
 
     // process external command to (dis)connect an A2DP device, obj is BtDeviceConnectionInfo
-    private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION = 29;
-    private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION = 30;
+    private static final int MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT = 29;
 
     // process external command to (dis)connect a hearing aid device
     private static final int MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT = 31;
@@ -1630,33 +1424,21 @@
     private static final int MSG_IL_SET_PREF_DEVICES_FOR_STRATEGY = 40;
     private static final int MSG_I_REMOVE_PREF_DEVICES_FOR_STRATEGY = 41;
 
-    private static final int MSG_IL_SET_LE_AUDIO_OUT_CONNECTION_STATE = 42;
-    private static final int MSG_IL_SET_LE_AUDIO_IN_CONNECTION_STATE = 43;
-    private static final int MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT = 44;
-    private static final int MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT = 45;
+    private static final int MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT = 45;
+    //
     // process set volume for Le Audio, obj is BleVolumeInfo
     private static final int MSG_II_SET_LE_AUDIO_OUT_VOLUME = 46;
 
-    private static final int MSG_L_BT_SERVICE_CONNECTED_PROFILE_LE_AUDIO = 47;
-    private static final int MSG_DISCONNECT_BT_LE_AUDIO = 48;
-
     private static boolean isMessageHandledUnderWakelock(int msgId) {
         switch(msgId) {
             case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
-            case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
-            case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
-            case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
-            case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+            case MSG_L_SET_BT_ACTIVE_DEVICE:
             case MSG_IL_BTA2DP_TIMEOUT:
             case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
             case MSG_TOGGLE_HDMI:
-            case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
-            case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION:
-            case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION:
+            case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT:
             case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT:
             case MSG_CHECK_MUTE_MUSIC:
-            case MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT:
-            case MSG_L_LE_AUDIO_DEVICE_IN_CONNECTION_CHANGE_EXT:
                 return true;
             default:
                 return false;
@@ -1739,14 +1521,10 @@
             long time = SystemClock.uptimeMillis() + delay;
 
             switch (msg) {
-                case MSG_IL_SET_A2DP_SOURCE_CONNECTION_STATE:
-                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED:
-                case MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED:
-                case MSG_IL_SET_HEARING_AID_CONNECTION_STATE:
+                case MSG_L_SET_BT_ACTIVE_DEVICE:
                 case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE:
                 case MSG_IL_BTA2DP_TIMEOUT:
                 case MSG_L_A2DP_DEVICE_CONFIG_CHANGE:
-                case MSG_L_A2DP_ACTIVE_DEVICE_CHANGE:
                     if (sLastDeviceConnectMsgTime >= time) {
                         // add a little delay to make sure messages are ordered as expected
                         time = sLastDeviceConnectMsgTime + 30;
@@ -1765,14 +1543,9 @@
     private static final Set<Integer> MESSAGES_MUTE_MUSIC;
     static {
         MESSAGES_MUTE_MUSIC = new HashSet<>();
-        MESSAGES_MUTE_MUSIC.add(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_CONNECTED);
-        MESSAGES_MUTE_MUSIC.add(MSG_IL_SET_A2DP_SINK_CONNECTION_STATE_DISCONNECTED);
-        MESSAGES_MUTE_MUSIC.add(MSG_IL_SET_LE_AUDIO_OUT_CONNECTION_STATE);
+        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_A2DP_ACTIVE_DEVICE_CHANGE);
-        MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_CONNECTION);
-        MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT_DISCONNECTION);
-        MESSAGES_MUTE_MUSIC.add(MSG_L_LE_AUDIO_DEVICE_OUT_CONNECTION_CHANGE_EXT);
+        MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT);
         MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE);
         MESSAGES_MUTE_MUSIC.add(MSG_REPORT_NEW_ROUTES_A2DP);
     }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 6c3c736..0a114b9 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -16,11 +16,8 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
-import android.bluetooth.BluetoothA2dp;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothHearingAid;
-import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.content.Intent;
 import android.media.AudioDeviceAttributes;
@@ -286,186 +283,102 @@
         }
     }
 
-    // only public for mocking/spying
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-    @VisibleForTesting
-    public void onSetA2dpSinkConnectionState(@NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo,
-            @AudioService.BtProfileConnectionState int state) {
-        final BluetoothDevice btDevice = btInfo.getBtDevice();
-        int a2dpVolume = btInfo.getVolume();
+    void onSetBtActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int streamType) {
         if (AudioService.DEBUG_DEVICES) {
-            Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state="
-                    + state + " vol=" + a2dpVolume);
+            Log.d(TAG, "onSetBtActiveDevice"
+                    + " btDevice=" + btInfo.mDevice
+                    + " profile=" + BluetoothProfile.getProfileName(btInfo.mProfile)
+                    + " state=" + BluetoothProfile.getConnectionStateName(btInfo.mState));
         }
-        String address = btDevice.getAddress();
-        if (address == null) {
-            address = "";
-        }
+        String address = btInfo.mDevice.getAddress();
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
         }
 
-        final @AudioSystem.AudioFormatNativeEnumForBtCodec int a2dpCodec = btInfo.getCodec();
+        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent("BT connected:"
+                        + " addr=" + address
+                        + " profile=" + btInfo.mProfile
+                        + " state=" + btInfo.mState
+                        + " codec=" + AudioSystem.audioFormatToString(btInfo.mCodec)));
 
-        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
-                "A2DP sink connected: device addr=" + address + " state=" + state
-                        + " codec=" + AudioSystem.audioFormatToString(a2dpCodec)
-                        + " vol=" + a2dpVolume));
-
-        new MediaMetrics.Item(mMetricsId + "a2dp")
+        new MediaMetrics.Item(mMetricsId + "onSetBtActiveDevice")
+                .set(MediaMetrics.Property.STATUS, btInfo.mProfile)
+                .set(MediaMetrics.Property.DEVICE,
+                        AudioSystem.getDeviceName(btInfo.mAudioSystemDevice))
                 .set(MediaMetrics.Property.ADDRESS, address)
-                .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(a2dpCodec))
-                .set(MediaMetrics.Property.EVENT, "onSetA2dpSinkConnectionState")
-                .set(MediaMetrics.Property.INDEX, a2dpVolume)
+                .set(MediaMetrics.Property.ENCODING,
+                        AudioSystem.audioFormatToString(btInfo.mCodec))
+                .set(MediaMetrics.Property.EVENT, "onSetBtActiveDevice")
+                .set(MediaMetrics.Property.STREAM_TYPE,
+                        AudioSystem.streamToString(streamType))
                 .set(MediaMetrics.Property.STATE,
-                        state == BluetoothProfile.STATE_CONNECTED
+                        btInfo.mState == BluetoothProfile.STATE_CONNECTED
                         ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
                 .record();
 
         synchronized (mDevicesLock) {
-            final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                    btDevice.getAddress());
+            final String key = DeviceInfo.makeDeviceListKey(btInfo.mAudioSystemDevice, address);
             final DeviceInfo di = mConnectedDevices.get(key);
-            boolean isConnected = di != null;
 
-            if (isConnected) {
-                if (state == BluetoothProfile.STATE_CONNECTED) {
-                    // device is already connected, but we are receiving a connection again,
-                    // it could be for a codec change
-                    if (a2dpCodec != di.mDeviceCodecFormat) {
-                        mDeviceBroker.postBluetoothA2dpDeviceConfigChange(btDevice);
+            final boolean isConnected = di != null;
+
+            final boolean switchToUnavailable = isConnected
+                    && btInfo.mState != BluetoothProfile.STATE_CONNECTED;
+            final boolean switchToAvailable = !isConnected
+                    && btInfo.mState == BluetoothProfile.STATE_CONNECTED;
+
+            switch (btInfo.mProfile) {
+                case BluetoothProfile.A2DP_SINK:
+                    if (switchToUnavailable) {
+                        makeA2dpSrcUnavailable(address);
+                    } else if (switchToAvailable) {
+                        makeA2dpSrcAvailable(address);
                     }
-                } else {
-                    makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
-                }
-            } else if (state == BluetoothProfile.STATE_CONNECTED) {
-                // device is not already connected
-                if (a2dpVolume != -1) {
-                    mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
-                            // convert index to internal representation in VolumeStreamState
-                            a2dpVolume * 10,
-                            AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "onSetA2dpSinkConnectionState");
-                }
-                makeA2dpDeviceAvailable(address, BtHelper.getName(btDevice),
-                        "onSetA2dpSinkConnectionState", a2dpCodec);
+                    break;
+                case BluetoothProfile.A2DP:
+                    if (switchToUnavailable) {
+                        makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat);
+                    } else if (switchToAvailable) {
+                        // device is not already connected
+                        if (btInfo.mVolume != -1) {
+                            mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC,
+                                    // convert index to internal representation in VolumeStreamState
+                                    btInfo.mVolume * 10, btInfo.mAudioSystemDevice,
+                                    "onSetBtActiveDevice");
+                        }
+                        makeA2dpDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
+                                "onSetBtActiveDevice", btInfo.mCodec);
+                    }
+                    break;
+                case BluetoothProfile.HEARING_AID:
+                    if (switchToUnavailable) {
+                        makeHearingAidDeviceUnavailable(address);
+                    } else if (switchToAvailable) {
+                        makeHearingAidDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
+                                streamType, "onSetBtActiveDevice");
+                    }
+                    break;
+                case BluetoothProfile.LE_AUDIO:
+                    if (switchToUnavailable) {
+                        makeLeAudioDeviceUnavailable(address, btInfo.mAudioSystemDevice);
+                    } else if (switchToAvailable) {
+                        makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice),
+                                streamType, btInfo.mAudioSystemDevice, "onSetBtActiveDevice");
+                    }
+                    break;
+                default: throw new IllegalArgumentException("Invalid profile "
+                                 + BluetoothProfile.getProfileName(btInfo.mProfile));
             }
         }
     }
 
-    /*package*/ void onSetA2dpSourceConnectionState(
-            @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int state) {
-        final BluetoothDevice btDevice = btInfo.getBtDevice();
-        if (AudioService.DEBUG_DEVICES) {
-            Log.d(TAG, "onSetA2dpSourceConnectionState btDevice=" + btDevice + " state="
-                    + state);
-        }
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-
-        synchronized (mDevicesLock) {
-            final String key = DeviceInfo.makeDeviceListKey(
-                    AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address);
-            final DeviceInfo di = mConnectedDevices.get(key);
-            boolean isConnected = di != null;
-
-            new MediaMetrics.Item(mMetricsId + "onSetA2dpSourceConnectionState")
-                    .set(MediaMetrics.Property.ADDRESS, address)
-                    .set(MediaMetrics.Property.DEVICE,
-                            AudioSystem.getDeviceName(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP))
-                    .set(MediaMetrics.Property.STATE,
-                            state == BluetoothProfile.STATE_CONNECTED
-                            ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
-                    .record();
-
-            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
-                makeA2dpSrcUnavailable(address);
-            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                makeA2dpSrcAvailable(address);
-            }
-        }
-    }
-
-    /*package*/ void onSetHearingAidConnectionState(BluetoothDevice btDevice,
-                @AudioService.BtProfileConnectionState int state, int streamType) {
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
-                "onSetHearingAidConnectionState addr=" + address));
-
-        new MediaMetrics.Item(mMetricsId + "onSetHearingAidConnectionState")
-                .set(MediaMetrics.Property.ADDRESS, address)
-                .set(MediaMetrics.Property.DEVICE,
-                        AudioSystem.getDeviceName(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP))
-                .set(MediaMetrics.Property.STATE,
-                        state == BluetoothProfile.STATE_CONNECTED
-                                ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
-                .set(MediaMetrics.Property.STREAM_TYPE,
-                        AudioSystem.streamToString(streamType))
-                .record();
-
-        synchronized (mDevicesLock) {
-            final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
-                    btDevice.getAddress());
-            final DeviceInfo di = mConnectedDevices.get(key);
-            boolean isConnected = di != null;
-
-            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
-                makeHearingAidDeviceUnavailable(address);
-            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                makeHearingAidDeviceAvailable(address, BtHelper.getName(btDevice), streamType,
-                        "onSetHearingAidConnectionState");
-            }
-        }
-    }
-
-    /*package*/ void onSetLeAudioConnectionState(BluetoothDevice btDevice,
-                @AudioService.BtProfileConnectionState int state, int streamType, int device) {
-        String address = btDevice.getAddress();
-        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-            address = "";
-        }
-        AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
-                "onSetLeAudioConnectionState addr=" + address));
-
-        synchronized (mDevicesLock) {
-            DeviceInfo di = null;
-            boolean isConnected = false;
-
-            String key = DeviceInfo.makeDeviceListKey(device, btDevice.getAddress());
-            di = mConnectedDevices.get(key);
-            isConnected = di != null;
-
-            if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
-                makeLeAudioDeviceUnavailable(address, device);
-            } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
-                makeLeAudioDeviceAvailable(address, BtHelper.getName(btDevice), streamType,
-                        device, "onSetLeAudioConnectionState");
-            }
-        }
-    }
-
-    /*package*/ void onSetLeAudioOutConnectionState(BluetoothDevice btDevice,
-                @AudioService.BtProfileConnectionState int state, int streamType) {
-        // TODO: b/198610537 clarify DEVICE_OUT_BLE_HEADSET vs DEVICE_OUT_BLE_SPEAKER criteria
-        onSetLeAudioConnectionState(btDevice, state, streamType,
-                AudioSystem.DEVICE_OUT_BLE_HEADSET);
-    }
-
-    /*package*/ void onSetLeAudioInConnectionState(BluetoothDevice btDevice,
-                @AudioService.BtProfileConnectionState int state) {
-        onSetLeAudioConnectionState(btDevice, state, AudioSystem.STREAM_DEFAULT,
-                AudioSystem.DEVICE_IN_BLE_HEADSET);
-    }
 
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-        /*package*/ void onBluetoothA2dpActiveDeviceChange(
+        /*package*/ void onBluetoothA2dpDeviceConfigChange(
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) {
         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
-                + "onBluetoothA2dpActiveDeviceChange")
+                + "onBluetoothA2dpDeviceConfigChange")
                 .set(MediaMetrics.Property.EVENT, BtHelper.a2dpDeviceEventToString(event));
 
         final BluetoothDevice btDevice = btInfo.getBtDevice();
@@ -474,7 +387,7 @@
             return;
         }
         if (AudioService.DEBUG_DEVICES) {
-            Log.d(TAG, "onBluetoothA2dpActiveDeviceChange btDevice=" + btDevice);
+            Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice);
         }
         int a2dpVolume = btInfo.getVolume();
         @AudioSystem.AudioFormatNativeEnumForBtCodec final int a2dpCodec = btInfo.getCodec();
@@ -484,11 +397,11 @@
             address = "";
         }
         AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
-                "onBluetoothA2dpActiveDeviceChange addr=" + address
+                "onBluetoothA2dpDeviceConfigChange addr=" + address
                     + " event=" + BtHelper.a2dpDeviceEventToString(event)));
 
         synchronized (mDevicesLock) {
-            if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
+            if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
                 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
                         "A2dp config change ignored (scheduled connection change)")
                         .printLog(TAG));
@@ -500,7 +413,7 @@
                     AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address);
             final DeviceInfo di = mConnectedDevices.get(key);
             if (di == null) {
-                Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpActiveDeviceChange");
+                Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange");
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
                 return;
             }
@@ -518,7 +431,7 @@
                             // convert index to internal representation in VolumeStreamState
                             a2dpVolume * 10,
                             AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                            "onBluetoothA2dpActiveDeviceChange");
+                            "onBluetoothA2dpDeviceConfigChange");
                 }
             } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) {
                 if (di.mDeviceCodecFormat != a2dpCodec) {
@@ -539,10 +452,9 @@
                 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
-                setBluetoothA2dpDeviceConnectionState(
-                        btDevice, BluetoothA2dp.STATE_DISCONNECTED, BluetoothProfile.A2DP,
-                        false /* suppressNoisyIntent */, musicDevice,
-                        -1 /* a2dpVolume */);
+                setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btDevice,
+                                BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED,
+                                musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
             } else {
                 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
                         "APM handleDeviceConfigChange success for A2DP device addr=" + address
@@ -828,7 +740,7 @@
     }
 
 
-    /*package*/ void disconnectA2dp() {
+    private void disconnectA2dp() {
         synchronized (mDevicesLock) {
             final ArraySet<String> toRemove = new ArraySet<>();
             // Disconnect ALL DEVICE_OUT_BLUETOOTH_A2DP devices
@@ -850,7 +762,7 @@
         }
     }
 
-    /*package*/ void disconnectA2dpSink() {
+    private void disconnectA2dpSink() {
         synchronized (mDevicesLock) {
             final ArraySet<String> toRemove = new ArraySet<>();
             // Disconnect ALL DEVICE_IN_BLUETOOTH_A2DP devices
@@ -865,7 +777,7 @@
         }
     }
 
-    /*package*/ void disconnectHearingAid() {
+    private void disconnectHearingAid() {
         synchronized (mDevicesLock) {
             final ArraySet<String> toRemove = new ArraySet<>();
             // Disconnect ALL DEVICE_OUT_HEARING_AID devices
@@ -887,6 +799,28 @@
         }
     }
 
+    /*package*/ synchronized void onBtProfileDisconnected(int profile) {
+        switch (profile) {
+            case BluetoothProfile.A2DP:
+                disconnectA2dp();
+                break;
+            case BluetoothProfile.A2DP_SINK:
+                disconnectA2dpSink();
+                break;
+            case BluetoothProfile.HEARING_AID:
+                disconnectHearingAid();
+                break;
+            case BluetoothProfile.LE_AUDIO:
+                disconnectLeAudio();
+                break;
+            default:
+                // Not a valid profile to disconnect
+                Log.e(TAG, "onBtProfileDisconnected: Not a valid profile to disconnect "
+                        + BluetoothProfile.getProfileName(profile));
+                break;
+        }
+    }
+
      /*package*/ void disconnectLeAudio() {
         synchronized (mDevicesLock) {
             final ArraySet<String> toRemove = new ArraySet<>();
@@ -934,46 +868,39 @@
     // only public for mocking/spying
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     @VisibleForTesting
-    public void setBluetoothA2dpDeviceConnectionState(
-            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
-            int profile, boolean suppressNoisyIntent, int musicDevice, int a2dpVolume) {
+    public int setBluetoothActiveDevice(@NonNull AudioDeviceBroker.BtDeviceInfo info) {
         int delay;
-        if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK) {
-            throw new IllegalArgumentException("invalid profile " + profile);
-        }
         synchronized (mDevicesLock) {
-            if (profile == BluetoothProfile.A2DP && !suppressNoisyIntent) {
+            if (!info.mSupprNoisy
+                    && ((info.mProfile == BluetoothProfile.LE_AUDIO && info.mIsLeOutput)
+                        || info.mProfile == BluetoothProfile.HEARING_AID
+                        || info.mProfile == BluetoothProfile.A2DP)) {
                 @AudioService.ConnectionState int asState =
-                        (state == BluetoothA2dp.STATE_CONNECTED)
+                        (info.mState == BluetoothProfile.STATE_CONNECTED)
                                 ? AudioService.CONNECTION_STATE_CONNECTED
                                 : AudioService.CONNECTION_STATE_DISCONNECTED;
-                delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
-                        asState, musicDevice);
+                delay = checkSendBecomingNoisyIntentInt(info.mAudioSystemDevice, asState,
+                        info.mMusicDevice);
             } else {
                 delay = 0;
             }
 
-            final int a2dpCodec = mDeviceBroker.getA2dpCodec(device);
-
             if (AudioService.DEBUG_DEVICES) {
-                Log.i(TAG, "setBluetoothA2dpDeviceConnectionState device: " + device
-                        + " state: " + state + " delay(ms): " + delay
-                        + " codec:" + Integer.toHexString(a2dpCodec)
-                        + " suppressNoisyIntent: " + suppressNoisyIntent);
+                Log.i(TAG, "setBluetoothActiveDevice device: " + info.mDevice
+                        + " profile: " + BluetoothProfile.getProfileName(info.mProfile)
+                        + " state: " + BluetoothProfile.getConnectionStateName(info.mState)
+                        + " delay(ms): " + delay
+                        + " codec:" + Integer.toHexString(info.mCodec)
+                        + " suppressNoisyIntent: " + info.mSupprNoisy);
             }
-
-            final BtHelper.BluetoothA2dpDeviceInfo a2dpDeviceInfo =
-                    new BtHelper.BluetoothA2dpDeviceInfo(device, a2dpVolume, a2dpCodec);
-            if (profile == BluetoothProfile.A2DP) {
-                mDeviceBroker.postA2dpSinkConnection(state,
-                            a2dpDeviceInfo,
-                            delay);
-            } else { //profile == BluetoothProfile.A2DP_SINK
-                mDeviceBroker.postA2dpSourceConnection(state,
-                        a2dpDeviceInfo,
-                        delay);
+            mDeviceBroker.postBluetoothActiveDevice(info, delay);
+            if (info.mProfile == BluetoothProfile.HEARING_AID
+                    && info.mState == BluetoothProfile.STATE_CONNECTED) {
+                mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE,
+                                "HEARING_AID set to CONNECTED");
             }
         }
+        return delay;
     }
 
     /*package*/ int setWiredDeviceConnectionState(int type, @AudioService.ConnectionState int state,
@@ -987,50 +914,6 @@
         }
     }
 
-    /*package*/ int  setBluetoothHearingAidDeviceConnectionState(
-            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
-            boolean suppressNoisyIntent, int musicDevice) {
-        int delay;
-        synchronized (mDevicesLock) {
-            if (!suppressNoisyIntent) {
-                int intState = (state == BluetoothHearingAid.STATE_CONNECTED) ? 1 : 0;
-                delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_HEARING_AID,
-                        intState, musicDevice);
-            } else {
-                delay = 0;
-            }
-            mDeviceBroker.postSetHearingAidConnectionState(state, device, delay);
-            if (state == BluetoothHearingAid.STATE_CONNECTED) {
-                mDeviceBroker.setForceUse_Async(AudioSystem.FOR_MEDIA, AudioSystem.FORCE_NONE,
-                                "HEARING_AID set to CONNECTED");
-            }
-            return delay;
-        }
-    }
-
-    /*package*/ int setBluetoothLeAudioOutDeviceConnectionState(
-            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state,
-            boolean suppressNoisyIntent) {
-        synchronized (mDevicesLock) {
-            /* Active device become null and it's previous device is not connected anymore */
-            int delay = 0;
-            if (!suppressNoisyIntent) {
-                int intState = (state == BluetoothLeAudio.STATE_CONNECTED) ? 1 : 0;
-                delay = checkSendBecomingNoisyIntentInt(AudioSystem.DEVICE_OUT_BLE_HEADSET,
-                        intState, AudioSystem.DEVICE_NONE);
-            }
-            mDeviceBroker.postSetLeAudioOutConnectionState(state, device, delay);
-            return delay;
-        }
-    }
-
-    /*package*/ void setBluetoothLeAudioInDeviceConnectionState(
-            @NonNull BluetoothDevice device, @AudioService.BtProfileConnectionState int state) {
-        synchronized (mDevicesLock) {
-            mDeviceBroker.postSetLeAudioInConnectionState(state, device);
-        }
-    }
-
     //-------------------------------------------------------------------
     // Internal utilities
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index d75f21c..51784bb 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -83,6 +83,7 @@
 import android.media.AudioRecordingConfiguration;
 import android.media.AudioRoutesInfo;
 import android.media.AudioSystem;
+import android.media.BtProfileConnectionInfo;
 import android.media.IAudioFocusDispatcher;
 import android.media.IAudioModeDispatcher;
 import android.media.IAudioRoutesObserver;
@@ -309,8 +310,8 @@
     private static final int MSG_UPDATE_A11Y_SERVICE_UIDS = 35;
     private static final int MSG_UPDATE_AUDIO_MODE = 36;
     private static final int MSG_RECORDING_CONFIG_CHANGE = 37;
-    private static final int MSG_SET_A2DP_DEV_CONNECTION_STATE = 38;
-    private static final int MSG_A2DP_DEV_CONFIG_CHANGE = 39;
+    private static final int MSG_BT_DEV_CHANGED = 38;
+
     private static final int MSG_DISPATCH_AUDIO_MODE = 40;
 
     // start of messages handled under wakelock
@@ -6266,77 +6267,44 @@
     public @interface BtProfileConnectionState {}
 
     /**
-     * See AudioManager.setBluetoothHearingAidDeviceConnectionState()
+     * @hide
+     * The profiles that can be used with AudioService.handleBluetoothActiveDeviceChanged()
      */
-    public void setBluetoothHearingAidDeviceConnectionState(
-            @NonNull BluetoothDevice device, @BtProfileConnectionState int state,
-            boolean suppressNoisyIntent, int musicDevice)
-    {
-        if (device == null) {
-            throw new IllegalArgumentException("Illegal null device");
-        }
-        if (state != BluetoothProfile.STATE_CONNECTED
-                && state != BluetoothProfile.STATE_DISCONNECTED) {
-            throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
-                    + " (dis)connection, got " + state);
-        }
-        mDeviceBroker.postBluetoothHearingAidDeviceConnectionState(
-                device, state, suppressNoisyIntent, musicDevice, "AudioService");
-    }
+    @IntDef({
+            BluetoothProfile.HEARING_AID,
+            BluetoothProfile.A2DP,
+            BluetoothProfile.A2DP_SINK,
+            BluetoothProfile.LE_AUDIO,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BtProfile {}
 
-    private void setBluetoothLeAudioDeviceConnectionState(@NonNull BluetoothDevice device,
-            @BtProfileConnectionState int state) {
-        if (device == null) {
-            throw new IllegalArgumentException("Illegal null device");
-        }
-        if (state != BluetoothProfile.STATE_CONNECTED
-                && state != BluetoothProfile.STATE_DISCONNECTED) {
-            throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
-                    + " (dis)connection, got " + state);
-        }
-    }
 
     /**
-     * See AudioManager.setBluetoothLeAudioOutDeviceConnectionState()
+     * See AudioManager.handleBluetoothActiveDeviceChanged(...)
      */
-    public void setBluetoothLeAudioOutDeviceConnectionState(
-            @NonNull BluetoothDevice device, @BtProfileConnectionState int state,
-            boolean suppressNoisyIntent) {
-        setBluetoothLeAudioDeviceConnectionState(device, state);
-        mDeviceBroker.postBluetoothLeAudioOutDeviceConnectionState(device, state,
-                suppressNoisyIntent, "AudioService");
-    }
-
-    /**
-     * See AudioManager.setBluetoothLeAudioInDeviceConnectionState()
-     */
-    public void setBluetoothLeAudioInDeviceConnectionState(
-            @NonNull BluetoothDevice device, @BtProfileConnectionState int state) {
-        setBluetoothLeAudioDeviceConnectionState(device, state);
-        mDeviceBroker.postBluetoothLeAudioInDeviceConnectionState(device, state, "AudioService");
-    }
-
-    /**
-     * See AudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent()
-     */
-    public void setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-            @NonNull BluetoothDevice device, @BtProfileConnectionState int state,
-            int profile, boolean suppressNoisyIntent, int a2dpVolume) {
-        if (device == null) {
-            throw new IllegalArgumentException("Illegal null device");
+    public void handleBluetoothActiveDeviceChanged(BluetoothDevice newDevice,
+            BluetoothDevice previousDevice, @NonNull BtProfileConnectionInfo info) {
+        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.BLUETOOTH_STACK)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("Bluetooth is the only caller allowed");
         }
-        if (state != BluetoothProfile.STATE_CONNECTED
-                && state != BluetoothProfile.STATE_DISCONNECTED) {
-            throw new IllegalArgumentException("Illegal BluetoothProfile state for device "
-                    + " (dis)connection, got " + state);
+        if (info == null) {
+            throw new IllegalArgumentException("Illegal null BtProfileConnectionInfo for device "
+                    + previousDevice + " -> " + newDevice);
         }
-
-        AudioDeviceBroker.BtDeviceConnectionInfo info =
-                new AudioDeviceBroker.BtDeviceConnectionInfo(device, state,
-                        profile, suppressNoisyIntent, a2dpVolume);
-        sendMsg(mAudioHandler, MSG_SET_A2DP_DEV_CONNECTION_STATE, SENDMSG_QUEUE,
-                0 /*arg1*/, 0 /*arg2*/,
-                /*obj*/ info, 0 /*delay*/);
+        final int profile = info.getProfile();
+        if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK
+                && profile != BluetoothProfile.LE_AUDIO
+                && profile != BluetoothProfile.HEARING_AID) {
+            throw new IllegalArgumentException("Illegal BluetoothProfile profile for device "
+                    + previousDevice + " -> " + newDevice + ". Got: " + profile);
+        }
+        AudioDeviceBroker.BtDeviceChangedData data =
+                new AudioDeviceBroker.BtDeviceChangedData(newDevice, previousDevice, info,
+                        "AudioService");
+        sendMsg(mAudioHandler, MSG_BT_DEV_CHANGED, SENDMSG_QUEUE, 0, 0,
+                /*obj*/ data, /*delay*/ 0);
     }
 
     /** only public for mocking/spying, do not call outside of AudioService */
@@ -6345,19 +6313,6 @@
         mStreamStates[AudioSystem.STREAM_MUSIC].muteInternally(mute);
     }
 
-    /**
-     * See AudioManager.handleBluetoothA2dpDeviceConfigChange()
-     * @param device
-     */
-    public void handleBluetoothA2dpDeviceConfigChange(BluetoothDevice device)
-    {
-        if (device == null) {
-            throw new IllegalArgumentException("Illegal null device");
-        }
-        sendMsg(mAudioHandler, MSG_A2DP_DEV_CONFIG_CHANGE, SENDMSG_QUEUE, 0, 0,
-                /*obj*/ device, /*delay*/ 0);
-    }
-
     private static final Set<Integer> DEVICE_MEDIA_UNMUTED_ON_PLUG_SET;
     static {
         DEVICE_MEDIA_UNMUTED_ON_PLUG_SET = new HashSet<>();
@@ -7709,13 +7664,9 @@
                     }
                     break;
 
-                case MSG_SET_A2DP_DEV_CONNECTION_STATE:
-                    mDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                            (AudioDeviceBroker.BtDeviceConnectionInfo) msg.obj);
-                    break;
-
-                case MSG_A2DP_DEV_CONFIG_CHANGE:
-                    mDeviceBroker.postBluetoothA2dpDeviceConfigChange((BluetoothDevice) msg.obj);
+                case MSG_BT_DEV_CHANGED:
+                    mDeviceBroker.queueOnBluetoothActiveDeviceChanged(
+                            (AudioDeviceBroker.BtDeviceChangedData) msg.obj);
                     break;
 
                 case MSG_DISPATCH_AUDIO_MODE:
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index c924fde..9273a5d 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -31,6 +31,7 @@
 import android.media.AudioDeviceAttributes;
 import android.media.AudioManager;
 import android.media.AudioSystem;
+import android.media.BtProfileConnectionInfo;
 import android.os.Binder;
 import android.os.UserHandle;
 import android.provider.Settings;
@@ -451,11 +452,11 @@
     }
 
     /*package*/ synchronized void disconnectAllBluetoothProfiles() {
-        mDeviceBroker.postDisconnectA2dp();
-        mDeviceBroker.postDisconnectA2dpSink();
-        mDeviceBroker.postDisconnectHeadset();
-        mDeviceBroker.postDisconnectHearingAid();
-        mDeviceBroker.postDisconnectLeAudio();
+        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.A2DP);
+        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.A2DP_SINK);
+        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.HEADSET);
+        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.HEARING_AID);
+        mDeviceBroker.postBtProfileDisconnected(BluetoothProfile.LE_AUDIO);
     }
 
     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
@@ -474,63 +475,32 @@
         mBluetoothHeadset = null;
     }
 
-    /*package*/ synchronized void onA2dpProfileConnected(BluetoothA2dp a2dp) {
-        mA2dp = a2dp;
-        final List<BluetoothDevice> deviceList = mA2dp.getConnectedDevices();
-        if (deviceList.isEmpty()) {
+    /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
+        if (profile == BluetoothProfile.HEADSET) {
+            onHeadsetProfileConnected((BluetoothHeadset) proxy);
             return;
         }
-        final BluetoothDevice btDevice = deviceList.get(0);
-        // the device is guaranteed CONNECTED
-        mDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                new AudioDeviceBroker.BtDeviceConnectionInfo(btDevice,
-                    BluetoothA2dp.STATE_CONNECTED, BluetoothProfile.A2DP_SINK,
-                        true, -1));
-    }
-
-    /*package*/ synchronized void onA2dpSinkProfileConnected(BluetoothProfile profile) {
-        final List<BluetoothDevice> deviceList = profile.getConnectedDevices();
+        if (profile == BluetoothProfile.A2DP) {
+            mA2dp = (BluetoothA2dp) proxy;
+        } else if (profile == BluetoothProfile.LE_AUDIO) {
+            mLeAudio = (BluetoothLeAudio) proxy;
+        }
+        final List<BluetoothDevice> deviceList = proxy.getConnectedDevices();
         if (deviceList.isEmpty()) {
             return;
         }
         final BluetoothDevice btDevice = deviceList.get(0);
         final @BluetoothProfile.BtProfileState int state =
-                profile.getConnectionState(btDevice);
-        mDeviceBroker.postSetA2dpSourceConnectionState(
-                state, new BluetoothA2dpDeviceInfo(btDevice));
-    }
-
-    /*package*/ synchronized void onHearingAidProfileConnected(BluetoothHearingAid hearingAid) {
-        mHearingAid = hearingAid;
-        final List<BluetoothDevice> deviceList = mHearingAid.getConnectedDevices();
-        if (deviceList.isEmpty()) {
-            return;
+                proxy.getConnectionState(btDevice);
+        if (state == BluetoothProfile.STATE_CONNECTED) {
+            mDeviceBroker.queueOnBluetoothActiveDeviceChanged(
+                    new AudioDeviceBroker.BtDeviceChangedData(btDevice, null,
+                        new BtProfileConnectionInfo(profile), "mBluetoothProfileServiceListener"));
+        } else {
+            mDeviceBroker.queueOnBluetoothActiveDeviceChanged(
+                    new AudioDeviceBroker.BtDeviceChangedData(null, btDevice,
+                        new BtProfileConnectionInfo(profile), "mBluetoothProfileServiceListener"));
         }
-        final BluetoothDevice btDevice = deviceList.get(0);
-        final @BluetoothProfile.BtProfileState int state =
-                mHearingAid.getConnectionState(btDevice);
-        mDeviceBroker.postBluetoothHearingAidDeviceConnectionState(
-                btDevice, state,
-                /*suppressNoisyIntent*/ false,
-                /*musicDevice*/ android.media.AudioSystem.DEVICE_NONE,
-                /*eventSource*/ "mBluetoothProfileServiceListener");
-    }
-
-    /*package*/ synchronized void onLeAudioProfileConnected(BluetoothLeAudio leAudio) {
-        mLeAudio = leAudio;
-        final List<BluetoothDevice> deviceList = mLeAudio.getConnectedDevices();
-        if (deviceList.isEmpty()) {
-            return;
-        }
-
-        final BluetoothDevice btDevice = deviceList.get(0);
-        final @BluetoothProfile.BtProfileState int state =
-        mLeAudio.getConnectionState(btDevice);
-        mDeviceBroker.postBluetoothLeAudioOutDeviceConnectionState(
-                btDevice, state,
-                /*suppressNoisyIntent*/ false,
-                /*musicDevice android.media.AudioSystem.DEVICE_NONE,*/
-                /*eventSource*/ "mBluetoothProfileServiceListener");
     }
 
     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
@@ -677,36 +647,16 @@
                 public void onServiceConnected(int profile, BluetoothProfile proxy) {
                     switch(profile) {
                         case BluetoothProfile.A2DP:
-                            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
-                                    "BT profile service: connecting A2DP profile"));
-                            mDeviceBroker.postBtA2dpProfileConnected((BluetoothA2dp) proxy);
-                            break;
-
                         case BluetoothProfile.A2DP_SINK:
-                            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
-                                    "BT profile service: connecting A2DP_SINK profile"));
-                            mDeviceBroker.postBtA2dpSinkProfileConnected(proxy);
-                            break;
-
                         case BluetoothProfile.HEADSET:
-                            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
-                                    "BT profile service: connecting HEADSET profile"));
-                            mDeviceBroker.postBtHeasetProfileConnected((BluetoothHeadset) proxy);
-                            break;
-
                         case BluetoothProfile.HEARING_AID:
-                            AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
-                                    "BT profile service: connecting HEARING_AID profile"));
-                            mDeviceBroker.postBtHearingAidProfileConnected(
-                                    (BluetoothHearingAid) proxy);
-                            break;
-
                         case BluetoothProfile.LE_AUDIO:
                             AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
-                                    "BT profile service: connecting LE_AUDIO profile"));
-                            mDeviceBroker.postBtLeAudioProfileConnected(
-                                    (BluetoothLeAudio) proxy);
+                                    "BT profile service: connecting "
+                                    + BluetoothProfile.getProfileName(profile) + " profile"));
+                            mDeviceBroker.postBtProfileConnected(profile, proxy);
                             break;
+
                         default:
                             break;
                     }
@@ -715,22 +665,11 @@
 
                     switch (profile) {
                         case BluetoothProfile.A2DP:
-                            mDeviceBroker.postDisconnectA2dp();
-                            break;
-
                         case BluetoothProfile.A2DP_SINK:
-                            mDeviceBroker.postDisconnectA2dpSink();
-                            break;
-
                         case BluetoothProfile.HEADSET:
-                            mDeviceBroker.postDisconnectHeadset();
-                            break;
-
                         case BluetoothProfile.HEARING_AID:
-                            mDeviceBroker.postDisconnectHearingAid();
-                            break;
                         case BluetoothProfile.LE_AUDIO:
-                            mDeviceBroker.postDisconnectLeAudio();
+                            mDeviceBroker.postBtProfileDisconnected(profile);
                             break;
 
                         default:
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
index 5c53d43..9e1445c 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java
@@ -26,11 +26,11 @@
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
 import android.media.AudioManager;
 import android.media.AudioSystem;
+import android.media.BtProfileConnectionInfo;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -98,16 +98,12 @@
         Log.i(TAG, "starting testPostA2dpDeviceConnectionChange");
         Assert.assertNotNull("invalid null BT device", mFakeBtDevice);
 
-        mAudioDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                new AudioDeviceBroker.BtDeviceConnectionInfo(mFakeBtDevice,
-                        BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1));
+        mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged(
+                new AudioDeviceBroker.BtDeviceChangedData(mFakeBtDevice, null,
+                    BtProfileConnectionInfo.a2dpInfo(true, 1), "testSource"));
         Thread.sleep(2 * MAX_MESSAGE_HANDLING_DELAY_MS);
-        verify(mSpyDevInventory, times(1)).setBluetoothA2dpDeviceConnectionState(
-                any(BluetoothDevice.class),
-                ArgumentMatchers.eq(BluetoothProfile.STATE_CONNECTED) /*state*/,
-                ArgumentMatchers.eq(BluetoothProfile.A2DP) /*profile*/,
-                ArgumentMatchers.eq(true) /*suppressNoisyIntent*/, anyInt() /*musicDevice*/,
-                ArgumentMatchers.eq(1) /*a2dpVolume*/
+        verify(mSpyDevInventory, times(1)).setBluetoothActiveDevice(
+                any(AudioDeviceBroker.BtDeviceInfo.class)
         );
 
         // verify the connection was reported to AudioSystem
@@ -210,30 +206,29 @@
         ((NoOpAudioSystemAdapter) mSpyAudioSystem).configureIsStreamActive(mockMediaPlayback);
 
         // first connection: ensure the device is connected as a starting condition for the test
-        mAudioDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                new AudioDeviceBroker.BtDeviceConnectionInfo(mFakeBtDevice,
-                        BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 1));
+        mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged(
+                new AudioDeviceBroker.BtDeviceChangedData(mFakeBtDevice, null,
+                    BtProfileConnectionInfo.a2dpInfo(true, 1), "testSource"));
         Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
 
         // disconnection
-        mAudioDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                new AudioDeviceBroker.BtDeviceConnectionInfo(mFakeBtDevice,
-                        BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP, false, -1));
+        mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged(
+                new AudioDeviceBroker.BtDeviceChangedData(null, mFakeBtDevice,
+                    BtProfileConnectionInfo.a2dpInfo(false, -1), "testSource"));
         if (delayAfterDisconnection > 0) {
             Thread.sleep(delayAfterDisconnection);
         }
 
         // reconnection
-        mAudioDeviceBroker.queueBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                new AudioDeviceBroker.BtDeviceConnectionInfo(mFakeBtDevice,
-                        BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP, true, 2));
+        mAudioDeviceBroker.queueOnBluetoothActiveDeviceChanged(
+                new AudioDeviceBroker.BtDeviceChangedData(mFakeBtDevice, null,
+                    BtProfileConnectionInfo.a2dpInfo(true, 2), "testSource"));
         Thread.sleep(AudioService.BECOMING_NOISY_DELAY_MS + MAX_MESSAGE_HANDLING_DELAY_MS);
 
         // Verify disconnection has been cancelled and we're seeing two connections attempts,
         // with the device connected at the end of the test
-        verify(mSpyDevInventory, times(2)).onSetA2dpSinkConnectionState(
-                any(BtHelper.BluetoothA2dpDeviceInfo.class),
-                ArgumentMatchers.eq(BluetoothProfile.STATE_CONNECTED));
+        verify(mSpyDevInventory, times(2)).onSetBtActiveDevice(
+                any(AudioDeviceBroker.BtDeviceInfo.class), anyInt());
         Assert.assertTrue("Mock device not connected",
                 mSpyDevInventory.isA2dpDeviceConnected(mFakeBtDevice));