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) {