Merge "Snap for 7925884 from 96fdc09881dba687586ca457d13729ed9e53b022 to sdk-release" into sdk-release
diff --git a/jni/com_android_bluetooth_btservice_AdapterService.cpp b/jni/com_android_bluetooth_btservice_AdapterService.cpp
index 40be390..bbf46bb 100644
--- a/jni/com_android_bluetooth_btservice_AdapterService.cpp
+++ b/jni/com_android_bluetooth_btservice_AdapterService.cpp
@@ -67,6 +67,7 @@
 static jmethodID method_pinRequestCallback;
 static jmethodID method_sspRequestCallback;
 static jmethodID method_bondStateChangeCallback;
+static jmethodID method_addressConsolidateCallback;
 static jmethodID method_aclStateChangeCallback;
 static jmethodID method_discoveryStateChangeCallback;
 static jmethodID method_linkQualityReportCallback;
@@ -302,6 +303,34 @@
                                (jint)fail_reason);
 }
 
+static void address_consolidate_callback(RawAddress* main_bd_addr,
+                                         RawAddress* secondary_bd_addr) {
+  CallbackEnv sCallbackEnv(__func__);
+
+  ScopedLocalRef<jbyteArray> main_addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!main_addr.get()) {
+    ALOGE("Address allocation failed in %s", __func__);
+    return;
+  }
+  sCallbackEnv->SetByteArrayRegion(main_addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)main_bd_addr);
+
+  ScopedLocalRef<jbyteArray> secondary_addr(
+      sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+  if (!secondary_addr.get()) {
+    ALOGE("Address allocation failed in %s", __func__);
+    return;
+  }
+
+  sCallbackEnv->SetByteArrayRegion(secondary_addr.get(), 0, sizeof(RawAddress),
+                                   (jbyte*)secondary_bd_addr);
+
+  sCallbackEnv->CallVoidMethod(sJniCallbacksObj,
+                               method_addressConsolidateCallback,
+                               main_addr.get(), secondary_addr.get());
+}
+
 static void acl_state_changed_callback(bt_status_t status, RawAddress* bd_addr,
                                        bt_acl_state_t state,
                                        int transport_link_type,
@@ -629,15 +658,23 @@
       p_energy_info->idle_time, p_energy_info->energy_used, array.get());
 }
 
-static bt_callbacks_t sBluetoothCallbacks = {
-    sizeof(sBluetoothCallbacks),  adapter_state_change_callback,
-    adapter_properties_callback,  remote_device_properties_callback,
-    device_found_callback,        discovery_state_changed_callback,
-    pin_request_callback,         ssp_request_callback,
-    bond_state_changed_callback,  acl_state_changed_callback,
-    callback_thread_event,        dut_mode_recv_callback,
-    le_test_mode_recv_callback,   energy_info_recv_callback,
-    link_quality_report_callback, generate_local_oob_data_callback};
+static bt_callbacks_t sBluetoothCallbacks = {sizeof(sBluetoothCallbacks),
+                                             adapter_state_change_callback,
+                                             adapter_properties_callback,
+                                             remote_device_properties_callback,
+                                             device_found_callback,
+                                             discovery_state_changed_callback,
+                                             pin_request_callback,
+                                             ssp_request_callback,
+                                             bond_state_changed_callback,
+                                             address_consolidate_callback,
+                                             acl_state_changed_callback,
+                                             callback_thread_event,
+                                             dut_mode_recv_callback,
+                                             le_test_mode_recv_callback,
+                                             energy_info_recv_callback,
+                                             link_quality_report_callback,
+                                             generate_local_oob_data_callback};
 
 // The callback to call when the wake alarm fires.
 static alarm_cb sAlarmCallback;
@@ -847,6 +884,9 @@
   method_bondStateChangeCallback =
       env->GetMethodID(jniCallbackClass, "bondStateChangeCallback", "(I[BII)V");
 
+  method_addressConsolidateCallback = env->GetMethodID(
+      jniCallbackClass, "addressConsolidateCallback", "([B[B)V");
+
   method_aclStateChangeCallback =
       env->GetMethodID(jniCallbackClass, "aclStateChangeCallback", "(I[BIII)V");
 
diff --git a/src/com/android/bluetooth/a2dp/A2dpService.java b/src/com/android/bluetooth/a2dp/A2dpService.java
index 542509e..557f3b2 100644
--- a/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -37,6 +37,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
+import android.media.BtProfileConnectionInfo;
 import android.os.HandlerThread;
 import android.util.Log;
 
@@ -462,13 +463,10 @@
             // device, the user has explicitly switched the output to the local device and music
             // should continue playing. Otherwise, the remote device has been indeed disconnected
             // and audio should be suspended before switching the output to the local device.
-            boolean suppressNoisyIntent = !forceStopPlayingAudio
-                    && (getConnectionState(previousActiveDevice)
-                    == BluetoothProfile.STATE_CONNECTED);
-            Log.i(TAG, "removeActiveDevice: suppressNoisyIntent=" + suppressNoisyIntent);
-            mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                    previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
-                    BluetoothProfile.A2DP, suppressNoisyIntent, -1);
+            boolean stopAudio = forceStopPlayingAudio || (getConnectionState(previousActiveDevice)
+                        != BluetoothProfile.STATE_CONNECTED);
+            mAudioManager.handleBluetoothActiveDeviceChanged(null, previousActiveDevice,
+                    BtProfileConnectionInfo.a2dpInfo(!stopAudio, -1));
 
             synchronized (mStateMachines) {
                 // Make sure the Active device in native layer is set to null and audio is off
@@ -552,13 +550,6 @@
             // This needs to happen before we inform the audio manager that the device
             // disconnected. Please see comment in updateAndBroadcastActiveDevice() for why.
             updateAndBroadcastActiveDevice(device);
-            // Make sure the Audio Manager knows the previous Active device is disconnected,
-            // and the new Active device is connected.
-            if (previousActiveDevice != null) {
-                mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                        previousActiveDevice, BluetoothProfile.STATE_DISCONNECTED,
-                        BluetoothProfile.A2DP, true, -1);
-            }
 
             BluetoothDevice newActiveDevice = null;
             synchronized (mStateMachines) {
@@ -583,13 +574,13 @@
                 rememberedVolume = mFactory.getAvrcpTargetService()
                         .getRememberedVolumeForDevice(newActiveDevice);
             }
-            mAudioManager.setBluetoothA2dpDeviceConnectionStateSuppressNoisyIntent(
-                    newActiveDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.A2DP,
-                    true, rememberedVolume);
-            // Inform the Audio Service about the codec configuration
+            // Make sure the Audio Manager knows the previous Active device is disconnected,
+            // and the new Active device is connected.
+            // And inform the Audio Service about the codec configuration
             // change, so the Audio Service can reset accordingly the audio
             // feeding parameters in the Audio HAL to the Bluetooth stack.
-            mAudioManager.handleBluetoothA2dpDeviceConfigChange(newActiveDevice);
+            mAudioManager.handleBluetoothActiveDeviceChanged(newActiveDevice, previousActiveDevice,
+                    BtProfileConnectionInfo.a2dpInfo(true, rememberedVolume));
         }
         return true;
     }
@@ -972,8 +963,13 @@
         // Inform the Audio Service about the codec configuration change,
         // so the Audio Service can reset accordingly the audio feeding
         // parameters in the Audio HAL to the Bluetooth stack.
-        if (isActiveDevice(device) && !sameAudioFeedingParameters) {
-            mAudioManager.handleBluetoothA2dpDeviceConfigChange(device);
+        // Until we are able to detect from device_port_proxy if the config has changed or not,
+        // the Bluetooth stack can only disable the audio session and need to ask audioManager to
+        // restart the session even if feeding parameter are the same. (sameAudioFeedingParameters
+        // is left unused until there)
+        if (isActiveDevice(device)) {
+            mAudioManager.handleBluetoothActiveDeviceChanged(device, device,
+                    BtProfileConnectionInfo.a2dpInfo(false, -1));
         }
     }
 
diff --git a/src/com/android/bluetooth/btservice/JniCallbacks.java b/src/com/android/bluetooth/btservice/JniCallbacks.java
index f00353a..03d9264 100644
--- a/src/com/android/bluetooth/btservice/JniCallbacks.java
+++ b/src/com/android/bluetooth/btservice/JniCallbacks.java
@@ -67,6 +67,10 @@
         mBondStateMachine.bondStateChangeCallback(status, address, newState, hciReason);
     }
 
+    void addressConsolidateCallback(byte[] mainAddress, byte[] secondaryAddress) {
+        mRemoteDevices.addressConsolidateCallback(mainAddress, secondaryAddress);
+    }
+
     void aclStateChangeCallback(int status, byte[] address, int newState,
             int transportLinkType, int hciReason) {
         mRemoteDevices.aclStateChangeCallback(status, address, newState,
diff --git a/src/com/android/bluetooth/btservice/RemoteDevices.java b/src/com/android/bluetooth/btservice/RemoteDevices.java
index 2f0a8d4..e6812ee 100644
--- a/src/com/android/bluetooth/btservice/RemoteDevices.java
+++ b/src/com/android/bluetooth/btservice/RemoteDevices.java
@@ -667,6 +667,19 @@
         }
     }
 
+    void addressConsolidateCallback(byte[] mainAddress, byte[] secondaryAddress) {
+        BluetoothDevice device = getDevice(mainAddress);
+        if (device == null) {
+            errorLog("addressConsolidateCallback: device is NULL, address="
+                    + Utils.getAddressStringFromByte(mainAddress) + ", secondaryAddress="
+                            + Utils.getAddressStringFromByte(secondaryAddress));
+            return;
+        }
+        Log.d(TAG, "addressConsolidateCallback device: " + device + ", secondaryAddress:"
+                + Utils.getAddressStringFromByte(secondaryAddress));
+        // TODO
+    }
+
     void aclStateChangeCallback(int status, byte[] address, int newState,
                                 int transportLinkType, int hciReason) {
         BluetoothDevice device = getDevice(address);
diff --git a/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java b/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java
index 7fe1345..3b8a9bb 100644
--- a/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java
+++ b/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java
@@ -599,7 +599,9 @@
 
         for (Map.Entry<Executor, IBluetoothCsipSetCoordinatorCallback> entry :
                 mCallbacks.get(uuid).entrySet()) {
-            Log.e(TAG, " executing " + uuid + " " + entry.getKey());
+            if (DBG) {
+                Log.d(TAG, " executing " + uuid + " " + entry.getKey());
+            }
             try {
                 executeCallback(entry.getKey(), entry.getValue(), device, groupId);
             } catch (RemoteException e) {
diff --git a/src/com/android/bluetooth/hearingaid/HearingAidService.java b/src/com/android/bluetooth/hearingaid/HearingAidService.java
index 2ab14cb..6199ff1 100644
--- a/src/com/android/bluetooth/hearingaid/HearingAidService.java
+++ b/src/com/android/bluetooth/hearingaid/HearingAidService.java
@@ -30,6 +30,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
+import android.media.BtProfileConnectionInfo;
 import android.os.HandlerThread;
 import android.os.ParcelUuid;
 import android.util.Log;
@@ -694,30 +695,15 @@
                 | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions());
 
-        if (device == null) {
-            if (DBG) {
-                Log.d(TAG, "Set Hearing Aid audio to disconnected");
-            }
-            boolean suppressNoisyIntent =
-                    (getConnectionState(mPreviousAudioDevice) == BluetoothProfile.STATE_CONNECTED);
-            mAudioManager.setBluetoothHearingAidDeviceConnectionState(
-                    mPreviousAudioDevice, BluetoothProfile.STATE_DISCONNECTED,
-                    suppressNoisyIntent, 0);
-            mPreviousAudioDevice = null;
-        } else {
-            if (DBG) {
-                Log.d(TAG, "Set Hearing Aid audio to connected");
-            }
-            if (mPreviousAudioDevice != null) {
-                mAudioManager.setBluetoothHearingAidDeviceConnectionState(
-                        mPreviousAudioDevice, BluetoothProfile.STATE_DISCONNECTED,
-                        true, 0);
-            }
-            mAudioManager.setBluetoothHearingAidDeviceConnectionState(
-                    device, BluetoothProfile.STATE_CONNECTED,
-                    true, 0);
-            mPreviousAudioDevice = device;
+        boolean stopAudio = device == null
+                && (getConnectionState(mPreviousAudioDevice) != BluetoothProfile.STATE_CONNECTED);
+        if (DBG) {
+            Log.d(TAG, "Hearing Aid audio: " + mPreviousAudioDevice + " -> " + device
+                    + ". Stop audio: " + stopAudio);
         }
+        mAudioManager.handleBluetoothActiveDeviceChanged(device, mPreviousAudioDevice,
+                BtProfileConnectionInfo.hearingAidInfo(!stopAudio));
+        mPreviousAudioDevice = device;
     }
 
     // Remove state machine if the bonding for a device is removed
diff --git a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index bbc7878..369b5d5 100644
--- a/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -860,6 +860,11 @@
                     break;
                 }
                 case DEVICE_STATE_CHANGED:
+                    if (mDeviceSilenced) {
+                        stateLogW("DEVICE_STATE_CHANGED: " + mDevice
+                                + " is silenced, skip notify state changed.");
+                        break;
+                    }
                     mNativeInterface.notifyDeviceStatus(mDevice, (HeadsetDeviceState) message.obj);
                     break;
                 case SEND_CCLC_RESPONSE:
diff --git a/src/com/android/bluetooth/le_audio/LeAudioService.java b/src/com/android/bluetooth/le_audio/LeAudioService.java
index da5828e..77bc559 100644
--- a/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -23,16 +23,12 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
-import android.bluetooth.BluetoothVolumeControl;
-import android.bluetooth.IBluetoothCsipSetCoordinator;
-import android.bluetooth.IBluetoothCsipSetCoordinatorCallback;
 import android.bluetooth.IBluetoothLeAudio;
-import android.content.AttributionSource;
 import android.bluetooth.IBluetoothVolumeControl;
+import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -40,6 +36,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.media.AudioManager;
+import android.media.BtProfileConnectionInfo;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.ParcelUuid;
@@ -51,7 +48,6 @@
 import com.android.bluetooth.btservice.ProfileService;
 import com.android.bluetooth.btservice.ServiceFactory;
 import com.android.bluetooth.btservice.storage.DatabaseManager;
-import com.android.bluetooth.csip.CsipSetCoordinatorService;
 import com.android.bluetooth.mcp.McpService;
 import com.android.bluetooth.vc.VolumeControlService;
 import com.android.internal.annotations.VisibleForTesting;
@@ -93,8 +89,8 @@
     private AdapterService mAdapterService;
     private DatabaseManager mDatabaseManager;
     private HandlerThread mStateMachinesThread;
-    private BluetoothDevice mPreviousAudioOutDevice;
-    private BluetoothDevice mPreviousAudioInDevice;
+    private BluetoothDevice mActiveAudioOutDevice;
+    private BluetoothDevice mActiveAudioInDevice;
     ServiceFactory mServiceFactory = new ServiceFactory();
 
     LeAudioNativeInterface mLeAudioNativeInterface;
@@ -116,7 +112,6 @@
     private final Map<BluetoothDevice, LeAudioStateMachine> mStateMachines = new HashMap<>();
 
     private final Map<BluetoothDevice, Integer> mDeviceGroupIdMap = new ConcurrentHashMap<>();
-    private final Map<BluetoothDevice, Integer> mSetMemberAvailable = new ConcurrentHashMap<>();
     private int mActiveDeviceGroupId = LE_AUDIO_GROUP_ID_INVALID;
     private final int mContextSupportingInputAudio =
             BluetoothLeAudio.CONTEXT_TYPE_COMMUNICATION |
@@ -137,50 +132,6 @@
     private BroadcastReceiver mBondStateChangedReceiver;
     private BroadcastReceiver mConnectionStateChangedReceiver;
 
-    class MyCsipSetCoordinatorCallbacks extends IBluetoothCsipSetCoordinatorCallback.Stub {
-        @Override
-        public void onCsisSetMemberAvailable(BluetoothDevice device, int groupId) {
-            synchronized (LeAudioService.this) {
-                LeAudioService.this.setMemberAvailable(device, groupId);
-            }
-        }
-    };
-
-    private volatile MyCsipSetCoordinatorCallbacks mCsipSetCoordinatorCallback =
-                                                            new MyCsipSetCoordinatorCallbacks();
-    private volatile IBluetoothCsipSetCoordinator mCsipSetCoordinatorProxy;
-    private final ServiceConnection mCsipSetCoordinatorProxyConnection = new ServiceConnection() {
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            if (DBG) {
-                Log.d(TAG, "CsisClientProxy connected");
-            }
-            synchronized (LeAudioService.this) {
-                mCsipSetCoordinatorProxy = IBluetoothCsipSetCoordinator.Stub.asInterface(service);
-                CsipSetCoordinatorService mCsipSetCoordinatorService =
-                    CsipSetCoordinatorService.getCsipSetCoordinatorService();
-                if (mCsipSetCoordinatorService == null) {
-                    Log.e(TAG, "CsisClientService is null on LeAudioService starts");
-                    return;
-                }
-                mCsipSetCoordinatorService.registerCsisMemberObserver(
-                                                    LeAudioService.this.getMainExecutor(),
-                                                    BluetoothUuid.CAP,
-                                                    mCsipSetCoordinatorCallback);
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName className) {
-            if (DBG) {
-                Log.d(TAG, "CsisClientProxy disconnected");
-            }
-            synchronized (LeAudioService.this) {
-                mCsipSetCoordinatorProxy = null;
-            }
-        }
-    };
-
     private volatile IBluetoothVolumeControl mVolumeControlProxy;
     VolumeControlService mVolumeControlService = null;
     private final ServiceConnection mVolumeControlProxyConnection = new ServiceConnection() {
@@ -245,7 +196,6 @@
         mStateMachinesThread.start();
 
         mDeviceGroupIdMap.clear();
-        mSetMemberAvailable.clear();
         mGroupDescriptors.clear();
 
         // Setup broadcast receivers
@@ -258,8 +208,6 @@
         mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
         registerReceiver(mConnectionStateChangedReceiver, filter);
 
-        /* Bind Csis Service */
-        bindCsisClientService();
 
         /* Bind Volume control service */
         bindVolumeControlService();
@@ -317,7 +265,6 @@
         }
 
         mDeviceGroupIdMap.clear();
-        mSetMemberAvailable.clear();
         mGroupDescriptors.clear();
 
         if (mStateMachinesThread != null) {
@@ -329,7 +276,6 @@
         mAdapterService = null;
         mAudioManager = null;
 
-        unbindCsisClientService();
         unbindVolumeControlService();
         return true;
     }
@@ -382,30 +328,6 @@
         }
     }
 
-    private void bindCsisClientService() {
-        synchronized (mCsipSetCoordinatorProxyConnection) {
-            Intent intent = new Intent(IBluetoothCsipSetCoordinator.class.getName());
-            ComponentName comp = intent.resolveSystemService(getPackageManager(), 0);
-            intent.setComponent(comp);
-            if (comp == null || !bindService(intent, mCsipSetCoordinatorProxyConnection, 0)) {
-                Log.wtf(TAG, "Could not bind to IBluetoothCsisClient Service with " +
-                        intent);
-            }
-        }
-    }
-    private void unbindCsisClientService() {
-        synchronized (mCsipSetCoordinatorProxyConnection) {
-            if (mCsipSetCoordinatorProxy != null) {
-                if (DBG) {
-                    Log.d(TAG, "Unbinding CsisClientProxyConnection");
-                }
-                mCsipSetCoordinatorProxy = null;
-                // Synchronization should make sure unbind can be successful
-                unbindService(mCsipSetCoordinatorProxyConnection);
-            }
-        }
-    }
-
     public boolean connect(BluetoothDevice device) {
         if (DBG) {
             Log.d(TAG, "connect(): " + device);
@@ -651,9 +573,9 @@
         return null;
     }
 
-    private void updateActiveInDevice(BluetoothDevice device, Integer groupId, Integer oldActiveContexts,
-    Integer newActiveContexts) {
-
+    private boolean updateActiveInDevice(BluetoothDevice device, Integer groupId,
+                                            Integer oldActiveContexts,
+                                            Integer newActiveContexts) {
         Integer oldSupportedAudioDirections =
                 getAudioDirectionsFromActiveContextsMap(oldActiveContexts);
         Integer newSupportedAudioDirections =
@@ -664,51 +586,47 @@
         boolean newSupportedByDeviceInput = (newSupportedAudioDirections
                 & AUDIO_DIRECTION_INPUT_BIT) != 0;
 
-        if (device != null && mPreviousAudioInDevice != null) {
-            int previousGroupId = getGroupId(mPreviousAudioInDevice);
+        if (device != null && mActiveAudioInDevice != null) {
+            int previousGroupId = getGroupId(mActiveAudioInDevice);
             if (previousGroupId == groupId) {
                 /* This is thes same group as aleady notified to the system.
                 * Therefore do not change the device we have connected to the group,
                 * unless, previous one is disconnected now
                 */
-                if (mPreviousAudioInDevice.isConnected())
-                    device = mPreviousAudioInDevice;
+                if (mActiveAudioInDevice.isConnected()) {
+                    device = mActiveAudioInDevice;
+                }
             }
         }
 
-        /* Disconnect input:
-         * - If active input device changed (to none or any)
-         * - If device stops supporting input
+        BluetoothDevice previousInDevice = mActiveAudioInDevice;
+
+        /*
+         * Do not update input if neither previous nor current device support input
          */
-        boolean inActiveDeviceReplace = (device != mPreviousAudioInDevice);
-        if (inActiveDeviceReplace && (mPreviousAudioInDevice != null)) {
-            mAudioManager.setBluetoothLeAudioInDeviceConnectionState(
-                    mPreviousAudioInDevice, BluetoothProfile.STATE_DISCONNECTED);
+        if (!oldSupportedByDeviceInput && !newSupportedByDeviceInput) {
+            Log.d(TAG, "updateActiveInDevice: Device does not support input.");
+            return false;
         }
 
-        mPreviousAudioInDevice = device;
-
-        if (device == null) {
-            Log.d(TAG,  " device is null.");
-            return;
-        }
-
-        if (inActiveDeviceReplace == false ||
-             (oldSupportedByDeviceInput == newSupportedByDeviceInput)) {
-            Log.d(TAG,  " Nothing to do.");
-            return;
-        }
-
-        /* Connect input:
-         * - If active input device changed
-         * - If device starts support input
+        /*
+         * Update input if:
+         * - Device changed
+         *     OR
+         * - Device stops / starts supporting input
          */
-        mAudioManager.setBluetoothLeAudioInDeviceConnectionState(
-                   device, BluetoothProfile.STATE_CONNECTED);
-
+        if (!Objects.equals(device, previousInDevice)
+                || (oldSupportedByDeviceInput != newSupportedByDeviceInput)) {
+            mActiveAudioInDevice = newSupportedByDeviceInput ? device : null;
+            mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioInDevice, previousInDevice,
+                    BtProfileConnectionInfo.leAudio(false, false));
+            return true;
+        }
+        Log.d(TAG, "updateActiveInDevice: Nothing to do.");
+        return false;
     }
 
-    private void updateActiveOutDevice(BluetoothDevice device, Integer groupId,
+    private boolean updateActiveOutDevice(BluetoothDevice device, Integer groupId,
                                        Integer oldActiveContexts,
                                        Integer newActiveContexts) {
         Integer oldSupportedAudioDirections =
@@ -721,52 +639,46 @@
         boolean newSupportedByDeviceOutput = (newSupportedAudioDirections
                 & AUDIO_DIRECTION_OUTPUT_BIT) != 0;
 
-
-        if (device != null && mPreviousAudioOutDevice != null) {
-            int previousGroupId = getGroupId(mPreviousAudioOutDevice);
+        if (device != null && mActiveAudioOutDevice != null) {
+            int previousGroupId = getGroupId(mActiveAudioOutDevice);
             if (previousGroupId == groupId) {
                 /* This is the same group as already notified to the system.
                 * Therefore do not change the device we have connected to the group,
                 * unless, previous one is disconnected now
                 */
-             if (mPreviousAudioOutDevice.isConnected())
-                device = mPreviousAudioOutDevice;
+                if (mActiveAudioOutDevice.isConnected()) {
+                    device = mActiveAudioOutDevice;
+                }
             }
         }
 
-         /* Disconnect output:
-         * - If active output device changed (to none or any)
-         * - If device stops supporting output
+        BluetoothDevice previousOutDevice = mActiveAudioOutDevice;
+
+        /*
+         * Do not update output if neither previous nor current device support output
          */
-        boolean outActiveDeviceReplace = (device != mPreviousAudioOutDevice);
-        if (outActiveDeviceReplace && (mPreviousAudioOutDevice != null)) {
-            boolean suppressNoisyIntent =
-                    (getConnectionState(mPreviousAudioOutDevice) ==
-                    BluetoothProfile.STATE_CONNECTED);
-            mAudioManager.setBluetoothLeAudioOutDeviceConnectionState(
-                    mPreviousAudioOutDevice, BluetoothProfile.STATE_DISCONNECTED,
-                    suppressNoisyIntent);
+        if (!oldSupportedByDeviceOutput && !newSupportedByDeviceOutput) {
+            Log.d(TAG, "updateActiveOutDevice: Device does not support output.");
+            return false;
         }
 
-        mPreviousAudioOutDevice = device;
-
-        if (device == null) {
-            Log.d(TAG,  " device is null.");
-            return;
-        }
-
-        if (outActiveDeviceReplace == false ||
-            (oldSupportedByDeviceOutput == newSupportedByDeviceOutput)) {
-            Log.d(TAG,  " Nothing to do.");
-            return;
-        }
-
-        /* Connect output:
-         * - If active output device changed
-         * - If device starts support output
+        /*
+         * Update output if:
+         * - Device changed
+         *     OR
+         * - Device stops / starts supporting output
          */
-         mAudioManager.setBluetoothLeAudioOutDeviceConnectionState(
-                    device, BluetoothProfile.STATE_CONNECTED, true);
+        if (!Objects.equals(device, previousOutDevice)
+                || (oldSupportedByDeviceOutput != newSupportedByDeviceOutput)) {
+            mActiveAudioOutDevice = newSupportedByDeviceOutput ? device : null;
+            final boolean suppressNoisyIntent = (mActiveAudioOutDevice != null)
+                    || (getConnectionState(previousOutDevice) == BluetoothProfile.STATE_CONNECTED);
+            mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioOutDevice,
+                    previousOutDevice, BtProfileConnectionInfo.leAudio(suppressNoisyIntent, true));
+            return true;
+        }
+        Log.d(TAG, "updateActiveOutDevice: Nothing to do.");
+        return false;
     }
 
     /**
@@ -782,14 +694,18 @@
         if (isActive)
             device = getFirstDeviceFromGroup(groupId);
 
-        updateActiveOutDevice(device, groupId, oldActiveContexts, newActiveContexts);
-        updateActiveInDevice(device, groupId, oldActiveContexts, newActiveContexts);
+        boolean outReplaced =
+            updateActiveOutDevice(device, groupId, oldActiveContexts, newActiveContexts);
+        boolean inReplaced =
+            updateActiveInDevice(device, groupId, oldActiveContexts, newActiveContexts);
 
-        Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
-        intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mPreviousAudioOutDevice);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
-                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-        sendBroadcast(intent, BLUETOOTH_CONNECT);
+        if (outReplaced || inReplaced) {
+            Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
+            intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mActiveAudioOutDevice);
+            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            sendBroadcast(intent, BLUETOOTH_CONNECT);
+        }
     }
 
     /**
@@ -1097,11 +1013,6 @@
             return;
         }
 
-        /* Remove bonded set member from outstanding list */
-        if (mSetMemberAvailable.containsKey(device)) {
-            mSetMemberAvailable.remove(device);
-        }
-
         int groupId = getGroupId(device);
         if (groupId != LE_AUDIO_GROUP_ID_INVALID) {
             /* In case device is still in the group, let's remove it */
@@ -1239,22 +1150,6 @@
         }
     }
 
-    synchronized void setMemberAvailable(BluetoothDevice device, int groupId) {
-        if (device == null) {
-            Log.e(TAG, "unexpected invocation. device=" + device);
-            return;
-        }
-
-        if (mSetMemberAvailable.containsKey(device)) {
-            if (DBG) {
-                Log.d(TAG, "Device " + device + " is already notified- drop it");
-            }
-            return;
-        }
-
-        mSetMemberAvailable.put(device, groupId);
-    }
-
    /**
      * Check whether can connect to a peer device.
      * The check considers a number of factors during the evaluation.
diff --git a/src/com/android/bluetooth/mcp/MediaControlProfile.java b/src/com/android/bluetooth/mcp/MediaControlProfile.java
index 724b969..fb72a36 100644
--- a/src/com/android/bluetooth/mcp/MediaControlProfile.java
+++ b/src/com/android/bluetooth/mcp/MediaControlProfile.java
@@ -192,7 +192,7 @@
         }
     }
 
-    private void removePendingStateRequests(Set<PlayerStateField> fields) {
+    private synchronized void removePendingStateRequests(Set<PlayerStateField> fields) {
         if (mPendingStateRequest == null) return;
 
         for (PlayerStateField field : fields) {
@@ -263,7 +263,9 @@
 
     @Override
     public void onPlayerStateRequest(PlayerStateField[] stateFields) {
-        mPendingStateRequest = Stream.of(stateFields).collect(Collectors.toList());
+        synchronized (this) {
+            mPendingStateRequest = Stream.of(stateFields).collect(Collectors.toList());
+        }
         processPendingPlayerStateRequest();
     }
 
@@ -513,70 +515,76 @@
 
         Map<PlayerStateField, Object> handled_request_map = new HashMap<>();
 
-        if (mPendingStateRequest == null) return;
-
-        // Notice: If we are unable to provide the requested field it will stay queued until we are
-        //         able to provide it.
-        for (PlayerStateField settings_field : mPendingStateRequest) {
-            switch (settings_field) {
-                case PLAYBACK_STATE:
-                    if (mCurrentData.state != null) {
-                        handled_request_map.put(settings_field,
-                                playerState2McsState(mCurrentData.state.getState()));
-                    }
-                    break;
-                case TRACK_DURATION:
-                    handled_request_map.put(settings_field, getCurrentTrackDuration());
-                    break;
-                case PLAYBACK_SPEED:
-                    if (mCurrentData.state != null) {
-                        handled_request_map.put(
-                                settings_field, mCurrentData.state.getPlaybackSpeed());
-                    }
-                    break;
-                case SEEKING_SPEED:
-                    float seeking_speed = 1.0f;
-                    if (mCurrentData.state != null) {
-                        if ((mCurrentData.state.getState() == PlaybackState.STATE_FAST_FORWARDING)
-                                || (mCurrentData.state.getState()
-                                        == PlaybackState.STATE_REWINDING)) {
-                            seeking_speed = mCurrentData.state.getPlaybackSpeed();
+        synchronized (this) {
+            if (mPendingStateRequest == null) return;
+            // Notice: If we are unable to provide the requested field it will stay queued until we
+            //         are able to provide it.
+            for (PlayerStateField settings_field : mPendingStateRequest) {
+                switch (settings_field) {
+                    case PLAYBACK_STATE:
+                        if (mCurrentData.state != null) {
+                            handled_request_map.put(settings_field,
+                                    playerState2McsState(mCurrentData.state.getState()));
                         }
-                    }
+                        break;
+                    case TRACK_DURATION:
+                        handled_request_map.put(settings_field, getCurrentTrackDuration());
+                        break;
+                    case PLAYBACK_SPEED:
+                        if (mCurrentData.state != null) {
+                            handled_request_map.put(
+                                    settings_field, mCurrentData.state.getPlaybackSpeed());
+                        }
+                        break;
+                    case SEEKING_SPEED:
+                        float seeking_speed = 1.0f;
+                        if (mCurrentData.state != null) {
+                            if ((mCurrentData.state.getState()
+                                    == PlaybackState.STATE_FAST_FORWARDING)
+                                    || (mCurrentData.state.getState()
+                                            == PlaybackState.STATE_REWINDING)) {
+                                seeking_speed = mCurrentData.state.getPlaybackSpeed();
+                            }
+                        }
 
-                    handled_request_map.put(settings_field, seeking_speed);
-                    break;
-                case PLAYING_ORDER:
-                    handled_request_map.put(settings_field, getCurrentPlayerPlayingOrder());
-                    break;
-                case TRACK_POSITION:
-                    if (mCurrentData.state != null) {
-                        handled_request_map.put(
-                                settings_field, getDriftCorrectedTrackPosition(mCurrentData.state));
-                    }
-                    break;
-                case PLAYER_NAME:
-                    String player_name = getCurrentPlayerName();
-                    if (player_name != null) handled_request_map.put(settings_field, player_name);
-                    break;
-                case ICON_URL:
-                    // Not implemented
-                    break;
-                case ICON_OBJ_ID:
-                    // TODO: Implement once we have Object Transfer Service
-                    break;
-                case PLAYING_ORDER_SUPPORTED:
-                    Integer playing_order = getSupportedPlayingOrder();
-                    if (playing_order != null) {
-                        handled_request_map.put(settings_field, playing_order.intValue());
-                    }
-                    break;
-                case OPCODES_SUPPORTED:
-                    if (mCurrentData.state != null) {
-                        handled_request_map.put(settings_field,
-                                playerActions2McsSupportedOpcodes(mCurrentData.state.getActions()));
-                    }
-                    break;
+                        handled_request_map.put(settings_field, seeking_speed);
+                        break;
+                    case PLAYING_ORDER:
+                        handled_request_map.put(settings_field, getCurrentPlayerPlayingOrder());
+                        break;
+                    case TRACK_POSITION:
+                        if (mCurrentData.state != null) {
+                            handled_request_map.put(
+                                    settings_field, getDriftCorrectedTrackPosition(
+                                            mCurrentData.state));
+                        }
+                        break;
+                    case PLAYER_NAME:
+                        String player_name = getCurrentPlayerName();
+                        if (player_name != null) {
+                            handled_request_map.put(settings_field, player_name);
+                        }
+                        break;
+                    case ICON_URL:
+                        // Not implemented
+                        break;
+                    case ICON_OBJ_ID:
+                        // TODO: Implement once we have Object Transfer Service
+                        break;
+                    case PLAYING_ORDER_SUPPORTED:
+                        Integer playing_order = getSupportedPlayingOrder();
+                        if (playing_order != null) {
+                            handled_request_map.put(settings_field, playing_order.intValue());
+                        }
+                        break;
+                    case OPCODES_SUPPORTED:
+                        if (mCurrentData.state != null) {
+                            handled_request_map.put(settings_field,
+                                    playerActions2McsSupportedOpcodes(
+                                            mCurrentData.state.getActions()));
+                        }
+                        break;
+                }
             }
         }
 
@@ -588,10 +596,12 @@
         }
 
         if (DBG) {
-            if (mPendingStateRequest != null && !mPendingStateRequest.isEmpty()) {
-                Log.w(TAG, "MCS service state fields left unhandled: ");
-                for (PlayerStateField item : mPendingStateRequest) {
-                    Log.w(TAG, "   > " + item);
+            synchronized (this) {
+                if (mPendingStateRequest != null && !mPendingStateRequest.isEmpty()) {
+                    Log.w(TAG, "MCS service state fields left unhandled: ");
+                    for (PlayerStateField item : mPendingStateRequest) {
+                        Log.w(TAG, "   > " + item);
+                    }
                 }
             }
         }
diff --git a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
index 7691c3f..df78600 100644
--- a/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
+++ b/src/com/android/bluetooth/pbap/BluetoothPbapUtils.java
@@ -49,7 +49,18 @@
     private static final String TAG = "BluetoothPbapUtils";
     private static final boolean V = BluetoothPbapService.VERBOSE;
 
+    // Filter constants from Bluetooth PBAP specification
     private static final int FILTER_PHOTO = 3;
+    private static final int FILTER_BDAY = 4;
+    private static final int FILTER_ADDRESS = 5;
+    private static final int FILTER_LABEL = 6;
+    private static final int FILTER_EMAIL = 8;
+    private static final int FILTER_MAILER = 9;
+    private static final int FILTER_ORG = 16;
+    private static final int FILTER_NOTE = 17;
+    private static final int FILTER_SOUND = 19;
+    private static final int FILTER_URL = 20;
+    private static final int FILTER_NICKNAME = 23;
 
     private static final long QUERY_CONTACT_RETRY_INTERVAL = 4000;
 
@@ -122,6 +133,40 @@
             }
             vType |= VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT;
         }
+        if (hasFilter(filter)) {
+            if (!isFilterBitSet(filter, FILTER_ADDRESS) && !isFilterBitSet(filter, FILTER_LABEL)) {
+                Log.i(TAG, "Excluding addresses from VCardComposer...");
+                vType |= VCardConfig.FLAG_REFRAIN_ADDRESS_EXPORT;
+            }
+            if (!isFilterBitSet(filter, FILTER_EMAIL) && !isFilterBitSet(filter, FILTER_MAILER)) {
+                Log.i(TAG, "Excluding email addresses from VCardComposer...");
+                vType |= VCardConfig.FLAG_REFRAIN_EMAIL_EXPORT;
+            }
+            if (!isFilterBitSet(filter, FILTER_ORG)) {
+                Log.i(TAG, "Excluding organization from VCardComposer...");
+                vType |= VCardConfig.FLAG_REFRAIN_ORGANIZATION_EXPORT;
+            }
+            if (!isFilterBitSet(filter, FILTER_URL)) {
+                Log.i(TAG, "Excluding URLS from VCardComposer...");
+                vType |= VCardConfig.FLAG_REFRAIN_WEBSITES_EXPORT;
+            }
+            if (!isFilterBitSet(filter, FILTER_NOTE)) {
+                Log.i(TAG, "Excluding notes from VCardComposer...");
+                vType |= VCardConfig.FLAG_REFRAIN_NOTES_EXPORT;
+            }
+            if (!isFilterBitSet(filter, FILTER_NICKNAME)) {
+                Log.i(TAG, "Excluding nickname from VCardComposer...");
+                vType |= VCardConfig.FLAG_REFRAIN_NICKNAME_EXPORT;
+            }
+            if (!isFilterBitSet(filter, FILTER_SOUND)) {
+                Log.i(TAG, "Excluding phonetic name from VCardComposer...");
+                vType |= VCardConfig.FLAG_REFRAIN_PHONETIC_NAME_EXPORT;
+            }
+            if (!isFilterBitSet(filter, FILTER_BDAY)) {
+                Log.i(TAG, "Excluding birthday from VCardComposer...");
+                vType |= VCardConfig.FLAG_REFRAIN_EVENTS_EXPORT;
+            }
+        }
         return new VCardComposer(ctx, vType, true);
     }
 
diff --git a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
index dab3099..b2996f9 100644
--- a/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -28,6 +28,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
+import android.media.BtProfileConnectionInfo;
 import android.os.Looper;
 import android.os.ParcelUuid;
 
@@ -521,9 +522,8 @@
         Assert.assertTrue(mService.getConnectedDevices().contains(mRightDevice));
 
         // Verify the audio is routed to Hearing Aid Profile
-        verify(mAudioManager).setBluetoothHearingAidDeviceConnectionState(
-                any(BluetoothDevice.class), eq(BluetoothProfile.STATE_CONNECTED),
-                eq(true), eq(0));
+        verify(mAudioManager).handleBluetoothActiveDeviceChanged(
+                any(BluetoothDevice.class), eq(null), any(BtProfileConnectionInfo.class));
 
         // Send a disconnect request
         Assert.assertTrue("Disconnect failed", mService.disconnect(mLeftDevice));
@@ -570,9 +570,8 @@
         Assert.assertFalse(mService.getConnectedDevices().contains(mRightDevice));
 
         // Verify the audio is not routed to Hearing Aid Profile
-        verify(mAudioManager).setBluetoothHearingAidDeviceConnectionState(
-                any(BluetoothDevice.class), eq(BluetoothProfile.STATE_DISCONNECTED),
-                eq(false), eq(0));
+        verify(mAudioManager).handleBluetoothActiveDeviceChanged(
+                eq(null), any(BluetoothDevice.class), any(BtProfileConnectionInfo.class));
     }
 
     /**
diff --git a/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java b/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
index 43ddfcc..db66f00 100644
--- a/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
+++ b/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
@@ -47,11 +47,13 @@
 import androidx.test.rule.ServiceTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.bluetooth.R;
 import com.android.bluetooth.TestUtils;
 import com.android.bluetooth.btservice.AdapterService;
 import com.android.bluetooth.btservice.storage.DatabaseManager;
 
 import org.junit.After;
+import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -98,6 +100,9 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
+        Assume.assumeTrue("Ignore test when LeAudioService is not enabled",
+        mTargetContext.getResources().getBoolean(R.bool.profile_supported_le_audio));
+
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
 
@@ -149,6 +154,10 @@
 
     @After
     public void tearDown() throws Exception {
+        if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_le_audio)) {
+            return;
+        }
+
         mBondedDevices.clear();
         mGroupIntentQueue.clear();
         stopService();