Merge "Add caching for {Map,Sap}.getConnectionState" into tm-dev
diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java
index 30144c8..8a095ba 100755
--- a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java
+++ b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java
@@ -752,7 +752,14 @@
                     BluetoothStatusCodes.ERROR_BAD_PARAMETERS);
             return;
         }
+        if (getConnectionState(sink) != BluetoothProfile.STATE_CONNECTED) {
+            log("addSource: device is not connected");
+            mCallbacks.notifySourceAddFailed(sink, sourceMetadata,
+                    BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
+            return;
+        }
         if (!hasRoomForBroadcastSourceAddition(sink)) {
+            log("addSource: device has no room");
             mCallbacks.notifySourceAddFailed(sink, sourceMetadata,
                     BluetoothStatusCodes.ERROR_REMOTE_NOT_ENOUGH_RESOURCES);
             return;
@@ -788,6 +795,12 @@
                     BluetoothStatusCodes.ERROR_BAD_PARAMETERS);
             return;
         }
+        if (getConnectionState(sink) != BluetoothProfile.STATE_CONNECTED) {
+            log("modifySource: device is not connected");
+            mCallbacks.notifySourceModifyFailed(sink, sourceId,
+                    BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
+            return;
+        }
         Message message = stateMachine.obtainMessage(BassClientStateMachine.UPDATE_BCAST_SOURCE);
         message.arg1 = sourceId;
         message.obj = updatedMetadata;
@@ -807,11 +820,17 @@
         BassClientStateMachine stateMachine = getOrCreateStateMachine(sink);
         if (sourceId == BassConstants.INVALID_SOURCE_ID
                 || stateMachine == null) {
-            log("Error bad parameters: sourceId = " + sourceId);
+            log("removeSource: Error bad parameters: sourceId = " + sourceId);
             mCallbacks.notifySourceRemoveFailed(sink, sourceId,
                     BluetoothStatusCodes.ERROR_BAD_PARAMETERS);
             return;
         }
+        if (getConnectionState(sink) != BluetoothProfile.STATE_CONNECTED) {
+            log("removeSource: device is not connected");
+            mCallbacks.notifySourceRemoveFailed(sink, sourceId,
+                    BluetoothStatusCodes.ERROR_REMOTE_LINK_ERROR);
+            return;
+        }
         Message message = stateMachine.obtainMessage(BassClientStateMachine.REMOVE_BCAST_SOURCE);
         message.arg1 = sourceId;
         stateMachine.sendMessage(message);
diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java
index 4dd90fe..ae187ea 100755
--- a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java
+++ b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java
@@ -63,9 +63,12 @@
 import android.bluetooth.BluetoothGattCharacteristic;
 import android.bluetooth.BluetoothGattDescriptor;
 import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothLeAudioCodecConfigMetadata;
 import android.bluetooth.BluetoothLeAudioContentMetadata;
+import android.bluetooth.BluetoothLeBroadcastChannel;
 import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothLeBroadcastSubgroup;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothStatusCodes;
 import android.bluetooth.le.PeriodicAdvertisingCallback;
@@ -73,6 +76,7 @@
 import android.bluetooth.le.PeriodicAdvertisingReport;
 import android.bluetooth.le.ScanRecord;
 import android.bluetooth.le.ScanResult;
+import android.os.Binder;
 import android.os.Looper;
 import android.os.Message;
 import android.os.ParcelUuid;
@@ -186,12 +190,14 @@
         if (mBluetoothAdapter != null) {
             mPeriodicAdvManager = mBluetoothAdapter.getPeriodicAdvertisingManager();
         }
+        long token = Binder.clearCallingIdentity();
         mIsAllowedList = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
                 "persist.vendor.service.bt.wl", true);
         mDefNoPAS = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
                 "persist.vendor.service.bt.defNoPAS", false);
         mForceSB = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
                 "persist.vendor.service.bt.forceSB", false);
+        Binder.restoreCallingIdentity(token);
     }
 
     static BassClientStateMachine make(BluetoothDevice device,
@@ -387,6 +393,40 @@
         mNoStopScanOffload = false;
     }
 
+    private BluetoothLeBroadcastMetadata getBroadcastMetadataFromBaseData(BaseData baseData,
+            BluetoothDevice device) {
+        BluetoothLeBroadcastMetadata.Builder metaData =
+                new BluetoothLeBroadcastMetadata.Builder();
+        int index = 0;
+        for (BaseData.BaseInformation baseLevel2 : baseData.getLevelTwo()) {
+            BluetoothLeBroadcastSubgroup.Builder subGroup =
+                    new BluetoothLeBroadcastSubgroup.Builder();
+            for (int j = 0; j < baseLevel2.numSubGroups; j ++) {
+                BaseData.BaseInformation baseLevel3 =
+                        baseData.getLevelThree().get(index++);
+                BluetoothLeBroadcastChannel.Builder channel =
+                        new BluetoothLeBroadcastChannel.Builder();
+                channel.setChannelIndex(baseLevel3.index);
+                channel.setCodecMetadata(BluetoothLeAudioCodecConfigMetadata.
+                        fromRawBytes(baseLevel3.codecConfigInfo));
+                channel.setSelected(false);
+                subGroup.addChannel(channel.build());
+            }
+            subGroup.setCodecId((long)(baseLevel2.codecId[4] << 32
+                    | baseLevel2.codecId[3] << 24
+                    | baseLevel2.codecId[2] << 16
+                    | baseLevel2.codecId[1] << 8
+                    | baseLevel2.codecId[0]));
+            subGroup.setCodecSpecificConfig(BluetoothLeAudioCodecConfigMetadata.
+                    fromRawBytes(baseLevel2.codecConfigInfo));
+            subGroup.setContentMetadata(BluetoothLeAudioContentMetadata.
+                    fromRawBytes(baseLevel2.metaData));
+            metaData.addSubgroup(subGroup.build());
+        }
+        metaData.setSourceDevice(device, device.getAddressType());
+        return metaData.build();
+    }
+
     /** Internal periodc Advertising manager callback */
     private PeriodicAdvertisingCallback mPeriodicAdvCallback =
             new PeriodicAdvertisingCallback() {
@@ -432,6 +472,13 @@
                     // Parse the BIS indices from report's service data
                     if (mFirstTimeBisDiscovery) {
                         parseScanRecord(report.getSyncHandle(), report.getData());
+                        BaseData baseData = mService.getBase(report.getSyncHandle());
+                        if (baseData != null) {
+                            BluetoothLeBroadcastMetadata metaData =
+                                    getBroadcastMetadataFromBaseData(baseData,
+                                            mService.getDeviceForSyncHandle(report.getSyncHandle()));
+                            mService.getCallbacks().notifySourceFound(metaData);
+                        }
                         mFirstTimeBisDiscovery = false;
                     }
                 }
@@ -521,13 +568,15 @@
         }
         log("processBroadcastReceiverState: receiverState length: " + receiverState.length);
 
+        BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
         BluetoothLeBroadcastReceiveState recvState = null;
         if (receiverState.length == 0
                 || isEmpty(Arrays.copyOfRange(receiverState, 1, receiverState.length - 1))) {
+            String emptyBluetoothDevice = "00:00:00:00:00:00";
             if (mPendingOperation == REMOVE_BCAST_SOURCE) {
                 recvState = new BluetoothLeBroadcastReceiveState(mPendingSourceId,
-                        BluetoothDevice.ADDRESS_TYPE_UNKNOWN, // sourceAddressType
-                        null,   // sourceDevice
+                        BluetoothDevice.ADDRESS_TYPE_PUBLIC, // sourceAddressType
+                        btAdapter.getRemoteDevice(emptyBluetoothDevice),  // sourceDevice
                         0,  // sourceAdvertisingSid
                         0,  // broadcastId
                         BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, // paSyncState
@@ -535,8 +584,8 @@
                         BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                         null,   // badCode
                         0,  // numSubgroups
-                        null,   // bisSyncState
-                        null    // subgroupMetadata
+                        Arrays.asList(new Long[0]),   // bisSyncState
+                        Arrays.asList(new BluetoothLeAudioContentMetadata[0])    // subgroupMetadata
                 );
             } else if (receiverState.length == 0) {
                 if (mBluetoothLeBroadcastReceiveStates != null) {
@@ -548,8 +597,8 @@
                 }
                 mNextSourceId++;
                 recvState = new BluetoothLeBroadcastReceiveState(mNextSourceId,
-                        BluetoothDevice.ADDRESS_TYPE_UNKNOWN, // sourceAddressType
-                        null,   // sourceDevice
+                        BluetoothDevice.ADDRESS_TYPE_PUBLIC, // sourceAddressType
+                        btAdapter.getRemoteDevice(emptyBluetoothDevice),   // sourceDevice
                         0,  // sourceAdvertisingSid
                         0,  // broadcastId
                         BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_IDLE, // paSyncState
@@ -557,8 +606,8 @@
                         BluetoothLeBroadcastReceiveState.BIG_ENCRYPTION_STATE_NOT_ENCRYPTED,
                         null,   // badCode
                         0,  // numSubgroups
-                        null,   // bisSyncState
-                        null    // subgroupMetadata
+                        Arrays.asList(new Long[0]),   // bisSyncState
+                        Arrays.asList(new BluetoothLeAudioContentMetadata[0])    // subgroupMetadata
                 );
             }
         } else {
@@ -611,7 +660,6 @@
                     0,
                     mBroadcastSourceIdLength);
             int broadcastId = BassUtils.parseBroadcastId(broadcastIdBytes);
-            BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
             byte[] sourceAddress = new byte[BassConstants.BCAST_RCVR_STATE_SRC_ADDR_SIZE];
             System.arraycopy(
                     receiverState,
@@ -669,7 +717,9 @@
             log("old sourceInfo: " + oldRecvState);
             log("new sourceInfo: " + recvState);
             mBluetoothLeBroadcastReceiveStates.replace(characteristic.getInstanceId(), recvState);
-            if (oldRecvState.getSourceDevice() == null) {
+            String emptyBluetoothDevice = "00:00:00:00:00:00";
+            if (oldRecvState.getSourceDevice() == null
+                    || oldRecvState.getSourceDevice().getAddress().equals(emptyBluetoothDevice)) {
                 log("New Source Addition");
                 mService.getCallbacks().notifySourceAdded(mDevice,
                         recvState.getSourceId(), BluetoothStatusCodes.REASON_LOCAL_APP_REQUEST);
@@ -679,7 +729,8 @@
                 checkAndUpdateBroadcastCode(recvState);
                 processPASyncState(recvState);
             } else {
-                if (recvState.getSourceDevice() == null) {
+                if (recvState.getSourceDevice() == null
+                        || recvState.getSourceDevice().getAddress().equals(emptyBluetoothDevice)) {
                     BluetoothDevice removedDevice = oldRecvState.getSourceDevice();
                     log("sourceInfo removal" + removedDevice);
                     cancelActiveSync(removedDevice);
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
index 9758843..c1aa0ff 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
@@ -426,6 +426,8 @@
                         return;
                     }
                     mRunningProfiles.add(profile);
+                    // TODO(b/228875190): GATT is assumed supported. GATT starting triggers hardware
+                    // initializtion. Configuring a device without GATT causes start up failures.
                     if (GattService.class.getSimpleName().equals(profile.getName())) {
                         enableNative();
                     } else if (mRegisteredProfiles.size() == Config.getSupportedProfiles().length
@@ -450,7 +452,9 @@
                         return;
                     }
                     mRunningProfiles.remove(profile);
-                    // If only GATT is left, send BREDR_STOPPED.
+                    // TODO(b/228875190): GATT is assumed supported. GATT is expected to be the only
+                    // profile available in the "BLE ON" state. If only GATT is left, send
+                    // BREDR_STOPPED. If GATT is stopped, deinitialize the hardware.
                     if ((mRunningProfiles.size() == 1 && (GattService.class.getSimpleName()
                             .equals(mRunningProfiles.get(0).getName())))) {
                         mAdapterStateMachine.sendMessage(AdapterState.BREDR_STOPPED);
@@ -647,7 +651,13 @@
         BluetoothStatsLog.write_non_chained(BluetoothStatsLog.BLE_SCAN_STATE_CHANGED, -1, null,
                 BluetoothStatsLog.BLE_SCAN_STATE_CHANGED__STATE__RESET, false, false, false);
 
-        //Start Gatt service
+        // TODO(b/228875190): GATT is assumed supported. As a result, we don't respect the
+        // configuration sysprop. Configuring a device without GATT, although rare, will cause stack
+        // start up errors yielding init loops.
+        if (!GattService.isEnabled()) {
+            Log.w(TAG,
+                    "GATT is configured off but the stack assumes it to be enabled. Start anyway.");
+        }
         setProfileServiceState(GattService.class, BluetoothAdapter.STATE_ON);
     }
 
@@ -684,10 +694,11 @@
     void startProfileServices() {
         debugLog("startCoreServices()");
         Class[] supportedProfileServices = Config.getSupportedProfiles();
-        // If we support no profiles, or we only support GATT/BLE, just move on to BREDR_STARTED
-        if (supportedProfileServices.length == 0
-                || (supportedProfileServices.length == 1 && GattService.class.getSimpleName()
-                .equals(supportedProfileServices[0].getSimpleName()))) {
+        // TODO(b/228875190): GATT is assumed supported. If we support no other profiles then just
+        // move on to BREDR_STARTED. Note that configuring GATT to NOT supported will cause adapter
+        // initialization failures
+        if (supportedProfileServices.length == 1 && GattService.class.getSimpleName()
+                .equals(supportedProfileServices[0].getSimpleName())) {
             mAdapterProperties.onBluetoothReady();
             updateUuids();
             setBluetoothClassFromConfig();
@@ -703,10 +714,10 @@
         mAdapterProperties.setScanMode(AbstractionLayer.BT_SCAN_MODE_NONE);
 
         Class[] supportedProfileServices = Config.getSupportedProfiles();
-        // If we support no profiles, or we only support GATT/BLE, just move on to BREDR_STOPPED
-        if (supportedProfileServices.length == 0
-                || (supportedProfileServices.length == 1 && (mRunningProfiles.size() == 1
-                && GattService.class.getSimpleName().equals(mRunningProfiles.get(0).getName())))) {
+        // TODO(b/228875190): GATT is assumed supported. If we support no profiles then just move on
+        // to BREDR_STOPPED
+        if (supportedProfileServices.length == 1 && (mRunningProfiles.size() == 1
+                && GattService.class.getSimpleName().equals(mRunningProfiles.get(0).getName()))) {
             debugLog("stopProfileServices() - No profiles services to stop or already stopped.");
             mAdapterStateMachine.sendMessage(AdapterState.BREDR_STOPPED);
         } else {
@@ -994,6 +1005,8 @@
 
     private void setAllProfileServiceStates(Class[] services, int state) {
         for (Class service : services) {
+            // TODO(b/228875190): GATT is assumed supported and treated differently as part of the
+            // "BLE ON" state, despite GATT not being BLE specific.
             if (GattService.class.getSimpleName().equals(service.getSimpleName())) {
                 continue;
             }
diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java
index f933b20..83d5368 100644
--- a/android/app/src/com/android/bluetooth/gatt/GattService.java
+++ b/android/app/src/com/android/bluetooth/gatt/GattService.java
@@ -270,7 +270,7 @@
     private final Object mTestModeLock = new Object();
 
     public static boolean isEnabled() {
-        return BluetoothProperties.isProfileGattEnabled().orElse(false);
+        return BluetoothProperties.isProfileGattEnabled().orElse(true);
     }
 
     /**
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java b/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
index c7baabf..7bfe9f8 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java
@@ -73,6 +73,7 @@
     private PhoneStateListener mPhoneStateListener;
     private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener;
     private SignalStrengthUpdateRequest mSignalStrengthUpdateRequest;
+    private final Object mPhoneStateListenerLock = new Object();
 
     HeadsetPhoneState(HeadsetService headsetService) {
         Objects.requireNonNull(headsetService, "headsetService is null");
@@ -145,39 +146,43 @@
     }
 
     private void startListenForPhoneState() {
-        if (mPhoneStateListener != null) {
-            Log.w(TAG, "startListenForPhoneState, already listening");
-            return;
-        }
-        int events = getTelephonyEventsToListen();
-        if (events == PhoneStateListener.LISTEN_NONE) {
-            Log.w(TAG, "startListenForPhoneState, no event to listen");
-            return;
-        }
-        int subId = SubscriptionManager.getDefaultSubscriptionId();
-        if (!SubscriptionManager.isValidSubscriptionId(subId)) {
-            // Will retry listening for phone state in onSubscriptionsChanged() callback
-            Log.w(TAG, "startListenForPhoneState, invalid subscription ID " + subId);
-            return;
-        }
-        Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events);
-        mPhoneStateListener = new HeadsetPhoneStateListener(command -> mHandler.post(command));
-        mTelephonyManager.listen(mPhoneStateListener, events);
-        if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
-            mTelephonyManager.setSignalStrengthUpdateRequest(mSignalStrengthUpdateRequest);
+        synchronized (mPhoneStateListenerLock) {
+            if (mPhoneStateListener != null) {
+                Log.w(TAG, "startListenForPhoneState, already listening");
+                return;
+            }
+            int events = getTelephonyEventsToListen();
+            if (events == PhoneStateListener.LISTEN_NONE) {
+                Log.w(TAG, "startListenForPhoneState, no event to listen");
+                return;
+            }
+            int subId = SubscriptionManager.getDefaultSubscriptionId();
+            if (!SubscriptionManager.isValidSubscriptionId(subId)) {
+                // Will retry listening for phone state in onSubscriptionsChanged() callback
+                Log.w(TAG, "startListenForPhoneState, invalid subscription ID " + subId);
+                return;
+            }
+            Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events);
+            mPhoneStateListener = new HeadsetPhoneStateListener(command -> mHandler.post(command));
+            mTelephonyManager.listen(mPhoneStateListener, events);
+            if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
+                mTelephonyManager.setSignalStrengthUpdateRequest(mSignalStrengthUpdateRequest);
+            }
         }
     }
 
     private void stopListenForPhoneState() {
-        if (mPhoneStateListener == null) {
-            Log.i(TAG, "stopListenForPhoneState(), no listener indicates nothing is listening");
-            return;
+        synchronized (mPhoneStateListenerLock) {
+            if (mPhoneStateListener == null) {
+                Log.i(TAG, "stopListenForPhoneState(), no listener indicates nothing is listening");
+                return;
+            }
+            Log.i(TAG, "stopListenForPhoneState(), stopping listener, enabled_events="
+                    + getTelephonyEventsToListen());
+            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+            mPhoneStateListener = null;
+            mTelephonyManager.clearSignalStrengthUpdateRequest(mSignalStrengthUpdateRequest);
         }
-        Log.i(TAG, "stopListenForPhoneState(), stopping listener, enabled_events="
-                + getTelephonyEventsToListen());
-        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
-        mPhoneStateListener = null;
-        mTelephonyManager.clearSignalStrengthUpdateRequest(mSignalStrengthUpdateRequest);
     }
 
     int getCindService() {
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
index 8c525ae..b13c9a4 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -31,7 +31,6 @@
 import android.bluetooth.IBluetoothHeadset;
 import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
-import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -137,7 +136,7 @@
     private VoiceRecognitionTimeoutEvent mVoiceRecognitionTimeoutEvent;
     // Timeout when voice recognition is started by remote device
     @VisibleForTesting static int sStartVrTimeoutMs = 5000;
-    private ArrayList<HeadsetClccResponse> mHeadsetClccResponses = new ArrayList<>();
+    private ArrayList<StateMachineTask> mPendingClccResponses = new ArrayList<>();
     private boolean mStarted;
     private boolean mCreated;
     private static HeadsetService sHeadsetService;
@@ -329,6 +328,14 @@
         }
     }
 
+    private void doForEachConnectedStateMachine(List<StateMachineTask> tasks) {
+        synchronized (mStateMachines) {
+            for (StateMachineTask task : tasks) {
+                doForEachConnectedStateMachine(task);
+            }
+        }
+    }
+
     void onDeviceStateChanged(HeadsetDeviceState deviceState) {
         doForEachConnectedStateMachine(
                 stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.DEVICE_STATE_CHANGED,
@@ -1855,18 +1862,20 @@
                 mSystemInterface.getAudioManager().setA2dpSuspended(false);
             }
         });
+
     }
 
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     private void clccResponse(int index, int direction, int status, int mode, boolean mpty,
             String number, int type) {
         enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "Need MODIFY_PHONE_STATE permission");
-        mHeadsetClccResponses.add(
-                new HeadsetClccResponse(index, direction, status, mode, mpty, number, type));
+        mPendingClccResponses.add(
+                stateMachine -> stateMachine.sendMessage(HeadsetStateMachine.SEND_CLCC_RESPONSE,
+                        new HeadsetClccResponse(index, direction, status, mode, mpty, number,
+                                type)));
         if (index == CLCC_END_MARK_INDEX) {
-            doForEachConnectedStateMachine(stateMachine -> stateMachine.sendMessage(
-                    HeadsetStateMachine.SEND_CLCC_RESPONSE, mHeadsetClccResponses));
-            mHeadsetClccResponses.clear();
+            doForEachConnectedStateMachine(mPendingClccResponses);
+            mPendingClccResponses.clear();
         }
     }
 
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
index 09cc75f..39d372f 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java
@@ -869,7 +869,7 @@
                     mNativeInterface.notifyDeviceStatus(mDevice, (HeadsetDeviceState) message.obj);
                     break;
                 case SEND_CLCC_RESPONSE:
-                    processSendClccResponse((ArrayList<HeadsetClccResponse>) message.obj);
+                    processSendClccResponse((HeadsetClccResponse) message.obj);
                     break;
                 case CLCC_RSP_TIMEOUT: {
                     BluetoothDevice device = (BluetoothDevice) message.obj;
@@ -2029,16 +2029,15 @@
         sendIndicatorIntent(device, indId, indValue);
     }
 
-    private void processSendClccResponse(ArrayList<HeadsetClccResponse> clccList) {
+    private void processSendClccResponse(HeadsetClccResponse clcc) {
         if (!hasMessages(CLCC_RSP_TIMEOUT)) {
             return;
         }
-        removeMessages(CLCC_RSP_TIMEOUT);
-
-        for (HeadsetClccResponse clcc : clccList) {
-            mNativeInterface.clccResponse(mDevice, clcc.mIndex, clcc.mDirection, clcc.mStatus,
-                    clcc.mMode, clcc.mMpty, clcc.mNumber, clcc.mType);
+        if (clcc.mIndex == 0) {
+            removeMessages(CLCC_RSP_TIMEOUT);
         }
+        mNativeInterface.clccResponse(mDevice, clcc.mIndex, clcc.mDirection, clcc.mStatus,
+                clcc.mMode, clcc.mMpty, clcc.mNumber, clcc.mType);
     }
 
     private void processSendVendorSpecificResultCode(HeadsetVendorSpecificResultCode resultCode) {
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioObjectsFactory.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioObjectsFactory.java
new file mode 100644
index 0000000..5f0f8c0
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioObjectsFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.le_audio;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Factory class for object initialization to help with unit testing
+ */
+public class LeAudioObjectsFactory {
+    private static final String TAG = LeAudioObjectsFactory.class.getSimpleName();
+    private static LeAudioObjectsFactory sInstance;
+    private static final Object INSTANCE_LOCK = new Object();
+
+    private LeAudioObjectsFactory() {}
+
+    /**
+     * Get the singleton instance of object factory
+     *
+     * @return the singleton instance, guaranteed not null
+     */
+    public static LeAudioObjectsFactory getInstance() {
+        synchronized (INSTANCE_LOCK) {
+            if (sInstance == null) {
+                sInstance = new LeAudioObjectsFactory();
+            }
+        }
+        return sInstance;
+    }
+
+    /**
+     * Allow unit tests to substitute {@link LeAudioObjectsFactory} with a test instance
+     *
+     * @param objectsFactory a test instance of the {@link LeAudioObjectsFactory}
+     */
+    @VisibleForTesting
+    public static void setInstanceForTesting(LeAudioObjectsFactory objectsFactory) {
+        Utils.enforceInstrumentationTestMode();
+        synchronized (INSTANCE_LOCK) {
+            Log.d(TAG, "setInstanceForTesting(), set to " + objectsFactory);
+            sInstance = objectsFactory;
+        }
+    }
+
+    /**
+     * Get a {@link LeAudioTmapGattServer} object
+     *
+     * @param context local context
+     * @return
+     */
+    public LeAudioTmapGattServer getTmapGattServer(Context context) {
+        return new LeAudioTmapGattServer(
+                new LeAudioTmapGattServer.BluetoothGattServerProxy(context));
+    }
+}
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
index 05e20f6..c0bcc2e 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -122,6 +122,7 @@
     LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface = null;
     @VisibleForTesting
     AudioManager mAudioManager;
+    LeAudioTmapGattServer mTmapGattServer;
 
     @VisibleForTesting
     RemoteCallbackList<IBluetoothLeBroadcastCallback> mBroadcastCallbacks;
@@ -135,12 +136,15 @@
             mIsActive = false;
             mActiveContexts = ACTIVE_CONTEXTS_NONE;
             mCodecStatus = null;
+            mLostDevicesWhileStreaming = new ArrayList<>();
         }
 
         public Boolean mIsConnected;
         public Boolean mIsActive;
         public Integer mActiveContexts;
         public BluetoothLeAudioCodecStatus mCodecStatus;
+        /* This can be non empty only for the streaming time */
+        List<BluetoothDevice> mLostDevicesWhileStreaming;
     }
 
     List<BluetoothLeAudioCodecConfig> mInputLocalCodecCapabilities = new ArrayList<>();
@@ -237,6 +241,9 @@
         registerReceiver(mConnectionStateChangedReceiver, filter);
         mLeAudioCallbacks = new RemoteCallbackList<IBluetoothLeAudioCallback>();
 
+        int tmapRoleMask =
+                LeAudioTmapGattServer.TMAP_ROLE_FLAG_CG | LeAudioTmapGattServer.TMAP_ROLE_FLAG_UMS;
+
         // Initialize Broadcast native interface
         if (mAdapterService.isLeAudioBroadcastSourceSupported()) {
             mBroadcastCallbacks = new RemoteCallbackList<IBluetoothLeBroadcastCallback>();
@@ -244,9 +251,18 @@
                     LeAudioBroadcasterNativeInterface.getInstance(),
                     "LeAudioBroadcasterNativeInterface cannot be null when LeAudioService starts");
             mLeAudioBroadcasterNativeInterface.init();
+            tmapRoleMask |= LeAudioTmapGattServer.TMAP_ROLE_FLAG_BMS;
         } else {
             Log.w(TAG, "Le Audio Broadcasts not supported.");
         }
+
+        // the role mask is fixed in Android
+        if (mTmapGattServer != null) {
+            throw new IllegalStateException("TMAP GATT server started before start() is called");
+        }
+        mTmapGattServer = LeAudioObjectsFactory.getInstance().getTmapGattServer(this);
+        mTmapGattServer.start(tmapRoleMask);
+
         // Mark service as started
         setLeAudioService(this);
 
@@ -277,6 +293,14 @@
         }
 
         setActiveDevice(null);
+
+        if (mTmapGattServer == null) {
+            Log.w(TAG, "TMAP GATT server should never be null before stop() is called");
+        } else {
+            mTmapGattServer.stop();
+            mTmapGattServer = null;
+        }
+
         //Don't wait for async call with INACTIVE group status, clean active
         //device for active group.
         synchronized (mGroupLock) {
@@ -440,7 +464,8 @@
     }
 
     BluetoothDevice getConnectedGroupLeadDevice(int groupId) {
-        if (mActiveAudioOutDevice != null) {
+        if (mActiveAudioOutDevice != null
+            && getGroupId(mActiveAudioOutDevice) == groupId) {
             return mActiveAudioOutDevice;
         }
         return getFirstDeviceFromGroup(groupId);
@@ -1013,6 +1038,21 @@
         return BluetoothProfileConnectionInfo.CREATOR.createFromParcel(parcel);
     }
 
+    private void clearLostDevicesWhileStreaming(LeAudioGroupDescriptor descriptor) {
+        for (BluetoothDevice dev : descriptor.mLostDevicesWhileStreaming) {
+            LeAudioStateMachine sm = mStateMachines.get(dev);
+            if (sm == null) {
+                continue;
+            }
+
+            LeAudioStackEvent stackEvent =
+                new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED);
+            stackEvent.device = dev;
+            stackEvent.valueInt1 = LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED;
+            sm.sendMessage(LeAudioStateMachine.STACK_EVENT, stackEvent);
+        }
+    }
+
     // Suppressed since this is part of a local process
     @SuppressLint("AndroidFrameworkRequiresPermission")
     void messageFromNative(LeAudioStackEvent stackEvent) {
@@ -1024,7 +1064,41 @@
         // Some events require device state machine
             synchronized (mStateMachines) {
                 LeAudioStateMachine sm = mStateMachines.get(device);
-                if (sm == null) {
+                if (sm != null) {
+                    /*
+                    * To improve scenario when lead Le Audio device is disconnected for the
+                    * streaming group, while there are still other devices streaming,
+                    * LeAudioService will not notify audio framework or other users about
+                    * Le Audio lead device disconnection. Instead we try to reconnect under the hood
+                    * and keep using lead device as a audio device indetifier in the audio framework
+                    * in order to not stop the stream.
+                    */
+                    int groupId = getGroupId(device);
+                    synchronized (mGroupLock) {
+                        LeAudioGroupDescriptor descriptor = mGroupDescriptors.get(groupId);
+                        switch (stackEvent.valueInt1) {
+                            case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTING:
+                            case LeAudioStackEvent.CONNECTION_STATE_DISCONNECTED:
+                                if (descriptor != null && (Objects.equals(device,
+                                        mActiveAudioOutDevice)
+                                        || Objects.equals(device, mActiveAudioInDevice))
+                                        && (getConnectedPeerDevices(groupId).size() > 1)) {
+                                    descriptor.mLostDevicesWhileStreaming.add(device);
+                                    return;
+                                }
+                                break;
+                            case LeAudioStackEvent.CONNECTION_STATE_CONNECTED:
+                            case LeAudioStackEvent.CONNECTION_STATE_CONNECTING:
+                                if (descriptor != null) {
+                                    descriptor.mLostDevicesWhileStreaming.remove(device);
+                                    /* Try to connect other devices from the group */
+                                    connectSet(device);
+                                }
+                                break;
+                        }
+                    }
+                } else {
+                    /* state machine does not exist yet */
                     switch (stackEvent.valueInt1) {
                         case LeAudioStackEvent.CONNECTION_STATE_CONNECTED:
                         case LeAudioStackEvent.CONNECTION_STATE_CONNECTING:
@@ -1035,11 +1109,11 @@
                         default:
                             break;
                     }
-                }
 
-                if (sm == null) {
-                    Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
-                    return;
+                    if (sm == null) {
+                        Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
+                        return;
+                    }
                 }
 
                 sm.sendMessage(LeAudioStateMachine.STACK_EVENT, stackEvent);
@@ -1152,6 +1226,8 @@
                             updateActiveDevices(groupId, descriptor.mActiveContexts,
                                     ACTIVE_CONTEXTS_NONE, descriptor.mIsActive);
                             notifyGroupStatus = true;
+                            /* Clear lost devices */
+                            clearLostDevicesWhileStreaming(descriptor);
                         }
                     } else {
                         Log.e(TAG, "no descriptors for group: " + groupId);
@@ -2395,6 +2471,9 @@
             ProfileService.println(sb, "    mActiveContexts: " + descriptor.mActiveContexts);
             ProfileService.println(sb, "    group lead: " + getConnectedGroupLeadDevice(groupId));
             ProfileService.println(sb, "    first device: " + getFirstDeviceFromGroup(groupId));
+            for (BluetoothDevice dev : descriptor.mLostDevicesWhileStreaming) {
+                ProfileService.println(sb, "        lost device: " + dev);
+            }
         }
     }
 }
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioTmapGattServer.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioTmapGattServer.java
new file mode 100644
index 0000000..230279c
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioTmapGattServer.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.le_audio;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothUuid;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * A GATT server for Telephony and Media Audio Profile (TMAP)
+ */
+@VisibleForTesting
+public class LeAudioTmapGattServer {
+    private static final boolean DBG = true;
+    private static final String TAG = "LeAudioTmapGattServer";
+
+    /* Telephony and Media Audio Profile Role Characteristic UUID */
+    @VisibleForTesting
+    public static final UUID UUID_TMAP_ROLE =
+            UUID.fromString("00002B51-0000-1000-8000-00805f9b34fb");
+
+    /* TMAP Role: Call Gateway */
+    public static final int TMAP_ROLE_FLAG_CG = 1;
+    /* TMAP Role: Call Terminal */
+    public static final int TMAP_ROLE_FLAG_CT = 1 << 1;
+    /* TMAP Role: Unicast Media Sender */
+    public static final int TMAP_ROLE_FLAG_UMS = 1 << 2;
+    /* TMAP Role: Unicast Media Receiver */
+    public static final int TMAP_ROLE_FLAG_UMR = 1 << 3;
+    /* TMAP Role: Broadcast Media Sender */
+    public static final int TMAP_ROLE_FLAG_BMS = 1 << 4;
+    /* TMAP Role: Broadcast Media Receiver */
+    public static final int TMAP_ROLE_FLAG_BMR = 1 << 5;
+
+    private final BluetoothGattServerProxy mBluetoothGattServer;
+
+    /*package*/ LeAudioTmapGattServer(BluetoothGattServerProxy gattServer) {
+        mBluetoothGattServer = gattServer;
+    }
+
+    /**
+     * Init TMAP server
+     * @param roleMask bit mask of supported roles.
+     */
+    @VisibleForTesting
+    public void start(int roleMask) {
+        if (DBG) {
+            Log.d(TAG, "start(roleMask:" + roleMask + ")");
+        }
+
+        if (!mBluetoothGattServer.open(mBluetoothGattServerCallback)) {
+            throw new IllegalStateException("Could not open Gatt server");
+        }
+
+        BluetoothGattService service =
+                new BluetoothGattService(BluetoothUuid.TMAP.getUuid(),
+                BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+        BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(
+                UUID_TMAP_ROLE,
+                BluetoothGattCharacteristic.PROPERTY_READ,
+                BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED);
+
+        characteristic.setValue(roleMask, BluetoothGattCharacteristic.FORMAT_UINT16, 0);
+        service.addCharacteristic(characteristic);
+
+        if (!mBluetoothGattServer.addService(service)) {
+            throw new IllegalStateException("Failed to add service for TMAP");
+        }
+    }
+
+    /**
+     * Stop TMAP server
+     */
+    @VisibleForTesting
+    public void stop() {
+        if (DBG) {
+            Log.d(TAG, "stop()");
+        }
+        if (mBluetoothGattServer == null) {
+            Log.w(TAG, "mBluetoothGattServer should not be null when stop() is called");
+            return;
+        }
+        mBluetoothGattServer.close();
+    }
+
+    /**
+     * Callback to handle incoming requests to the GATT server.
+     * All read/write requests for characteristics and descriptors are handled here.
+     */
+    private final BluetoothGattServerCallback mBluetoothGattServerCallback =
+            new BluetoothGattServerCallback() {
+        @Override
+        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset,
+                                                BluetoothGattCharacteristic characteristic) {
+            byte[] value = characteristic.getValue();
+            if (DBG) {
+                Log.d(TAG, "value " + value);
+            }
+            if (value != null) {
+                Log.e(TAG, "value null");
+                value = Arrays.copyOfRange(value, offset, value.length);
+            }
+            mBluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
+                    offset, value);
+        }
+    };
+
+     /**
+     * A proxy class that facilitates testing.
+     *
+     * This is necessary due to the "final" attribute of the BluetoothGattServer class.
+     */
+    public static class BluetoothGattServerProxy {
+        private final Context mContext;
+        private final BluetoothManager mBluetoothManager;
+
+        private BluetoothGattServer mBluetoothGattServer;
+
+         /**
+          * Create a new GATT server proxy object
+          * @param context context to use
+          */
+        public BluetoothGattServerProxy(Context context) {
+            mContext = context;
+            mBluetoothManager = context.getSystemService(BluetoothManager.class);
+        }
+
+         /**
+          * Open with GATT server callback
+          * @param callback callback to invoke
+          * @return true on success
+          */
+        public boolean open(BluetoothGattServerCallback callback) {
+            mBluetoothGattServer = mBluetoothManager.openGattServer(mContext, callback);
+            return mBluetoothGattServer != null;
+        }
+
+         /**
+          * Close the GATT server, should be called as soon as the server is not needed
+          */
+        public void close() {
+            if (mBluetoothGattServer == null) {
+                Log.w(TAG, "BluetoothGattServerProxy.close() called without open()");
+                return;
+            }
+            mBluetoothGattServer.close();
+            mBluetoothGattServer = null;
+        }
+
+         /**
+          * Add a GATT service
+          * @param service added service
+          * @return true on success
+          */
+        public boolean addService(BluetoothGattService service) {
+            return mBluetoothGattServer.addService(service);
+        }
+
+         /**
+          * Send GATT response to remote
+          * @param device remote device
+          * @param requestId request id
+          * @param status status of response
+          * @param offset offset of the value
+          * @param value value content
+          * @return true on success
+          */
+        public boolean sendResponse(
+                BluetoothDevice device, int requestId, int status, int offset, byte[] value) {
+            return mBluetoothGattServer.sendResponse(device, requestId, status, offset, value);
+        }
+
+         /**
+          * Gatt a list of devices connected to this GATT server
+          * @return list of connected devices at this moment
+          */
+        public List<BluetoothDevice> getConnectedDevices() {
+            return mBluetoothManager.getConnectedDevices(BluetoothProfile.GATT_SERVER);
+        }
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
index 1ad6bc9..416661d 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpServiceTest.java
@@ -83,7 +83,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when A2dpService is not enabled", A2dpService.isEnabled());
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
 
@@ -123,9 +122,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!A2dpService.isEnabled()) {
-            return;
-        }
         stopService();
         mTargetContext.unregisterReceiver(mA2dpIntentReceiver);
         mConnectionStateChangedQueue.clear();
diff --git a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
index fae79ec..f2a81dd 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/a2dp/A2dpStateMachineTest.java
@@ -70,7 +70,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when A2dpService is not enabled", A2dpService.isEnabled());
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
@@ -116,9 +115,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!A2dpService.isEnabled()) {
-            return;
-        }
         mA2dpStateMachine.doQuit();
         mHandlerThread.quit();
         mHandlerThread.join(TIMEOUT_MS);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
index 5cc900b..42b5ff7 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkServiceTest.java
@@ -75,8 +75,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when A2dpSinkService is not enabled",
-                A2dpSinkService.isEnabled());
         MockitoAnnotations.initMocks(this);
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -110,9 +108,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!A2dpSinkService.isEnabled()) {
-            return;
-        }
         TestUtils.stopService(mServiceRule, A2dpSinkService.class);
         mService = A2dpSinkService.getA2dpSinkService();
         assertThat(mService).isNull();
diff --git a/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachineTest.java
index 7721bdb..a1ccf07 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStateMachineTest.java
@@ -63,8 +63,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when A2dpSinkService is not enabled",
-                A2dpSinkService.isEnabled());
         MockitoAnnotations.initMocks(this);
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -82,9 +80,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!A2dpSinkService.isEnabled()) {
-            return;
-        }
         mStateMachine = null;
         mDevice = null;
         mAdapter = null;
diff --git a/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
index 16e5c8a..2361a13 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/A2dpSinkStreamHandlerTest.java
@@ -62,8 +62,6 @@
     @Before
     public void setUp() {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when A2dpSinkService is not enabled",
-                A2dpSinkService.isEnabled());
         MockitoAnnotations.initMocks(this);
         // Mock the looper
         if (Looper.myLooper() == null) {
diff --git a/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/StackEventTest.java b/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/StackEventTest.java
index e8ebb5e..1d53d10 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/StackEventTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/a2dpsink/StackEventTest.java
@@ -42,8 +42,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when A2dpSinkService is not enabled",
-                A2dpSinkService.isEnabled());
         mAdapter = BluetoothAdapter.getDefaultAdapter();
         assertThat(mAdapter).isNotNull();
         mDevice = mAdapter.getRemoteDevice(TEST_ADDRESS);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryStateMachineTest.java
index c1665a0..f5470dd 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/bas/BatteryStateMachineTest.java
@@ -77,10 +77,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        //TODO: check flag or override it
-        Assume.assumeTrue("Ignore test when BatteryService is not enabled",
-                BatteryService.isEnabled());
-
         TestUtils.setAdapterService(mAdapterService);
 
         mAdapter = BluetoothAdapter.getDefaultAdapter();
@@ -100,9 +96,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!BatteryService.isEnabled()) {
-            return;
-        }
         mBatteryStateMachine.doQuit();
         mHandlerThread.quit();
         TestUtils.clearAdapterService(mAdapterService);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorServiceTest.java
index 96ff121..692d8ae 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorServiceTest.java
@@ -78,9 +78,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when CsipSetCoordinatorService is not enabled",
-                CsipSetCoordinatorService.isEnabled());
-
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
@@ -134,9 +131,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!CsipSetCoordinatorService.isEnabled()) {
-            return;
-        }
         stopService();
         mTargetContext.unregisterReceiver(mCsipSetCoordinatorIntentReceiver);
         TestUtils.clearAdapterService(mAdapterService);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java
index a57c668..8a08dfe 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/csip/CsipSetCoordinatorStateMachineTest.java
@@ -61,9 +61,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when CsipSetCoordinatorService is not enabled",
-                CsipSetCoordinatorService.isEnabled());
-
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
@@ -86,10 +83,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!CsipSetCoordinatorService.isEnabled()) {
-            return;
-        }
-
         mStateMachine.doQuit();
         mHandlerThread.quit();
         TestUtils.clearAdapterService(mAdapterService);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientStateMachineTest.java
index 3fe799f..3f88b34 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientStateMachineTest.java
@@ -64,8 +64,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when HearingAccessClientService is not enabled",
-                HapClientService.isEnabled());
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
@@ -87,9 +85,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!HapClientService.isEnabled()) {
-            return;
-        }
         mHapClientStateMachine.doQuit();
         mHandlerThread.quit();
         TestUtils.clearAdapterService(mAdapterService);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java
index b20918f..ade84d1 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hap/HapClientTest.java
@@ -99,9 +99,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when HearingAccessClientService is not enabled",
-                HapClientService.isEnabled());
-
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
 
@@ -176,9 +173,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!HapClientService.isEnabled()) {
-            return;
-        }
         mService.mCallbacks.unregister(mCallback);
 
         stopService();
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
index 9b67c4e..95a4bac 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidServiceTest.java
@@ -83,8 +83,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
-                HearingAidService.isEnabled());
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
 
@@ -127,9 +125,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!HearingAidService.isEnabled()) {
-            return;
-        }
         stopService();
         mTargetContext.unregisterReceiver(mHearingAidIntentReceiver);
         mDeviceQueueMap.clear();
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
index 29cd098..df822f2 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hearingaid/HearingAidStateMachineTest.java
@@ -63,8 +63,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when HearingAidService is not enabled",
-                HearingAidService.isEnabled());
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
@@ -86,9 +84,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!HearingAidService.isEnabled()) {
-            return;
-        }
         mHearingAidStateMachine.doQuit();
         mHandlerThread.quit();
         TestUtils.clearAdapterService(mAdapterService);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
index b8a6ccb..c89e135 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetServiceAndStateMachineTest.java
@@ -155,8 +155,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when HeadsetService is not enabled",
-                HeadsetService.isEnabled());
         MockitoAnnotations.initMocks(this);
         PowerManager powerManager = mTargetContext.getSystemService(PowerManager.class);
         mVoiceRecognitionWakeLock =
@@ -233,9 +231,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!HeadsetService.isEnabled()) {
-            return;
-        }
         mTargetContext.unregisterReceiver(mHeadsetIntentReceiver);
         TestUtils.stopService(mServiceRule, HeadsetService.class);
         HeadsetService.sStartVrTimeoutMs = mOriginalVrTimeoutMs;
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java b/android/app/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
index 0e1743e..0b865c2 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hid/HidDeviceTest.java
@@ -100,8 +100,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when HidDeviceService is not enabled",
-                HidDeviceService.isEnabled());
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
@@ -145,9 +143,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!HidDeviceService.isEnabled()) {
-            return;
-        }
         TestUtils.stopService(mServiceRule, HidDeviceService.class);
         mHidDeviceService = HidDeviceService.getHidDeviceService();
         Assert.assertNull(mHidDeviceService);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java
index 3081df5..a33824a 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioBroadcastServiceTest.java
@@ -45,6 +45,7 @@
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeoutException;
@@ -70,6 +71,8 @@
     private AudioManager mAudioManager;
     @Mock
     private LeAudioBroadcasterNativeInterface mNativeInterface;
+    @Mock private LeAudioTmapGattServer mTmapGattServer;
+    @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance();
 
     private static final String TEST_MAC_ADDRESS = "00:11:22:33:44:55";
     private static final int TEST_BROADCAST_ID = 42;
@@ -155,6 +158,12 @@
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
 
+        // Use spied objects factory
+        doNothing().when(mTmapGattServer).start(anyInt());
+        doNothing().when(mTmapGattServer).stop();
+        LeAudioObjectsFactory.setInstanceForTesting(mObjectsFactory);
+        doReturn(mTmapGattServer).when(mObjectsFactory).getTmapGattServer(any());
+
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
index c666474..a72010a 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
@@ -24,18 +24,16 @@
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.nullable;
 import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.eq;
 
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothLeAudioCodecConfig;
 import android.bluetooth.BluetoothLeAudioCodecStatus;
+import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothUuid;
 import android.bluetooth.IBluetoothLeAudioCallback;
@@ -44,7 +42,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
-import android.media.BluetoothProfileConnectionInfo;
 import android.os.ParcelUuid;
 
 import androidx.test.InstrumentationRegistry;
@@ -63,13 +60,11 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
 
-import java.util.Arrays;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -104,6 +99,9 @@
     @Mock private DatabaseManager mDatabaseManager;
     @Mock private LeAudioNativeInterface mNativeInterface;
     @Mock private AudioManager mAudioManager;
+    @Mock private LeAudioTmapGattServer mTmapGattServer;
+    @Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance();
+
 
     @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
 
@@ -135,7 +133,6 @@
                     add(LC3_48KHZ_16KHZ_CONFIG);
             }};
 
-
     private static final List<BluetoothLeAudioCodecConfig> INPUT_SELECTABLE_CONFIG =
             new ArrayList() {{
                     add(LC3_16KHZ_CONFIG);
@@ -149,12 +146,16 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when LeAudioService is not enabled",
-                LeAudioService.isEnabled());
 
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
 
+        // Use spied objects factory
+        doNothing().when(mTmapGattServer).start(anyInt());
+        doNothing().when(mTmapGattServer).stop();
+        LeAudioObjectsFactory.setInstanceForTesting(mObjectsFactory);
+        doReturn(mTmapGattServer).when(mObjectsFactory).getTmapGattServer(any());
+
         TestUtils.setAdapterService(mAdapterService);
         doReturn(MAX_LE_AUDIO_CONNECTIONS).when(mAdapterService).getMaxConnectedAudioDevices();
         doReturn(new ParcelUuid[]{BluetoothUuid.LE_AUDIO}).when(mAdapterService)
@@ -162,7 +163,9 @@
         doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
         doReturn(true, false).when(mAdapterService).isStartedProfile(anyString());
 
-        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        BluetoothManager manager = mTargetContext.getSystemService(BluetoothManager.class);
+        assertThat(manager).isNotNull();
+        mAdapter = manager.getAdapter();
         // Mock methods in AdapterService
         doAnswer(invocation -> mBondedDevices.toArray(new BluetoothDevice[]{})).when(
                 mAdapterService).getBondedDevices();
@@ -202,10 +205,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!LeAudioService.isEnabled()) {
-            return;
-        }
-
         mBondedDevices.clear();
         mGroupIntentQueue.clear();
         stopService();
diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioTmapGattServerTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioTmapGattServerTest.java
new file mode 100644
index 0000000..230bfbf
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioTmapGattServerTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.le_audio;
+
+import static android.bluetooth.BluetoothGattCharacteristic.FORMAT_UINT16;
+import static android.bluetooth.BluetoothGattCharacteristic.PERMISSION_READ_ENCRYPTED;
+import static android.bluetooth.BluetoothGattCharacteristic.PROPERTY_READ;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothUuid;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class LeAudioTmapGattServerTest {
+    private static final int TEST_ROLE_MASK =
+            LeAudioTmapGattServer.TMAP_ROLE_FLAG_CG | LeAudioTmapGattServer.TMAP_ROLE_FLAG_UMS;
+
+    @Mock
+    private LeAudioTmapGattServer.BluetoothGattServerProxy mGattServerProxy;
+
+    private LeAudioTmapGattServer mServer;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        doReturn(true).when(mGattServerProxy).open(any());
+        doReturn(true).when(mGattServerProxy).addService(any());
+        mServer = new LeAudioTmapGattServer(mGattServerProxy);
+    }
+
+    @After
+    public void tearDown() {
+        mServer = null;
+    }
+
+    @Test
+    public void testStartStopService() {
+        ArgumentCaptor<BluetoothGattService> captor =
+                ArgumentCaptor.forClass(BluetoothGattService.class);
+        mServer.start(TEST_ROLE_MASK);
+        verify(mGattServerProxy, times(1)).open(any());
+        verify(mGattServerProxy, times(1)).addService(captor.capture());
+
+        // verify primary service with TMAP UUID
+        BluetoothGattService service = captor.getValue();
+        assertThat(service).isNotNull();
+        assertThat(service.getUuid()).isEqualTo(BluetoothUuid.TMAP.getUuid());
+        assertThat(service.getType()).isEqualTo(BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+        // verify characteristic UUID, permission and property
+        BluetoothGattCharacteristic characteristic =
+                service.getCharacteristic(LeAudioTmapGattServer.UUID_TMAP_ROLE);
+        assertThat(characteristic).isNotNull();
+        assertThat(characteristic.getProperties()).isEqualTo(PROPERTY_READ);
+        assertThat(characteristic.getPermissions()).isEqualTo(PERMISSION_READ_ENCRYPTED);
+
+        // verify characteristic value
+        int value = characteristic.getIntValue(FORMAT_UINT16, 0);
+        assertThat(value).isEqualTo(TEST_ROLE_MASK);
+
+        // verify stop triggers stop method call
+        mServer.stop();
+        verify(mGattServerProxy, times(1)).close();
+    }
+
+    @Test
+    public void testStartServiceFailed() {
+        // Verify throw exception when failed to open GATT server
+        doReturn(false).when(mGattServerProxy).open(any());
+        assertThrows(IllegalStateException.class, () -> mServer.start(TEST_ROLE_MASK));
+    }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mcp/McpServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/mcp/McpServiceTest.java
index d8134c0..00b4d92 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mcp/McpServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mcp/McpServiceTest.java
@@ -63,7 +63,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when MCP Server is not enabled", McpService.isEnabled());
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
@@ -82,9 +81,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!McpService.isEnabled()) {
-            return;
-        }
         doReturn(false).when(mAdapterService).isStartedProfile(anyString());
         TestUtils.stopService(mServiceRule, McpService.class);
         mMcpService = McpService.getMcpService();
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java
index 7c203f1..aa90a28 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlGattServiceTest.java
@@ -78,7 +78,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when MCP Server is not enabled", McpService.isEnabled());
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
@@ -100,9 +99,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!McpService.isEnabled()) {
-            return;
-        }
         mMcpService = null;
         reset(mMockGattServer);
         reset(mMockMcpService);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlProfileTest.java b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlProfileTest.java
index 5a055c0..002c322 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlProfileTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/mcp/MediaControlProfileTest.java
@@ -92,7 +92,6 @@
         }
 
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when MCP Server is not enabled", McpService.isEnabled());
         MediaControlProfile.ListCallback listCallback;
         MockitoAnnotations.initMocks(this);
 
@@ -145,10 +144,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!McpService.isEnabled()) {
-            return;
-        }
-
         TestUtils.clearAdapterService(mAdapterService);
 
         mMediaControlProfile.cleanup();
diff --git a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java
index dcfec36..f127ede 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java
@@ -79,8 +79,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when VolumeControl is not enabled",
-                VolumeControlService.isEnabled());
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
 
@@ -120,9 +118,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!VolumeControlService.isEnabled()) {
-            return;
-        }
         stopService();
         mTargetContext.unregisterReceiver(mVolumeControlIntentReceiver);
         mDeviceQueueMap.clear();
diff --git a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlStateMachineTest.java b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlStateMachineTest.java
index d7b7fe2..f0cf5b6 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlStateMachineTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlStateMachineTest.java
@@ -62,8 +62,6 @@
     @Before
     public void setUp() throws Exception {
         mTargetContext = InstrumentationRegistry.getTargetContext();
-        Assume.assumeTrue("Ignore test when VolumeControl is not enabled",
-                VolumeControlService.isEnabled());
         // Set up mocks and test assets
         MockitoAnnotations.initMocks(this);
         TestUtils.setAdapterService(mAdapterService);
@@ -85,10 +83,6 @@
 
     @After
     public void tearDown() throws Exception {
-        if (!VolumeControlService.isEnabled()) {
-            return;
-        }
-        mVolumeControlStateMachine.doQuit();
         mHandlerThread.quit();
         TestUtils.clearAdapterService(mAdapterService);
     }
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java
index daf7107..4bd1dee 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java
@@ -374,6 +374,7 @@
 
         @Override
         public void onSourceFound(BluetoothLeBroadcastMetadata source) {
+            Log.d("BluetoothProxy", "onSourceFound");
             if (mBassEventListener != null) {
                 mBassEventListener.onSourceFound(source);
             }
@@ -401,6 +402,7 @@
         @Override
         public void onReceiveStateChanged(BluetoothDevice sink, int sourceId,
                 BluetoothLeBroadcastReceiveState state) {
+            Log.d("BluetoothProxy", "onReceiveStateChanged");
             if (allLeAudioDevicesMutable.getValue() != null) {
                 Optional<LeAudioDeviceStateWrapper> valid_device_opt = allLeAudioDevicesMutable
                         .getValue().stream()
@@ -437,18 +439,20 @@
                  *
                  * Broadcast receiver's endpoint identifier.
                  */
+                synchronized(this) {
+                    HashMap<Integer, BluetoothLeBroadcastReceiveState> states =
+                            svc_data.receiverStatesMutable.getValue();
+                    if (states == null)
+                        states = new HashMap<>();
+                    states.put(state.getSourceId(), state);
 
-                HashMap<Integer, BluetoothLeBroadcastReceiveState> states =
-                        svc_data.receiverStatesMutable.getValue();
-                if (states == null)
-                    states = new HashMap<>();
-                states.put(state.getSourceId(), state);
-
-                // Use SetValue instead of PostValue() since we want to make it
-                // synchronous due to getValue() we do here as well
-                // Otherwise we could miss the update and store only the last
-                // receiver ID
-                svc_data.receiverStatesMutable.setValue(states);
+                    // Use SetValue instead of PostValue() since we want to make it
+                    // synchronous due to getValue() we do here as well
+                    // Otherwise we could miss the update and store only the last
+                    // receiver ID
+//                    svc_data.receiverStatesMutable.setValue(states);
+                    svc_data.receiverStatesMutable.postValue(states);
+                }
             }
         }
     };
@@ -642,6 +646,8 @@
         profileListener = new BluetoothProfile.ServiceListener() {
             @Override
             public void onServiceConnected(int i, BluetoothProfile bluetoothProfile) {
+                Log.d("BluetoothProxy", "onServiceConnected(): i = " + i + " bluetoothProfile = " +
+                        bluetoothProfile);
                 switch (i) {
                     case BluetoothProfile.CSIP_SET_COORDINATOR:
                         bluetoothCsis = (BluetoothCsipSetCoordinator) bluetoothProfile;
@@ -670,6 +676,7 @@
                         mBluetoothLeBroadcast.registerCallback(mExecutor, mBroadcasterCallback);
                         break;
                     case BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT:
+                        Log.d("BluetoothProxy", "LE_AUDIO_BROADCAST_ASSISTANT Service connected");
                         mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant)
                                 bluetoothProfile;
                         mBluetoothLeBroadcastAssistant.registerCallback(mExecutor,
@@ -1364,7 +1371,10 @@
     }
 
     public int getMaximumNumberOfBroadcast() {
-        if (mBluetoothLeBroadcast == null) return 0;
+        if (mBluetoothLeBroadcast == null) {
+            Log.d("BluetoothProxy", "mBluetoothLeBroadcast is null");
+            return 0;
+        }
         return mBluetoothLeBroadcast.getMaximumNumberOfBroadcasts();
     }
 
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java
index 7bd4091..a53bd46 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastItemsAdapter.java
@@ -20,6 +20,7 @@
 import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.content.res.ColorStateList;
 import android.graphics.Color;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -114,7 +115,8 @@
             if (mBroadcastPlaybackMap.containsKey(broadcastId)) {
                 continue;
             }
-            mBroadcastPlaybackMap.remove(broadcastId);
+//          mBroadcastPlaybackMap.remove(broadcastId);
+            mBroadcastPlaybackMap.put(broadcastId, false);
         }
         notifyDataSetChanged();
     }
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanViewModel.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanViewModel.java
index 42f87d5..397a738 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanViewModel.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanViewModel.java
@@ -52,6 +52,7 @@
         @Override
         public void onSourceFound(BluetoothLeBroadcastMetadata source) {
             mScanSessionBroadcasts.put(source.getBroadcastId(), source);
+            refreshBroadcasts();
         }
 
         @Override
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java
index 74ae408..4a8b328 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java
@@ -39,6 +39,7 @@
 import android.os.ParcelUuid;
 import android.text.InputFilter;
 import android.text.InputType;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -671,16 +672,27 @@
             Map<Integer, BluetoothLeBroadcastReceiveState> states =
                     leAudioDeviceStateWrapper.bassData.receiverStatesMutable.getValue();
 
+            Log.d("LeAudioRecycleViewAdapter",
+                    "BluetoothLeBroadcastReceiveState " + holder.bassReceiverIdSpinner.getSelectedItem());
             if (states != null) {
                 if (states.containsKey(receiver_id)) {
                     BluetoothLeBroadcastReceiveState state =
                             states.get(holder.bassReceiverIdSpinner.getSelectedItem());
-                    final int paSyncState = state.getPaSyncState();
-                    final int bigEncryptionState = state.getBigEncryptionState();
+                    int paSyncState = state.getPaSyncState();
+                    int bigEncryptionState = state.getBigEncryptionState();
 
                     Resources res = this.parent.getResources();
                     String stateName = null;
 
+                    if (paSyncState == 0xffff) {// invalid sync state
+                        paSyncState = PA_SYNC_STATE_IDLE;
+                    }
+                    if (bigEncryptionState == 0xffff) {// invalid encryption state
+                        bigEncryptionState = BIG_ENCRYPTION_STATE_NOT_ENCRYPTED;
+                    }
+                    Log.d("LeAudioRecycleViewAdapter", "paSyncState " + paSyncState +
+                            " bigEncryptionState" + bigEncryptionState);
+
                     // Set the icon
                     if (paSyncState == PA_SYNC_STATE_IDLE) {
                         holder.bassScanButton.setImageResource(R.drawable.ic_cast_black_24dp);
@@ -1754,6 +1766,11 @@
                     alert.setTitle("Scan and add a source or remove the currently set one.");
 
                     BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
+                    if (bassReceiverIdSpinner.getSelectedItem() == null) {
+                        Toast.makeText(view.getContext(), "Not available",
+                                Toast.LENGTH_SHORT).show();
+                        return;
+                    }
                     int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
 
                     alert.setPositiveButton("Scan", (dialog, whichButton) -> {
@@ -1780,6 +1797,11 @@
                     alert.setView(pass_input_view);
 
                     BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
+                    if (bassReceiverIdSpinner.getSelectedItem() == null) {
+                        Toast.makeText(view.getContext(), "Not available",
+                                Toast.LENGTH_SHORT).show();
+                        return;
+                    }
                     int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
 
                     alert.setPositiveButton("Set", (dialog, whichButton) -> {
@@ -1796,6 +1818,11 @@
                     alert.setTitle("Stop the synchronization?");
 
                     BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
+                    if (bassReceiverIdSpinner.getSelectedItem() == null) {
+                        Toast.makeText(view.getContext(), "Not available",
+                                Toast.LENGTH_SHORT).show();
+                        return;
+                    }
                     int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
 
                     alert.setPositiveButton("Yes", (dialog, whichButton) -> {
@@ -1816,10 +1843,15 @@
                     AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
                     alert.setTitle("Retry broadcast audio announcement scan?");
 
+                    if (bassReceiverIdSpinner.getSelectedItem() == null) {
+                        Toast.makeText(view.getContext(), "Not available",
+                                Toast.LENGTH_SHORT).show();
+                        return;
+                    }
+                    int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
                     alert.setPositiveButton("Yes", (dialog, whichButton) -> {
                         // Scan for new announcements
                         Intent intent = new Intent(view.getContext(), BroadcastScanActivity.class);
-                        int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
                         intent.putExtra(EXTRA_BASS_RECEIVER_ID, receiver_id);
                         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, devices.get(ViewHolder.this.getAdapterPosition()).device);
                         parent.startActivityForResult(intent, 666);
@@ -1834,6 +1866,11 @@
                     alert.setTitle("Stop the synchronization?");
 
                     BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
+                    if (bassReceiverIdSpinner.getSelectedItem() == null) {
+                        Toast.makeText(view.getContext(), "Not available",
+                                Toast.LENGTH_SHORT).show();
+                        return;
+                    }
                     int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
 
                     alert.setPositiveButton("Yes", (dialog, whichButton) -> {
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java
index ae31538..a9ee880 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioViewModel.java
@@ -138,14 +138,14 @@
 
     public boolean removeBroadcastSource(BluetoothDevice sink, int receiver_id) {
         // TODO: Find source ID from receiver_id. What is receiver_id?
-        int sourceId = 0;
+        int sourceId = receiver_id;
         return bluetoothProxy.removeBroadcastSource(sink, sourceId);
     }
 
     public boolean setBroadcastCode(BluetoothDevice sink, int receiver_id, byte[] bcast_code) {
         // TODO: Find source ID from receiver_id. What is receiver_id?
         // TODO: Build BluetoothLeBroadcastMetadata with the new bcast_code.
-        int sourceId = 0;
+        int sourceId = receiver_id;
         BluetoothLeBroadcastMetadata metadata = null;
         return bluetoothProxy.modifyBroadcastSource(sink, sourceId, metadata);
     }
diff --git a/apex/Android.bp b/apex/Android.bp
index 6ccc9b8..26ad1ef 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -7,8 +7,17 @@
     default_applicable_licenses: ["system_bt_license"],
 }
 
-apex_defaults {
-    name: "com.android.bluetooth-defaults",
+linker_config {
+    name: "bluetooth-linker-config",
+    src: "linker.config.json",
+    installable: false,
+}
+
+//Mainline bluetooth apex module.
+apex {
+    name: "com.android.bluetooth",
+    defaults: ["t-launched-apex-module"],
+    manifest: "apex_manifest.json",
     bootclasspath_fragments: ["com.android.bluetooth-bootclasspath-fragment"],
     systemserverclasspath_fragments: ["com.android.bluetooth-systemserverclasspath-fragment"],
     apps: ["Bluetooth"],
@@ -23,31 +32,24 @@
     },
 
     prebuilts: [
+        "audio_set_configurations_bfbs",
+        "audio_set_configurations_json",
+        "audio_set_scenarios_bfbs",
+        "audio_set_scenarios_json",
         "bluetooth-linker-config",
+        "bt_did.conf",
+        "bt_stack.conf",
+        "privapp_allowlist_com.android.bluetooth.services.xml",
     ],
+
     key: "com.android.bluetooth.key",
     certificate: ":com.android.bluetooth.certificate",
-    defaults: ["t-launched-apex-module"],
     updatable: true,
     // Indicates that pre-installed version of this apex can be compressed.
     // Whether it actually will be compressed is controlled on per-device basis.
     compressible: true,
 }
 
-linker_config {
-    name: "bluetooth-linker-config",
-    src: "linker.config.json",
-    installable: false,
-}
-
-//Mainline bluetooth apex module.
-apex {
-    name: "com.android.bluetooth",
-    defaults: ["com.android.bluetooth-defaults"],
-    manifest: "apex_manifest.json",
-    prebuilts: ["privapp_allowlist_com.android.bluetooth.services.xml"],
-}
-
 apex_key {
     name: "com.android.bluetooth.key",
     public_key: "com.android.bluetooth.avbpubkey",
diff --git a/framework/Android.bp b/framework/Android.bp
index a3bacd2..92a7793 100644
--- a/framework/Android.bp
+++ b/framework/Android.bp
@@ -66,7 +66,15 @@
     },
     hostdex: true, // for hiddenapi check
 
-    impl_library_visibility: ["//visibility:public"],
+    impl_library_visibility: [
+        "//external/sl4a/Common",
+        "//frameworks/opt/wear",
+        "//packages/modules/Bluetooth/android/app/tests/unit",
+        "//packages/modules/Bluetooth/service",
+        "//packages/modules/Connectivity/nearby/tests/multidevices/clients/test_support/fastpair_provider",
+        "//packages/services/Car/car-builtin-lib",
+        ":__subpackages__",
+    ],
 
     apex_available: [
         "com.android.bluetooth",
diff --git a/framework/java/android/bluetooth/BluetoothLeAudio.java b/framework/java/android/bluetooth/BluetoothLeAudio.java
index 0c819db..6d9fa07 100644
--- a/framework/java/android/bluetooth/BluetoothLeAudio.java
+++ b/framework/java/android/bluetooth/BluetoothLeAudio.java
@@ -803,7 +803,9 @@
      * of the system, which wants to set to active a particular Le Audio group.
      *
      * Note: getActiveDevice() returns the Lead device for the currently active LE Audio group.
-     * Note: When lead device gets disconnected, there will be new lead device for the group.
+     * Note: When Lead device gets disconnected while Le Audio group is active and has more devices
+     * in the group, then Lead device will not change. If Lead device gets disconnected, for the
+     * Le Audio group which is not active, a new Lead device will be chosen
      *
      * @param groupId The group id.
      * @return group lead device.
diff --git a/framework/java/android/bluetooth/BluetoothLeAudioContentMetadata.java b/framework/java/android/bluetooth/BluetoothLeAudioContentMetadata.java
index 4f02e89..b1557ae 100644
--- a/framework/java/android/bluetooth/BluetoothLeAudioContentMetadata.java
+++ b/framework/java/android/bluetooth/BluetoothLeAudioContentMetadata.java
@@ -25,7 +25,9 @@
 
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A class representing the media metadata information defined in the Basic Audio Profile.
@@ -50,6 +52,22 @@
         mRawMetadata = rawMetadata;
     }
 
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (!(o instanceof BluetoothLeAudioContentMetadata)) {
+            return false;
+        }
+        final BluetoothLeAudioContentMetadata other = (BluetoothLeAudioContentMetadata) o;
+        return Objects.equals(mProgramInfo, other.getProgramInfo())
+                && Objects.equals(mLanguage, other.getLanguage())
+                && Arrays.equals(mRawMetadata, other.getRawMetadata());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mProgramInfo, mLanguage, Arrays.hashCode(mRawMetadata));
+    }
+
     /**
      * Get the title and/or summary of Audio Stream content in UTF-8 format.
      *
diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastAssistant.java b/framework/java/android/bluetooth/BluetoothLeBroadcastAssistant.java
index 7095525..05716eb 100755
--- a/framework/java/android/bluetooth/BluetoothLeBroadcastAssistant.java
+++ b/framework/java/android/bluetooth/BluetoothLeBroadcastAssistant.java
@@ -363,6 +363,7 @@
     @Override
     public @BluetoothProfile.BtProfileState int getConnectionState(@NonNull BluetoothDevice sink) {
         log("getConnectionState(" + sink + ")");
+        Objects.requireNonNull(sink, "sink cannot be null");
         final IBluetoothLeBroadcastAssistant service = getService();
         final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
         if (service == null) {
@@ -392,6 +393,7 @@
     public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
             @NonNull int[] states) {
         log("getDevicesMatchingConnectionStates()");
+        Objects.requireNonNull(states, "states cannot be null");
         final IBluetoothLeBroadcastAssistant service = getService();
         final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
         if (service == null) {
@@ -445,6 +447,7 @@
      * @param device Paired bluetooth device
      * @param connectionPolicy is the connection policy to set to for this profile
      * @return true if connectionPolicy is set, false on error
+     * @throws NullPointerException if <var>device</var> is null
      * @hide
      */
     @SystemApi
@@ -456,6 +459,7 @@
     public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
             @ConnectionPolicy int connectionPolicy) {
         log("setConnectionPolicy()");
+        Objects.requireNonNull(device, "device cannot be null");
         final IBluetoothLeBroadcastAssistant service = getService();
         final boolean defaultValue = false;
         if (service == null) {
@@ -482,6 +486,7 @@
      *
      * @param device Bluetooth device
      * @return connection policy of the device
+     * @throws NullPointerException if <var>device</var> is null
      * @hide
      */
     @SystemApi
@@ -492,6 +497,7 @@
     })
     public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
         log("getConnectionPolicy()");
+        Objects.requireNonNull(device, "device cannot be null");
         final IBluetoothLeBroadcastAssistant service = getService();
         final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
         if (service == null) {
@@ -518,8 +524,8 @@
      *
      * @param executor an {@link Executor} to execute given callback
      * @param callback user implementation of the {@link Callback}
-     * @throws NullPointerException if a null executor, or callback is given, or
-     *  IllegalArgumentException if the same <var>callback<var> is already registered.
+     * @throws NullPointerException if a null executor, or callback is given
+     * @throws IllegalArgumentException if the same <var>callback<var> is already registered
      * @hide
      */
     @SystemApi
@@ -554,8 +560,8 @@
      * <p>Callbacks are automatically unregistered when application process goes away.
      *
      * @param callback user implementation of the {@link Callback}
-     * @throws NullPointerException when callback is null or IllegalArgumentException when no
-     *  callback is registered
+     * @throws NullPointerException when callback is null
+     * @throws IllegalArgumentException when the <var>callback</var> was not registered before
      * @hide
      */
     @SystemApi
@@ -572,9 +578,10 @@
             Log.w(TAG, "Proxy not attached to service");
             if (DBG) log(Log.getStackTraceString(new Throwable()));
         } else if (mBluetoothAdapter.isEnabled()) {
-            if (mCallback != null) {
-                mCallback.unregister(callback);
+            if (mCallback == null) {
+                throw new IllegalArgumentException("no callback was ever registered");
             }
+            mCallback.unregister(callback);
         }
     }
 
@@ -605,7 +612,7 @@
      *
      * @param filters {@link ScanFilter}s for finding exact Broadcast Source, if no filter is
      *               needed, please provide an empty list instead
-     * @throws IllegalArgumentException when <var>filters</var> argument is null
+     * @throws NullPointerException when <var>filters</var> argument is null
      * @throws IllegalStateException when no callback is registered
      * @hide
      */
@@ -618,8 +625,12 @@
     })
     public void startSearchingForSources(@NonNull List<ScanFilter> filters) {
         log("searchForBroadcastSources");
-        if (filters == null) {
-            throw new IllegalArgumentException("filters can be empty, but not null");
+        Objects.requireNonNull(filters, "filters can be empty, but not null");
+        if (mCallback == null) {
+            throw new IllegalStateException("No callback was ever registered");
+        }
+        if (!mCallback.isAtLeastOneCallbackRegistered()) {
+            throw new IllegalStateException("All callbacks are unregistered");
         }
         final IBluetoothLeBroadcastAssistant service = getService();
         if (service == null) {
@@ -652,6 +663,12 @@
     })
     public void stopSearchingForSources() {
         log("stopSearchingForSources:");
+        if (mCallback == null) {
+            throw new IllegalStateException("No callback was ever registered");
+        }
+        if (!mCallback.isAtLeastOneCallbackRegistered()) {
+            throw new IllegalStateException("All callbacks are unregistered");
+        }
         final IBluetoothLeBroadcastAssistant service = getService();
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
@@ -751,7 +768,7 @@
      * @param isGroupOp {@code true} if Application wants to perform this operation for all
      *                  coordinated set members throughout this session. Otherwise, caller
      *                  would have to add, modify, and remove individual set members.
-     * @throws IllegalArgumentException if <var>sink</var> or <var>source</var> are null
+     * @throws NullPointerException if <var>sink</var> or <var>source</var> is null
      * @throws IllegalStateException if callback was not registered
      * @hide
      */
@@ -764,6 +781,14 @@
     public void addSource(@NonNull BluetoothDevice sink,
             @NonNull BluetoothLeBroadcastMetadata sourceMetadata, boolean isGroupOp) {
         log("addBroadcastSource: " + sourceMetadata + " on " + sink);
+        Objects.requireNonNull(sink, "sink cannot be null");
+        Objects.requireNonNull(sourceMetadata, "sourceMetadata cannot be null");
+        if (mCallback == null) {
+            throw new IllegalStateException("No callback was ever registered");
+        }
+        if (!mCallback.isAtLeastOneCallbackRegistered()) {
+            throw new IllegalStateException("All callbacks are unregistered");
+        }
         final IBluetoothLeBroadcastAssistant service = getService();
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
@@ -818,6 +843,7 @@
      * {@link Callback#onSourceAdded(BluetoothDevice, int, int)}
      * @param updatedMetadata  updated Broadcast Source metadata to be updated on the Broadcast Sink
      * @throws IllegalStateException if callback was not registered
+     * @throws NullPointerException if <var>sink</var> or <var>updatedMetadata</var> is null
      * @hide
      */
     @SystemApi
@@ -829,6 +855,14 @@
     public void modifySource(@NonNull BluetoothDevice sink, int sourceId,
             @NonNull BluetoothLeBroadcastMetadata updatedMetadata) {
         log("updateBroadcastSource: " + updatedMetadata + " on " + sink);
+        Objects.requireNonNull(sink, "sink cannot be null");
+        Objects.requireNonNull(updatedMetadata, "updatedMetadata cannot be null");
+        if (mCallback == null) {
+            throw new IllegalStateException("No callback was ever registered");
+        }
+        if (!mCallback.isAtLeastOneCallbackRegistered()) {
+            throw new IllegalStateException("All callbacks are unregistered");
+        }
         final IBluetoothLeBroadcastAssistant service = getService();
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
@@ -862,7 +896,7 @@
      * @param sink Broadcast Sink from which a Broadcast Source should be removed
      * @param sourceId source ID as delivered in
      * {@link Callback#onSourceAdded(BluetoothDevice, int, int)}
-     * @throws IllegalArgumentException when the <var>sink</var> is null
+     * @throws NullPointerException when the <var>sink</var> is null
      * @throws IllegalStateException if callback was not registered
      * @hide
      */
@@ -874,6 +908,13 @@
     })
     public void removeSource(@NonNull BluetoothDevice sink, int sourceId) {
         log("removeBroadcastSource: " + sourceId + " from " + sink);
+        Objects.requireNonNull(sink, "sink cannot be null");
+        if (mCallback == null) {
+            throw new IllegalStateException("No callback was ever registered");
+        }
+        if (!mCallback.isAtLeastOneCallbackRegistered()) {
+            throw new IllegalStateException("All callbacks are unregistered");
+        }
         final IBluetoothLeBroadcastAssistant service = getService();
         if (service == null) {
             Log.w(TAG, "Proxy not attached to service");
@@ -894,7 +935,7 @@
      * @param sink Broadcast Sink from which to get all Broadcast Sources
      * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState}
      *         stored in the Broadcast Sink
-     * @throws IllegalArgumentException when <var>sink</var> is null
+     * @throws NullPointerException when <var>sink</var> is null
      * @hide
      */
     @SystemApi
@@ -906,6 +947,7 @@
     public @NonNull List<BluetoothLeBroadcastReceiveState> getAllSources(
             @NonNull BluetoothDevice sink) {
         log("getAllSources()");
+        Objects.requireNonNull(sink, "sink cannot be null");
         final IBluetoothLeBroadcastAssistant service = getService();
         final List<BluetoothLeBroadcastReceiveState> defaultValue =
                 new ArrayList<BluetoothLeBroadcastReceiveState>();
@@ -927,11 +969,12 @@
      *
      * @param sink Broadcast Sink device
      * @return maximum number of sources that can be added to this Broadcast Sink
-     * @throws IllegalArgumentException when <var>sink</var> is null
+     * @throws NullPointerException when <var>sink</var> is null
      * @hide
      */
     @SystemApi
     public int getMaximumSourceCapacity(@NonNull BluetoothDevice sink) {
+        Objects.requireNonNull(sink, "sink cannot be null");
         final IBluetoothLeBroadcastAssistant service = getService();
         final int defaultValue = 0;
         if (service == null) {
diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java b/framework/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java
index 96e04a9..aa7ca8e 100755
--- a/framework/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java
+++ b/framework/java/android/bluetooth/BluetoothLeBroadcastAssistantCallback.java
@@ -44,12 +44,13 @@
      * @hide
      * @param executor an {@link Executor} to execute given callback
      * @param callback user implementation of the {@link BluetoothLeBroadcastAssistant#Callback}
+     * @throws IllegalArgumentException if the same <var>callback<var> is already registered.
      */
     public void register(@NonNull Executor executor,
                          @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
         synchronized (this) {
             if (mCallbackMap.containsKey(callback)) {
-                return;
+                throw new IllegalArgumentException("callback is already registered");
             }
             mCallbackMap.put(callback, executor);
 
@@ -58,7 +59,7 @@
                     mAdapter.registerCallback(this);
                     mIsRegistered = true;
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Failed to register broaddcast assistant callback");
+                    Log.w(TAG, "Failed to register broadcast assistant callback");
                     Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 }
             }
@@ -68,11 +69,12 @@
     /**
      * @hide
      * @param callback user implementation of the {@link BluetoothLeBroadcastAssistant#Callback}
+     * @throws IllegalArgumentException if <var>callback</var> was not registered before
      */
     public void unregister(@NonNull BluetoothLeBroadcastAssistant.Callback callback) {
         synchronized (this) {
             if (!mCallbackMap.containsKey(callback)) {
-                return;
+                throw new IllegalArgumentException("callback was not registered before");
             }
             mCallbackMap.remove(callback);
             if (mCallbackMap.isEmpty() && mIsRegistered) {
@@ -80,13 +82,25 @@
                     mAdapter.unregisterCallback(this);
                     mIsRegistered = false;
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Failed to unregister broaddcast assistant with service");
+                    Log.w(TAG, "Failed to unregister callback with service");
                     Log.e(TAG, Log.getStackTraceString(new Throwable()));
                 }
             }
         }
     }
 
+    /**
+     * Check if at least one callback is registered from this App
+     *
+     * @return true if at least one callback is registered
+     * @hide
+     */
+    public boolean isAtLeastOneCallbackRegistered() {
+        synchronized (this) {
+            return !mCallbackMap.isEmpty();
+        }
+    }
+
     @Override
     public void onSearchStarted(int reason) {
         synchronized (this) {
diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastChannel.java b/framework/java/android/bluetooth/BluetoothLeBroadcastChannel.java
index 05f7f4a..3040af7 100644
--- a/framework/java/android/bluetooth/BluetoothLeBroadcastChannel.java
+++ b/framework/java/android/bluetooth/BluetoothLeBroadcastChannel.java
@@ -17,6 +17,7 @@
 package android.bluetooth;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -44,6 +45,22 @@
         mCodecMetadata = codecMetadata;
     }
 
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (!(o instanceof BluetoothLeBroadcastChannel)) {
+            return false;
+        }
+        final BluetoothLeBroadcastChannel other = (BluetoothLeBroadcastChannel) o;
+        return mIsSelected == other.isSelected()
+                && mChannelIndex == other.getChannelIndex()
+                && mCodecMetadata.equals(other.getCodecMetadata());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIsSelected, mChannelIndex, mCodecMetadata);
+    }
+
     /**
      * Return true if the channel is selected by Broadcast Assistant for the Broadcast Sink.
      *
diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastMetadata.java b/framework/java/android/bluetooth/BluetoothLeBroadcastMetadata.java
index c63061a..b79f31e 100644
--- a/framework/java/android/bluetooth/BluetoothLeBroadcastMetadata.java
+++ b/framework/java/android/bluetooth/BluetoothLeBroadcastMetadata.java
@@ -24,6 +24,7 @@
 import android.os.Parcelable;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 
@@ -76,6 +77,30 @@
         mSubgroups = subgroups;
     }
 
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (!(o instanceof BluetoothLeBroadcastMetadata)) {
+            return false;
+        }
+        final BluetoothLeBroadcastMetadata other = (BluetoothLeBroadcastMetadata) o;
+        return mSourceAddressType == other.getSourceAddressType()
+                && mSourceDevice.equals(other.getSourceDevice())
+                && mSourceAdvertisingSid == other.getSourceAdvertisingSid()
+                && mBroadcastId == other.getBroadcastId()
+                && mPaSyncInterval == other.getPaSyncInterval()
+                && mIsEncrypted == other.isEncrypted()
+                && Arrays.equals(mBroadcastCode, other.getBroadcastCode())
+                && mPresentationDelayMicros == other.getPresentationDelayMicros()
+                && mSubgroups.equals(other.getSubgroups());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSourceAddressType, mSourceDevice, mSourceAdvertisingSid,
+                mBroadcastId, mPaSyncInterval, mIsEncrypted, Arrays.hashCode(mBroadcastCode),
+                mPresentationDelayMicros, mSubgroups);
+    }
+
     /**
      * Get the address type of the Broadcast Source.
      *
diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastReceiveState.java b/framework/java/android/bluetooth/BluetoothLeBroadcastReceiveState.java
index 70a2add..a7ac7ed 100644
--- a/framework/java/android/bluetooth/BluetoothLeBroadcastReceiveState.java
+++ b/framework/java/android/bluetooth/BluetoothLeBroadcastReceiveState.java
@@ -224,9 +224,12 @@
             throw new IllegalArgumentException("subgroupMetadata.size()  "
                     + subgroupMetadata.size() + " must be equal to numSubgroups " + numSubgroups);
         }
-        if (paSyncState != PA_SYNC_STATE_IDLE && paSyncState != PA_SYNC_STATE_SYNCINFO_REQUEST
+        if (paSyncState != PA_SYNC_STATE_IDLE
+                && paSyncState != PA_SYNC_STATE_SYNCINFO_REQUEST
+                && paSyncState != PA_SYNC_STATE_SYNCHRONIZED
                 && paSyncState != PA_SYNC_STATE_FAILED_TO_SYNCHRONIZE
-                && paSyncState != PA_SYNC_STATE_NO_PAST && paSyncState != PA_SYNC_STATE_INVALID) {
+                && paSyncState != PA_SYNC_STATE_NO_PAST
+                && paSyncState != PA_SYNC_STATE_INVALID) {
             throw new IllegalArgumentException("unrecognized paSyncState " + paSyncState);
         }
         if (bigEncryptionState != BIG_ENCRYPTION_STATE_NOT_ENCRYPTED
diff --git a/framework/java/android/bluetooth/BluetoothLeBroadcastSubgroup.java b/framework/java/android/bluetooth/BluetoothLeBroadcastSubgroup.java
index b2abe06..38a747b 100644
--- a/framework/java/android/bluetooth/BluetoothLeBroadcastSubgroup.java
+++ b/framework/java/android/bluetooth/BluetoothLeBroadcastSubgroup.java
@@ -17,6 +17,7 @@
 package android.bluetooth;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -48,6 +49,23 @@
         mChannels = channels;
     }
 
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (!(o instanceof BluetoothLeBroadcastSubgroup)) {
+            return false;
+        }
+        final BluetoothLeBroadcastSubgroup other = (BluetoothLeBroadcastSubgroup) o;
+        return mCodecId == other.getCodecId()
+                && mCodecSpecificConfig.equals(other.getCodecSpecificConfig())
+                && mContentMetadata.equals(other.getContentMetadata())
+                && mChannels.equals(other.getChannels());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mCodecId, mCodecSpecificConfig, mContentMetadata, mChannels);
+    }
+
     /**
      * Get the codec ID field as defined by the Basic Audio Profile.
      *
diff --git a/framework/java/android/bluetooth/BluetoothUuid.java b/framework/java/android/bluetooth/BluetoothUuid.java
index 8de213f..63f293a 100644
--- a/framework/java/android/bluetooth/BluetoothUuid.java
+++ b/framework/java/android/bluetooth/BluetoothUuid.java
@@ -365,11 +365,15 @@
             ParcelUuid.fromString("0000184F-0000-1000-8000-00805F9B34FB");
 
     /**
-     * Base UUID to calculate all other UUIDs defined in this class.
-     *
+     * Telephony and Media Audio Profile (TMAP) UUID
      * @hide
      */
     @NonNull
+    public static final ParcelUuid TMAP =
+            ParcelUuid.fromString("00001855-0000-1000-8000-00805F9B34FB");
+
+    /** @hide */
+    @NonNull
     @SystemApi
     public static final ParcelUuid BASE_UUID =
             ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
diff --git a/system/blueberry/tests/gd_sl4a/lib/bt_constants.py b/system/blueberry/tests/gd_sl4a/lib/bt_constants.py
index f144ef5..2dab54d 100644
--- a/system/blueberry/tests/gd_sl4a/lib/bt_constants.py
+++ b/system/blueberry/tests/gd_sl4a/lib/bt_constants.py
@@ -60,6 +60,7 @@
 bluetooth_off = "BluetoothStateChangedOff"
 bluetooth_on = "BluetoothStateChangedOn"
 mtu_changed = "GattConnect{}onMtuChanged"
+gatt_connection_state_change = "GattConnect{}onConnectionStateChange"
 advertising_set_started = "AdvertisingSet{}onAdvertisingSetStarted"
 advertising_set_stopped = "AdvertisingSet{}onAdvertisingSetStopped"
 advertising_set_on_own_address_read = "AdvertisingSet{}onOwnAddressRead"
diff --git a/system/blueberry/tests/sl4a_sl4a/gatt/gatt_connect_test.py b/system/blueberry/tests/sl4a_sl4a/gatt/gatt_connect_test.py
new file mode 100644
index 0000000..a4cd003
--- /dev/null
+++ b/system/blueberry/tests/sl4a_sl4a/gatt/gatt_connect_test.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+#
+#   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.
+
+import io
+import os
+import queue
+import logging
+
+from blueberry.tests.gd.cert.context import get_current_context
+from blueberry.tests.gd.cert.truth import assertThat
+from blueberry.tests.gd_sl4a.lib.bt_constants import adv_succ, ble_advertise_settings_modes, ble_scan_settings_modes, ble_address_types, ble_scan_settings_phys, gatt_connection_state_change, gatt_transport, scan_result
+from blueberry.tests.gd_sl4a.lib.ble_lib import generate_ble_scan_objects, generate_ble_advertise_objects
+from blueberry.tests.sl4a_sl4a.lib.sl4a_sl4a_base_test import Sl4aSl4aBaseTestClass
+from blueberry.facade import common_pb2 as common
+
+from mobly.controllers.android_device_lib.adb import AdbError
+
+
+class GattConnectTest(Sl4aSl4aBaseTestClass):
+
+    def setup_class(self):
+        super().setup_class()
+        self.default_timeout = 10  # seconds
+
+    def setup_test(self):
+        super().setup_test()
+
+    def teardown_test(self):
+        super().teardown_test()
+
+    def _wait_for_event(self, expected_event_name, device):
+        try:
+            event_info = device.ed.pop_event(expected_event_name, self.default_timeout)
+            logging.info(event_info)
+        except queue.Empty as error:
+            logging.error("Failed to find event: %s", expected_event_name)
+            return False
+        return True
+
+    def _wait_for_scan_result_event(self, expected_event_name, device):
+        try:
+            event_info = device.ed.pop_event(expected_event_name, self.default_timeout)
+        except queue.Empty as error:
+            logging.error("Could not find scan result event: %s", expected_event_name)
+            return None
+        return event_info['data']['Result']['deviceInfo']['address']
+
+    def _get_cert_public_address_and_irk_from_bt_config(self):
+        # Pull IRK from SL4A cert side to pass in from SL4A DUT side when scanning
+        bt_config_file_path = os.path.join(get_current_context().get_full_output_path(),
+                                           "DUT_%s_bt_config.conf" % self.cert.serial)
+        try:
+            self.cert.adb.pull(["/data/misc/bluedroid/bt_config.conf", bt_config_file_path])
+        except AdbError as error:
+            logging.error("Failed to pull SL4A cert BT config")
+            return False
+        logging.debug("Reading SL4A cert BT config")
+        with io.open(bt_config_file_path) as f:
+            for line in f.readlines():
+                stripped_line = line.strip()
+                if (stripped_line.startswith("Address")):
+                    address_fields = stripped_line.split(' ')
+                    # API currently requires public address to be capitalized
+                    address = address_fields[2].upper()
+                    logging.debug("Found cert address: %s" % address)
+                    continue
+                if (stripped_line.startswith("LE_LOCAL_KEY_IRK")):
+                    irk_fields = stripped_line.split(' ')
+                    irk = irk_fields[2]
+                    logging.debug("Found cert IRK: %s" % irk)
+                    continue
+
+        return address, irk
+
+    def test_scan_connect_unbonded_device_public_address_with_irk(self):
+        # Set up SL4A cert side to advertise
+        logging.info("Starting advertising")
+        self.cert.sl4a.bleSetAdvertiseSettingsIsConnectable(True)
+        self.cert.sl4a.bleSetAdvertiseDataIncludeDeviceName(True)
+        self.cert.sl4a.bleSetAdvertiseSettingsAdvertiseMode(ble_advertise_settings_modes['low_latency'])
+        self.cert.sl4a.bleSetAdvertiseSettingsOwnAddressType(common.RANDOM_DEVICE_ADDRESS)
+        advertise_callback, advertise_data, advertise_settings = generate_ble_advertise_objects(self.cert.sl4a)
+        self.cert.sl4a.bleStartBleAdvertising(advertise_callback, advertise_data, advertise_settings)
+
+        # Wait for SL4A cert to start advertising
+        assertThat(self._wait_for_event(adv_succ.format(advertise_callback), self.cert)).isTrue()
+        logging.info("Advertising started")
+
+        # Pull IRK from SL4A cert side to pass in from SL4A DUT side when scanning
+        cert_public_address, irk = self._get_cert_public_address_and_irk_from_bt_config()
+
+        # Set up SL4A DUT side to scan
+        addr_type = ble_address_types["public"]
+        logging.info("Start scanning for PUBLIC_ADDRESS %s with address type %d and IRK %s" % (cert_public_address,
+                                                                                               addr_type, irk))
+        self.dut.sl4a.bleSetScanSettingsScanMode(ble_scan_settings_modes['low_latency'])
+        self.dut.sl4a.bleSetScanSettingsLegacy(False)
+        filter_list, scan_settings, scan_callback = generate_ble_scan_objects(self.dut.sl4a)
+        expected_event_name = scan_result.format(scan_callback)
+
+        # Start scanning on SL4A DUT
+        self.dut.sl4a.bleSetScanFilterDeviceAddressTypeAndIrkHexString(cert_public_address, int(addr_type), irk)
+        self.dut.sl4a.bleBuildScanFilter(filter_list)
+        self.dut.sl4a.bleStartBleScan(filter_list, scan_settings, scan_callback)
+        logging.info("Started scanning")
+
+        # Verify that scan result is received on SL4A DUT
+        mac_address = self._wait_for_scan_result_event(expected_event_name, self.dut)
+        assertThat(mac_address).isNotNone()
+        logging.info("Filter advertisement with address {}".format(mac_address))
+
+        # Stop scanning and try to connect GATT
+        self.dut.sl4a.bleStopBleScan(scan_callback)
+        gatt_callback = self.dut.sl4a.gattCreateGattCallback()
+        bluetooth_gatt = self.dut.sl4a.gattClientConnectGatt(gatt_callback, mac_address, False, gatt_transport['le'],
+                                                             False, None)
+        assertThat(bluetooth_gatt).isNotNone()
+
+        # Verify that GATT connect event occurs on SL4A DUT
+        expected_event_name = gatt_connection_state_change.format(gatt_callback)
+        assertThat(self._wait_for_event(expected_event_name, self.dut)).isTrue()
+
+        # Test over
+        self.cert.sl4a.bleStopBleAdvertising(advertise_callback)
diff --git a/system/blueberry/tests/sl4a_sl4a/lib/sl4a_sl4a_base_test.py b/system/blueberry/tests/sl4a_sl4a/lib/sl4a_sl4a_base_test.py
index 6c3fa38..14eeaf4 100644
--- a/system/blueberry/tests/sl4a_sl4a/lib/sl4a_sl4a_base_test.py
+++ b/system/blueberry/tests/sl4a_sl4a/lib/sl4a_sl4a_base_test.py
@@ -144,13 +144,13 @@
         if not callable(attr) or not Sl4aSl4aBaseTestClass.__is_entry_function(name):
             return attr
 
-    @wraps(attr)
-    def __wrapped(*args, **kwargs):
-        try:
-            return attr(*args, **kwargs)
-        except RpcError as e:
-            exception_info = "".join(traceback.format_exception(e.__class__, e, e.__traceback__))
-            raise signals.TestFailure("RpcError during test\n\nRpcError:\n\n%s" % (exception_info))
+        @wraps(attr)
+        def __wrapped(*args, **kwargs):
+            try:
+                return attr(*args, **kwargs)
+            except RpcError as e:
+                exception_info = "".join(traceback.format_exception(e.__class__, e, e.__traceback__))
+                raise signals.TestFailure("RpcError during test\n\nRpcError:\n\n%s" % (exception_info))
 
         return __wrapped
 
diff --git a/system/blueberry/tests/sl4a_sl4a/sl4a_sl4a_test_runner.py b/system/blueberry/tests/sl4a_sl4a/sl4a_sl4a_test_runner.py
index a04b097..96c4736 100644
--- a/system/blueberry/tests/sl4a_sl4a/sl4a_sl4a_test_runner.py
+++ b/system/blueberry/tests/sl4a_sl4a/sl4a_sl4a_test_runner.py
@@ -14,10 +14,12 @@
 #   See the License for the specific language governing permissions and
 #   limitations under the License.
 
+from blueberry.tests.sl4a_sl4a.gatt.gatt_connect_test import GattConnectTest
+
 from mobly import suite_runner
 import argparse
 
-ALL_TESTS = []
+ALL_TESTS = [GattConnectTest]
 
 
 def main():
diff --git a/system/bta/dm/bta_dm_act.cc b/system/bta/dm/bta_dm_act.cc
index cf597cb..75b22f7 100644
--- a/system/bta/dm/bta_dm_act.cc
+++ b/system/bta/dm/bta_dm_act.cc
@@ -253,8 +253,6 @@
 #define MAX_DISC_RAW_DATA_BUF (4096)
 uint8_t g_disc_raw_data_buf[MAX_DISC_RAW_DATA_BUF];
 
-extern DEV_CLASS local_device_default_class;
-
 // Stores the local Input/Output Capabilities of the Bluetooth device.
 static uint8_t btm_local_io_caps;
 
@@ -359,7 +357,10 @@
   memset(&bta_dm_conn_srvcs, 0, sizeof(bta_dm_conn_srvcs));
   memset(&bta_dm_di_cb, 0, sizeof(tBTA_DM_DI_CB));
 
-  memcpy(dev_class, p_bta_dm_cfg->dev_class, sizeof(dev_class));
+  btif_dm_get_local_class_of_device(dev_class);
+  LOG_INFO("%s: Read default class of device {0x%x, 0x%x, 0x%x}", __func__,
+      dev_class[0], dev_class[1], dev_class[2]);
+
   if (bluetooth::shim::is_gd_security_enabled()) {
     bluetooth::shim::BTM_SetDeviceClass(dev_class);
   } else {
diff --git a/system/bta/dm/bta_dm_cfg.cc b/system/bta/dm/bta_dm_cfg.cc
index d5c7575..4a05f8e 100644
--- a/system/bta/dm/bta_dm_cfg.cc
+++ b/system/bta/dm/bta_dm_cfg.cc
@@ -53,8 +53,6 @@
 #endif
 
 const tBTA_DM_CFG bta_dm_cfg = {
-    /* mobile phone COD */
-    BTA_DM_COD,
     /* page timeout in 625uS */
     BTA_DM_PAGE_TIMEOUT,
     /* true to avoid scatternet when av is streaming (be the central) */
diff --git a/system/bta/dm/bta_dm_int.h b/system/bta/dm/bta_dm_int.h
index fc5b9bc..e51d9ac 100644
--- a/system/bta/dm/bta_dm_int.h
+++ b/system/bta/dm/bta_dm_int.h
@@ -393,7 +393,6 @@
 };
 
 typedef struct {
-  DEV_CLASS dev_class; /* local device class */
   uint16_t page_timeout; /* timeout for page in slots */
   bool avoid_scatter; /* true to avoid scatternet when av is streaming (be the
                          central) */
diff --git a/system/bta/le_audio/audio_set_configurations.fbs b/system/bta/le_audio/audio_set_configurations.fbs
index 2aa7bab..e31af47 100644
--- a/system/bta/le_audio/audio_set_configurations.fbs
+++ b/system/bta/le_audio/audio_set_configurations.fbs
@@ -80,7 +80,7 @@
 table AudioSetConfiguration {
     name: string (key, required);
     codec_config_name: string (required);
-    qos_config_name: string (required);
+    qos_config_name: [string] (required);
 }
 table AudioSetConfigurations {
     _comments_: [string];
diff --git a/system/bta/le_audio/audio_set_configurations.json b/system/bta/le_audio/audio_set_configurations.json
index 81fdaf2..380d08c 100644
--- a/system/bta/le_audio/audio_set_configurations.json
+++ b/system/bta/le_audio/audio_set_configurations.json
@@ -45,332 +45,462 @@
         {
             "name": "DualDev_OneChanStereoSnk_16_1_Server_Preferred",
             "codec_config_name": "DualDev_OneChanStereoSnk_16_1",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_16_1_1",
             "codec_config_name": "DualDev_OneChanStereoSnk_16_1",
-            "qos_config_name": "QoS_Config_16_1_1"
+            "qos_config_name": ["QoS_Config_16_1_1"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_16_1_2",
             "codec_config_name": "DualDev_OneChanStereoSnk_16_1",
-            "qos_config_name": "QoS_Config_16_1_2"
+            "qos_config_name": ["QoS_Config_16_1_2"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_16_2_Server_Preferred",
             "codec_config_name": "DualDev_OneChanStereoSnk_16_2",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_16_2_1",
             "codec_config_name": "DualDev_OneChanStereoSnk_16_2",
-            "qos_config_name": "QoS_Config_16_2_1"
+            "qos_config_name": ["QoS_Config_16_2_1"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_16_2_2",
             "codec_config_name": "DualDev_OneChanStereoSnk_16_2",
-            "qos_config_name": "QoS_Config_16_2_2"
+            "qos_config_name": ["QoS_Config_16_2_2"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_16_1_Server_Preferred",
             "codec_config_name": "SingleDev_OneChanStereoSnk_16_1",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_16_1_1",
             "codec_config_name": "SingleDev_OneChanStereoSnk_16_1",
-            "qos_config_name": "QoS_Config_16_1_1"
+            "qos_config_name": ["QoS_Config_16_1_1"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_16_1_2",
             "codec_config_name": "SingleDev_OneChanStereoSnk_16_1",
-            "qos_config_name": "QoS_Config_16_1_2"
+            "qos_config_name": ["QoS_Config_16_1_2"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_16_2_Server_Preferred",
             "codec_config_name": "SingleDev_OneChanStereoSnk_16_2",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_16_2_1",
             "codec_config_name": "SingleDev_OneChanStereoSnk_16_2",
-            "qos_config_name": "QoS_Config_16_2_1"
+            "qos_config_name": ["QoS_Config_16_2_1"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_16_2_2",
             "codec_config_name": "SingleDev_OneChanStereoSnk_16_2",
-            "qos_config_name": "QoS_Config_16_2_2"
+            "qos_config_name": ["QoS_Config_16_2_2"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_16_1_Server_Preferred",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_16_1",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_16_1_1",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_16_1",
-            "qos_config_name": "QoS_Config_16_1_1"
+            "qos_config_name": ["QoS_Config_16_1_1"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_16_1_2",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_16_1",
-            "qos_config_name": "QoS_Config_16_1_2"
+            "qos_config_name": ["QoS_Config_16_1_2"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_16_2_Server_Preferred",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_16_2",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_16_2_1",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_16_2",
-            "qos_config_name": "QoS_Config_16_2_1"
+            "qos_config_name": ["QoS_Config_16_2_1"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_16_2_2",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_16_2",
-            "qos_config_name": "QoS_Config_16_2_2"
+            "qos_config_name": ["QoS_Config_16_2_2"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_16_1_Server_Preferred",
             "codec_config_name": "SingleDev_OneChanMonoSnk_16_1",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_16_1_1",
             "codec_config_name": "SingleDev_OneChanMonoSnk_16_1",
-            "qos_config_name": "QoS_Config_16_1_1"
+            "qos_config_name": ["QoS_Config_16_1_1"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_16_1_2",
             "codec_config_name": "SingleDev_OneChanMonoSnk_16_1",
-            "qos_config_name": "QoS_Config_16_1_2"
+            "qos_config_name": ["QoS_Config_16_1_2"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_16_2_Server_Preferred",
             "codec_config_name": "SingleDev_OneChanMonoSnk_16_2",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_16_2_1",
             "codec_config_name": "SingleDev_OneChanMonoSnk_16_2",
-            "qos_config_name": "QoS_Config_16_2_1"
+            "qos_config_name": ["QoS_Config_16_2_1"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_16_2_2",
             "codec_config_name": "SingleDev_OneChanMonoSnk_16_2",
-            "qos_config_name": "QoS_Config_16_2_2"
+            "qos_config_name": ["QoS_Config_16_2_2"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_1_Server_Preferred",
             "codec_config_name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_1_1",
             "codec_config_name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_16_1_1"
+            "qos_config_name": ["QoS_Config_16_1_1"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_1_2",
             "codec_config_name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_16_1_2"
+            "qos_config_name": ["QoS_Config_16_1_2"]
+        },
+        {
+            "name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_2_Server_Preferred",
+            "codec_config_name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_2",
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
+        },
+        {
+            "name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_2_2",
+            "codec_config_name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_2",
+            "qos_config_name": ["QoS_Config_16_2_2"]
+        },
+        {
+            "name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_2_1",
+            "codec_config_name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_2",
+            "qos_config_name": ["QoS_Config_16_2_1"]
+        },
+        {
+            "name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_1_Server_Preferred",
+            "codec_config_name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_1",
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
+        },
+        {
+            "name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_1_2",
+            "codec_config_name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_1",
+            "qos_config_name": ["QoS_Config_16_1_2"]
+        },
+        {
+            "name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_1_1",
+            "codec_config_name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_1",
+            "qos_config_name": ["QoS_Config_16_1_1"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_2_Server_Preferred",
             "codec_config_name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_2_1",
             "codec_config_name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_16_2_1"
+            "qos_config_name": ["QoS_Config_16_2_1"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_2_2",
             "codec_config_name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_16_2_2"
+            "qos_config_name": ["QoS_Config_16_2_2"]
         },
         {
             "name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1_Server_Preferred",
             "codec_config_name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1_1",
             "codec_config_name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_16_1_1"
+            "qos_config_name": ["QoS_Config_16_1_1"]
+        },
+        {
+            "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_2_Server_Preferred",
+            "codec_config_name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_2",
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
+        },
+        {
+            "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_2_2",
+            "codec_config_name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_2",
+            "qos_config_name": ["QoS_Config_16_2_2"]
+        },
+        {
+            "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_2_1",
+            "codec_config_name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_2",
+            "qos_config_name": ["QoS_Config_16_2_1"]
+        },
+        {
+            "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_1_Server_Preferred",
+            "codec_config_name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_1",
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
+        },
+        {
+            "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_1_2",
+            "codec_config_name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_1",
+            "qos_config_name": ["QoS_Config_16_1_2"]
+        },
+        {
+            "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_1_1",
+            "codec_config_name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_1",
+            "qos_config_name": ["QoS_Config_16_1_1"]
         },
         {
             "name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1_2",
             "codec_config_name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_16_1_2"
+            "qos_config_name": ["QoS_Config_16_1_2"]
         },
         {
             "name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2_Server_Preferred",
             "codec_config_name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2_1",
             "codec_config_name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_16_2_1"
+            "qos_config_name": ["QoS_Config_16_2_1"]
         },
         {
             "name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2_2",
             "codec_config_name": "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_16_2_2"
+            "qos_config_name": ["QoS_Config_16_2_2"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1_Server_Preferred",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1_1",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_16_1_1"
+            "qos_config_name": ["QoS_Config_16_1_1"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1_2",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_16_1_2"
+            "qos_config_name": ["QoS_Config_16_1_2"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2_Server_Preferred",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2_1",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_16_2_1"
+            "qos_config_name": ["QoS_Config_16_2_1"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2_2",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_16_2_2"
+            "qos_config_name": ["QoS_Config_16_2_2"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1_Server_Preferred",
             "codec_config_name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1_1",
             "codec_config_name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_16_1_1"
+            "qos_config_name": ["QoS_Config_16_1_1"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1_2",
             "codec_config_name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_16_1_2"
+            "qos_config_name": ["QoS_Config_16_1_2"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2_Server_Preferred",
             "codec_config_name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2_1",
             "codec_config_name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_16_2_1"
+            "qos_config_name": ["QoS_Config_16_2_1"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2_2",
             "codec_config_name": "SingleDev_OneChanStereoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_16_2_2"
+            "qos_config_name": ["QoS_Config_16_2_2"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1_Server_Preferred",
             "codec_config_name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1_1",
             "codec_config_name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_16_1_1"
+            "qos_config_name": ["QoS_Config_16_1_1"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1_2",
             "codec_config_name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1",
-            "qos_config_name": "QoS_Config_16_1_2"
+            "qos_config_name": ["QoS_Config_16_1_2"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2_Server_Preferred",
             "codec_config_name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2_1",
             "codec_config_name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_16_2_1"
+            "qos_config_name": ["QoS_Config_16_2_1"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2_2",
             "codec_config_name": "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2",
-            "qos_config_name": "QoS_Config_16_2_2"
+            "qos_config_name": ["QoS_Config_16_2_2"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_48_4_Server_Preferred",
             "codec_config_name": "DualDev_OneChanStereoSnk_48_4",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_48_4_1",
             "codec_config_name": "DualDev_OneChanStereoSnk_48_4",
-            "qos_config_name": "QoS_Config_48_4_1"
+            "qos_config_name": ["QoS_Config_48_4_1"]
         },
         {
             "name": "DualDev_OneChanStereoSnk_48_4_2",
             "codec_config_name": "DualDev_OneChanStereoSnk_48_4",
-            "qos_config_name": "QoS_Config_48_4_2"
+            "qos_config_name": ["QoS_Config_48_4_2"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_48_4_Server_Preferred",
             "codec_config_name": "SingleDev_OneChanStereoSnk_48_4",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_48_4_1",
             "codec_config_name": "SingleDev_OneChanStereoSnk_48_4",
-            "qos_config_name": "QoS_Config_48_4_1"
+            "qos_config_name": ["QoS_Config_48_4_1"]
         },
         {
             "name": "SingleDev_OneChanStereoSnk_48_4_2",
             "codec_config_name": "SingleDev_OneChanStereoSnk_48_4",
-            "qos_config_name": "QoS_Config_48_4_2"
+            "qos_config_name": ["QoS_Config_48_4_2"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_48_4_Server_Preferred",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_48_4",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_48_4_1",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_48_4",
-            "qos_config_name": "QoS_Config_48_4_1"
+            "qos_config_name": ["QoS_Config_48_4_1"]
         },
         {
             "name": "SingleDev_TwoChanStereoSnk_48_4_2",
             "codec_config_name": "SingleDev_TwoChanStereoSnk_48_4",
-            "qos_config_name": "QoS_Config_48_4_2"
+            "qos_config_name": ["QoS_Config_48_4_2"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_48_4_Server_Preferred",
             "codec_config_name": "SingleDev_OneChanMonoSnk_48_4",
-            "qos_config_name": "QoS_Config_Server_Preferred"
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_48_4_1",
             "codec_config_name": "SingleDev_OneChanMonoSnk_48_4",
-            "qos_config_name": "QoS_Config_48_4_1"
+            "qos_config_name": ["QoS_Config_48_4_1"]
         },
         {
             "name": "SingleDev_OneChanMonoSnk_48_4_2",
             "codec_config_name": "SingleDev_OneChanMonoSnk_48_4",
-            "qos_config_name": "QoS_Config_48_4_2"
+            "qos_config_name": ["QoS_Config_48_4_2"]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSnk_OneChanStereoSrc_32khz_60octs_Server_Preferred_1",
+            "codec_config_name": "VND_SingleDev_TwoChanStereoSnk_OneChanStereoSrc_32khz_60octs_1",
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSnk_OneChanStereoSrc_32khz_60oct_R3_L22_1",
+            "codec_config_name": "VND_SingleDev_TwoChanStereoSnk_OneChanStereoSrc_32khz_60octs_1",
+            "qos_config_name": ["VND_QoS_Config_R3_L22"]
+        },
+        {
+            "name": "VND_DualDev_OneChanStereoSnk_48khz_100octs_Server_Preferred_1",
+            "codec_config_name": "VND_DualDev_OneChanStereoSnk_48khz_100octs_1",
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
+        },
+        {
+            "name": "VND_DualDev_OneChanStereoSnk_48khz_100octs_R15_L70_1",
+            "codec_config_name": "VND_DualDev_OneChanStereoSnk_48khz_100octs_1",
+            "qos_config_name": ["VND_QoS_Config_R15_L70"]
+        },
+        {
+            "name": "VND_SingleDev_OneChanStereoSnk_48khz_100octs_Server_Preferred_1",
+            "codec_config_name": "VND_SingleDev_OneChanStereoSnk_48khz_100octs_1",
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
+        },
+        {
+            "name": "VND_SingleDev_OneChanStereoSnk_48khz_100octs_R15_L70_1",
+            "codec_config_name": "VND_SingleDev_OneChanStereoSnk_48khz_100octs_1",
+            "qos_config_name": ["VND_QoS_Config_R15_L70"]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSnk_48khz_100octs_Server_Preferred_1",
+            "codec_config_name": "VND_SingleDev_TwoChanStereoSnk_48khz_100octs_1",
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSnk_48khz_100octs_R15_L70_1",
+            "codec_config_name": "VND_SingleDev_TwoChanStereoSnk_48khz_100octs_1",
+            "qos_config_name": ["VND_QoS_Config_R15_L70"]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_Server_Preferred_1",
+            "codec_config_name": "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_1",
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_R5_L12_1",
+            "codec_config_name": "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_1",
+            "qos_config_name": ["VND_QoS_Config_R5_L12"]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSrc_48khz_100octs_Server_Preferred_1",
+            "codec_config_name": "VND_SingleDev_TwoChanStereoSrc_48khz_100octs_1",
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSrc_48khz_100octs_R11_L40_1",
+            "codec_config_name": "VND_SingleDev_TwoChanStereoSrc_48khz_100octs_1",
+            "qos_config_name": ["VND_QoS_Config_R11_L40"]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_R5_L12_TwoChanStereoSrc_16khz_30octs_R3_L12_1",
+            "codec_config_name": "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_TwoChanStereoSrc_16khz_30octs_1",
+            "qos_config_name": ["VND_QoS_Config_R5_L12", "VND_QoS_Config_R3_L12"]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_TwoChanStereoSrc_16khz_30octs_Server_Preferred_1",
+            "codec_config_name": "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_TwoChanStereoSrc_16khz_30octs_1",
+            "qos_config_name": ["QoS_Config_Server_Preferred"]
         }
     ],
     "codec_configurations": [
@@ -1038,6 +1168,260 @@
             ]
         },
         {
+            "name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_2",
+            "subconfigurations": [
+                {
+                    "device_cnt": 2,
+                    "ase_cnt": 2,
+                    "direction": "SINK",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    3
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    1,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    40,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                },
+                {
+                    "device_cnt": 2,
+                    "ase_cnt": 2,
+                    "direction": "SOURCE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    3
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    1,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    40,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_1",
+            "subconfigurations": [
+                {
+                    "device_cnt": 2,
+                    "ase_cnt": 2,
+                    "direction": "SINK",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    3
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    1,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    30,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                },
+                {
+                    "device_cnt": 2,
+                    "ase_cnt": 2,
+                    "direction": "SOURCE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    3
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    1,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    30,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                }
+            ]
+        },
+        {
             "name": "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_1",
             "subconfigurations": [
                 {
@@ -1421,6 +1805,264 @@
             ]
         },
         {
+            "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_2",
+            "subconfigurations": [
+                {
+                    "device_cnt": 1,
+                    "ase_cnt": 1,
+                    "direction": "SINK",
+                    "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    3
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    3,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    40,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                },
+                {
+                    "device_cnt": 1,
+                    "ase_cnt": 1,
+                    "direction": "SOURCE",
+                    "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    3
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    3,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    40,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_1",
+            "subconfigurations": [
+                {
+                    "device_cnt": 1,
+                    "ase_cnt": 1,
+                    "direction": "SINK",
+                    "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    3
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    3,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    30,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                },
+                {
+                    "device_cnt": 1,
+                    "ase_cnt": 1,
+                    "direction": "SOURCE",
+                    "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    3
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    3,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    30,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                }
+            ]
+        },
+        {
             "name": "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2",
             "subconfigurations": [
                 {
@@ -2453,6 +3095,598 @@
                     ]
                 }
             ]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSnk_48khz_100octs_1",
+            "subconfigurations": [
+                {
+                    "device_cnt": 1,
+                    "ase_cnt": 1,
+                    "direction": "SINK",
+                    "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    8
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    3,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    100,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "VND_DualDev_OneChanStereoSnk_48khz_100octs_1",
+            "subconfigurations": [
+                {
+                    "device_cnt": 2,
+                    "ase_cnt": 2,
+                    "direction": "SINK",
+                    "configuration_strategy": "MONO_ONE_CIS_PER_DEVICE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    8
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    1,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    100,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "VND_SingleDev_OneChanStereoSnk_48khz_100octs_1",
+            "subconfigurations": [
+                {
+                    "device_cnt": 1,
+                    "ase_cnt": 2,
+                    "direction": "SINK",
+                    "configuration_strategy": "STEREO_TWO_CISES_PER_DEVICE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    8
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    1,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    100,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_1",
+            "subconfigurations": [
+                {
+                    "device_cnt": 1,
+                    "ase_cnt": 1,
+                    "direction": "SINK",
+                    "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    8
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    3,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    75,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSrc_48khz_100octs_1",
+            "subconfigurations": [
+                {
+                    "device_cnt": 1,
+                    "ase_cnt": 1,
+                    "direction": "SOURCE",
+                    "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    8
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    3,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    100,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSnk_OneChanStereoSrc_32khz_60octs_1",
+            "subconfigurations": [
+                {
+                    "device_cnt": 1,
+                    "ase_cnt": 1,
+                    "direction": "SINK",
+                    "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    6
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    3,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    60,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                },
+                {
+                    "device_cnt": 1,
+                    "ase_cnt": 1,
+                    "direction": "SOURCE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    6
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    1,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    60,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                }
+            ]
+        },
+        {
+            "name": "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_TwoChanStereoSrc_16khz_30octs_1",
+            "subconfigurations": [
+                {
+                    "device_cnt": 1,
+                    "ase_cnt": 1,
+                    "direction": "SINK",
+                    "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    8
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    3,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    75,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                },
+                {
+                    "device_cnt": 1,
+                    "ase_cnt": 1,
+                    "direction": "SOURCE",
+                    "configuration_strategy": "STEREO_ONE_CIS_PER_DEVICE",
+                    "codec_id": {
+                        "coding_format": 6,
+                        "vendor_company_id": 0,
+                        "vendor_codec_id": 0
+                    },
+                    "codec_configuration": [
+                        {
+                            "name": "sampling_frequency",
+                            "type": 1,
+                            "compound_value": {
+                                "value": [
+                                    3
+                                ]
+                            }
+                        },
+                        {
+                            "name": "frame_duration",
+                            "type": 2,
+                            "compound_value": {
+                                "value": [
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "audio_channel_allocation",
+                            "type": 3,
+                            "compound_value": {
+                                "value": [
+                                    3,
+                                    0,
+                                    0,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "octets_per_codec_frame",
+                            "type": 4,
+                            "compound_value": {
+                                "value": [
+                                    30,
+                                    0
+                                ]
+                            }
+                        },
+                        {
+                            "name": "codec_frame_blocks_per_sdu",
+                            "type": 5,
+                            "compound_value": {
+                                "value": [
+                                    1
+                                ]
+                            }
+                        }
+                    ]
+                }
+            ]
         }
     ],
     "qos_configurations": [
@@ -2487,6 +3721,31 @@
             "max_transport_latency": 100
         },
         {
+            "name": "VND_QoS_Config_R3_L22",
+            "retransmission_number": 3,
+            "max_transport_latency": 22
+        },
+        {
+            "name": "VND_QoS_Config_R15_L70",
+            "retransmission_number": 15,
+            "max_transport_latency": 70
+        },
+        {
+            "name": "VND_QoS_Config_R5_L12",
+            "retransmission_number": 5,
+            "max_transport_latency": 12
+        },
+        {
+            "name": "VND_QoS_Config_R11_L40",
+            "retransmission_number": 11,
+            "max_transport_latency": 40
+        },
+        {
+            "name": "VND_QoS_Config_R3_L12",
+            "retransmission_number": 3,
+            "max_transport_latency": 12
+        },
+        {
             "name": "QoS_Config_Server_Preferred",
             "retransmission_number": 0,
             "max_transport_latency": 0
diff --git a/system/bta/le_audio/audio_set_scenarios.json b/system/bta/le_audio/audio_set_scenarios.json
index d1314a4..3b749a0 100644
--- a/system/bta/le_audio/audio_set_scenarios.json
+++ b/system/bta/le_audio/audio_set_scenarios.json
@@ -29,6 +29,12 @@
         {
             "name": "Conversational",
             "configurations": [
+                "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_2_Server_Preferred",
+                "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_2_1",
+                "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_2_2",
+                "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_1_Server_Preferred",
+                "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_1_1",
+                "DualDev_OneChanStereoSnk_OneChanStereoSrc_16_1_2",
                 "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_2_Server_Preferred",
                 "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_2_1",
                 "DualDev_OneChanStereoSnk_OneChanMonoSrc_16_1_Server_Preferred",
@@ -37,6 +43,12 @@
                 "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_2_1",
                 "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1_Server_Preferred",
                 "DualDev_OneChanDoubleStereoSnk_OneChanMonoSrc_16_1_1",
+                "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_2_Server_Preferred",
+                "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_2_1",
+                "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_2_2",
+                "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_1_Server_Preferred",
+                "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_1_1",
+                "SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_16_1_2",
                 "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2_Server_Preferred",
                 "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_2_1",
                 "SingleDev_TwoChanStereoSnk_OneChanMonoSrc_16_1_Server_Preferred",
@@ -48,7 +60,9 @@
                 "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2_Server_Preferred",
                 "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_2_1",
                 "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1_Server_Preferred",
-                "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1_1"
+                "SingleDev_OneChanMonoSnk_OneChanMonoSrc_16_1_1",
+                "VND_SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_32khz_Server_Prefered_1",
+                "VND_SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_32khz_60oct_R3_L22_1"
             ]
         },
         {
@@ -77,7 +91,29 @@
                 "SingleDev_OneChanMonoSnk_16_2_Server_Preferred",
                 "SingleDev_OneChanMonoSnk_16_2_2",
                 "SingleDev_OneChanMonoSnk_16_1_Server_Preferred",
-                "SingleDev_OneChanMonoSnk_16_1_2"
+                "SingleDev_OneChanMonoSnk_16_1_2",
+                "VND_DualDev_OneChanStereoSnk_48khz_100octs_Server_Preferred_1",
+                "VND_DualDev_OneChanStereoSnk_48khz_100octs_R15_L70_1",
+                "VND_SingleDev_TwoChanStereoSnk_48khz_100octs_Server_Preferred_1",
+                "VND_SingleDev_TwoChanStereoSnk_48khz_100octs_R15_L70_1",
+                "VND_SingleDev_OneChanStereoSnk_48khz_100octs_Server_Preferred_1",
+                "VND_SingleDev_OneChanStereoSnk_48khz_100octs_R15_L70_1"
+            ]
+        },
+        {
+            "name": "Game",
+            "configurations": [
+                "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_TwoChanStereoSrc_16khz_30octs_Server_Preferred_1",
+                "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_R5_L12_TwoChanStereoSrc_16khz_30octs_R3_L12_1",
+                "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_Server_Preferred_1",
+                "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_R5_L12_1"
+            ]
+        },
+        {
+            "name": "Recording",
+            "configurations": [
+                "VND_SingleDev_TwoChanStereoSrc_48khz_100octs_Server_Preferred_1",
+                "VND_SingleDev_TwoChanStereoSrc_48khz_100octs_R11_L40_1"
             ]
         },
         {
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index c924766..7547a97 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -883,6 +883,18 @@
     BTA_GATTC_CancelOpen(0, address, false);
 
     if (leAudioDevice->conn_id_ != GATT_INVALID_CONN_ID) {
+      /* User is disconnecting the device, we shall remove the autoconnect flag
+       */
+      btif_storage_set_leaudio_autoconnect(address, false);
+
+      auto group = aseGroups_.FindById(leAudioDevice->group_id_);
+      if (group &&
+          group->GetState() ==
+              le_audio::types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+        leAudioDevice->closing_stream_for_disconnection_ = true;
+        groupStateMachine_->StopStream(group);
+        return;
+      }
       DisconnectDevice(leAudioDevice);
       return;
     }
@@ -1308,6 +1320,7 @@
 
     callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address);
     leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
+    leAudioDevice->closing_stream_for_disconnection_ = false;
     leAudioDevice->encrypted_ = false;
 
     if (leAudioDevice->removing_device_) {
@@ -2559,10 +2572,15 @@
 
   void Cleanup(base::Callback<void()> cleanupCb) {
     if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_);
+
+    if (active_group_id_ != bluetooth::groups::kGroupUnknown) {
+      /* Bluetooth turned off while streaming */
+      StopAudio();
+      ClientAudioIntefraceRelease();
+    }
     groupStateMachine_->Cleanup();
-    leAudioDevices_.Cleanup();
     aseGroups_.Cleanup();
-    StopAudio();
+    leAudioDevices_.Cleanup();
     if (gatt_if_) BTA_GATTC_AppDeregister(gatt_if_);
 
     std::move(cleanupCb).Run();
@@ -3233,6 +3251,20 @@
     }
   }
 
+  void HandlePendingDeviceDisconnection(LeAudioDeviceGroup* group) {
+    LOG_DEBUG();
+    auto leAudioDevice = group->GetFirstDevice();
+    while (leAudioDevice) {
+      if (leAudioDevice->closing_stream_for_disconnection_) {
+        leAudioDevice->closing_stream_for_disconnection_ = false;
+        LOG_DEBUG("Disconnecting group id: %d, address: %s", group->group_id_,
+                  leAudioDevice->address_.ToString().c_str());
+        DisconnectDevice(leAudioDevice);
+      }
+      leAudioDevice = group->GetNextDevice(leAudioDevice);
+    }
+  }
+
   void StatusReportCb(int group_id, GroupStreamStatus status) {
     LOG(INFO) << __func__ << "status: " << static_cast<int>(status)
               << " audio_sender_state_: " << audio_sender_state_
@@ -3284,7 +3316,10 @@
           }
         }
         CancelStreamingRequest();
-        HandlePendingAvailableContexts(group);
+        if (group) {
+          HandlePendingAvailableContexts(group);
+          HandlePendingDeviceDisconnection(group);
+        }
         break;
       }
       case GroupStreamStatus::RELEASING:
diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc
index 0ac9e43..3d344ce 100644
--- a/system/bta/le_audio/devices.cc
+++ b/system/bta/le_audio/devices.cc
@@ -102,7 +102,42 @@
       });
 }
 
-void LeAudioDeviceGroup::Cleanup(void) { leAudioDevices_.clear(); }
+void LeAudioDeviceGroup::Cleanup(void) {
+  /* Bluetooth is off while streaming - disconnect CISes and remove CIG */
+  if (GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+    if (!stream_conf.sink_streams.empty()) {
+      for (auto [cis_handle, audio_location] : stream_conf.sink_streams) {
+        bluetooth::hci::IsoManager::GetInstance()->DisconnectCis(
+            cis_handle, HCI_ERR_PEER_USER);
+
+        if (stream_conf.source_streams.empty()) {
+          continue;
+        }
+        uint16_t cis_hdl = cis_handle;
+        stream_conf.source_streams.erase(
+            std::remove_if(
+                stream_conf.source_streams.begin(),
+                stream_conf.source_streams.end(),
+                [cis_hdl](auto& pair) { return pair.first == cis_hdl; }),
+            stream_conf.source_streams.end());
+      }
+    }
+
+    if (!stream_conf.source_streams.empty()) {
+      for (auto [cis_handle, audio_location] : stream_conf.source_streams) {
+        bluetooth::hci::IsoManager::GetInstance()->DisconnectCis(
+            cis_handle, HCI_ERR_PEER_USER);
+      }
+    }
+  }
+
+  /* Note: CIG will stay in the controller. We cannot remove it here, because
+   * Cises are not yet disconnected.
+   * When user start Bluetooth, HCI Reset should remove it
+   */
+
+  leAudioDevices_.clear();
+}
 
 void LeAudioDeviceGroup::Deactivate(void) {
   for (auto* leAudioDevice = GetFirstActiveDevice(); leAudioDevice;
@@ -912,11 +947,15 @@
       }
       break;
     default:
-      LOG_ASSERT(0) << " Shall never happen ";
+      LOG_ALWAYS_FATAL("%s: Unknown strategy: %hhu", __func__, strategy);
       return 0;
   }
 
-  LOG_ASSERT(0) << " Shall never happen ";
+  LOG_ALWAYS_FATAL(
+      "%s: Shall never exit switch statement, strategy: %hhu, "
+      "locations: %lx, group_locations: %lx",
+      __func__, strategy, audio_locations.to_ulong(),
+      group_audio_locations->to_ulong());
   return 0;
 }
 
@@ -1900,6 +1939,11 @@
   }
 }
 
-void LeAudioDevices::Cleanup(void) { leAudioDevices_.clear(); }
+void LeAudioDevices::Cleanup(void) {
+  for (auto const& device : leAudioDevices_) {
+    device->DisconnectAcl();
+  }
+  leAudioDevices_.clear();
+}
 
 }  // namespace le_audio
diff --git a/system/bta/le_audio/devices.h b/system/bta/le_audio/devices.h
index 1217812..199171e 100644
--- a/system/bta/le_audio/devices.h
+++ b/system/bta/le_audio/devices.h
@@ -57,6 +57,7 @@
    * This is true only during initial phase of first connection. */
   bool first_connection_;
   bool connecting_actively_;
+  bool closing_stream_for_disconnection_;
   uint16_t conn_id_;
   bool encrypted_;
   int group_id_;
@@ -87,6 +88,7 @@
         removing_device_(false),
         first_connection_(first_connection),
         connecting_actively_(first_connection),
+        closing_stream_for_disconnection_(false),
         conn_id_(GATT_INVALID_CONN_ID),
         encrypted_(false),
         group_id_(group_id),
diff --git a/system/bta/le_audio/devices_test.cc b/system/bta/le_audio/devices_test.cc
index bb8a939..e24c39b 100644
--- a/system/bta/le_audio/devices_test.cc
+++ b/system/bta/le_audio/devices_test.cc
@@ -188,6 +188,7 @@
   LC3_48_4,
   LC3_48_5,
   LC3_48_6,
+  LC3_VND_1,
   _END,
   UNSUPPORTED = _END,
 };
@@ -206,7 +207,8 @@
 
     case LeAudioContextType::MEDIA:
       if (id == Lc3SettingId::LC3_16_1 || id == Lc3SettingId::LC3_16_2 ||
-          id == Lc3SettingId::LC3_48_4)
+          id == Lc3SettingId::LC3_48_4 || id == Lc3SettingId::LC3_48_2 ||
+          id == Lc3SettingId::LC3_VND_1)
         return true;
 
       break;
@@ -244,6 +246,7 @@
     case Lc3SettingId::LC3_48_4:
     case Lc3SettingId::LC3_48_5:
     case Lc3SettingId::LC3_48_6:
+    case Lc3SettingId::LC3_VND_1:
       return ::le_audio::codec_spec_conf::kLeAudioSamplingFreq48000Hz;
     case Lc3SettingId::UNSUPPORTED:
       return kLeAudioSamplingFreqRfu;
@@ -270,6 +273,7 @@
     case Lc3SettingId::LC3_48_2:
     case Lc3SettingId::LC3_48_4:
     case Lc3SettingId::LC3_48_6:
+    case Lc3SettingId::LC3_VND_1:
       return ::le_audio::codec_spec_conf::kLeAudioCodecLC3FrameDur10000us;
     case Lc3SettingId::UNSUPPORTED:
       return kLeAudioCodecLC3FrameDurRfu;
@@ -300,6 +304,7 @@
     case Lc3SettingId::LC3_48_1:
       return 75;
     case Lc3SettingId::LC3_48_2:
+    case Lc3SettingId::LC3_VND_1:
       return 100;
     case Lc3SettingId::LC3_48_3:
       return 90;
@@ -429,7 +434,8 @@
         ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft |
         ::le_audio::codec_spec_conf::kLeAudioLocationFrontRight;
     device->src_audio_locations_ =
-        ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft;
+        ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft |
+        ::le_audio::codec_spec_conf::kLeAudioLocationFrontRight;
 
     device->conn_id_ = index;
     return device.get();
@@ -519,6 +525,11 @@
                              data[i].active_channel_num_src) > 0;
 
         /* Prepare PAC's */
+        /* Note this test requires that reach TwoStereoChan configuration
+         * version has similar version for OneStereoChan (both SingleDev,
+         * DualDev). This is just how the test is created and this limitation
+         * should be removed b/230107540
+         */
         PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder;
         for (const auto& entry : (*audio_set_conf).confs) {
           if (entry.direction == kLeAudioDirectionSink) {
@@ -746,7 +757,7 @@
       {left, kLeAudioCodecLC3ChannelCountSingleChannel,
        kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1},
       {right, kLeAudioCodecLC3ChannelCountSingleChannel,
-       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}};
+       kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1}};
 
   TestGroupAseConfiguration(LeAudioContextType::CONVERSATIONAL, data, 2);
 }
diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc
index 9c0af41..f3db83a 100644
--- a/system/bta/le_audio/le_audio_client_test.cc
+++ b/system/bta/le_audio/le_audio_client_test.cc
@@ -437,7 +437,7 @@
         }));
 
     global_conn_id = 1;
-    ON_CALL(mock_gatt_interface_, Open(_, _, _, _))
+    ON_CALL(mock_gatt_interface_, Open(_, _, true, _))
         .WillByDefault(
             Invoke([&](tGATT_IF client_if, const RawAddress& remote_bda,
                        bool is_direct, bool opportunistic) {
@@ -1094,6 +1094,8 @@
     tracks_[0].content_type = content_type;
 
     if (reconfigure_existing_stream) {
+      EXPECT_CALL(*mock_unicast_audio_source_, SuspendedForReconfiguration())
+          .Times(1);
       EXPECT_CALL(*mock_unicast_audio_source_, ConfirmStreamingRequest())
           .Times(1);
     } else {
@@ -1630,7 +1632,8 @@
   }
 
   void TestAudioDataTransfer(int group_id, uint8_t cis_count_out,
-                             uint8_t cis_count_in, int data_len) {
+                             uint8_t cis_count_in, int data_len,
+                             int in_data_len = 40) {
     ASSERT_NE(audio_unicast_sink_receiver_, nullptr);
 
     // Expect two channels ISO Data to be sent
@@ -1643,8 +1646,9 @@
     std::vector<uint8_t> data(data_len);
     audio_unicast_sink_receiver_->OnAudioDataReady(data);
 
-    // Inject microphone data from a single peer device
-    EXPECT_CALL(*mock_audio_sink_, SendData(_, _)).Times(cis_count_in);
+    // Inject microphone data from group
+    EXPECT_CALL(*mock_audio_sink_, SendData(_, _))
+        .Times(cis_count_in > 0 ? 1 : 0);
     ASSERT_EQ(streaming_groups.count(group_id), 1u);
 
     if (cis_count_in) {
@@ -1655,7 +1659,7 @@
            device = group->GetNextDevice(device)) {
         for (auto& ase : device->ases_) {
           if (ase.direction == le_audio::types::kLeAudioDirectionSource) {
-            InjectIncomingIsoData(group_id, ase.cis_conn_hdl);
+            InjectIncomingIsoData(group_id, ase.cis_conn_hdl, in_data_len);
             --cis_count_in;
             if (!cis_count_in) break;
           }
@@ -1676,7 +1680,7 @@
   }
 
   void InjectIncomingIsoData(uint16_t cig_id, uint16_t cis_con_hdl,
-                             size_t payload_size = 40) {
+                             size_t payload_size) {
     BT_HDR* bt_hdr = (BT_HDR*)malloc(sizeof(BT_HDR) + payload_size);
 
     bt_hdr->offset = 0;
@@ -1930,11 +1934,16 @@
   /* For remote disconnection, expect stack to try background re-connect */
   EXPECT_CALL(mock_gatt_interface_, Open(gatt_if, test_address0, false, _))
       .Times(1);
-  global_conn_id = 1; /* Reset to keep conn_id same during re-connect */
+
   EXPECT_CALL(mock_client_callbacks_,
               OnConnectionState(ConnectionState::CONNECTED, test_address0))
       .Times(1);
   InjectDisconnectedEvent(1, GATT_CONN_TERMINATE_PEER_USER);
+  SyncOnMainLoop();
+
+  /* For background connect, test needs to Inject Connected Event */
+  InjectConnectedEvent(test_address0, 1);
+  SyncOnMainLoop();
 }
 
 TEST_F(UnicastTest, ConnectTwoEarbudsCsisGrouped) {
@@ -1963,6 +1972,13 @@
                     codec_spec_conf::kLeAudioLocationFrontRight, group_size,
                     group_id, 2 /* rank*/, true /*connect_through_csis*/);
 
+  Mock::VerifyAndClearExpectations(&mock_btif_storage_);
+
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, false))
+      .Times(1);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, false))
+      .Times(1);
+
   // Verify grouping information
   std::vector<RawAddress> devs =
       LeAudioClient::Get()->GetGroupDevices(group_id);
@@ -1999,12 +2015,18 @@
                     codec_spec_conf::kLeAudioLocationFrontRight, group_size,
                     group_id, 2 /* rank*/, true /*connect_through_csis*/);
 
+  Mock::VerifyAndClearExpectations(&mock_btif_storage_);
+
   // Verify grouping information
   std::vector<RawAddress> devs =
       LeAudioClient::Get()->GetGroupDevices(group_id);
   ASSERT_NE(std::find(devs.begin(), devs.end(), test_address0), devs.end());
   ASSERT_NE(std::find(devs.begin(), devs.end(), test_address1), devs.end());
 
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address1, false))
+      .Times(1);
+  EXPECT_CALL(mock_btif_storage_, AddLeaudioAutoconnect(test_address0, false))
+      .Times(1);
   DisconnectLeAudio(test_address0, 1);
   DisconnectLeAudio(test_address1, 2);
 }
@@ -2084,6 +2106,10 @@
       framework_encode_preference);
   if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS);
 
+  /* For background connect, test needs to Inject Connected Event */
+  InjectConnectedEvent(test_address0, 1);
+  InjectConnectedEvent(test_address1, 2);
+
   // We need to wait for the storage callback before verifying stuff
   SyncOnMainLoop();
   ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning());
@@ -2174,6 +2200,9 @@
       framework_encode_preference);
   if (app_register_callback) app_register_callback.Run(gatt_if, GATT_SUCCESS);
 
+ /* For background connect, test needs to Inject Connected Event */
+  InjectConnectedEvent(test_address0, 1);
+
   // We need to wait for the storage callback before verifying stuff
   SyncOnMainLoop();
   ASSERT_TRUE(LeAudioClient::IsLeAudioClientRunning());
@@ -2652,8 +2681,8 @@
 
   // Verify Data transfer on two peer sinks and one source
   uint8_t cis_count_out = 2;
-  uint8_t cis_count_in = 1;
-  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+  uint8_t cis_count_in = 2;
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40);
 
   // Suspend
   LeAudioClient::Get()->GroupSuspend(group_id);
@@ -2667,7 +2696,7 @@
   Mock::VerifyAndClearExpectations(mock_audio_sink_);
 
   // Verify Data transfer still works
-  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40);
 
   // Stop
   StopStreaming(group_id, true);
@@ -2810,8 +2839,8 @@
 
   // Verify Data transfer on two peer sinks and one source
   cis_count_out = 2;
-  cis_count_in = 1;
-  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+  cis_count_in = 2;
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920, 40);
 }
 
 TEST_F(UnicastTest, TwoEarbuds2ndLateConnect) {
@@ -2862,7 +2891,7 @@
   TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
 }
 
-TEST_F(UnicastTest, TwoEarbuds2ndDisconnect) {
+TEST_F(UnicastTest, TwoEarbuds2ndDisconnected) {
   uint8_t group_size = 2;
   int group_id = 2;
 
@@ -2907,7 +2936,12 @@
   for (auto& ase : device->ases_) {
     InjectCisDisconnected(group_id, ase.cis_conn_hdl);
   }
-  DisconnectLeAudio(device->address_, 1);
+
+  EXPECT_CALL(mock_gatt_interface_, Open(_, device->address_, false, false))
+      .Times(1);
+
+  auto conn_id = device->conn_id_;
+  InjectDisconnectedEvent(device->conn_id_, GATT_CONN_TERMINATE_PEER_USER);
   SyncOnMainLoop();
   Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
 
@@ -2916,16 +2950,8 @@
   cis_count_in = 0;
   TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
 
-  // Reconnect the disconnected device
-  auto rank = 1;
-  auto location = codec_spec_conf::kLeAudioLocationFrontLeft;
-  if (device->address_ == test_address1) {
-    rank = 2;
-    location = codec_spec_conf::kLeAudioLocationFrontRight;
-  }
-  ConnectCsisDevice(device->address_, 3 /*conn_id*/, location, location,
-                    group_size, group_id, rank, true /*connect_through_csis*/,
-                    false /* New device */);
+  InjectConnectedEvent(device->address_, conn_id);
+  SyncOnMainLoop();
 
   // Expect two iso channels to be fed with data
   cis_count_out = 2;
@@ -2933,5 +2959,54 @@
   TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
 }
 
+TEST_F(UnicastTest, TwoEarbudsStreamingProfileDisconnect) {
+  uint8_t group_size = 2;
+  int group_id = 2;
+
+  // Report working CSIS
+  ON_CALL(mock_csis_client_module_, IsCsisClientRunning())
+      .WillByDefault(Return(true));
+
+  // First earbud
+  const RawAddress test_address0 = GetTestAddress(0);
+  ConnectCsisDevice(test_address0, 1 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontLeft,
+                    codec_spec_conf::kLeAudioLocationFrontLeft, group_size,
+                    group_id, 1 /* rank*/);
+
+  // Second earbud
+  const RawAddress test_address1 = GetTestAddress(1);
+  ConnectCsisDevice(test_address1, 2 /*conn_id*/,
+                    codec_spec_conf::kLeAudioLocationFrontRight,
+                    codec_spec_conf::kLeAudioLocationFrontRight, group_size,
+                    group_id, 2 /* rank*/, true /*connect_through_csis*/);
+
+  // Audio sessions are started only when device gets active
+  EXPECT_CALL(*mock_unicast_audio_source_, Start(_, _)).Times(1);
+  EXPECT_CALL(*mock_audio_sink_, Start(_, _)).Times(1);
+  LeAudioClient::Get()->GroupSetActive(group_id);
+
+  StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);
+
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+  Mock::VerifyAndClearExpectations(mock_unicast_audio_source_);
+  SyncOnMainLoop();
+
+  // Expect two iso channels to be fed with data
+  uint8_t cis_count_out = 2;
+  uint8_t cis_count_in = 0;
+  TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+
+  // Disconnect one device and expect the group to keep on streaming
+  EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(1);
+  EXPECT_CALL(mock_gatt_interface_, Open(_, _, _, _)).Times(0);
+
+  DisconnectLeAudio(test_address0, 1);
+  DisconnectLeAudio(test_address1, 2);
+
+  SyncOnMainLoop();
+  Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+}
+
 }  // namespace
 }  // namespace le_audio
diff --git a/system/bta/le_audio/le_audio_set_configuration_provider_json.cc b/system/bta/le_audio/le_audio_set_configuration_provider_json.cc
index 6e04c29..6bd8a3b 100644
--- a/system/bta/le_audio/le_audio_set_configuration_provider_json.cc
+++ b/system/bta/le_audio/le_audio_set_configuration_provider_json.cc
@@ -15,6 +15,9 @@
  *
  */
 
+#include <string>
+#include <string_view>
+
 #include "audio_set_configurations_generated.h"
 #include "audio_set_scenarios_generated.h"
 #include "codec_manager.h"
@@ -237,41 +240,88 @@
       const bluetooth::le_audio::AudioSetConfiguration* flat_cfg,
       std::vector<const bluetooth::le_audio::CodecConfiguration*>* codec_cfgs,
       std::vector<const bluetooth::le_audio::QosConfiguration*>* qos_cfgs) {
-    std::vector<SetConfiguration> subconfigs;
-    QosConfigSetting qos;
-    const bluetooth::le_audio::CodecConfiguration* codec_cfg = NULL;
-    const bluetooth::le_audio::QosConfiguration* qos_cfg = NULL;
+    ASSERT_LOG(flat_cfg != nullptr, "flat_cfg cannot be null");
+    std::string codec_config_key = flat_cfg->codec_config_name()->str();
+    auto* qos_config_key_array = flat_cfg->qos_config_name();
 
-    const char* codec_config_key = flat_cfg->codec_config_name()->c_str();
-    const char* qos_config_key = flat_cfg->qos_config_name()->c_str();
+    constexpr std::string_view default_qos = "QoS_Config_Server_Preferred";
 
+    std::string qos_sink_key(default_qos);
+    std::string qos_source_key(default_qos);
+
+    /* We expect maximum two QoS settings. First for Sink and second for Source
+     */
+    if (qos_config_key_array->size() > 0) {
+      qos_sink_key = qos_config_key_array->Get(0)->str();
+      if (qos_config_key_array->size() > 1) {
+        qos_source_key = qos_config_key_array->Get(1)->str();
+      } else {
+        qos_source_key = qos_sink_key;
+      }
+    }
+
+    LOG_INFO("Config name %s, qos_sink %s, qos_source %s",
+             codec_config_key.c_str(), qos_sink_key.c_str(),
+             qos_source_key.c_str());
+
+    const bluetooth::le_audio::QosConfiguration* qos_sink_cfg = nullptr;
     for (auto i = qos_cfgs->begin(); i != qos_cfgs->end(); ++i) {
-      if (0 == strcmp((*i)->name()->c_str(), qos_config_key)) {
-        qos_cfg = *i;
+      if ((*i)->name()->str() == qos_sink_key) {
+        qos_sink_cfg = *i;
         break;
       }
     }
-    if (qos_cfg != NULL) {
-      qos.retransmission_number = qos_cfg->retransmission_number();
-      qos.max_transport_latency = qos_cfg->max_transport_latency();
-    } else {
-      LOG_ERROR("No qos config matching key %s found", qos_config_key);
+
+    const bluetooth::le_audio::QosConfiguration* qos_source_cfg = nullptr;
+    for (auto i = qos_cfgs->begin(); i != qos_cfgs->end(); ++i) {
+      if ((*i)->name()->str() == qos_source_key) {
+        qos_source_cfg = *i;
+        break;
+      }
     }
 
+    QosConfigSetting qos_sink;
+    if (qos_sink_cfg != nullptr) {
+      qos_sink.retransmission_number = qos_sink_cfg->retransmission_number();
+      qos_sink.max_transport_latency = qos_sink_cfg->max_transport_latency();
+    } else {
+      LOG_ERROR("No qos config matching key %s found", qos_sink_key.c_str());
+    }
+
+    QosConfigSetting qos_source;
+    if (qos_source_cfg != nullptr) {
+      qos_source.retransmission_number =
+          qos_source_cfg->retransmission_number();
+      qos_source.max_transport_latency =
+          qos_source_cfg->max_transport_latency();
+    } else {
+      LOG_ERROR("No qos config matching key %s found", qos_source_key.c_str());
+    }
+
+    const bluetooth::le_audio::CodecConfiguration* codec_cfg = nullptr;
     for (auto i = codec_cfgs->begin(); i != codec_cfgs->end(); ++i) {
-      if (0 == strcmp((*i)->name()->c_str(), codec_config_key)) {
+      if ((*i)->name()->str() == codec_config_key) {
         codec_cfg = *i;
         break;
       }
     }
-    if (codec_cfg != NULL && codec_cfg->subconfigurations()) {
+
+    std::vector<SetConfiguration> subconfigs;
+    if (codec_cfg != nullptr && codec_cfg->subconfigurations()) {
       /* Load subconfigurations */
       for (auto subconfig : *codec_cfg->subconfigurations()) {
-        subconfigs.push_back(SetConfigurationFromFlatSubconfig(subconfig, qos));
+        if (subconfig->direction() == le_audio::types::kLeAudioDirectionSink) {
+          subconfigs.push_back(
+              SetConfigurationFromFlatSubconfig(subconfig, qos_sink));
+        } else {
+          subconfigs.push_back(
+              SetConfigurationFromFlatSubconfig(subconfig, qos_source));
+        }
       }
     } else {
-      if (codec_cfg == NULL) {
-        LOG_ERROR("No codec config matching key %s found", codec_config_key);
+      if (codec_cfg == nullptr) {
+        LOG_ERROR("No codec config matching key %s found",
+                  codec_config_key.c_str());
       } else {
         LOG_ERROR("Configuration '%s' has no valid subconfigurations.",
                   flat_cfg->name()->c_str());
@@ -437,6 +487,8 @@
             {"Media", types::LeAudioContextType::MEDIA},
             {"Conversational", types::LeAudioContextType::CONVERSATIONAL},
             {"Ringtone", types::LeAudioContextType::RINGTONE},
+            {"Recording", types::LeAudioContextType::LIVE},
+            {"Game", types::LeAudioContextType::GAME},
             {"Default", types::LeAudioContextType::UNSPECIFIED},
         };
     return scenarios.count(scenario) ? scenarios.at(scenario)
@@ -466,7 +518,7 @@
 
     for (LeAudioContextType context : types::kLeAudioContextAllTypesArray) {
       auto confs = Get()->GetConfigurations(context);
-      stream << "  === Configurations for context type: " << (int)context
+      stream << "\n  === Configurations for context type: " << (int)context
              << ", num: " << (confs == nullptr ? 0 : confs->size()) << " \n";
       if (confs->size() > 0) {
         for (const auto& conf : *confs) {
@@ -483,7 +535,9 @@
                    << "     qos->retransmission_number: "
                    << +ent.qos.retransmission_number << " \n"
                    << "     qos->max_transport_latency: "
-                   << +ent.qos.max_transport_latency << " \n";
+                   << +ent.qos.max_transport_latency << " \n"
+                   << "     channel count: "
+                   << +ent.codec.GetConfigChannelCount() << "\n";
           }
         }
       }
diff --git a/system/bta/le_audio/le_audio_types.cc b/system/bta/le_audio/le_audio_types.cc
index f6c41a1..ca33dc2 100644
--- a/system/bta/le_audio/le_audio_types.cc
+++ b/system/bta/le_audio/le_audio_types.cc
@@ -122,9 +122,7 @@
   DLOG(INFO) << __func__ << " Pac:SamplFreq=" << loghex(u16_pac_val);
 
   /* TODO: Integrate with codec capabilities */
-  if ((u8_req_val != codec_spec_conf::kLeAudioSamplingFreq16000Hz &&
-       u8_req_val != codec_spec_conf::kLeAudioSamplingFreq48000Hz) ||
-      !(u16_pac_val &
+  if (!(u16_pac_val &
         codec_spec_caps::SamplingFreqConfig2Capability(u8_req_val))) {
     DLOG(ERROR) << __func__ << ", sampling frequency not supported";
     return false;
diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc
index 347a6a8..ba8d267 100644
--- a/system/bta/le_audio/state_machine_test.cc
+++ b/system/bta/le_audio/state_machine_test.cc
@@ -38,6 +38,7 @@
 using ::testing::AtLeast;
 using ::testing::DoAll;
 using ::testing::Invoke;
+using ::testing::NiceMock;
 using ::testing::Return;
 using ::testing::SaveArg;
 using ::testing::Test;
@@ -731,7 +732,8 @@
           src_context_type |= kContextTypeConversational;
 
           leAudioDevice->src_audio_locations_ =
-              ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft;
+              ::le_audio::codec_spec_conf::kLeAudioLocationFrontLeft |
+              ::le_audio::codec_spec_conf::kLeAudioLocationFrontRight;
         }
 
         leAudioDevice->SetSupportedContexts(snk_context_type, src_context_type);
@@ -923,7 +925,7 @@
 
             auto meta_len = *ase_p++;
             auto num_handled_bytes = ase_p - value.data();
-            ase_p += num_handled_bytes;
+            ase_p += meta_len;
 
             client_parser::ascs::ase_transient_state_params enable_params = {
                 .metadata = std::vector<uint8_t>(
@@ -1104,8 +1106,8 @@
         };
   }
 
-  controller::MockControllerInterface mock_controller_;
-  bluetooth::manager::MockBtmInterface btm_interface;
+  NiceMock<controller::MockControllerInterface> mock_controller_;
+  NiceMock<bluetooth::manager::MockBtmInterface> btm_interface;
   gatt::MockBtaGattInterface gatt_interface;
   gatt::MockBtaGattQueue gatt_queue;
 
@@ -1441,11 +1443,11 @@
   PrepareConfigureCodecHandler(group);
   PrepareConfigureQosHandler(group);
   PrepareEnableHandler(group);
-  PrepareReceiverStartReady(group, 1);
+  PrepareReceiverStartReady(group);
 
   EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(1);
   EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(AtLeast(1));
-  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(3);
+  EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4);
   EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
   EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
   EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
@@ -1459,7 +1461,7 @@
                 WriteCharacteristic(leAudioDevice->conn_id_,
                                     leAudioDevice->ctp_hdls_.val_hdl, _,
                                     GATT_WRITE_NO_RSP, _, _))
-        .Times(AtLeast(3));
+        .Times(4);
     expected_devices_written++;
     leAudioDevice = group->GetNextDevice(leAudioDevice);
   }
diff --git a/system/btif/include/btif_dm.h b/system/btif/include/btif_dm.h
index b3f7021..5645992 100644
--- a/system/btif/include/btif_dm.h
+++ b/system/btif/include/btif_dm.h
@@ -52,6 +52,11 @@
                          tBTM_OOB_DATA oob_data, tBTM_AUTH_REQ auth_req);
 
 /**
+ * Device Configuration Queries
+ */
+void btif_dm_get_local_class_of_device(DEV_CLASS device_class);
+
+/**
  * Out-of-band functions
  */
 void btif_dm_set_oob_for_io_req(tBTM_OOB_DATA* p_oob_data);
diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc
index da3c8a9..d1f2b7c 100644
--- a/system/btif/src/btif_dm.cc
+++ b/system/btif/src/btif_dm.cc
@@ -51,6 +51,10 @@
 
 #include <mutex>
 
+#ifdef OS_ANDROID
+#include <android/sysprop/BluetoothProperties.sysprop.h>
+#endif
+
 #include "advertise_data_parser.h"
 #include "bta_csis_api.h"
 #include "bta_dm_int.h"
@@ -120,6 +124,10 @@
 
 #define BTIF_DM_MAX_SDP_ATTEMPTS_AFTER_PAIRING 2
 
+#ifndef PROPERTY_CLASS_OF_DEVICE
+#define PROPERTY_CLASS_OF_DEVICE "bluetooth.device.class_of_device"
+#endif
+
 #define NUM_TIMEOUT_RETRIES 5
 #ifndef PROPERTY_DEFAULT_DEVICE_NAME
 #define PROPERTY_DEFAULT_DEVICE_NAME "bluetooth.device.default_name"
@@ -132,6 +140,10 @@
 #error "default btif local name size exceeds stack supported length"
 #endif
 
+#ifndef PROPERTY_BLE_PRIVACY_ENABLED
+#define PROPERTY_BLE_PRIVACY_ENABLED "bluetooth.core.gap.le.privacy.enabled"
+#endif
+
 #define ENCRYPTED_BREDR 2
 #define ENCRYPTED_LE 4
 
@@ -1543,8 +1555,22 @@
     BTA_DmSetDeviceName(btif_get_default_local_name());
   }
 
-  /* Enable local privacy */
-  BTA_DmBleConfigLocalPrivacy(BLE_LOCAL_PRIVACY_ENABLED);
+  /* Enable or disable local privacy */
+  bool ble_privacy_enabled = true;
+#ifdef OS_ANDROID
+  ble_privacy_enabled =
+      android::sysprop::BluetoothProperties::isGapLePrivacyEnabled().value_or(
+          true);
+#else
+  char ble_privacy_text[PROPERTY_VALUE_MAX] = "true";  // default is enabled
+  if (osi_property_get(PROPERTY_BLE_PRIVACY_ENABLED, ble_privacy_text,
+                       "true") &&
+      !strcmp(ble_privacy_text, "false")) {
+    ble_privacy_enabled = false;
+  }
+#endif
+  LOG_INFO("%s BLE Privacy: %d", __func__, ble_privacy_enabled);
+  BTA_DmBleConfigLocalPrivacy(ble_privacy_enabled);
 
   /* for each of the enabled services in the mask, trigger the profile
    * enable */
@@ -2168,6 +2194,139 @@
 
 /*******************************************************************************
  *
+ * Function         btif_dm_get_local_class_of_device
+ *
+ * Description      Reads the system property configured class of device
+ *
+ * Inputs           A pointer to a DEV_CLASS that you want filled with the
+ *                  current class of device. Size is assumed to be 3.
+ *
+ * Returns          Nothing. device_class will contain the current class of
+ *                  device. If no value is present, or the value is malformed
+ *                  the default "unclassified" value will be used
+ *
+ ******************************************************************************/
+void btif_dm_get_local_class_of_device(DEV_CLASS device_class) {
+  /* A class of device is a {SERVICE_CLASS, MAJOR_CLASS, MINOR_CLASS}
+   *
+   * The input is expected to be a string of the following format:
+   * <decimal number>,<decimal number>,<decimal number>
+   *
+   * For example, "90,2,12" (Hex: 0x5A, 0x2, 0xC)
+   *
+   * Notice there is always two commas and no spaces.
+   */
+
+  device_class[0] = 0x00;
+  device_class[1] = BTM_COD_MAJOR_UNCLASSIFIED;
+  device_class[2] = BTM_COD_MINOR_UNCLASSIFIED;
+
+#ifdef OS_ANDROID
+  std::vector<std::optional<std::uint32_t>> local_device_class =
+          android::sysprop::BluetoothProperties::getClassOfDevice();
+  // Error check the inputs. Use the default if problems are found
+  if (local_device_class.size() != 3) {
+    LOG_ERROR("COD malformed, must have unsigned 3 integers.");
+  } else if (local_device_class[0].has_value()
+      || local_device_class[0].value() > 0xFF) {
+    LOG_ERROR("COD malformed, first value is missing or too large.");
+  } else if (local_device_class[1].has_value()
+      || local_device_class[1].value() > 0xFF) {
+    LOG_ERROR("COD malformed, second value is missing or too large.");
+  } else if (local_device_class[2].has_value()
+      || local_device_class[2].value() > 0xFF) {
+    LOG_ERROR("COD malformed, third value is missing or too large.");
+  } else {
+    device_class[0] = (uint8_t) local_device_class[0].value();
+    device_class[1] = (uint8_t) local_device_class[1].value();
+    device_class[2] = (uint8_t) local_device_class[2].value();
+  }
+#else
+  char prop_cod[PROPERTY_VALUE_MAX];
+  osi_property_get(PROPERTY_CLASS_OF_DEVICE, prop_cod, "");
+
+  // If the property is empty, use the default
+  if (prop_cod[0] == '\0') {
+    LOG_ERROR("%s: COD property is empty", __func__);
+    return;
+  }
+
+  // Start reading the contents of the property string. If at any point anything
+  // is malformed, use the default.
+  DEV_CLASS temp_device_class;
+  int i = 0;
+  int j = 0;
+  for (;;) {
+    // Build a string of all the chars until the next comma, null, or end of the
+    // buffer is reached. If any char is not a digit, then return the default.
+    std::string value;
+    while (i < PROPERTY_VALUE_MAX && prop_cod[i] != ',' && prop_cod[i] != '\0') {
+      char c = prop_cod[i++];
+      if (!std::isdigit(c)) {
+        LOG_ERROR("%s: COD malformed, '%c' is a non-digit", __func__, c);
+        return;
+      }
+      value += c;
+    }
+
+    // If we hit the end and it wasn't null terminated then return the default
+    if (i == PROPERTY_VALUE_MAX && prop_cod[PROPERTY_VALUE_MAX - 1] != '\0') {
+      LOG_ERROR("%s: COD malformed, value was truncated", __func__);
+      return;
+    }
+
+    // Each number in the list must be one byte, meaning 0 (0x00) -> 255 (0xFF)
+    if (value.size() > 3 || value.size() == 0) {
+      LOG_ERROR("%s: COD malformed, '%s' must be between [0, 255]", __func__,
+          value.c_str());
+      return;
+    }
+
+    // Grab the value. If it's too large, then return the default
+    uint32_t uint32_val = static_cast<uint32_t>(std::stoul(value.c_str()));
+    if (uint32_val > 0xFF) {
+      LOG_ERROR("%s: COD malformed, '%s' must be between [0, 255]", __func__,
+          value.c_str());
+      return;
+    }
+
+    // Otherwise, it's safe to use
+    temp_device_class[j++] = uint32_val;
+
+    // If we've reached 3 numbers then make sure we're at a null terminator
+    if (j >= 3) {
+      if (prop_cod[i] != '\0') {
+        LOG_ERROR("%s: COD malformed, more than three numbers", __func__);
+        return;
+      }
+      break;
+    }
+
+    // If we're at a null terminator then we're done
+    if (prop_cod[i] == '\0') {
+      break;
+    }
+
+    // Otherwise, skip over the comma
+    ++i;
+  }
+
+  // We must have read exactly 3 numbers
+  if (j == 3) {
+    device_class[0] = temp_device_class[0];
+    device_class[1] = temp_device_class[1];
+    device_class[2] = temp_device_class[2];
+  } else {
+    LOG_ERROR("%s: COD malformed, fewer than three numbers", __func__);
+  }
+#endif
+
+  LOG_DEBUG("%s: Using class of device '0x%x, 0x%x, 0x%x'", __func__,
+      device_class[0], device_class[1], device_class[2]);
+}
+
+/*******************************************************************************
+ *
  * Function         btif_dm_get_adapter_property
  *
  * Description     Queries the BTA for the adapter property
@@ -2201,7 +2360,8 @@
     } break;
 
     case BT_PROPERTY_CLASS_OF_DEVICE: {
-      DEV_CLASS dev_class = BTA_DM_COD;
+      DEV_CLASS dev_class;
+      btif_dm_get_local_class_of_device(dev_class);
       memcpy(prop->val, dev_class, sizeof(DEV_CLASS));
       prop->len = sizeof(DEV_CLASS);
     } break;
diff --git a/system/btif/src/btif_le_audio.cc b/system/btif/src/btif_le_audio.cc
index 0150b53..ba004f3 100644
--- a/system/btif/src/btif_le_audio.cc
+++ b/system/btif/src/btif_le_audio.cc
@@ -151,8 +151,6 @@
     do_in_main_thread(FROM_HERE,
                       Bind(&LeAudioClient::Disconnect,
                            Unretained(LeAudioClient::Get()), address));
-    do_in_jni_thread(
-        FROM_HERE, Bind(&btif_storage_set_leaudio_autoconnect, address, false));
   }
 
   void GroupAddNode(const int group_id, const RawAddress& address) override {
diff --git a/system/btif/src/stack_manager.cc b/system/btif/src/stack_manager.cc
index e56ef7a..c5faa2e 100644
--- a/system/btif/src/stack_manager.cc
+++ b/system/btif/src/stack_manager.cc
@@ -76,16 +76,6 @@
 
 // Validate or respond to various conditional compilation flags
 
-#if BLE_PRIVACY_SPT != TRUE
-// Once BLE_PRIVACY_SPT is no longer exposed via bt_target.h
-// this check and error statement may be removed.
-#warning \
-    "#define BLE_PRIVACY_SPT FALSE preprocessor compilation flag is unsupported"
-#warning \
-    "  To disable LE privacy for a device use: #define BLE_LOCAL_PRIVACY_ENABLED FALSE"
-#error "*** Conditional Compilation Directive error"
-#endif
-
 #if SDP_RAW_DATA_INCLUDED != TRUE
 // Once SDP_RAW_DATA_INCLUDED is no longer exposed via bt_target.h
 // this check and error statement may be removed.
diff --git a/system/gd/hal/hci_hal_android_hidl.cc b/system/gd/hal/hci_hal_android_hidl.cc
index c33c6f3..ffb75ff 100644
--- a/system/gd/hal/hci_hal_android_hidl.cc
+++ b/system/gd/hal/hci_hal_android_hidl.cc
@@ -246,7 +246,10 @@
     if (!death_unlink.isOk()) {
       LOG_ERROR("Error unlinking death recipient from the Bluetooth HAL");
     }
-    bt_hci_->close();
+    auto close_status = bt_hci_->close();
+    if (!close_status.isOk()) {
+      LOG_ERROR("Error calling close on the Bluetooth HAL");
+    }
     callbacks_->ResetCallback();
     bt_hci_ = nullptr;
     bt_hci_1_1_ = nullptr;
diff --git a/system/gd/hci/acl_manager/le_impl.h b/system/gd/hci/acl_manager/le_impl.h
index 85cdd5b..8692127 100644
--- a/system/gd/hci/acl_manager/le_impl.h
+++ b/system/gd/hci/acl_manager/le_impl.h
@@ -292,7 +292,6 @@
       on_le_connection_canceled_on_pause();
       return;
     }
-    // TODO: find out which address and type was used to initiate the connection
     AddressWithType remote_address(address, peer_address_type);
     AddressWithType local_address = le_address_manager_->GetCurrentAddress();
     on_common_le_connection_complete(remote_address);
@@ -363,9 +362,15 @@
       return;
     }
     AddressWithType remote_address(address, peer_address_type);
-    if (!peer_resolvable_address.IsEmpty()) {
+    // The address added to the connect list is the one that callbacks
+    // should be sent for, given that is the address passed in
+    // call to BluetoothDevice#connectGatt and tied to app layer callbacks.
+    if (!peer_resolvable_address.IsEmpty() &&
+        is_device_in_connect_list(AddressWithType(peer_resolvable_address, AddressType::RANDOM_DEVICE_ADDRESS))) {
+      LOG_INFO("Using resolvable address");
       remote_address = AddressWithType(peer_resolvable_address, AddressType::RANDOM_DEVICE_ADDRESS);
     }
+
     on_common_le_connection_complete(remote_address);
     if (status == ErrorCode::UNKNOWN_CONNECTION) {
       if (remote_address.GetAddress() != Address::kEmpty) {
@@ -556,6 +561,10 @@
         address_with_type.ToFilterAcceptListAddressType(), address_with_type.GetAddress());
   }
 
+  bool is_device_in_connect_list(AddressWithType address_with_type) {
+    return (connect_list.find(address_with_type) != connect_list.end());
+  }
+
   void remove_device_from_connect_list(AddressWithType address_with_type) {
     if (connect_list.find(address_with_type) == connect_list.end()) {
       LOG_WARN("Device not in acceptlist and cannot be removed:%s", PRIVATE_ADDRESS_WITH_TYPE(address_with_type));
diff --git a/system/gd/hci/hci_layer.cc b/system/gd/hci/hci_layer.cc
index fd37ae4..57d7e55 100644
--- a/system/gd/hci/hci_layer.cc
+++ b/system/gd/hci/hci_layer.cc
@@ -176,6 +176,10 @@
 
     ASSERT_LOG(!command_queue_.empty(), "Unexpected %s event with OpCode 0x%02hx (%s)", logging_id.c_str(), op_code,
                OpCodeText(op_code).c_str());
+    if (waiting_command_ == OpCode::CONTROLLER_DEBUG_INFO && op_code != OpCode::CONTROLLER_DEBUG_INFO) {
+      LOG_ERROR("Discarding event that came after timeout 0x%02hx (%s)", op_code, OpCodeText(op_code).c_str());
+      return;
+    }
     ASSERT_LOG(waiting_command_ == op_code, "Waiting for 0x%02hx (%s), got 0x%02hx (%s)", waiting_command_,
                OpCodeText(waiting_command_).c_str(), op_code, OpCodeText(op_code).c_str());
 
diff --git a/system/internal_include/bt_target.h b/system/internal_include/bt_target.h
index 9eb31d1..8ff2b37 100644
--- a/system/internal_include/bt_target.h
+++ b/system/internal_include/bt_target.h
@@ -295,20 +295,6 @@
 #define BTM_DEFAULT_DISC_INTERVAL 0x0800
 #endif
 
-/* Default class of device
-* {SERVICE_CLASS, MAJOR_CLASS, MINOR_CLASS}
-*
-* SERVICE_CLASS:0x5A (Bit17 -Networking,Bit19 - Capturing,Bit20 -Object
-* Transfer,Bit22 -Telephony)
-* MAJOR_CLASS:0x02 - PHONE
-* MINOR_CLASS:0x0C - SMART_PHONE
-*
-*/
-#ifndef BTA_DM_COD
-#define BTA_DM_COD \
-  { 0x5A, 0x02, 0x0C }
-#endif
-
 /* The number of SCO links. */
 #ifndef BTM_MAX_SCO_LINKS
 #define BTM_MAX_SCO_LINKS 6
@@ -525,21 +511,6 @@
 #endif
 
 /*
- * Toggles support for general LE privacy features such as remote address
- * resolution, local address rotation etc.
- */
-#ifndef BLE_PRIVACY_SPT
-#define BLE_PRIVACY_SPT TRUE
-#endif
-
-/*
- * Enables or disables support for local privacy (ex. address rotation)
- */
-#ifndef BLE_LOCAL_PRIVACY_ENABLED
-#define BLE_LOCAL_PRIVACY_ENABLED TRUE
-#endif
-
-/*
  * Toggles support for vendor specific extensions such as RPA offloading,
  * feature discovery, multi-adv etc.
  */
diff --git a/system/main/shim/acl.cc b/system/main/shim/acl.cc
index 654a076..345f8ea 100644
--- a/system/main/shim/acl.cc
+++ b/system/main/shim/acl.cc
@@ -1516,6 +1516,7 @@
 
   // Once an le connection has successfully been established
   // the device address is removed from the controller accept list.
+
   if (IsRpa(address_with_type)) {
     LOG_DEBUG("Connection address is rpa:%s identity_addr:%s",
               PRIVATE_ADDRESS(address_with_type),
diff --git a/system/stack/acl/btm_pm.cc b/system/stack/acl/btm_pm.cc
index 1a6417b..549d2ae 100644
--- a/system/stack/acl/btm_pm.cc
+++ b/system/stack/acl/btm_pm.cc
@@ -334,6 +334,12 @@
     return BTM_UNKNOWN_ADDR;
   }
 
+  const controller_t* controller = controller_get_interface();
+  if (!controller->supports_sniff_subrating()) {
+    LOG_INFO("No controller support for sniff subrating");
+    return BTM_SUCCESS;
+  }
+
   if (bluetooth::shim::is_gd_link_policy_enabled()) {
     return bluetooth::shim::BTM_SetSsrParams(p_cb->handle_, max_lat, min_rmt_to,
                                              min_loc_to);
@@ -558,9 +564,12 @@
     LOG_DEBUG("Need to wake first");
     md_res.mode = BTM_PM_MD_ACTIVE;
   } else if (BTM_PM_MD_SNIFF == md_res.mode && p_cb->max_lat) {
-    LOG_DEBUG("Sending sniff subrating to controller");
-    send_sniff_subrating(handle, p_cb->bda_, p_cb->max_lat, p_cb->min_rmt_to,
-                         p_cb->min_loc_to);
+    const controller_t* controller = controller_get_interface();
+    if (controller->supports_sniff_subrating()) {
+      LOG_DEBUG("Sending sniff subrating to controller");
+      send_sniff_subrating(handle, p_cb->bda_, p_cb->max_lat, p_cb->min_rmt_to,
+                           p_cb->min_loc_to);
+    }
     p_cb->max_lat = 0;
   }
   /* Default is failure */
diff --git a/system/stack/btm/btm_sco.cc b/system/stack/btm/btm_sco.cc
index a48bdfd..9c5c41f 100644
--- a/system/stack/btm/btm_sco.cc
+++ b/system/stack/btm/btm_sco.cc
@@ -723,8 +723,8 @@
  * Returns          void
  *
  ******************************************************************************/
-void btm_sco_connected(tHCI_STATUS hci_status, const RawAddress& bda,
-                       uint16_t hci_handle, tBTM_ESCO_DATA* p_esco_data) {
+void btm_sco_connected(const RawAddress& bda, uint16_t hci_handle,
+                       tBTM_ESCO_DATA* p_esco_data) {
   tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0];
   uint16_t xx;
   bool spt = false;
@@ -734,49 +734,6 @@
     if (((p->state == SCO_ST_CONNECTING) || (p->state == SCO_ST_LISTENING) ||
          (p->state == SCO_ST_W4_CONN_RSP)) &&
         (p->rem_bd_known) && (p->esco.data.bd_addr == bda)) {
-      if (hci_status != HCI_SUCCESS) {
-        /* Report the error if originator, otherwise remain in Listen mode */
-        if (p->is_orig) {
-          LOG_DEBUG("SCO initiating connection failed handle:0x%04x reason:%s",
-                    hci_handle, hci_error_code_text(hci_status).c_str());
-          switch (hci_status) {
-            case HCI_ERR_ROLE_SWITCH_PENDING:
-              /* If role switch is pending, we need try again after role switch
-               * is complete */
-              p->state = SCO_ST_PEND_ROLECHANGE;
-              break;
-            case HCI_ERR_LMP_ERR_TRANS_COLLISION:
-              /* Avoid calling disconnect callback because of sco creation race
-               */
-              break;
-            default: /* Notify client about SCO failure */
-              p->state = SCO_ST_UNUSED;
-              (*p->p_disc_cb)(xx);
-          }
-          BTM_LogHistory(
-              kBtmLogTag, bda, "Connection failed",
-              base::StringPrintf(
-                  "locally_initiated reason:%s",
-                  hci_reason_code_text(static_cast<tHCI_REASON>(hci_status))
-                      .c_str()));
-        } else {
-          LOG_DEBUG("SCO terminating connection failed handle:0x%04x reason:%s",
-                    hci_handle, hci_error_code_text(hci_status).c_str());
-          if (p->state == SCO_ST_CONNECTING) {
-            p->state = SCO_ST_UNUSED;
-            (*p->p_disc_cb)(xx);
-          } else
-            p->state = SCO_ST_LISTENING;
-          BTM_LogHistory(
-              kBtmLogTag, bda, "Connection failed",
-              base::StringPrintf(
-                  "remote_initiated reason:%s",
-                  hci_reason_code_text(static_cast<tHCI_REASON>(hci_status))
-                      .c_str()));
-        }
-        return;
-      }
-
       BTM_LogHistory(
           kBtmLogTag, bda, "Connection created",
           base::StringPrintf("sco_idx:%hu handle:0x%04x ", xx, hci_handle));
@@ -786,18 +743,9 @@
       p->state = SCO_ST_CONNECTED;
       p->hci_handle = hci_handle;
 
-      if (hci_status == HCI_SUCCESS) {
-        BTM_LogHistory(kBtmLogTag, bda, "Connection success",
-                       base::StringPrintf("handle:0x%04x %s", hci_handle,
-                                          (spt) ? "listener" : "initiator"));
-      } else {
-        BTM_LogHistory(
-            kBtmLogTag, bda, "Connection failed",
-            base::StringPrintf(
-                "reason:%s",
-                hci_reason_code_text(static_cast<tHCI_REASON>(hci_status))
-                    .c_str()));
-      }
+      BTM_LogHistory(kBtmLogTag, bda, "Connection success",
+                     base::StringPrintf("handle:0x%04x %s", hci_handle,
+                                        (spt) ? "listener" : "initiator"));
 
       if (!btm_cb.sco_cb.esco_supported) {
         p->esco.data.link_type = BTM_LINK_TYPE_SCO;
@@ -824,6 +772,71 @@
 
 /*******************************************************************************
  *
+ * Function         btm_sco_connection_failed
+ *
+ * Description      This function is called by BTIF when an (e)SCO connection
+ *                  setup is failed.
+ *
+ * Returns          void
+ *
+ ******************************************************************************/
+void btm_sco_connection_failed(tHCI_STATUS hci_status, const RawAddress& bda,
+                               uint16_t hci_handle,
+                               tBTM_ESCO_DATA* p_esco_data) {
+  tSCO_CONN* p = &btm_cb.sco_cb.sco_db[0];
+  uint16_t xx;
+
+  for (xx = 0; xx < BTM_MAX_SCO_LINKS; xx++, p++) {
+    if (((p->state == SCO_ST_CONNECTING) || (p->state == SCO_ST_LISTENING) ||
+         (p->state == SCO_ST_W4_CONN_RSP)) &&
+        (p->rem_bd_known) &&
+        (p->esco.data.bd_addr == bda || bda == RawAddress::kEmpty)) {
+      /* Report the error if originator, otherwise remain in Listen mode */
+      if (p->is_orig) {
+        LOG_DEBUG("SCO initiating connection failed handle:0x%04x reason:%s",
+                  hci_handle, hci_error_code_text(hci_status).c_str());
+        switch (hci_status) {
+          case HCI_ERR_ROLE_SWITCH_PENDING:
+            /* If role switch is pending, we need try again after role switch
+             * is complete */
+            p->state = SCO_ST_PEND_ROLECHANGE;
+            break;
+          case HCI_ERR_LMP_ERR_TRANS_COLLISION:
+            /* Avoid calling disconnect callback because of sco creation race
+             */
+            break;
+          default: /* Notify client about SCO failure */
+            p->state = SCO_ST_UNUSED;
+            (*p->p_disc_cb)(xx);
+        }
+        BTM_LogHistory(
+            kBtmLogTag, bda, "Connection failed",
+            base::StringPrintf(
+                "locally_initiated reason:%s",
+                hci_reason_code_text(static_cast<tHCI_REASON>(hci_status))
+                    .c_str()));
+      } else {
+        LOG_DEBUG("SCO terminating connection failed handle:0x%04x reason:%s",
+                  hci_handle, hci_error_code_text(hci_status).c_str());
+        if (p->state == SCO_ST_CONNECTING) {
+          p->state = SCO_ST_UNUSED;
+          (*p->p_disc_cb)(xx);
+        } else
+          p->state = SCO_ST_LISTENING;
+        BTM_LogHistory(
+            kBtmLogTag, bda, "Connection failed",
+            base::StringPrintf(
+                "remote_initiated reason:%s",
+                hci_reason_code_text(static_cast<tHCI_REASON>(hci_status))
+                    .c_str()));
+      }
+      return;
+    }
+  }
+}
+
+/*******************************************************************************
+ *
  * Function         BTM_RemoveSco
  *
  * Description      This function is called to remove a specific SCO connection.
diff --git a/system/stack/btu/btu_hcif.cc b/system/stack/btu/btu_hcif.cc
index 0a02a1c..b191e8a 100644
--- a/system/stack/btu/btu_hcif.cc
+++ b/system/stack/btu/btu_hcif.cc
@@ -1138,7 +1138,12 @@
   handle = HCID_GET_HANDLE(handle);
 
   data.bd_addr = bda;
-  btm_sco_connected(static_cast<tHCI_STATUS>(status), bda, handle, &data);
+  if (status == HCI_SUCCESS) {
+    btm_sco_connected(bda, handle, &data);
+  } else {
+    btm_sco_connection_failed(static_cast<tHCI_STATUS>(status), bda, handle,
+                              &data);
+  }
 }
 
 /*******************************************************************************
@@ -1393,7 +1398,9 @@
     case HCI_ENH_SETUP_ESCO_CONNECTION:
       if (status != HCI_SUCCESS) {
         STREAM_TO_UINT16(handle, p_cmd);
-        // Determine if initial connection failed or is a change of setup
+        RawAddress addr(RawAddress::kEmpty);
+        btm_sco_connection_failed(static_cast<tHCI_STATUS>(status), addr,
+                                  handle, nullptr);
       }
       break;
 
@@ -1436,6 +1443,12 @@
   }
 }
 
+void bluetooth::legacy::testing::btu_hcif_hdl_command_status(
+    uint16_t opcode, uint8_t status, const uint8_t* p_cmd,
+    void* p_vsc_status_cback) {
+  ::btu_hcif_hdl_command_status(opcode, status, p_cmd, p_vsc_status_cback);
+}
+
 /*******************************************************************************
  *
  * Function         btu_hcif_command_status_evt
diff --git a/system/stack/gatt/gatt_api.cc b/system/stack/gatt/gatt_api.cc
index 065d3e7..dc3a06a 100644
--- a/system/stack/gatt/gatt_api.cc
+++ b/system/stack/gatt/gatt_api.cc
@@ -182,6 +182,8 @@
     s_hdl = gatt_cb.hdl_cfg.gmcs_start_hdl;
   } else if (svc_uuid == Uuid::From16Bit(UUID_SERVCLASS_GTBS_SERVER)) {
     s_hdl = gatt_cb.hdl_cfg.gtbs_start_hdl;
+  } else if (svc_uuid == Uuid::From16Bit(UUID_SERVCLASS_TMAS_SERVER)) {
+    s_hdl = gatt_cb.hdl_cfg.tmas_start_hdl;
   } else {
     if (!gatt_cb.hdl_list_info->empty()) {
       s_hdl = gatt_cb.hdl_list_info->front().asgn_range.e_handle + 1;
diff --git a/system/stack/gatt/gatt_int.h b/system/stack/gatt/gatt_int.h
index 9c6403e..0641d4c 100644
--- a/system/stack/gatt/gatt_int.h
+++ b/system/stack/gatt/gatt_int.h
@@ -262,7 +262,8 @@
 #define GATT_GAP_START_HANDLE 20
 #define GATT_GMCS_START_HANDLE 40
 #define GATT_GTBS_START_HANDLE 90
-#define GATT_APP_START_HANDLE 130
+#define GATT_TMAS_START_HANDLE 130
+#define GATT_APP_START_HANDLE 134
 
 #ifndef GATT_DEFAULT_START_HANDLE
 #define GATT_DEFAULT_START_HANDLE GATT_GATT_START_HANDLE
@@ -277,6 +278,7 @@
   uint16_t gap_start_hdl;
   uint16_t gmcs_start_hdl;
   uint16_t gtbs_start_hdl;
+  uint16_t tmas_start_hdl;
   uint16_t app_start_hdl;
 } tGATT_HDL_CFG;
 
diff --git a/system/stack/gatt/gatt_main.cc b/system/stack/gatt/gatt_main.cc
index c4e1039..6b61708 100644
--- a/system/stack/gatt/gatt_main.cc
+++ b/system/stack/gatt/gatt_main.cc
@@ -125,6 +125,7 @@
   gatt_cb.hdl_cfg.gap_start_hdl = GATT_GAP_START_HANDLE;
   gatt_cb.hdl_cfg.gmcs_start_hdl = GATT_GMCS_START_HANDLE;
   gatt_cb.hdl_cfg.gtbs_start_hdl = GATT_GTBS_START_HANDLE;
+  gatt_cb.hdl_cfg.tmas_start_hdl = GATT_TMAS_START_HANDLE;
   gatt_cb.hdl_cfg.app_start_hdl = GATT_APP_START_HANDLE;
 
   gatt_cb.hdl_list_info = new std::list<tGATT_HDL_LIST_ELEM>();
diff --git a/system/stack/include/btu.h b/system/stack/include/btu.h
index 65a752a..30a8e14 100644
--- a/system/stack/include/btu.h
+++ b/system/stack/include/btu.h
@@ -53,6 +53,11 @@
                                uint16_t opcode, uint8_t* params,
                                uint8_t params_len,
                                base::OnceCallback<void(uint8_t*, uint16_t)> cb);
+namespace bluetooth::legacy::testing {
+void btu_hcif_hdl_command_status(uint16_t opcode, uint8_t status,
+                                 const uint8_t* p_cmd,
+                                 void* p_vsc_status_cback);
+}  // namespace bluetooth::legacy::testing
 
 /* Functions provided by btu_task.cc
  ***********************************
diff --git a/system/stack/include/sco_hci_link_interface.h b/system/stack/include/sco_hci_link_interface.h
index 52499f5..e764cbf 100644
--- a/system/stack/include/sco_hci_link_interface.h
+++ b/system/stack/include/sco_hci_link_interface.h
@@ -30,8 +30,12 @@
                                     uint16_t hci_handle);
 extern void btm_sco_conn_req(const RawAddress& bda, const DEV_CLASS& dev_class,
                              uint8_t link_type);
-extern void btm_sco_connected(tHCI_STATUS hci_status, const RawAddress& bda,
-                              uint16_t hci_handle, tBTM_ESCO_DATA* p_esco_data);
+extern void btm_sco_connected(const RawAddress& bda, uint16_t hci_handle,
+                              tBTM_ESCO_DATA* p_esco_data);
+extern void btm_sco_connection_failed(tHCI_STATUS hci_status,
+                                      const RawAddress& bda,
+                                      uint16_t hci_handle,
+                                      tBTM_ESCO_DATA* p_esco_data);
 extern bool btm_sco_removed(uint16_t hci_handle, tHCI_REASON reason);
 
 void btm_sco_on_disconnected(uint16_t hci_handle, tHCI_REASON reason);
diff --git a/system/stack/include/sdpdefs.h b/system/stack/include/sdpdefs.h
index 0039ba0..1156722 100644
--- a/system/stack/include/sdpdefs.h
+++ b/system/stack/include/sdpdefs.h
@@ -199,6 +199,8 @@
 
 #define UUID_SERVCLASS_GMCS_SERVER 0x1849 /* Generic Media Control Service */
 #define UUID_SERVCLASS_GTBS_SERVER 0x184c /* Generic Telephony Bearer Service*/
+#define UUID_SERVCLASS_TMAS_SERVER \
+  0x1855 /* Telephone and Media Audio Service */
 
 #define UUID_CODEC_CVSD 0x0001 /* CVSD */
 #define UUID_CODEC_MSBC 0x0002 /* mSBC */
diff --git a/system/stack/sdp/sdp_discovery.cc b/system/stack/sdp/sdp_discovery.cc
index 3a3bacf..bd8af8e 100644
--- a/system/stack/sdp/sdp_discovery.cc
+++ b/system/stack/sdp/sdp_discovery.cc
@@ -641,6 +641,8 @@
 
   if ((type >> 3) != DATA_ELE_SEQ_DESC_TYPE) {
     LOG_WARN("Wrong element in attr_rsp type:0x%02x", type);
+    android_errorWriteLog(0x534e4554, "224545125");
+    sdp_disconnect(p_ccb, SDP_ILLEGAL_PARAMETER);
     return;
   }
   p = sdpu_get_len_from_type(p, p + p_ccb->list_len, type, &seq_len);
diff --git a/system/stack/test/stack_btu_test.cc b/system/stack/test/stack_btu_test.cc
index 86011d0..507d50c 100644
--- a/system/stack/test/stack_btu_test.cc
+++ b/system/stack/test/stack_btu_test.cc
@@ -15,16 +15,34 @@
  */
 
 #include <gtest/gtest.h>
+
 #include <cstdint>
 #include <map>
 #include <string>
 
 #include "stack/include/btu.h"
+#include "stack/include/hci_error_code.h"
+#include "stack/include/hcidefs.h"
 
 std::map<std::string, int> mock_function_count_map;
 
+/* Function for test provided by btu_hcif.cc */
+void btu_hcif_hdl_command_status(uint16_t opcode, uint8_t status,
+                                 const uint8_t* p_cmd,
+                                 void* p_vsc_status_cback);
+
 void LogMsg(uint32_t trace_set_mask, const char* fmt_str, ...) {}
 
-class StackBtuTest : public ::testing::Test {};
+class StackBtuTest : public ::testing::Test {
+ protected:
+  void SetUp() override { mock_function_count_map.clear(); }
+};
 
 TEST_F(StackBtuTest, post_on_main) {}
+
+TEST_F(StackBtuTest, btm_sco_connection_failed_called) {
+  uint8_t p_cmd[10];  // garbage data for testing
+  bluetooth::legacy::testing::btu_hcif_hdl_command_status(
+      HCI_SETUP_ESCO_CONNECTION, HCI_ERR_UNSPECIFIED, p_cmd, nullptr);
+  ASSERT_EQ(1, mock_function_count_map["btm_sco_connection_failed"]);
+}
diff --git a/system/test/mock/mock_btif_dm.cc b/system/test/mock/mock_btif_dm.cc
index e3f1c1d..bc93a78 100644
--- a/system/test/mock/mock_btif_dm.cc
+++ b/system/test/mock/mock_btif_dm.cc
@@ -106,6 +106,9 @@
   mock_function_count_map[__func__]++;
 }
 void btif_dm_init(uid_set_t* set) { mock_function_count_map[__func__]++; }
+void btif_dm_get_local_class_of_device(DEV_CLASS device_class) {
+  mock_function_count_map[__func__]++;
+}
 void btif_dm_load_ble_local_keys(void) { mock_function_count_map[__func__]++; }
 void btif_dm_on_disable() { mock_function_count_map[__func__]++; }
 void btif_dm_pin_reply(const RawAddress bd_addr, uint8_t accept,
diff --git a/system/test/mock/mock_stack_btm_sco.cc b/system/test/mock/mock_stack_btm_sco.cc
index fdc189e..ded6941 100644
--- a/system/test/mock/mock_stack_btm_sco.cc
+++ b/system/test/mock/mock_stack_btm_sco.cc
@@ -97,8 +97,13 @@
                       uint8_t link_type) {
   mock_function_count_map[__func__]++;
 }
-void btm_sco_connected(tHCI_STATUS hci_status, const RawAddress& bda,
-                       uint16_t hci_handle, tBTM_ESCO_DATA* p_esco_data) {
+void btm_sco_connected(const RawAddress& bda, uint16_t hci_handle,
+                       tBTM_ESCO_DATA* p_esco_data) {
+  mock_function_count_map[__func__]++;
+}
+void btm_sco_connection_failed(tHCI_STATUS hci_status, const RawAddress& bda,
+                               uint16_t hci_handle,
+                               tBTM_ESCO_DATA* p_esco_data) {
   mock_function_count_map[__func__]++;
 }
 void btm_sco_disc_chk_pend_for_modechange(uint16_t hci_handle) {