Merge "Fix admin enable/disable of Bluetooth file sharing" into tm-dev am: c001bc5f22
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/modules/Bluetooth/+/19219300
Change-Id: Ic0beb31a5db2deaab49826a0bb4a93fdc6baba0d
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/OWNERS b/OWNERS
index 2d3b648..02b536d 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,6 +1,7 @@
# Project owners
sattiraju@google.com
siyuanh@google.com
+sungsoo@google.com
zachoverflow@google.com
# Per-file ownership
diff --git a/android/app/jni/com_android_bluetooth_vc.cpp b/android/app/jni/com_android_bluetooth_vc.cpp
index ad31918..4c4103e 100644
--- a/android/app/jni/com_android_bluetooth_vc.cpp
+++ b/android/app/jni/com_android_bluetooth_vc.cpp
@@ -346,7 +346,7 @@
env->ReleaseByteArrayElements(address, addr, 0);
}
-static void setVolumeGroupNative(JNIEnv* env, jobject object, jint group_id,
+static void setGroupVolumeNative(JNIEnv* env, jobject object, jint group_id,
jint volume) {
if (!sVolumeControlInterface) {
LOG(ERROR) << __func__
@@ -547,7 +547,7 @@
{"disconnectVolumeControlNative", "([B)Z",
(void*)disconnectVolumeControlNative},
{"setVolumeNative", "([BI)V", (void*)setVolumeNative},
- {"setVolumeGroupNative", "(II)V", (void*)setVolumeGroupNative},
+ {"setGroupVolumeNative", "(II)V", (void*)setGroupVolumeNative},
{"muteNative", "([B)V", (void*)muteNative},
{"muteGroupNative", "(I)V", (void*)muteGroupNative},
{"unmuteNative", "([B)V", (void*)unmuteNative},
diff --git a/android/app/res/values-or/strings.xml b/android/app/res/values-or/strings.xml
index a0a3f2f..0392c89 100644
--- a/android/app/res/values-or/strings.xml
+++ b/android/app/res/values-or/strings.xml
@@ -28,7 +28,7 @@
<string name="bt_enable_title" msgid="4484289159118416315"></string>
<string name="bt_enable_line1" msgid="8429910585843481489">"ବ୍ଲୁଟୂଥ୍ ସେବା ବ୍ୟବହାର କରିବା ପାଇଁ, ଆପଣଙ୍କୁ ପ୍ରଥମେ ବ୍ଲୁଟୂଥ୍ ଅନ୍ କରିବାକୁ ପଡ଼ିବ।"</string>
<string name="bt_enable_line2" msgid="1466367120348920892">"ବ୍ଲୁ-ଟୁଥ୍କୁ ଏବେ ଅନ୍ କରିବେ?"</string>
- <string name="bt_enable_cancel" msgid="6770180540581977614">"ବାତିଲ୍ କରନ୍ତୁ"</string>
+ <string name="bt_enable_cancel" msgid="6770180540581977614">"ବାତିଲ କରନ୍ତୁ"</string>
<string name="bt_enable_ok" msgid="4224374055813566166">"ଚାଲୁ କରନ୍ତୁ"</string>
<string name="incoming_file_confirm_title" msgid="938251186275547290">"ଫାଇଲ୍ ଟ୍ରାନ୍ସଫର୍ କରନ୍ତୁ"</string>
<string name="incoming_file_confirm_content" msgid="6573502088511901157">"ଆସୁଥିବା ଫାଇଲକୁ ଗ୍ରହଣ କରିବେ?"</string>
@@ -117,7 +117,7 @@
<string name="transfer_clear_dlg_title" msgid="128904516163257225">"ଖାଲି କରନ୍ତୁ"</string>
<string name="bluetooth_a2dp_sink_queue_name" msgid="7521243473328258997">"ବର୍ତ୍ତମାନ ଚାଲୁଛି"</string>
<string name="bluetooth_map_settings_save" msgid="8309113239113961550">"ସେଭ୍ କରନ୍ତୁ"</string>
- <string name="bluetooth_map_settings_cancel" msgid="3374494364625947793">"ବାତିଲ୍ କରନ୍ତୁ"</string>
+ <string name="bluetooth_map_settings_cancel" msgid="3374494364625947793">"ବାତିଲ କରନ୍ତୁ"</string>
<string name="bluetooth_map_settings_intro" msgid="4748160773998753325">"ବ୍ଲୁଟୂଥ୍ ମାଧ୍ୟମରେ ସେୟାର୍ କରିବାକୁ ଚାହୁଁଥିବା ଆକାଉଣ୍ଟଗୁଡ଼ିକୁ ଚୟନ କରନ୍ତୁ। ଆପଣଙ୍କୁ ତଥାପି ସଂଯୋଗ କରୁଥିବା ସମୟରେ ଆକାଉଣ୍ଟଗୁଡ଼ିକ ପ୍ରତି ଯେକୌଣସି ଆକ୍ସେସ୍କୁ ସ୍ୱୀକାର କରିବାକୁ ପଡ଼ିବ।"</string>
<string name="bluetooth_map_settings_count" msgid="183013143617807702">"ବଳକା ସ୍ଲଟ୍:"</string>
<string name="bluetooth_map_settings_app_icon" msgid="3501432663809664982">"ଆପ୍ଲିକେଶନ୍ ଆଇକନ୍"</string>
diff --git a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index b6321a4..61fbb24 100644
--- a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -217,7 +217,7 @@
break; // The device is already connected
}
mA2dpConnectedDevices.add(device);
- if (mHearingAidActiveDevice == null) {
+ if (mHearingAidActiveDevice == null && mLeAudioActiveDevice == null) {
// New connected device: select it as active
setA2dpActiveDevice(device);
break;
@@ -277,7 +277,7 @@
break; // The device is already connected
}
mHfpConnectedDevices.add(device);
- if (mHearingAidActiveDevice == null) {
+ if (mHearingAidActiveDevice == null && mLeAudioActiveDevice == null) {
// New connected device: select it as active
setHfpActiveDevice(device);
break;
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
index 0251a22..53ff384 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
@@ -5034,7 +5034,8 @@
private static final String GD_L2CAP_FLAG = "INIT_gd_l2cap";
private static final String GD_RUST_FLAG = "INIT_gd_rust";
private static final String GD_LINK_POLICY_FLAG = "INIT_gd_link_policy";
- private static final String GATT_ROBUST_CACHING_FLAG = "INIT_gatt_robust_caching";
+ private static final String GATT_ROBUST_CACHING_CLIENT_FLAG = "INIT_gatt_robust_caching_client";
+ private static final String GATT_ROBUST_CACHING_SERVER_FLAG = "INIT_gatt_robust_caching_server";
/**
* Logging flags logic (only applies to DEBUG and VERBOSE levels):
@@ -5088,8 +5089,12 @@
initFlags.add(String.format("%s=%s", GD_LINK_POLICY_FLAG, "true"));
}
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
- GATT_ROBUST_CACHING_FLAG, false)) {
- initFlags.add(String.format("%s=%s", GATT_ROBUST_CACHING_FLAG, "true"));
+ GATT_ROBUST_CACHING_CLIENT_FLAG, false)) {
+ initFlags.add(String.format("%s=%s", GATT_ROBUST_CACHING_CLIENT_FLAG, "true"));
+ }
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
+ GATT_ROBUST_CACHING_SERVER_FLAG, false)) {
+ initFlags.add(String.format("%s=%s", GATT_ROBUST_CACHING_SERVER_FLAG, "true"));
}
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
LOGGING_DEBUG_ENABLED_FOR_ALL_FLAG, false)) {
diff --git a/android/app/src/com/android/bluetooth/btservice/Config.java b/android/app/src/com/android/bluetooth/btservice/Config.java
index 5dad27b..1c02159 100644
--- a/android/app/src/com/android/bluetooth/btservice/Config.java
+++ b/android/app/src/com/android/bluetooth/btservice/Config.java
@@ -19,6 +19,7 @@
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.res.Resources;
+import android.os.SystemProperties;
import android.util.Log;
import com.android.bluetooth.R;
@@ -61,6 +62,11 @@
private static final String FEATURE_BATTERY = "settings_bluetooth_battery";
private static long sSupportedMask = 0;
+ private static final String LE_AUDIO_DYNAMIC_SWITCH_PROPERTY =
+ "ro.bluetooth.leaudio_switcher.supported";
+ private static final String LE_AUDIO_DYNAMIC_ENABLED_PROPERTY =
+ "persist.bluetooth.leaudio_switcher.enabled";
+
private static class ProfileConfig {
Class mClass;
boolean mSupported;
@@ -159,6 +165,19 @@
private static boolean sIsGdEnabledUptoScanningLayer = false;
static void init(Context ctx) {
+ final boolean leAudioDynamicSwitchSupported =
+ SystemProperties.getBoolean(LE_AUDIO_DYNAMIC_SWITCH_PROPERTY, false);
+
+ if (leAudioDynamicSwitchSupported) {
+ final String leAudioDynamicEnabled = SystemProperties
+ .get(LE_AUDIO_DYNAMIC_ENABLED_PROPERTY, "none");
+ if (leAudioDynamicEnabled.equals("true")) {
+ setLeAudioProfileStatus(true);
+ } else if (leAudioDynamicEnabled.equals("false")) {
+ setLeAudioProfileStatus(false);
+ }
+ }
+
ArrayList<Class> profiles = new ArrayList<>(PROFILE_SERVICES_AND_FLAGS.length);
for (ProfileConfig config : PROFILE_SERVICES_AND_FLAGS) {
Log.i(TAG, "init: profile=" + config.mClass.getSimpleName() + ", enabled="
@@ -179,6 +198,15 @@
sIsGdEnabledUptoScanningLayer = resources.getBoolean(R.bool.enable_gd_up_to_scanning_layer);
}
+ static void setLeAudioProfileStatus(Boolean enable) {
+ setProfileEnabled(CsipSetCoordinatorService.class, enable);
+ setProfileEnabled(HapClientService.class, enable);
+ setProfileEnabled(LeAudioService.class, enable);
+ setProfileEnabled(TbsService.class, enable);
+ setProfileEnabled(McpService.class, enable);
+ setProfileEnabled(VolumeControlService.class, enable);
+ }
+
/**
* Remove the input profiles from the supported list.
*/
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 33fa96f..101dc41 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -128,6 +128,9 @@
LeAudioTmapGattServer mTmapGattServer;
@VisibleForTesting
+ VolumeControlService mVolumeControlService;
+
+ @VisibleForTesting
RemoteCallbackList<IBluetoothLeBroadcastCallback> mBroadcastCallbacks;
@VisibleForTesting
@@ -384,6 +387,7 @@
mAdapterService = null;
mAudioManager = null;
+ mVolumeControlService = null;
return true;
}
@@ -412,6 +416,18 @@
sLeAudioService = instance;
}
+ private int getGroupVolume(int groupId) {
+ if (mVolumeControlService == null) {
+ mVolumeControlService = mServiceFactory.getVolumeControlService();
+ }
+ if (mVolumeControlService == null) {
+ Log.e(TAG, "Volume control service is not available");
+ return -1;
+ }
+
+ return mVolumeControlService.getGroupVolume(groupId);
+ }
+
public boolean connect(BluetoothDevice device) {
if (DBG) {
Log.d(TAG, "connect(): " + device);
@@ -830,6 +846,7 @@
+ previousInDevice + ", mActiveAudioInDevice" + mActiveAudioInDevice
+ " isLeOutput: false");
}
+
mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioInDevice,previousInDevice,
BluetoothProfileConnectionInfo.createLeAudioInfo(false, false));
@@ -899,9 +916,14 @@
+ previousOutDevice + ", mActiveOutDevice: " + mActiveAudioOutDevice
+ " isLeOutput: true");
}
+ int volume = -1;
+ if (mActiveAudioOutDevice != null) {
+ volume = getGroupVolume(groupId);
+ }
+
mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioOutDevice,
previousOutDevice,
- BluetoothProfileConnectionInfo.createLeAudioInfo(suppressNoisyIntent, true));
+ getLeAudioOutputProfile(suppressNoisyIntent, volume));
return true;
}
Log.d(TAG, "updateActiveOutDevice: Nothing to do.");
@@ -1054,6 +1076,22 @@
}
}
+ BluetoothProfileConnectionInfo getLeAudioOutputProfile(boolean suppressNoisyIntent,
+ int volume) {
+ /* TODO - b/236618595 */
+ Parcel parcel = Parcel.obtain();
+ parcel.writeInt(BluetoothProfile.LE_AUDIO);
+ parcel.writeBoolean(suppressNoisyIntent);
+ parcel.writeInt(volume);
+ parcel.writeBoolean(true /* isLeOutput */);
+ parcel.setDataPosition(0);
+
+ BluetoothProfileConnectionInfo profileInfo =
+ BluetoothProfileConnectionInfo.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+ return profileInfo;
+ }
+
BluetoothProfileConnectionInfo getBroadcastProfile(boolean suppressNoisyIntent) {
Parcel parcel = Parcel.obtain();
parcel.writeInt(BluetoothProfile.LE_AUDIO_BROADCAST);
@@ -1062,7 +1100,10 @@
parcel.writeBoolean(true /* mIsLeOutput */);
parcel.setDataPosition(0);
- return BluetoothProfileConnectionInfo.CREATOR.createFromParcel(parcel);
+ BluetoothProfileConnectionInfo profileInfo =
+ BluetoothProfileConnectionInfo.CREATOR.createFromParcel(parcel);
+ parcel.recycle();
+ return profileInfo;
}
private void clearLostDevicesWhileStreaming(LeAudioGroupDescriptor descriptor) {
@@ -1766,9 +1807,11 @@
return;
}
- VolumeControlService service = mServiceFactory.getVolumeControlService();
- if (service != null) {
- service.setVolumeGroup(currentlyActiveGroupId, volume);
+ if (mVolumeControlService == null) {
+ mVolumeControlService = mServiceFactory.getVolumeControlService();
+ }
+ if (mVolumeControlService != null) {
+ mVolumeControlService.setGroupVolume(currentlyActiveGroupId, volume);
}
}
diff --git a/android/app/src/com/android/bluetooth/mcp/McpService.java b/android/app/src/com/android/bluetooth/mcp/McpService.java
index 208997a..dca1c1d 100644
--- a/android/app/src/com/android/bluetooth/mcp/McpService.java
+++ b/android/app/src/com/android/bluetooth/mcp/McpService.java
@@ -176,6 +176,11 @@
}
public void onDeviceUnauthorized(BluetoothDevice device) {
+ if (Utils.isPtsTestMode()) {
+ Log.d(TAG, "PTS test: setDeviceAuthorized");
+ setDeviceAuthorized(device, true);
+ return;
+ }
Log.w(TAG, "onDeviceUnauthorized - authorization notification not implemented yet ");
}
@@ -194,9 +199,10 @@
}
public int getDeviceAuthorization(BluetoothDevice device) {
- // TODO: For now just reject authorization for other than LeAudio device already authorized.
- // Consider intent based authorization mechanism for non-LeAudio devices.
- return mDeviceAuthorizations.getOrDefault(device, BluetoothDevice.ACCESS_UNKNOWN);
+ // TODO: For now just reject authorization for other than LeAudio device already authorized
+ // except for PTS. Consider intent based authorization mechanism for non-LeAudio devices.
+ return mDeviceAuthorizations.getOrDefault(device, Utils.isPtsTestMode()
+ ? BluetoothDevice.ACCESS_ALLOWED : BluetoothDevice.ACCESS_UNKNOWN);
}
@GuardedBy("mLock")
diff --git a/android/app/src/com/android/bluetooth/tbs/BluetoothLeCallControlProxy.java b/android/app/src/com/android/bluetooth/tbs/BluetoothLeCallControlProxy.java
index ca0534b..164eadc 100644
--- a/android/app/src/com/android/bluetooth/tbs/BluetoothLeCallControlProxy.java
+++ b/android/app/src/com/android/bluetooth/tbs/BluetoothLeCallControlProxy.java
@@ -17,10 +17,9 @@
package com.android.bluetooth.tbs;
-import android.bluetooth.BluetoothLeCallControl;
import android.bluetooth.BluetoothLeCall;
+import android.bluetooth.BluetoothLeCallControl;
-import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executor;
@@ -36,6 +35,16 @@
private BluetoothLeCallControl mBluetoothLeCallControl;
+ public static final int BEARER_TECHNOLOGY_3G = 0x01;
+ public static final int BEARER_TECHNOLOGY_4G = 0x02;
+ public static final int BEARER_TECHNOLOGY_LTE = 0x03;
+ public static final int BEARER_TECHNOLOGY_WIFI = 0x04;
+ public static final int BEARER_TECHNOLOGY_5G = 0x05;
+ public static final int BEARER_TECHNOLOGY_GSM = 0x06;
+ public static final int BEARER_TECHNOLOGY_CDMA = 0x07;
+ public static final int BEARER_TECHNOLOGY_2G = 0x08;
+ public static final int BEARER_TECHNOLOGY_WCDMA = 0x09;
+
public BluetoothLeCallControlProxy(BluetoothLeCallControl tbs) {
mBluetoothLeCallControl = tbs;
}
diff --git a/android/app/src/com/android/bluetooth/tbs/TbsGeneric.java b/android/app/src/com/android/bluetooth/tbs/TbsGeneric.java
index b8dbbff..d73ae5b 100644
--- a/android/app/src/com/android/bluetooth/tbs/TbsGeneric.java
+++ b/android/app/src/com/android/bluetooth/tbs/TbsGeneric.java
@@ -52,7 +52,11 @@
private static final String UCI = "GTBS";
private static final String DEFAULT_PROVIDER_NAME = "none";
- private static final int DEFAULT_BEARER_TECHNOLOGY = 0x00;
+ /* Use GSM as default technology value. It is used only
+ * when bearer is not registered. It will be updated on the phone call
+ */
+ private static final int DEFAULT_BEARER_TECHNOLOGY =
+ BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM;
private static final String UNKNOWN_FRIENDLY_NAME = "unknown";
/** Class representing the pending request sent to the application */
diff --git a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
index 2313ff0..85ebcab 100644
--- a/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
+++ b/android/app/src/com/android/bluetooth/telephony/BluetoothInCallService.java
@@ -417,6 +417,62 @@
}
}
+ /**
+ * Gets the brearer technology.
+ *
+ * @return bearer technology as defined in Bluetooth Assigned Numbers
+ */
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public int getBearerTechnology() {
+ synchronized (LOCK) {
+ enforceModifyPermission();
+ Log.i(TAG, "getBearerTechnology");
+ // Get the network name from telephony.
+ int dataNetworkType = mTelephonyManager.getDataNetworkType();
+ switch (dataNetworkType) {
+ case TelephonyManager.NETWORK_TYPE_UNKNOWN:
+ case TelephonyManager.NETWORK_TYPE_GSM:
+ return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM;
+
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_2G;
+
+ case TelephonyManager.NETWORK_TYPE_EDGE :
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_3G;
+
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ case TelephonyManager.NETWORK_TYPE_TD_SCDMA:
+ return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_WCDMA;
+
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_LTE;
+
+ case TelephonyManager.NETWORK_TYPE_EHRPD:
+ case TelephonyManager.NETWORK_TYPE_CDMA:
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_CDMA;
+
+ case TelephonyManager.NETWORK_TYPE_HSPAP:
+ return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_4G;
+
+ case TelephonyManager.NETWORK_TYPE_IWLAN:
+ return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_WIFI;
+
+ case TelephonyManager.NETWORK_TYPE_NR:
+ return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_5G;
+ }
+
+ return BluetoothLeCallControlProxy.BEARER_TECHNOLOGY_GSM;
+ }
+ }
+
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
public String getSubscriberNumber() {
synchronized (LOCK) {
@@ -1241,9 +1297,10 @@
mBluetoothLeCallControl = bluetoothTbs;
if ((mBluetoothLeCallControl) != null && (mTelecomManager != null)) {
- mBluetoothLeCallControl.registerBearer(TAG, new ArrayList<String>(Arrays.asList("tel")),
- BluetoothLeCallControl.CAPABILITY_HOLD_CALL, getNetworkOperator(), 0x01, mExecutor,
- mBluetoothLeCallControlCallback);
+ mBluetoothLeCallControl.registerBearer(TAG,
+ new ArrayList<String>(Arrays.asList("tel")),
+ BluetoothLeCallControl.CAPABILITY_HOLD_CALL, getNetworkOperator(),
+ getBearerTechnology(), mExecutor, mBluetoothLeCallControlCallback);
}
}
diff --git a/android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java b/android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java
index 0a50c6d..2c3c20c 100644
--- a/android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java
+++ b/android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java
@@ -113,8 +113,8 @@
* @param volume
*/
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
- public void setVolumeGroup(int groupId, int volume) {
- setVolumeGroupNative(groupId, volume);
+ public void setGroupVolume(int groupId, int volume) {
+ setGroupVolumeNative(groupId, volume);
}
/**
@@ -370,7 +370,7 @@
private native boolean connectVolumeControlNative(byte[] address);
private native boolean disconnectVolumeControlNative(byte[] address);
private native void setVolumeNative(byte[] address, int volume);
- private native void setVolumeGroupNative(int groupId, int volume);
+ private native void setGroupVolumeNative(int groupId, int volume);
private native void muteNative(byte[] address);
private native void muteGroupNative(int groupId);
private native void unmuteNative(byte[] address);
diff --git a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
index e04c2e6..078ebef 100644
--- a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
+++ b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
@@ -179,6 +179,7 @@
private final Map<BluetoothDevice, VolumeControlStateMachine> mStateMachines = new HashMap<>();
private final Map<BluetoothDevice, VolumeControlOffsetDescriptor> mAudioOffsets =
new HashMap<>();
+ private final Map<Integer, Integer> mGroupVolumeCache = new HashMap<>();
private BroadcastReceiver mBondStateChangedReceiver;
private BroadcastReceiver mConnectionStateChangedReceiver;
@@ -242,6 +243,7 @@
registerReceiver(mConnectionStateChangedReceiver, filter);
mAudioOffsets.clear();
+ mGroupVolumeCache.clear();
mCallbacks = new RemoteCallbackList<IBluetoothVolumeControlCallback>();
// Mark service as started
@@ -296,6 +298,7 @@
mVolumeControlNativeInterface = null;
mAudioOffsets.clear();
+ mGroupVolumeCache.clear();
// Clear AdapterService, VolumeControlNativeInterface
mAudioManager = null;
@@ -578,8 +581,17 @@
/**
* {@hide}
*/
- public void setVolumeGroup(int groupId, int volume) {
- mVolumeControlNativeInterface.setVolumeGroup(groupId, volume);
+ public void setGroupVolume(int groupId, int volume) {
+ mGroupVolumeCache.put(groupId, volume);
+ mVolumeControlNativeInterface.setGroupVolume(groupId, volume);
+ }
+
+ /**
+ * {@hide}
+ * @param groupId
+ */
+ public int getGroupVolume(int groupId) {
+ return mGroupVolumeCache.getOrDefault(groupId, -1);
}
/**
@@ -618,6 +630,11 @@
}
// TODO: Handle the other arguments: device, groupId, mute.
+ /* We are interested only in the group volume as any LeAudio device is a part of group */
+ if (device == null) {
+ mGroupVolumeCache.put(groupId, volume);
+ }
+
int streamType = getBluetoothContextualVolumeStream();
mAudioManager.setStreamVolume(streamType, getDeviceVolume(streamType, volume),
AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_BLUETOOTH_ABS_VOLUME);
@@ -1125,7 +1142,7 @@
}
@Override
- public void setVolumeGroup(int groupId, int volume, AttributionSource source,
+ public void setGroupVolume(int groupId, int volume, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
Objects.requireNonNull(source, "source cannot be null");
@@ -1133,7 +1150,7 @@
VolumeControlService service = getService(source);
if (service != null) {
- service.setVolumeGroup(groupId, volume);
+ service.setGroupVolume(groupId, volume);
}
receiver.send(null);
} catch (RuntimeException e) {
@@ -1142,6 +1159,24 @@
}
@Override
+ public void getGroupVolume(int groupId, AttributionSource source,
+ SynchronousResultReceiver receiver) {
+ try {
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
+ VolumeControlService service = getService(source);
+ if (service != null) {
+ service.getGroupVolume(groupId);
+ }
+ receiver.send(null);
+ } catch (RuntimeException e) {
+ receiver.propagateException(e);
+ }
+ }
+
+
+ @Override
public void mute(BluetoothDevice device, AttributionSource source,
SynchronousResultReceiver receiver) {
try {
@@ -1271,5 +1306,9 @@
ProfileService.println(sb, " Volume offset cnt: " + descriptor.size());
descriptor.dump(sb);
}
+ for (Map.Entry<Integer, Integer> entry : mGroupVolumeCache.entrySet()) {
+ ProfileService.println(sb, " GroupId: " + entry.getKey() + " volume: "
+ + entry.getValue());
+ }
}
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
index bc4221e..f6e8015 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
@@ -23,21 +23,21 @@
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHeadset;
import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothLeAudio;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.media.AudioManager;
-import android.sysprop.BluetoothProperties;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.a2dp.A2dpService;
import com.android.bluetooth.hearingaid.HearingAidService;
import com.android.bluetooth.hfp.HeadsetService;
+import com.android.bluetooth.le_audio.LeAudioService;
import org.junit.After;
import org.junit.Assert;
@@ -57,6 +57,7 @@
private BluetoothDevice mHeadsetDevice;
private BluetoothDevice mA2dpHeadsetDevice;
private BluetoothDevice mHearingAidDevice;
+ private BluetoothDevice mLeAudioDevice;
private ActiveDeviceManager mActiveDeviceManager;
private static final int TIMEOUT_MS = 1000;
@@ -65,6 +66,7 @@
@Mock private A2dpService mA2dpService;
@Mock private HeadsetService mHeadsetService;
@Mock private HearingAidService mHearingAidService;
+ @Mock private LeAudioService mLeAudioService;
@Mock private AudioManager mAudioManager;
@Before
@@ -83,9 +85,11 @@
when(mServiceFactory.getA2dpService()).thenReturn(mA2dpService);
when(mServiceFactory.getHeadsetService()).thenReturn(mHeadsetService);
when(mServiceFactory.getHearingAidService()).thenReturn(mHearingAidService);
+ when(mServiceFactory.getLeAudioService()).thenReturn(mLeAudioService);
when(mA2dpService.setActiveDevice(any())).thenReturn(true);
when(mHeadsetService.setActiveDevice(any())).thenReturn(true);
when(mHearingAidService.setActiveDevice(any())).thenReturn(true);
+ when(mLeAudioService.setActiveDevice(any())).thenReturn(true);
mActiveDeviceManager = new ActiveDeviceManager(mAdapterService, mServiceFactory);
mActiveDeviceManager.start();
@@ -96,6 +100,7 @@
mHeadsetDevice = TestUtils.getTestDevice(mAdapter, 1);
mA2dpHeadsetDevice = TestUtils.getTestDevice(mAdapter, 2);
mHearingAidDevice = TestUtils.getTestDevice(mAdapter, 3);
+ mLeAudioDevice = TestUtils.getTestDevice(mAdapter, 4);
}
@After
@@ -212,6 +217,7 @@
Assert.assertEquals(mHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice());
}
+
/**
* A combo (A2DP + Headset) device is connected. Then a Hearing Aid is connected.
*/
@@ -288,6 +294,81 @@
}
/**
+ * A combo (A2DP + Headset) device is connected. Then an LE Audio is connected.
+ */
+ @Test
+ public void leAudioActive_clearA2dpAndHeadsetActive() {
+ Assume.assumeTrue("Ignore test when LeAudioService is not enabled",
+ LeAudioService.isEnabled());
+
+ a2dpConnected(mA2dpHeadsetDevice);
+ headsetConnected(mA2dpHeadsetDevice);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
+ verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpHeadsetDevice);
+
+ leAudioActiveDeviceChanged(mLeAudioDevice);
+ verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
+ verify(mHeadsetService, timeout(TIMEOUT_MS)).setActiveDevice(isNull());
+ }
+
+ /**
+ * An LE Audio is connected. Then a combo (A2DP + Headset) device is connected.
+ */
+ @Test
+ public void leAudioActive_dontSetA2dpAndHeadsetActive() {
+ Assume.assumeTrue("Ignore test when LeAudioService is not enabled",
+ LeAudioService.isEnabled());
+
+ leAudioActiveDeviceChanged(mLeAudioDevice);
+ a2dpConnected(mA2dpHeadsetDevice);
+ headsetConnected(mA2dpHeadsetDevice);
+
+ TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
+ verify(mA2dpService, never()).setActiveDevice(mA2dpHeadsetDevice);
+ verify(mHeadsetService, never()).setActiveDevice(mA2dpHeadsetDevice);
+ }
+
+ /**
+ * An LE Audio is connected. Then an A2DP active device is explicitly set.
+ */
+ @Test
+ public void leAudioActive_setA2dpActiveExplicitly() {
+ Assume.assumeTrue("Ignore test when LeAudioService is not enabled",
+ LeAudioService.isEnabled());
+
+ leAudioActiveDeviceChanged(mLeAudioDevice);
+ a2dpConnected(mA2dpHeadsetDevice);
+ a2dpActiveDeviceChanged(mA2dpHeadsetDevice);
+
+ TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
+ verify(mLeAudioService).setActiveDevice(isNull());
+ // Don't call mA2dpService.setActiveDevice()
+ verify(mA2dpService, never()).setActiveDevice(mA2dpHeadsetDevice);
+ Assert.assertEquals(mA2dpHeadsetDevice, mActiveDeviceManager.getA2dpActiveDevice());
+ Assert.assertEquals(null, mActiveDeviceManager.getLeAudioActiveDevice());
+ }
+
+ /**
+ * An LE Audio is connected. Then a Headset active device is explicitly set.
+ */
+ @Test
+ public void leAudioActive_setHeadsetActiveExplicitly() {
+ Assume.assumeTrue("Ignore test when LeAudioService is not enabled",
+ LeAudioService.isEnabled());
+
+ leAudioActiveDeviceChanged(mLeAudioDevice);
+ headsetConnected(mA2dpHeadsetDevice);
+ headsetActiveDeviceChanged(mA2dpHeadsetDevice);
+
+ TestUtils.waitForLooperToFinishScheduledTask(mActiveDeviceManager.getHandlerLooper());
+ verify(mLeAudioService).setActiveDevice(isNull());
+ // Don't call mLeAudioService.setActiveDevice()
+ verify(mLeAudioService, never()).setActiveDevice(mA2dpHeadsetDevice);
+ Assert.assertEquals(mA2dpHeadsetDevice, mActiveDeviceManager.getHfpActiveDevice());
+ Assert.assertEquals(null, mActiveDeviceManager.getLeAudioActiveDevice());
+ }
+
+ /**
* A wired audio device is connected. Then all active devices are set to null.
*/
@Test
@@ -373,4 +454,14 @@
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
}
+
+ /**
+ * Helper to indicate LE Audio active device changed for a device.
+ */
+ private void leAudioActiveDeviceChanged(BluetoothDevice device) {
+ Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
+ intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ mActiveDeviceManager.getBroadcastReceiver().onReceive(mContext, intent);
+ }
+
}
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 ae41ce0..b999f6c 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
@@ -47,6 +47,7 @@
import android.content.IntentFilter;
import android.media.AudioManager;
import android.media.BluetoothProfileConnectionInfo;
+import android.os.Parcel;
import android.os.ParcelUuid;
import androidx.test.InstrumentationRegistry;
@@ -58,6 +59,7 @@
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.bluetooth.vc.VolumeControlService;
import org.junit.After;
import org.junit.Assume;
@@ -65,6 +67,7 @@
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;
@@ -105,6 +108,7 @@
@Mock private DatabaseManager mDatabaseManager;
@Mock private LeAudioNativeInterface mNativeInterface;
@Mock private AudioManager mAudioManager;
+ @Mock private VolumeControlService mVolumeControlService;
@Mock private LeAudioTmapGattServer mTmapGattServer;
@Spy private LeAudioObjectsFactory mObjectsFactory = LeAudioObjectsFactory.getInstance();
@@ -179,6 +183,7 @@
startService();
mService.mLeAudioNativeInterface = mNativeInterface;
mService.mAudioManager = mAudioManager;
+ mService.mVolumeControlService = mVolumeControlService;
LeAudioStackEvent stackEvent =
new LeAudioStackEvent(LeAudioStackEvent.EVENT_TYPE_NATIVE_INITIALIZED);
@@ -1381,4 +1386,53 @@
verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(null), eq(leadDevice),
any(BluetoothProfileConnectionInfo.class));
}
+
+ /**
+ * Test volume caching for the group
+ */
+ @Test
+ public void testVolumeCache() {
+ int groupId = 1;
+ int volume = 100;
+ int availableContexts = 4;
+
+ doReturn(true).when(mNativeInterface).connectLeAudio(any(BluetoothDevice.class));
+ connectTestDevice(mLeftDevice, groupId);
+ connectTestDevice(mRightDevice, groupId);
+
+ assertThat(mService.setActiveDevice(mLeftDevice)).isTrue();
+
+ ArgumentCaptor<BluetoothProfileConnectionInfo> profileInfo =
+ ArgumentCaptor.forClass(BluetoothProfileConnectionInfo.class);
+
+ //Add location support.
+ injectAudioConfChanged(groupId, availableContexts);
+
+ doReturn(-1).when(mVolumeControlService).getGroupVolume(groupId);
+ //Set group and device as active.
+ injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
+
+ verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(any(), eq(null),
+ profileInfo.capture());
+ assertThat(profileInfo.getValue().getVolume()).isEqualTo(-1);
+
+ mService.setVolume(volume);
+ verify(mVolumeControlService, times(1)).setGroupVolume(groupId, volume);
+
+ // Set group to inactive.
+ injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_INACTIVE);
+
+ verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(null), any(),
+ any(BluetoothProfileConnectionInfo.class));
+
+ doReturn(100).when(mVolumeControlService).getGroupVolume(groupId);
+
+ //Set back to active and check if last volume is restored.
+ injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
+
+ verify(mAudioManager, times(2)).handleBluetoothActiveDeviceChanged(any(), eq(null),
+ profileInfo.capture());
+
+ assertThat(profileInfo.getValue().getVolume()).isEqualTo(volume);
+ }
}
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 f127ede..6808758 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
@@ -496,6 +496,33 @@
mService.messageFromNative(stackEvent);
}
+ /**
+ * Test Volume Control cache.
+ */
+ @Test
+ public void testVolumeCache() {
+ int groupId = 1;
+ int volume = 6;
+
+ Assert.assertEquals(-1, mService.getGroupVolume(groupId));
+ mService.setGroupVolume(groupId, volume);
+ Assert.assertEquals(volume, mService.getGroupVolume(groupId));
+
+ volume = 10;
+
+ // Send autonomus volume change.
+ VolumeControlStackEvent stackEvent = new VolumeControlStackEvent(
+ VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED);
+ stackEvent.device = null;
+ stackEvent.valueInt1 = groupId;
+ stackEvent.valueInt2 = volume;
+ stackEvent.valueBool1 = false;
+ stackEvent.valueBool2 = true; /* autonomus */
+ mService.messageFromNative(stackEvent);
+
+ Assert.assertEquals(volume, mService.getGroupVolume(groupId));
+ }
+
private void connectDevice(BluetoothDevice device) {
VolumeControlStackEvent connCompletedEvent;
diff --git a/service/java/com/android/server/bluetooth/BluetoothManagerService.java b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
index dda3f04..2d00f88 100644
--- a/service/java/com/android/server/bluetooth/BluetoothManagerService.java
+++ b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
@@ -95,6 +95,7 @@
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -218,6 +219,7 @@
private final ReentrantReadWriteLock mBluetoothLock = new ReentrantReadWriteLock();
private boolean mBinding;
private boolean mUnbinding;
+ private List<Integer> mSupportedProfileList = new ArrayList<>();
private BluetoothModeChangeHelper mBluetoothModeChangeHelper;
@@ -872,6 +874,25 @@
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
}
+ @GuardedBy("mBluetoothLock")
+ private List<Integer> synchronousGetSupportedProfiles(AttributionSource attributionSource)
+ throws RemoteException, TimeoutException {
+ final ArrayList<Integer> supportedProfiles = new ArrayList<Integer>();
+ if (mBluetooth == null) return supportedProfiles;
+ final SynchronousResultReceiver<Long> recv = SynchronousResultReceiver.get();
+ mBluetooth.getSupportedProfiles(attributionSource, recv);
+ final long supportedProfilesBitMask =
+ recv.awaitResultNoInterrupt(getSyncTimeout()).getValue((long) 0);
+
+ for (int i = 0; i <= BluetoothProfile.MAX_PROFILE_ID; i++) {
+ if ((supportedProfilesBitMask & (1 << i)) != 0) {
+ supportedProfiles.add(i);
+ }
+ }
+
+ return supportedProfiles;
+ }
+
/**
* Sends the current foreground user id to the Bluetooth process. This user id is used to
* determine if Binder calls are coming from the active user.
@@ -1449,10 +1470,10 @@
ProfileServiceConnections psc = mProfileServices.get(new Integer(bluetoothProfile));
Intent intent;
if (bluetoothProfile == BluetoothProfile.HEADSET
- && BluetoothProperties.isProfileHfpAgEnabled().orElse(false)) {
+ && mSupportedProfileList.contains(BluetoothProfile.HEADSET)) {
intent = new Intent(IBluetoothHeadset.class.getName());
} else if (bluetoothProfile == BluetoothProfile.LE_CALL_CONTROL
- && BluetoothProperties.isProfileCcpServerEnabled().orElse(false)) {
+ && mSupportedProfileList.contains(BluetoothProfile.LE_CALL_CONTROL)) {
intent = new Intent(IBluetoothLeCallControl.class.getName());
} else {
return false;
@@ -2241,6 +2262,14 @@
//Inform BluetoothAdapter instances that service is up
sendBluetoothServiceUpCallback();
+ // Get the supported profiles list
+ try {
+ mSupportedProfileList = synchronousGetSupportedProfiles(
+ mContext.getAttributionSource());
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, "Unable to get the supported profiles list", e);
+ }
+
//Do enable request
try {
if (!synchronousEnable(mQuietEnable, mContext.getAttributionSource())) {
@@ -2319,6 +2348,7 @@
break;
}
mBluetooth = null;
+ mSupportedProfileList.clear();
} else if (msg.arg1 == SERVICE_IBLUETOOTHGATT) {
mBluetoothGatt = null;
break;
diff --git a/system/audio_hal_interface/aidl/client_interface_aidl.cc b/system/audio_hal_interface/aidl/client_interface_aidl.cc
index 814c6c7..6c7a687 100644
--- a/system/audio_hal_interface/aidl/client_interface_aidl.cc
+++ b/system/audio_hal_interface/aidl/client_interface_aidl.cc
@@ -46,6 +46,7 @@
BluetoothAudioClientInterface::BluetoothAudioClientInterface(
IBluetoothTransportInstance* instance)
: provider_(nullptr),
+ provider_factory_(nullptr),
session_started_(false),
data_mq_(nullptr),
transport_(instance) {
@@ -134,8 +135,12 @@
}
CHECK(provider_ != nullptr);
- AIBinder_linkToDeath(provider_factory->asBinder().get(),
- death_recipient_.get(), this);
+ binder_status_t binder_status = AIBinder_linkToDeath(
+ provider_factory->asBinder().get(), death_recipient_.get(), this);
+ if (binder_status != STATUS_OK) {
+ LOG(ERROR) << "Failed to linkToDeath " << static_cast<int>(binder_status);
+ }
+ provider_factory_ = std::move(provider_factory);
LOG(INFO) << "IBluetoothAudioProvidersFactory::openProvider() returned "
<< provider_.get()
@@ -150,8 +155,8 @@
}
BluetoothAudioSinkClientInterface::~BluetoothAudioSinkClientInterface() {
- if (provider_ != nullptr) {
- AIBinder_unlinkToDeath(provider_->asBinder().get(), death_recipient_.get(),
+ if (provider_factory_ != nullptr) {
+ AIBinder_unlinkToDeath(provider_factory_->asBinder().get(), death_recipient_.get(),
nullptr);
}
}
@@ -164,8 +169,8 @@
}
BluetoothAudioSourceClientInterface::~BluetoothAudioSourceClientInterface() {
- if (provider_ != nullptr) {
- AIBinder_unlinkToDeath(provider_->asBinder().get(), death_recipient_.get(),
+ if (provider_factory_ != nullptr) {
+ AIBinder_unlinkToDeath(provider_factory_->asBinder().get(), death_recipient_.get(),
nullptr);
}
}
diff --git a/system/audio_hal_interface/aidl/client_interface_aidl.h b/system/audio_hal_interface/aidl/client_interface_aidl.h
index 87dd450..17abefe 100644
--- a/system/audio_hal_interface/aidl/client_interface_aidl.h
+++ b/system/audio_hal_interface/aidl/client_interface_aidl.h
@@ -107,6 +107,8 @@
std::shared_ptr<IBluetoothAudioProvider> provider_;
+ std::shared_ptr<IBluetoothAudioProviderFactory> provider_factory_;
+
bool session_started_;
std::unique_ptr<DataMQ> data_mq_;
diff --git a/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl b/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl
index eb6ddd1..d5f157c 100644
--- a/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl
+++ b/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl
@@ -50,7 +50,9 @@
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void setVolumeOffset(in BluetoothDevice device, int volumeOffset, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
- void setVolumeGroup(int group_id, int volume, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ void setGroupVolume(int group_id, int volume, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+ void getGroupVolume(int group_id, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void mute(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
diff --git a/system/bta/csis/csis_client.cc b/system/bta/csis/csis_client.cc
index a1199f6..afd1b11 100644
--- a/system/bta/csis/csis_client.cc
+++ b/system/bta/csis/csis_client.cc
@@ -142,6 +142,7 @@
if (!csis_group) {
if (create_group_if_non_existing) {
/* Let's create a group */
+ LOG(INFO) << __func__ << ": Create a new group";
auto g = std::make_shared<CsisGroup>(group_id, uuid);
csis_groups_.push_back(g);
csis_group = FindCsisGroup(group_id);
@@ -1340,11 +1341,6 @@
group_id =
dev_groups_->AddDevice(device->addr, csis_instance->GetUuid());
LOG_ASSERT(group_id != -1);
-
- /* Create new group */
- auto g =
- std::make_shared<CsisGroup>(group_id, csis_instance->GetUuid());
- csis_groups_.push_back(g);
} else {
dev_groups_->AddDevice(device->addr, csis_instance->GetUuid(),
group_id);
diff --git a/system/bta/gatt/bta_gattc_utils.cc b/system/bta/gatt/bta_gattc_utils.cc
index f49ae36..1412702 100644
--- a/system/bta/gatt/bta_gattc_utils.cc
+++ b/system/bta/gatt/bta_gattc_utils.cc
@@ -677,5 +677,5 @@
*
******************************************************************************/
bool bta_gattc_is_robust_caching_enabled() {
- return bluetooth::common::init_flags::gatt_robust_caching_is_enabled();
+ return bluetooth::common::init_flags::gatt_robust_caching_client_is_enabled();
}
diff --git a/system/bta/le_audio/audio_set_scenarios.json b/system/bta/le_audio/audio_set_scenarios.json
index c92fbd6..628dfbe 100644
--- a/system/bta/le_audio/audio_set_scenarios.json
+++ b/system/bta/le_audio/audio_set_scenarios.json
@@ -128,7 +128,8 @@
"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"
+ "VND_SingleDev_TwoChanStereoSnk_48khz_75octs_R5_L12_1",
+ "DualDev_OneChanStereoSnk_48_4_Server_Preferred"
]
},
{
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index 5206f7b..fbb07f4 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -82,6 +82,12 @@
using le_audio::client_parser::ascs::kCtpResponseNoReason;
/* Enums */
+enum class AudioReconfigurationResult {
+ RECONFIGURATION_NEEDED = 0x00,
+ RECONFIGURATION_NOT_NEEDED,
+ RECONFIGURATION_NOT_POSSIBLE
+};
+
enum class AudioState {
IDLE = 0x00,
READY_TO_START,
@@ -90,6 +96,25 @@
RELEASING,
};
+std::ostream& operator<<(std::ostream& os,
+ const AudioReconfigurationResult& state) {
+ switch (state) {
+ case AudioReconfigurationResult::RECONFIGURATION_NEEDED:
+ os << "RECONFIGURATION_NEEDED";
+ break;
+ case AudioReconfigurationResult::RECONFIGURATION_NOT_NEEDED:
+ os << "RECONFIGURATION_NOT_NEEDED";
+ break;
+ case AudioReconfigurationResult::RECONFIGURATION_NOT_POSSIBLE:
+ os << "RECONFIGRATION_NOT_POSSIBLE";
+ break;
+ default:
+ os << "UNKNOWN";
+ break;
+ }
+ return os;
+}
+
std::ostream& operator<<(std::ostream& os, const AudioState& audio_state) {
switch (audio_state) {
case AudioState::IDLE:
@@ -487,7 +512,11 @@
std::optional<AudioContexts> old_group_updated_contexts =
old_group->UpdateActiveContextsMap(old_group->GetActiveContexts());
- if (old_group_updated_contexts || old_group->ReloadAudioLocations()) {
+ bool group_conf_changed = old_group->ReloadAudioLocations();
+ group_conf_changed |= old_group->ReloadAudioDirections();
+ group_conf_changed |= old_group_updated_contexts.has_value();
+
+ if (group_conf_changed) {
callbacks_->OnAudioConf(old_group->audio_directions_, old_group_id,
old_group->snk_audio_locations_.to_ulong(),
old_group->src_audio_locations_.to_ulong(),
@@ -550,7 +579,11 @@
std::optional<AudioContexts> updated_contexts =
group->UpdateActiveContextsMap(group->GetActiveContexts());
- if (updated_contexts || group->ReloadAudioLocations())
+ bool group_conf_changed = group->ReloadAudioLocations();
+ group_conf_changed |= group->ReloadAudioDirections();
+ group_conf_changed |= updated_contexts.has_value();
+
+ if (group_conf_changed)
callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
group->snk_audio_locations_.to_ulong(),
group->src_audio_locations_.to_ulong(),
@@ -827,11 +860,24 @@
}
}
- /* Configure audio HAL sessions with most frequent context.
- * If reconfiguration is not needed it means, context type is not supported
+ /* Try configure audio HAL sessions with most frequent context.
+ * If reconfiguration is not needed it means, context type is not supported.
+ * If most frequest scenario is not supported, try to find first supported.
*/
+ LeAudioContextType default_context_type = LeAudioContextType::UNSPECIFIED;
+ if (group->IsContextSupported(LeAudioContextType::MEDIA)) {
+ default_context_type = LeAudioContextType::MEDIA;
+ } else {
+ for (LeAudioContextType context_type :
+ le_audio::types::kLeAudioContextAllTypesArray) {
+ if (group->IsContextSupported(context_type)) {
+ default_context_type = context_type;
+ break;
+ }
+ }
+ }
UpdateConfigAndCheckIfReconfigurationIsNeeded(group_id,
- LeAudioContextType::MEDIA);
+ default_context_type);
if (current_source_codec_config.IsInvalid() &&
current_sink_codec_config.IsInvalid()) {
LOG(WARNING) << __func__ << ", unsupported device configurations";
@@ -1144,7 +1190,12 @@
/* Read of source audio locations during initial attribute discovery.
* Group would be assigned once service search is completed.
*/
- if (group && group->ReloadAudioLocations()) {
+ if (!group) return;
+
+ bool group_conf_changed = group->ReloadAudioLocations();
+ group_conf_changed |= group->ReloadAudioDirections();
+
+ if (group_conf_changed) {
callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
group->snk_audio_locations_.to_ulong(),
group->src_audio_locations_.to_ulong(),
@@ -1173,7 +1224,12 @@
/* Read of source audio locations during initial attribute discovery.
* Group would be assigned once service search is completed.
*/
- if (group && group->ReloadAudioLocations()) {
+ if (!group) return;
+
+ bool group_conf_changed = group->ReloadAudioLocations();
+ group_conf_changed |= group->ReloadAudioDirections();
+
+ if (group_conf_changed) {
callbacks_->OnAudioConf(group->audio_directions_, group->group_id_,
group->snk_audio_locations_.to_ulong(),
group->src_audio_locations_.to_ulong(),
@@ -1279,10 +1335,6 @@
BtaGattQueue::ConfigureMtu(leAudioDevice->conn_id_, 240);
}
- /* If we know services, register for notifications */
- if (leAudioDevice->known_service_handles_)
- RegisterKnownNotifications(leAudioDevice);
-
if (BTM_SecIsSecurityPending(address)) {
/* if security collision happened, wait for encryption done
* (BTA_GATTC_ENC_CMPL_CB_EVT) */
@@ -1376,6 +1428,10 @@
return;
}
+ /* If we know services, register for notifications */
+ if (leAudioDevice->known_service_handles_)
+ RegisterKnownNotifications(leAudioDevice);
+
if (leAudioDevice->encrypted_) {
LOG(INFO) << __func__ << " link already encrypted, nothing to do";
return;
@@ -1993,8 +2049,8 @@
if (bytes_per_sample == 2) {
int16_t* out = (int16_t*)mono_out.data();
+ const int16_t* in = (int16_t*)(buf.data());
for (size_t i = 0; i < frames; ++i) {
- const int16_t* in = (int16_t*)(buf.data());
int accum = 0;
accum += *in++;
accum += *in++;
@@ -2003,8 +2059,8 @@
}
} else if (bytes_per_sample == 4) {
int32_t* out = (int32_t*)mono_out.data();
+ const int32_t* in = (int32_t*)(buf.data());
for (size_t i = 0; i < frames; ++i) {
- const int32_t* in = (int32_t*)(buf.data());
int accum = 0;
accum += *in++;
accum += *in++;
@@ -2017,7 +2073,7 @@
return mono_out;
}
- void PrepareAndSendToTwoDevices(
+ void PrepareAndSendToTwoCises(
const std::vector<uint8_t>& data,
struct le_audio::stream_configuration* stream_conf) {
uint16_t byte_count = stream_conf->sink_octets_per_codec_frame;
@@ -2087,7 +2143,7 @@
right_cis_handle, chan_right_enc.data(), chan_right_enc.size());
}
- void PrepareAndSendToSingleDevice(
+ void PrepareAndSendToSingleCis(
const std::vector<uint8_t>& data,
struct le_audio::stream_configuration* stream_conf) {
int num_channels = stream_conf->sink_num_of_channels;
@@ -2270,9 +2326,12 @@
}
if (stream_conf.sink_num_of_devices == 2) {
- PrepareAndSendToTwoDevices(data, &stream_conf);
+ PrepareAndSendToTwoCises(data, &stream_conf);
+ } else if (stream_conf.sink_streams.size() == 2 ) {
+ /* Streaming to one device but 2 CISes */
+ PrepareAndSendToTwoCises(data, &stream_conf);
} else {
- PrepareAndSendToSingleDevice(data, &stream_conf);
+ PrepareAndSendToSingleCis(data, &stream_conf);
}
}
@@ -2701,14 +2760,17 @@
std::move(cleanupCb).Run();
}
- bool UpdateConfigAndCheckIfReconfigurationIsNeeded(
+ AudioReconfigurationResult UpdateConfigAndCheckIfReconfigurationIsNeeded(
int group_id, LeAudioContextType context_type) {
bool reconfiguration_needed = false;
+ bool sink_cfg_available = true;
+ bool source_cfg_available = true;
+
auto group = aseGroups_.FindById(group_id);
if (!group) {
LOG(ERROR) << __func__
<< ", Invalid group: " << static_cast<int>(group_id);
- return reconfiguration_needed;
+ return AudioReconfigurationResult::RECONFIGURATION_NOT_NEEDED;
}
std::optional<LeAudioCodecConfiguration> source_configuration =
@@ -2728,11 +2790,7 @@
current_source_codec_config = {0, 0, 0, 0};
reconfiguration_needed = true;
}
-
- LOG(INFO) << __func__
- << ", group does not supports source direction for"
- " context: "
- << static_cast<int>(context_type);
+ source_cfg_available = false;
}
if (sink_configuration) {
@@ -2746,20 +2804,28 @@
reconfiguration_needed = true;
}
- LOG(INFO) << __func__
- << ", group does not supports sink direction for"
- " context: "
- << static_cast<int>(context_type);
+ sink_cfg_available = false;
}
- if (reconfiguration_needed) {
- LOG(INFO) << __func__
- << " Session reconfiguration needed group: " << group->group_id_
- << " for context type: " << static_cast<int>(context_type);
+ LOG_DEBUG(
+ " Context: %s Reconfigufation_needed = %d, sink_cfg_available = %d, "
+ "source_cfg_available = %d",
+ ToString(context_type).c_str(), reconfiguration_needed,
+ sink_cfg_available, source_cfg_available);
+
+ if (!reconfiguration_needed) {
+ return AudioReconfigurationResult::RECONFIGURATION_NOT_NEEDED;
}
+ if (!sink_cfg_available && !source_cfg_available) {
+ return AudioReconfigurationResult::RECONFIGURATION_NOT_POSSIBLE;
+ }
+
+ LOG_INFO(" Session reconfiguration needed group: %d for context type: %s",
+ group->group_id_, ToString(context_type).c_str());
+
current_context_type_ = context_type;
- return reconfiguration_needed;
+ return AudioReconfigurationResult::RECONFIGURATION_NEEDED;
}
bool OnAudioResume(LeAudioDeviceGroup* group) {
@@ -3088,16 +3154,19 @@
}
LeAudioContextType AudioContentToLeAudioContext(
- LeAudioContextType current_context_type,
audio_content_type_t content_type, audio_usage_t usage) {
/* Check audio attribute usage of stream */
switch (usage) {
case AUDIO_USAGE_MEDIA:
return LeAudioContextType::MEDIA;
case AUDIO_USAGE_VOICE_COMMUNICATION:
- case AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING:
case AUDIO_USAGE_CALL_ASSISTANT:
return LeAudioContextType::CONVERSATIONAL;
+ case AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING:
+ if (content_type == AUDIO_CONTENT_TYPE_SPEECH)
+ return LeAudioContextType::CONVERSATIONAL;
+ else
+ return LeAudioContextType::MEDIA;
case AUDIO_USAGE_GAME:
return LeAudioContextType::GAME;
case AUDIO_USAGE_NOTIFICATION:
@@ -3119,13 +3188,17 @@
LeAudioContextType ChooseContextType(
std::vector<LeAudioContextType>& available_contents) {
- /* Mini policy. Voice is prio 1, media is prio 2 */
+ /* Mini policy. Voice is prio 1, game prio 2, media is prio 3 */
auto iter = find(available_contents.begin(), available_contents.end(),
LeAudioContextType::CONVERSATIONAL);
if (iter != available_contents.end())
return LeAudioContextType::CONVERSATIONAL;
iter = find(available_contents.begin(), available_contents.end(),
+ LeAudioContextType::GAME);
+ if (iter != available_contents.end()) return LeAudioContextType::GAME;
+
+ iter = find(available_contents.begin(), available_contents.end(),
LeAudioContextType::MEDIA);
if (iter != available_contents.end()) return LeAudioContextType::MEDIA;
@@ -3135,10 +3208,19 @@
bool StopStreamIfNeeded(LeAudioDeviceGroup* group,
LeAudioContextType new_context_type) {
- DLOG(INFO) << __func__ << " context type " << int(new_context_type);
- if (!UpdateConfigAndCheckIfReconfigurationIsNeeded(group->group_id_,
- new_context_type)) {
- DLOG(INFO) << __func__ << " reconfiguration not needed";
+ auto reconfig_result = UpdateConfigAndCheckIfReconfigurationIsNeeded(
+ group->group_id_, new_context_type);
+
+ LOG_INFO("group_id %d, context type %s, reconfig_needed %s",
+ group->group_id_, ToString(new_context_type).c_str(),
+ ToString(reconfig_result).c_str());
+ if (reconfig_result ==
+ AudioReconfigurationResult::RECONFIGURATION_NOT_NEEDED) {
+ return false;
+ }
+
+ if (reconfig_result ==
+ AudioReconfigurationResult::RECONFIGURATION_NOT_POSSIBLE) {
return false;
}
@@ -3159,8 +3241,24 @@
auto tracks = source_metadata.tracks;
auto track_count = source_metadata.track_count;
+ if (active_group_id_ == bluetooth::groups::kGroupUnknown) {
+ LOG(WARNING) << ", cannot start streaming if no active group set";
+ return;
+ }
+
+ auto group = aseGroups_.FindById(active_group_id_);
+ if (!group) {
+ LOG(ERROR) << __func__
+ << ", Invalid group: " << static_cast<int>(active_group_id_);
+ return;
+ }
+ bool is_group_streaming =
+ (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+
std::vector<LeAudioContextType> contexts;
+ auto supported_context_type = group->GetActiveContexts();
+
while (track_count) {
if (tracks->content_type == 0 && tracks->usage == 0) {
--track_count;
@@ -3171,37 +3269,31 @@
LOG_INFO("%s: usage=%d, content_type=%d, gain=%f", __func__,
tracks->usage, tracks->content_type, tracks->gain);
- auto new_context = AudioContentToLeAudioContext(
- current_context_type_, tracks->content_type, tracks->usage);
- contexts.push_back(new_context);
+ auto new_context =
+ AudioContentToLeAudioContext(tracks->content_type, tracks->usage);
+
+ /* Check only supported context types.*/
+ if (static_cast<int>(new_context) & supported_context_type.to_ulong()) {
+ contexts.push_back(new_context);
+ } else {
+ LOG_WARN(" Context type %s not supported by remote device",
+ ToString(new_context).c_str());
+ }
--track_count;
++tracks;
}
if (contexts.empty()) {
- DLOG(INFO) << __func__ << " invalid metadata update";
+ LOG_WARN(" invalid/unknown metadata update");
return;
}
auto new_context = ChooseContextType(contexts);
- DLOG(INFO) << __func__
- << " new_context_type: " << static_cast<int>(new_context);
-
- auto group = aseGroups_.FindById(active_group_id_);
- if (!group) {
- LOG(ERROR) << __func__
- << ", Invalid group: " << static_cast<int>(active_group_id_);
- return;
- }
+ LOG_DEBUG("new_context_type: %s", ToString(new_context).c_str());
if (new_context == current_context_type_) {
- LOG(INFO) << __func__ << " Context did not changed.";
- return;
- }
-
- if (active_group_id_ == bluetooth::groups::kGroupUnknown) {
- LOG(WARNING) << ", cannot start streaming if no active group set";
+ LOG_INFO("Context did not changed.");
return;
}
@@ -3210,7 +3302,7 @@
return;
}
- if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+ if (is_group_streaming) {
/* Configuration is the same for new context, just will do update
* metadata of stream
*/
diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc
index 6943f3e..0158b48 100644
--- a/system/bta/le_audio/devices.cc
+++ b/system/bta/le_audio/devices.cc
@@ -551,8 +551,8 @@
/* This method returns AudioContext value if support for any type has changed */
std::optional<AudioContexts> LeAudioDeviceGroup::UpdateActiveContextsMap(void) {
- DLOG(INFO) << __func__ << " group id: " << group_id_ << " active contexts: "
- << loghex(active_contexts_mask_.to_ulong());
+ LOG_DEBUG(" group id: %d, active contexts: 0x%04lx", group_id_,
+ active_contexts_mask_.to_ulong());
return UpdateActiveContextsMap(active_contexts_mask_);
}
@@ -562,10 +562,19 @@
AudioContexts contexts = 0x0000;
bool active_contexts_has_been_modified = false;
+ if (update_contexts.none()) {
+ LOG_DEBUG("No context updated");
+ return contexts;
+ }
+
for (LeAudioContextType ctx_type : types::kLeAudioContextAllTypesArray) {
AudioContexts type_set = static_cast<uint16_t>(ctx_type);
-
+ LOG_DEBUG("Taking context: %s, 0x%04lx",
+ bluetooth::common::ToString(ctx_type).c_str(),
+ update_contexts.to_ulong());
if ((type_set & update_contexts).none()) {
+ LOG_INFO("Configuration not in updated context %s",
+ bluetooth::common::ToString(ctx_type).c_str());
/* Fill context bitset for possible returned value if updated */
if (active_context_to_configuration_map.count(ctx_type) > 0)
contexts |= type_set;
@@ -575,9 +584,11 @@
auto new_conf = FindFirstSupportedConfiguration(ctx_type);
+ bool ctx_previously_not_supported =
+ (active_context_to_configuration_map.count(ctx_type) == 0 ||
+ active_context_to_configuration_map[ctx_type] == nullptr);
/* Check if support for context type has changed */
- if (active_context_to_configuration_map.count(ctx_type) == 0 ||
- active_context_to_configuration_map[ctx_type] == nullptr) {
+ if (ctx_previously_not_supported) {
/* Current configuration for context type is empty */
if (new_conf == nullptr) {
/* Configuration remains empty */
@@ -604,12 +615,14 @@
}
}
- LOG(INFO) << __func__ << ", updated context: " << loghex(int(ctx_type))
- << ", "
- << (active_context_to_configuration_map[ctx_type] != nullptr
- ? active_context_to_configuration_map[ctx_type]->name
- : "empty")
- << " -> " << (new_conf != nullptr ? new_conf->name : "empty");
+ LOG_INFO(
+ "updated context: %s, %s -> %s",
+ bluetooth::common::ToString(ctx_type).c_str(),
+ (ctx_previously_not_supported
+ ? "empty"
+ : active_context_to_configuration_map[ctx_type]->name.c_str()),
+ (new_conf != nullptr ? new_conf->name.c_str() : "empty"));
+
active_context_to_configuration_map[ctx_type] = new_conf;
}
@@ -646,6 +659,22 @@
return true;
}
+bool LeAudioDeviceGroup::ReloadAudioDirections(void) {
+ uint8_t updated_audio_directions = 0x00;
+
+ for (const auto& device : leAudioDevices_) {
+ if (device.expired()) continue;
+ updated_audio_directions |= device.lock().get()->audio_directions_;
+ }
+
+ /* Nothing has changed */
+ if (updated_audio_directions == audio_directions_) return false;
+
+ audio_directions_ = updated_audio_directions;
+
+ return true;
+}
+
bool LeAudioDeviceGroup::IsInTransition(void) {
return target_state_ != current_state_;
}
@@ -744,10 +773,9 @@
types::LeAudioContextType context_type) {
if (!set_configurations::check_if_may_cover_scenario(
audio_set_conf, NumOfConnected(context_type))) {
- DLOG(INFO) << __func__ << " cannot cover scenario "
- << static_cast<int>(context_type)
- << " size of for context type: "
- << +NumOfConnected(context_type);
+ LOG_DEBUG(" cannot cover scenario %s: size of for context type %d",
+ bluetooth::common::ToString(context_type).c_str(),
+ +NumOfConnected(context_type));
return false;
}
@@ -759,11 +787,9 @@
* 3) ASEs should be filled according to performance profile.
*/
for (const auto& ent : (*audio_set_conf).confs) {
- DLOG(INFO) << __func__
- << " Looking for configuration: " << audio_set_conf->name
- << " - "
- << (ent.direction == types::kLeAudioDirectionSink ? "snk"
- : "src");
+ LOG_DEBUG(" Looking for configuration: %s - %s",
+ audio_set_conf->name.c_str(),
+ (ent.direction == types::kLeAudioDirectionSink ? "snk" : "src"));
uint8_t required_device_cnt = ent.device_cnt;
uint8_t max_required_ase_per_dev =
@@ -771,10 +797,11 @@
uint8_t active_ase_num = 0;
auto strategy = ent.strategy;
- DLOG(INFO) << __func__ << " Number of devices: " << +required_device_cnt
- << " number of ASEs: " << +ent.ase_cnt
- << " Max ASE per device: " << +max_required_ase_per_dev
- << " strategy: " << static_cast<int>(strategy);
+ LOG_DEBUG(
+ " Number of devices: %d, number of ASEs: %d, Max ASE per device: %d "
+ "strategy: %d",
+ +required_device_cnt, +ent.ase_cnt, +max_required_ase_per_dev,
+ static_cast<int>(strategy));
for (auto* device = GetFirstDeviceWithActiveContext(context_type);
device != nullptr && required_device_cnt > 0;
@@ -806,8 +833,8 @@
strategy, audio_locations,
std::get<LeAudioLc3Config>(ent.codec.config).GetChannelCount(),
device->GetLc3SupportedChannelCount(ent.direction))) {
- DLOG(INFO) << __func__ << " insufficient device audio allocation: "
- << audio_locations;
+ LOG_DEBUG(" insufficient device audio allocation: %lu",
+ audio_locations.to_ulong());
continue;
}
@@ -825,13 +852,13 @@
if (required_device_cnt > 0) {
/* Don't left any active devices if requirements are not met */
- DLOG(INFO) << __func__ << " could not configure all the devices";
+ LOG_DEBUG(" could not configure all the devices");
return false;
}
}
- DLOG(INFO) << "Choosed ASE Configuration for group: " << this->group_id_
- << " configuration: " << audio_set_conf->name;
+ LOG_DEBUG("Chosen ASE Configuration for group: %d, configuration: %s",
+ this->group_id_, audio_set_conf->name.c_str());
return true;
}
@@ -1205,13 +1232,14 @@
const set_configurations::AudioSetConfigurations* confs =
AudioSetConfigurationProvider::Get()->GetConfigurations(context_type);
- DLOG(INFO) << __func__ << " context type: " << (int)context_type
- << " number of connected devices: " << NumOfConnected();
+ LOG_DEBUG("context type: %s, number of connected devices: %d",
+ bluetooth::common::ToString(context_type).c_str(),
+ +NumOfConnected());
/* Filter out device set for all scenarios */
if (!set_configurations::check_if_may_cover_scenario(confs,
NumOfConnected())) {
- LOG(ERROR) << __func__ << ", group is unable to cover scenario";
+ LOG_ERROR(", group is unable to cover scenario");
return nullptr;
}
@@ -1219,7 +1247,7 @@
for (const auto& conf : *confs) {
if (IsConfigurationSupported(conf, context_type)) {
- DLOG(INFO) << __func__ << " found: " << conf->name;
+ LOG_DEBUG("found: %s", conf->name.c_str());
return conf;
}
}
@@ -1656,7 +1684,7 @@
direction == types::kLeAudioDirectionSink ? snk_pacs_ : src_pacs_;
if (pacs.size() == 0) {
- LOG(ERROR) << __func__ << " missing PAC for direction " << +direction;
+ LOG_ERROR("missing PAC for direction %d", direction);
return nullptr;
}
@@ -1738,12 +1766,12 @@
updated_contexts = snk_contexts ^ avail_snk_contexts_;
updated_contexts |= src_contexts ^ avail_src_contexts_;
- DLOG(INFO) << __func__
- << "\n\t avail_snk_contexts_: " << avail_snk_contexts_.to_string()
- << "\n\t avail_src_contexts_: " << avail_src_contexts_.to_string()
- << "\n\t snk_contexts:" << snk_contexts.to_string()
- << "\n\t src_contexts: " << src_contexts.to_string()
- << "\n\t updated_contexts: " << updated_contexts.to_string();
+ LOG_DEBUG(
+ "\n\t avail_snk_contexts_: %s \n\t avail_src_contexts_: %s \n\t "
+ "snk_contexts: %s \n\t src_contexts: %s \n\t updated_contexts: %s",
+ avail_snk_contexts_.to_string().c_str(),
+ avail_src_contexts_.to_string().c_str(), snk_contexts.to_string().c_str(),
+ src_contexts.to_string().c_str(), updated_contexts.to_string().c_str());
avail_snk_contexts_ = snk_contexts;
avail_src_contexts_ = src_contexts;
diff --git a/system/bta/le_audio/devices.h b/system/bta/le_audio/devices.h
index 30e4bbc..2a7be7f 100644
--- a/system/bta/le_audio/devices.h
+++ b/system/bta/le_audio/devices.h
@@ -249,6 +249,7 @@
types::AudioContexts contexts);
std::optional<types::AudioContexts> UpdateActiveContextsMap(void);
bool ReloadAudioLocations(void);
+ bool ReloadAudioDirections(void);
const set_configurations::AudioSetConfiguration* GetActiveConfiguration(void);
types::LeAudioContextType GetCurrentContextType(void);
bool IsPendingConfiguration(void);
diff --git a/system/bta/le_audio/devices_test.cc b/system/bta/le_audio/devices_test.cc
index 7628182..706bfa2 100644
--- a/system/bta/le_audio/devices_test.cc
+++ b/system/bta/le_audio/devices_test.cc
@@ -526,6 +526,13 @@
for (const auto& audio_set_conf : *configurations) {
// the configuration should fail if there are no active ases expected
bool success_expected = data_size > 0;
+ bool not_matching_scenario = false;
+ uint8_t snk_ases_cnt = 0;
+ uint8_t src_ases_cnt = 0;
+ PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder;
+ snk_pac_builder.Reset();
+ src_pac_builder.Reset();
+
for (int i = 0; i < data_size; i++) {
success_expected &= (data[i].active_channel_num_snk +
data[i].active_channel_num_src) > 0;
@@ -536,19 +543,34 @@
* 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) {
+ /* Configuration requires more devices than are supplied */
+ if (entry.device_cnt > data_size) {
+ not_matching_scenario = true;
+ break;
+ }
if (entry.direction == kLeAudioDirectionSink) {
+ snk_ases_cnt += entry.ase_cnt;
snk_pac_builder.Add(entry.codec, data[i].audio_channel_counts_snk);
} else {
src_pac_builder.Add(entry.codec, data[i].audio_channel_counts_src);
}
}
+ /* Scenario requires more ASEs than defined requirement */
+ if (snk_ases_cnt < data[i].audio_channel_counts_snk ||
+ src_ases_cnt < data[i].audio_channel_counts_src) {
+ not_matching_scenario = true;
+ }
+
+ if (not_matching_scenario) break;
+
data[i].device->snk_pacs_ = snk_pac_builder.Get();
data[i].device->src_pacs_ = src_pac_builder.Get();
}
+ if (not_matching_scenario) continue;
+
/* Stimulate update of active context map */
group_->UpdateActiveContextsMap(static_cast<uint16_t>(context_type));
ASSERT_EQ(success_expected, group_->Configure(context_type));
diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc
index f2983dc..2525d23 100644
--- a/system/bta/le_audio/le_audio_client_test.cc
+++ b/system/bta/le_audio/le_audio_client_test.cc
@@ -808,7 +808,6 @@
void SetUp() override {
init_message_loop_thread();
-
ON_CALL(controller_interface_, SupportsBleConnectedIsochronousStreamCentral)
.WillByDefault(Return(true));
ON_CALL(controller_interface_,
@@ -3161,5 +3160,47 @@
LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown);
Mock::VerifyAndClearExpectations(mock_unicast_audio_source_);
}
+
+TEST_F(UnicastTest, StartNotSupportedContextType) {
+ const RawAddress test_address0 = GetTestAddress(0);
+ int group_id = bluetooth::groups::kGroupUnknown;
+
+ SetSampleDatabaseEarbudsValid(
+ 1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
+ codec_spec_conf::kLeAudioLocationStereo, 0x0004, false /*add_csis*/,
+ true /*add_cas*/, true /*add_pacs*/, true /*add_ascs*/, 1 /*set_size*/,
+ 0 /*rank*/);
+ EXPECT_CALL(mock_client_callbacks_,
+ OnConnectionState(ConnectionState::CONNECTED, test_address0))
+ .Times(1);
+ EXPECT_CALL(mock_client_callbacks_,
+ OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED))
+ .WillOnce(DoAll(SaveArg<1>(&group_id)));
+
+ ConnectLeAudio(test_address0);
+ ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown);
+
+ // Start streaming
+ uint8_t cis_count_out = 1;
+ uint8_t cis_count_in = 0;
+
+ // 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_NOTIFICATION_TELEPHONY_RINGTONE,
+ AUDIO_CONTENT_TYPE_UNKNOWN, group_id);
+
+ Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+ Mock::VerifyAndClearExpectations(mock_unicast_audio_source_);
+ SyncOnMainLoop();
+
+ // Verify Data transfer on one audio source cis
+ TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+
+ EXPECT_CALL(mock_state_machine_, StopStream(_)).Times(0);
+ UpdateMetadata(AUDIO_USAGE_GAME, AUDIO_CONTENT_TYPE_UNKNOWN);
+}
} // namespace
} // namespace le_audio
diff --git a/system/bta/le_audio/le_audio_types.cc b/system/bta/le_audio/le_audio_types.cc
index 44d581b..69e958b 100644
--- a/system/bta/le_audio/le_audio_types.cc
+++ b/system/bta/le_audio/le_audio_types.cc
@@ -104,27 +104,33 @@
auto req = reqs.Find(codec_spec_conf::kLeAudioCodecLC3TypeSamplingFreq);
auto pac = pacs.Find(codec_spec_caps::kLeAudioCodecLC3TypeSamplingFreq);
if (!req || !pac) {
- DLOG(ERROR) << __func__ << ", lack of sampling frequency fields";
+ LOG_DEBUG(", lack of sampling frequency fields");
return false;
}
u8_req_val = VEC_UINT8_TO_UINT8(req.value());
u16_pac_val = VEC_UINT8_TO_UINT16(pac.value());
- /*
- * Note: Requirements are in the codec configuration specification which
- * are values coming from BAP Appendix A1.2.1
- */
- DLOG(INFO) << __func__ << " Req:SamplFreq=" << loghex(u8_req_val);
- /* NOTE: Below is Codec specific cababilities comes form BAP Appendix A A1.1.1
- * Note this is a bitfield
- */
- DLOG(INFO) << __func__ << " Pac:SamplFreq=" << loghex(u16_pac_val);
-
/* TODO: Integrate with codec capabilities */
if (!(u16_pac_val &
codec_spec_caps::SamplingFreqConfig2Capability(u8_req_val))) {
- DLOG(ERROR) << __func__ << ", sampling frequency not supported";
+ /*
+ * Note: Requirements are in the codec configuration specification which
+ * are values coming from Assigned Numbers: Codec_Specific_Configuration
+ */
+ LOG_DEBUG(
+ " Req:SamplFreq= 0x%04x (Assigned Numbers: "
+ "Codec_Specific_Configuration)",
+ u8_req_val);
+ /* NOTE: Below is Codec specific cababilities comes from Assigned Numbers:
+ * Codec_Specific_Capabilities
+ */
+ LOG_DEBUG(
+ " Pac:SamplFreq= 0x%04x (Assigned numbers: "
+ "Codec_Specific_Capabilities - bitfield)",
+ u16_pac_val);
+
+ LOG_DEBUG(", sampling frequency not supported");
return false;
}
@@ -132,20 +138,20 @@
req = reqs.Find(codec_spec_conf::kLeAudioCodecLC3TypeFrameDuration);
pac = pacs.Find(codec_spec_caps::kLeAudioCodecLC3TypeFrameDuration);
if (!req || !pac) {
- DLOG(ERROR) << __func__ << ", lack of frame duration fields";
+ LOG_DEBUG(", lack of frame duration fields");
return false;
}
u8_req_val = VEC_UINT8_TO_UINT8(req.value());
u8_pac_val = VEC_UINT8_TO_UINT8(pac.value());
- DLOG(INFO) << __func__ << " Req:FrameDur=" << loghex(u8_req_val);
- DLOG(INFO) << __func__ << " Pac:FrameDur=" << loghex(u8_pac_val);
if ((u8_req_val != codec_spec_conf::kLeAudioCodecLC3FrameDur7500us &&
u8_req_val != codec_spec_conf::kLeAudioCodecLC3FrameDur10000us) ||
!(u8_pac_val &
(codec_spec_caps::FrameDurationConfig2Capability(u8_req_val)))) {
- DLOG(ERROR) << __func__ << ", frame duration not supported";
+ LOG_DEBUG(" Req:FrameDur=0x%04x", u8_req_val);
+ LOG_DEBUG(" Pac:FrameDur=0x%04x", u8_pac_val);
+ LOG_DEBUG(", frame duration not supported");
return false;
}
@@ -162,15 +168,16 @@
* the Unicast Server supports mandatory one channel.
*/
if (!pac) {
- DLOG(WARNING) << __func__ << ", no Audio_Channel_Counts field in PAC";
+ LOG_DEBUG(", no Audio_Channel_Counts field in PAC, using default 0x01");
u8_pac_val = 0x01;
} else {
u8_pac_val = VEC_UINT8_TO_UINT8(pac.value());
}
- DLOG(INFO) << __func__ << " Pac:AudioChanCnt=" << loghex(u8_pac_val);
if (!((1 << (required_audio_chan_num - 1)) & u8_pac_val)) {
- DLOG(ERROR) << __func__ << ", channel count warning";
+ LOG_DEBUG(" Req:AudioChanCnt=0x%04x", 1 << (required_audio_chan_num - 1));
+ LOG_DEBUG(" Pac:AudioChanCnt=0x%04x", u8_pac_val);
+ LOG_DEBUG(", channel count warning");
return false;
}
@@ -179,26 +186,26 @@
pac = pacs.Find(codec_spec_caps::kLeAudioCodecLC3TypeOctetPerFrame);
if (!req || !pac) {
- DLOG(ERROR) << __func__ << ", lack of octet per frame fields";
+ LOG_DEBUG(", lack of octet per frame fields");
return false;
}
u16_req_val = VEC_UINT8_TO_UINT16(req.value());
- DLOG(INFO) << __func__ << " Req:OctetsPerFrame=" << int(u16_req_val);
-
/* Minimal value 0-1 byte */
u16_pac_val = VEC_UINT8_TO_UINT16(pac.value());
- DLOG(INFO) << __func__ << " Pac:MinOctetsPerFrame=" << int(u16_pac_val);
if (u16_req_val < u16_pac_val) {
- DLOG(ERROR) << __func__ << ", octet per frame below minimum";
+ LOG_DEBUG(" Req:OctetsPerFrame=%d", int(u16_req_val));
+ LOG_DEBUG(" Pac:MinOctetsPerFrame=%d", int(u16_pac_val));
+ LOG_DEBUG(", octet per frame below minimum");
return false;
}
/* Maximal value 2-3 byte */
u16_pac_val = OFF_VEC_UINT8_TO_UINT16(pac.value(), 2);
- DLOG(INFO) << __func__ << " Pac:MaxOctetsPerFrame=" << int(u16_pac_val);
if (u16_req_val > u16_pac_val) {
- DLOG(ERROR) << __func__ << ", octet per frame above maximum";
+ LOG_DEBUG(" Req:MaxOctetsPerFrame=%d", int(u16_req_val));
+ LOG_DEBUG(" Pac:MaxOctetsPerFrame=%d", int(u16_pac_val));
+ LOG_DEBUG(", octet per frame above maximum");
return false;
}
@@ -212,7 +219,7 @@
if (codec_id != pac.codec_id) return false;
- DLOG(INFO) << __func__ << ": Settings for format " << +codec_id.coding_format;
+ LOG_DEBUG(": Settings for format: 0x%02x ", codec_id.coding_format);
switch (codec_id.coding_format) {
case kLeAudioCodingFormatLC3:
@@ -229,7 +236,7 @@
case kLeAudioCodingFormatLC3:
return std::get<types::LeAudioLc3Config>(config).GetSamplingFrequencyHz();
default:
- DLOG(WARNING) << __func__ << ", invalid codec id";
+ LOG_WARN(", invalid codec id: 0x%02x", id.coding_format);
return 0;
}
};
@@ -239,7 +246,7 @@
case kLeAudioCodingFormatLC3:
return std::get<types::LeAudioLc3Config>(config).GetFrameDurationUs();
default:
- DLOG(WARNING) << __func__ << ", invalid codec id";
+ LOG_WARN(", invalid codec id: 0x%02x", id.coding_format);
return 0;
}
};
@@ -250,7 +257,7 @@
/* XXX LC3 supports 16, 24, 32 */
return 16;
default:
- DLOG(WARNING) << __func__ << ", invalid codec id";
+ LOG_WARN(", invalid codec id: 0x%02x", id.coding_format);
return 0;
}
};
@@ -258,12 +265,12 @@
uint8_t CodecCapabilitySetting::GetConfigChannelCount() const {
switch (id.coding_format) {
case kLeAudioCodingFormatLC3:
- DLOG(INFO) << __func__ << ", count = "
- << static_cast<int>(std::get<types::LeAudioLc3Config>(config)
- .channel_count);
+ LOG_DEBUG("count = %d",
+ static_cast<int>(
+ std::get<types::LeAudioLc3Config>(config).channel_count));
return std::get<types::LeAudioLc3Config>(config).channel_count;
default:
- DLOG(WARNING) << __func__ << ", invalid codec id";
+ LOG_WARN(", invalid codec id: 0x%02x", id.coding_format);
return 0;
}
}
@@ -469,6 +476,49 @@
<< ", AudioChanLoc=" << loghex(*config.audio_channel_allocation) << ")";
return os;
}
+std::ostream& operator<<(std::ostream& os, const LeAudioContextType& context) {
+ switch (context) {
+ case LeAudioContextType::UNINITIALIZED:
+ os << "UNINITIALIZED";
+ break;
+ case LeAudioContextType::UNSPECIFIED:
+ os << "UNSPECIFIED";
+ break;
+ case LeAudioContextType::CONVERSATIONAL:
+ os << "CONVERSATIONAL";
+ break;
+ case LeAudioContextType::MEDIA:
+ os << "MEDIA";
+ break;
+ case LeAudioContextType::GAME:
+ os << "GAME";
+ break;
+ case LeAudioContextType::INSTRUCTIONAL:
+ os << "INSTRUCTIONAL";
+ break;
+ case LeAudioContextType::VOICEASSISTANTS:
+ os << "VOICEASSISTANTS";
+ break;
+ case LeAudioContextType::LIVE:
+ os << "LIVE";
+ break;
+ case LeAudioContextType::SOUNDEFFECTS:
+ os << "SOUNDEFFECTS";
+ break;
+ case LeAudioContextType::NOTIFICATIONS:
+ os << "NOTIFICATIONS";
+ break;
+ case LeAudioContextType::RINGTONE:
+ os << "RINGTONE";
+ break;
+ case LeAudioContextType::EMERGENCYALARM:
+ os << "EMERGENCYALARM";
+ break;
+ default:
+ os << "UNKNOWN";
+ break;
+ }
+ return os;
+}
} // namespace types
-
} // namespace le_audio
diff --git a/system/bta/le_audio/le_audio_types.h b/system/bta/le_audio/le_audio_types.h
index 7960359..b64db3b 100644
--- a/system/bta/le_audio/le_audio_types.h
+++ b/system/bta/le_audio/le_audio_types.h
@@ -572,6 +572,7 @@
std::ostream& operator<<(std::ostream& os, const AseState& state);
std::ostream& operator<<(std::ostream& os, const CigState& state);
std::ostream& operator<<(std::ostream& os, const LeAudioLc3Config& config);
+std::ostream& operator<<(std::ostream& os, const LeAudioContextType& context);
} // namespace types
namespace set_configurations {
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index 2265f42..aaeba87 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -579,6 +579,7 @@
if ((group->GetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) &&
(group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_IDLE)) {
LOG(INFO) << __func__ << " group: " << group->group_id_ << " is in IDLE";
+ group->UpdateActiveContextsMap();
return;
}
@@ -609,10 +610,10 @@
/* mark ASEs as not used. */
leAudioDevice->DeactivateAllAses();
- DLOG(INFO) << __func__ << " device: " << leAudioDevice->address_
- << " group connected: " << group->IsAnyDeviceConnected()
- << " all active ase disconnected: "
- << group->HaveAllActiveDevicesCisDisc();
+ LOG_DEBUG(
+ " device: %s, group connected: %d, all active ase disconnected:: %d",
+ leAudioDevice->address_.ToString().c_str(),
+ group->IsAnyDeviceConnected(), group->HaveAllActiveDevicesCisDisc());
/* Group has changed. Lets update available contexts */
group->UpdateActiveContextsMap();
diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc
index ff33fd3..7d158cc 100644
--- a/system/bta/le_audio/state_machine_test.cc
+++ b/system/bta/le_audio/state_machine_test.cc
@@ -27,6 +27,7 @@
#include "btm_api_mock.h"
#include "client_parser.h"
#include "fake_osi.h"
+#include "gd/common/init_flags.h"
#include "le_audio_set_configuration_provider.h"
#include "mock_codec_manager.h"
#include "mock_controller.h"
diff --git a/system/btif/avrcp/avrcp_service.cc b/system/btif/avrcp/avrcp_service.cc
index 01b0998..7f33f36 100644
--- a/system/btif/avrcp/avrcp_service.cc
+++ b/system/btif/avrcp/avrcp_service.cc
@@ -106,6 +106,12 @@
BT_HDR* p_pkt) override {
return AVRC_MsgReq(handle, label, ctype, p_pkt);
}
+
+ void SaveControllerVersion(const RawAddress& bdaddr,
+ uint16_t version) override {
+ AVRC_SaveControllerVersion(bdaddr, version);
+ }
+
} avrcp_interface_;
class SdpInterfaceImpl : public SdpInterface {
diff --git a/system/gd/hci/controller.cc b/system/gd/hci/controller.cc
index da5986f..cd6fa1f 100644
--- a/system/gd/hci/controller.cc
+++ b/system/gd/hci/controller.cc
@@ -146,7 +146,7 @@
LOG_INFO("LE_READ_PERIODIC_ADVERTISING_LIST_SIZE not supported, defaulting to 0");
le_periodic_advertiser_list_size_ = 0;
}
- if (is_supported(OpCode::LE_SET_HOST_FEATURE)) {
+ if (is_supported(OpCode::LE_SET_HOST_FEATURE) && module_.SupportsBleConnectedIsochronousStreamCentral()) {
hci_->EnqueueCommand(
LeSetHostFeatureBuilder::Create(LeHostFeatureBits::CONNECTED_ISO_STREAM_HOST_SUPPORT, Enable::ENABLED),
handler->BindOnceOn(this, &Controller::impl::le_set_host_feature_handler));
diff --git a/system/gd/hci/le_scanning_manager.cc b/system/gd/hci/le_scanning_manager.cc
index bc80498..1c1b562 100644
--- a/system/gd/hci/le_scanning_manager.cc
+++ b/system/gd/hci/le_scanning_manager.cc
@@ -501,6 +501,16 @@
return;
}
+ switch (address_type) {
+ case (uint8_t)AddressType::PUBLIC_DEVICE_ADDRESS:
+ case (uint8_t)AddressType::PUBLIC_IDENTITY_ADDRESS:
+ address_type = (uint8_t)AddressType::PUBLIC_DEVICE_ADDRESS;
+ break;
+ case (uint8_t)AddressType::RANDOM_DEVICE_ADDRESS:
+ case (uint8_t)AddressType::RANDOM_IDENTITY_ADDRESS:
+ address_type = (uint8_t)AddressType::RANDOM_DEVICE_ADDRESS;
+ break;
+ }
scanning_callbacks_->OnScanResult(
event_type,
address_type,
diff --git a/system/gd/rust/common/src/init_flags.rs b/system/gd/rust/common/src/init_flags.rs
index 80fe6da..33a41a1 100644
--- a/system/gd/rust/common/src/init_flags.rs
+++ b/system/gd/rust/common/src/init_flags.rs
@@ -77,7 +77,8 @@
gd_core,
gd_security,
gd_l2cap,
- gatt_robust_caching,
+ gatt_robust_caching_client,
+ gatt_robust_caching_server,
btaa_hci,
gd_rust,
gd_link_policy
diff --git a/system/gd/rust/shim/src/init_flags.rs b/system/gd/rust/shim/src/init_flags.rs
index fd3015c..688daa6 100644
--- a/system/gd/rust/shim/src/init_flags.rs
+++ b/system/gd/rust/shim/src/init_flags.rs
@@ -7,7 +7,8 @@
fn gd_core_is_enabled() -> bool;
fn gd_security_is_enabled() -> bool;
fn gd_l2cap_is_enabled() -> bool;
- fn gatt_robust_caching_is_enabled() -> bool;
+ fn gatt_robust_caching_client_is_enabled() -> bool;
+ fn gatt_robust_caching_server_is_enabled() -> bool;
fn btaa_hci_is_enabled() -> bool;
fn gd_rust_is_enabled() -> bool;
fn gd_link_policy_is_enabled() -> bool;
diff --git a/system/profile/avrcp/avrcp_internal.h b/system/profile/avrcp/avrcp_internal.h
index 89dfcb1..74620dc 100644
--- a/system/profile/avrcp/avrcp_internal.h
+++ b/system/profile/avrcp/avrcp_internal.h
@@ -64,6 +64,9 @@
virtual uint16_t MsgReq(uint8_t handle, uint8_t label, uint8_t ctype,
BT_HDR* p_pkt) = 0;
+ virtual void SaveControllerVersion(const RawAddress& bdaddr,
+ uint16_t version) = 0;
+
virtual ~AvrcpInterface() = default;
};
diff --git a/system/profile/avrcp/connection_handler.cc b/system/profile/avrcp/connection_handler.cc
index 680f1ac..db86cde 100644
--- a/system/profile/avrcp/connection_handler.cc
+++ b/system/profile/avrcp/connection_handler.cc
@@ -18,6 +18,7 @@
#include <base/bind.h>
#include <base/logging.h>
+
#include <map>
#include "avrc_defs.h"
@@ -488,6 +489,10 @@
}
}
}
+
+ if (osi_property_get_bool(AVRC_DYNAMIC_AVRCP_ENABLE_PROPERTY, true)) {
+ avrc_->SaveControllerVersion(bdaddr, peer_avrcp_version);
+ }
}
}
diff --git a/system/profile/avrcp/tests/avrcp_test_helper.h b/system/profile/avrcp/tests/avrcp_test_helper.h
index 0ed5911..eed3dbc 100644
--- a/system/profile/avrcp/tests/avrcp_test_helper.h
+++ b/system/profile/avrcp/tests/avrcp_test_helper.h
@@ -75,6 +75,7 @@
MOCK_METHOD1(Close, uint16_t(uint8_t));
MOCK_METHOD1(CloseBrowse, uint16_t(uint8_t));
MOCK_METHOD4(MsgReq, uint16_t(uint8_t, uint8_t, uint8_t, BT_HDR*));
+ MOCK_METHOD2(SaveControllerVersion, void(const RawAddress&, uint16_t));
};
class MockA2dpInterface : public A2dpInterface {
diff --git a/system/stack/Android.bp b/system/stack/Android.bp
index 85095ae..bcdfc93 100644
--- a/system/stack/Android.bp
+++ b/system/stack/Android.bp
@@ -1265,3 +1265,36 @@
},
},
}
+
+// Bluetooth stack connection multiplexing
+cc_test {
+ name: "net_test_stack_sdp",
+ defaults: ["fluoride_defaults"],
+ local_include_dirs: [
+ "include",
+ "test/common",
+ ],
+ include_dirs: [
+ "packages/modules/Bluetooth/system",
+ "packages/modules/Bluetooth/system/device/include/",
+ "packages/modules/Bluetooth/system/gd",
+ "packages/modules/Bluetooth/system/internal_include",
+ "packages/modules/Bluetooth/system/utils/include",
+ ],
+ srcs: [
+ ":TestCommonMockFunctions",
+ "sdp/sdp_main.cc",
+ "sdp/sdp_utils.cc",
+ "test/common/mock_btif_config.cc",
+ "test/stack_sdp_utils_test.cc",
+ ],
+ shared_libs: [
+ "libcutils",
+ ],
+ static_libs: [
+ "libbt-common",
+ "libbluetooth-types",
+ "liblog",
+ "libgmock",
+ ],
+}
diff --git a/system/stack/avrc/avrc_api.cc b/system/stack/avrc/avrc_api.cc
index d9b9014..c8f2f1c 100644
--- a/system/stack/avrc/avrc_api.cc
+++ b/system/stack/avrc/avrc_api.cc
@@ -27,6 +27,7 @@
#include <string.h>
#include "avrc_int.h"
+#include "btif/include/btif_config.h"
#include "osi/include/allocator.h"
#include "osi/include/fixed_queue.h"
#include "osi/include/log.h"
@@ -1363,3 +1364,44 @@
if (p_buf) return AVCT_MsgReq(handle, label, AVCT_RSP, p_buf);
return AVRC_NO_RESOURCES;
}
+
+/******************************************************************************
+ *
+ * Function AVRC_SaveControllerVersion
+ *
+ * Description Save AVRC controller version of peer device into bt_config.
+ * This version is used to send same AVRC target version to
+ * peer device to avoid version mismatch IOP issue.
+ *
+ * Input Parameters:
+ * bdaddr: BD address of peer device.
+ *
+ * version: AVRC controller version of peer device.
+ *
+ * Output Parameters:
+ * None.
+ *
+ * Returns Nothing
+ *
+ *****************************************************************************/
+void AVRC_SaveControllerVersion(const RawAddress& bdaddr,
+ uint16_t new_version) {
+ // store AVRC controller version into BT config
+ uint16_t old_version = 0;
+ size_t version_value_size = sizeof(old_version);
+ if (btif_config_get_bin(bdaddr.ToString(),
+ AVRCP_CONTROLLER_VERSION_CONFIG_KEY,
+ (uint8_t*)&old_version, &version_value_size) &&
+ new_version == old_version) {
+ LOG_INFO("AVRC controller version same as cached config");
+ } else if (btif_config_set_bin(
+ bdaddr.ToString(), AVRCP_CONTROLLER_VERSION_CONFIG_KEY,
+ (const uint8_t*)&new_version, sizeof(new_version))) {
+ btif_config_save();
+ LOG_INFO("store AVRC controller version %x for %s into config.",
+ new_version, bdaddr.ToString().c_str());
+ } else {
+ LOG_WARN("Failed to store AVRC controller version for %s",
+ bdaddr.ToString().c_str());
+ }
+}
diff --git a/system/stack/eatt/eatt_impl.h b/system/stack/eatt/eatt_impl.h
index 0324808..b2ea48d 100644
--- a/system/stack/eatt/eatt_impl.h
+++ b/system/stack/eatt/eatt_impl.h
@@ -118,10 +118,14 @@
*/
eatt_device* eatt_dev = this->find_device_by_address(bda);
if (!eatt_dev) {
- LOG(ERROR) << __func__ << " unknown device: " << bda;
- L2CA_ConnectCreditBasedRsp(bda, identifier, lcids,
- L2CAP_CONN_NO_RESOURCES, NULL);
- return;
+ /* If there is no device it means, Android did not read yet Server
+ * supported features, but according to Core 5.3, Vol 3, Part G, 6.2.1,
+ * for LE case it is not necessary to read it before establish connection.
+ * Therefore assume, device supports EATT since we got request to create
+ * EATT channels. Just create device here. */
+ LOG(INFO) << __func__ << " Adding device: " << bda
+ << " on incoming EATT creation request";
+ eatt_dev = add_eatt_device(bda);
}
uint16_t max_mps = controller_get_interface()->get_acl_data_size_ble();
diff --git a/system/stack/gatt/att_protocol.cc b/system/stack/gatt/att_protocol.cc
index 63847db..3683d40 100644
--- a/system/stack/gatt/att_protocol.cc
+++ b/system/stack/gatt/att_protocol.cc
@@ -353,8 +353,8 @@
}
/** Build ATT Server PDUs */
-BT_HDR* attp_build_sr_msg(tGATT_TCB& tcb, uint8_t op_code,
- tGATT_SR_MSG* p_msg) {
+BT_HDR* attp_build_sr_msg(tGATT_TCB& tcb, uint8_t op_code, tGATT_SR_MSG* p_msg,
+ uint16_t payload_size) {
uint16_t offset = 0;
switch (op_code) {
@@ -370,7 +370,7 @@
case GATT_HANDLE_VALUE_NOTIF:
case GATT_HANDLE_VALUE_IND:
return attp_build_value_cmd(
- tcb.payload_size, op_code, p_msg->attr_value.handle, offset,
+ payload_size, op_code, p_msg->attr_value.handle, offset,
p_msg->attr_value.len, p_msg->attr_value.value);
case GATT_RSP_WRITE:
diff --git a/system/stack/gatt/gatt_api.cc b/system/stack/gatt/gatt_api.cc
index 303223f..c523c86 100644
--- a/system/stack/gatt/gatt_api.cc
+++ b/system/stack/gatt/gatt_api.cc
@@ -468,8 +468,10 @@
tGATT_SR_MSG gatt_sr_msg;
gatt_sr_msg.attr_value = indication;
- BT_HDR* p_msg =
- attp_build_sr_msg(*p_tcb, GATT_HANDLE_VALUE_IND, &gatt_sr_msg);
+
+ uint16_t payload_size = gatt_tcb_get_payload_size_tx(*p_tcb, cid);
+ BT_HDR* p_msg = attp_build_sr_msg(*p_tcb, GATT_HANDLE_VALUE_IND, &gatt_sr_msg,
+ payload_size);
if (!p_msg) return GATT_NO_RESOURCES;
tGATT_STATUS cmd_status = attp_send_sr_msg(*p_tcb, cid, p_msg);
@@ -526,9 +528,9 @@
gatt_sr_msg.attr_value = notif;
uint16_t cid = gatt_tcb_get_att_cid(*p_tcb, p_reg->eatt_support);
-
- BT_HDR* p_buf =
- attp_build_sr_msg(*p_tcb, GATT_HANDLE_VALUE_NOTIF, &gatt_sr_msg);
+ uint16_t payload_size = gatt_tcb_get_payload_size_tx(*p_tcb, cid);
+ BT_HDR* p_buf = attp_build_sr_msg(*p_tcb, GATT_HANDLE_VALUE_NOTIF,
+ &gatt_sr_msg, payload_size);
if (p_buf != NULL) {
cmd_sent = attp_send_sr_msg(*p_tcb, cid, p_buf);
} else
diff --git a/system/stack/gatt/gatt_attr.cc b/system/stack/gatt/gatt_attr.cc
index ccb9645..c10e157 100644
--- a/system/stack/gatt/gatt_attr.cc
+++ b/system/stack/gatt/gatt_attr.cc
@@ -822,7 +822,7 @@
*
******************************************************************************/
static bool gatt_sr_is_robust_caching_enabled() {
- return bluetooth::common::init_flags::gatt_robust_caching_is_enabled();
+ return bluetooth::common::init_flags::gatt_robust_caching_server_is_enabled();
}
/*******************************************************************************
diff --git a/system/stack/gatt/gatt_int.h b/system/stack/gatt/gatt_int.h
index 0641d4c..ccc8be8 100644
--- a/system/stack/gatt/gatt_int.h
+++ b/system/stack/gatt/gatt_int.h
@@ -495,7 +495,7 @@
extern tGATT_STATUS attp_send_cl_msg(tGATT_TCB& tcb, tGATT_CLCB* p_clcb,
uint8_t op_code, tGATT_CL_MSG* p_msg);
extern BT_HDR* attp_build_sr_msg(tGATT_TCB& tcb, uint8_t op_code,
- tGATT_SR_MSG* p_msg);
+ tGATT_SR_MSG* p_msg, uint16_t payload_size);
extern tGATT_STATUS attp_send_sr_msg(tGATT_TCB& tcb, uint16_t cid,
BT_HDR* p_msg);
extern tGATT_STATUS attp_send_msg_to_l2cap(tGATT_TCB& tcb, uint16_t cid,
diff --git a/system/stack/gatt/gatt_sr.cc b/system/stack/gatt/gatt_sr.cc
index 8e642d0..903cdaa 100644
--- a/system/stack/gatt/gatt_sr.cc
+++ b/system/stack/gatt/gatt_sr.cc
@@ -286,7 +286,7 @@
tGATTS_RSP* p_msg,
tGATT_SR_CMD* sr_res_p) {
tGATT_STATUS ret_code = GATT_SUCCESS;
- uint16_t payload_size = gatt_tcb_get_payload_size_rx(tcb, sr_res_p->cid);
+ uint16_t payload_size = gatt_tcb_get_payload_size_tx(tcb, sr_res_p->cid);
VLOG(1) << __func__ << " gatt_if=" << +gatt_if;
@@ -308,8 +308,8 @@
if (gatt_sr_is_cback_cnt_zero(tcb) && status == GATT_SUCCESS) {
if (sr_res_p->p_rsp_msg == NULL) {
- sr_res_p->p_rsp_msg = attp_build_sr_msg(tcb, (uint8_t)(op_code + 1),
- (tGATT_SR_MSG*)p_msg);
+ sr_res_p->p_rsp_msg = attp_build_sr_msg(
+ tcb, (uint8_t)(op_code + 1), (tGATT_SR_MSG*)p_msg, payload_size);
} else {
LOG(ERROR) << "Exception!!! already has respond message";
}
@@ -828,7 +828,8 @@
tGATT_SR_MSG gatt_sr_msg;
gatt_sr_msg.mtu = tcb.payload_size;
- BT_HDR* p_buf = attp_build_sr_msg(tcb, GATT_RSP_MTU, &gatt_sr_msg);
+ BT_HDR* p_buf =
+ attp_build_sr_msg(tcb, GATT_RSP_MTU, &gatt_sr_msg, tcb.payload_size);
attp_send_sr_msg(tcb, cid, p_buf);
tGATTS_DATA gatts_data;
diff --git a/system/stack/gatt/gatt_utils.cc b/system/stack/gatt/gatt_utils.cc
index 991944a..471a5cb 100644
--- a/system/stack/gatt/gatt_utils.cc
+++ b/system/stack/gatt/gatt_utils.cc
@@ -813,7 +813,8 @@
msg.error.reason = err_code;
msg.error.handle = handle;
- p_buf = attp_build_sr_msg(tcb, GATT_RSP_ERROR, &msg);
+ uint16_t payload_size = gatt_tcb_get_payload_size_tx(tcb, cid);
+ p_buf = attp_build_sr_msg(tcb, GATT_RSP_ERROR, &msg, payload_size);
if (p_buf != NULL) {
status = attp_send_sr_msg(tcb, cid, p_buf);
} else
@@ -1397,7 +1398,12 @@
/** Cancel LE Create Connection request */
bool gatt_cancel_open(tGATT_IF gatt_if, const RawAddress& bda) {
tGATT_TCB* p_tcb = gatt_find_tcb_by_addr(bda, BT_TRANSPORT_LE);
- if (!p_tcb) return true;
+ if (!p_tcb) {
+ LOG_WARN(
+ "Unable to cancel open for unknown connection gatt_if:%hhu peer:%s",
+ gatt_if, PRIVATE_ADDRESS(bda));
+ return true;
+ }
if (gatt_get_ch_state(p_tcb) == GATT_CH_OPEN) {
LOG(ERROR) << __func__ << ": link connected Too late to cancel";
@@ -1406,9 +1412,21 @@
gatt_update_app_use_link_flag(gatt_if, p_tcb, false, false);
- if (p_tcb->app_hold_link.empty()) gatt_disconnect(p_tcb);
+ if (p_tcb->app_hold_link.empty()) {
+ LOG_DEBUG(
+ "Client reference count is zero disconnecting device gatt_if:%hhu "
+ "peer:%s",
+ gatt_if, PRIVATE_ADDRESS(bda));
+ gatt_disconnect(p_tcb);
+ }
- connection_manager::direct_connect_remove(gatt_if, bda);
+ if (!connection_manager::direct_connect_remove(gatt_if, bda)) {
+ BTM_AcceptlistRemove(bda);
+ LOG_INFO(
+ "GATT connection manager has no record but removed filter acceptlist "
+ "gatt_if:%hhu peer:%s",
+ gatt_if, PRIVATE_ADDRESS(bda));
+ }
return true;
}
diff --git a/system/stack/include/avrc_api.h b/system/stack/include/avrc_api.h
index 134f463..877dafe 100644
--- a/system/stack/include/avrc_api.h
+++ b/system/stack/include/avrc_api.h
@@ -138,6 +138,17 @@
#define AVRC_DEFAULT_VERSION AVRC_1_5_STRING
#endif
+/* Configurable dynamic avrcp version enable key*/
+#ifndef AVRC_DYNAMIC_AVRCP_ENABLE_PROPERTY
+#define AVRC_DYNAMIC_AVRCP_ENABLE_PROPERTY \
+ "persist.bluetooth.dynamic_avrcp.enable"
+#endif
+
+/* Avrcp controller version key for bt_config.conf */
+#ifndef AVRCP_CONTROLLER_VERSION_CONFIG_KEY
+#define AVRCP_CONTROLLER_VERSION_CONFIG_KEY "AvrcpControllerVersion"
+#endif
+
/* Supported categories */
#define AVRC_SUPF_CT_CAT1 0x0001 /* Category 1 */
#define AVRC_SUPF_CT_CAT2 0x0002 /* Category 2 */
@@ -468,6 +479,28 @@
/******************************************************************************
*
+ * Function AVRC_SaveControllerVersion
+ *
+ * Description Save AVRC controller version of peer device into bt_config.
+ * This version is used to send same AVRC target version to
+ * peer device to avoid version mismatch IOP issue.
+ *
+ * Input Parameters:
+ * bdaddr: BD address of peer device.
+ *
+ * version: AVRC controller version of peer device.
+ *
+ * Output Parameters:
+ * None.
+ *
+ * Returns Nothing
+ *
+ *****************************************************************************/
+extern void AVRC_SaveControllerVersion(const RawAddress& bdaddr,
+ uint16_t new_version);
+
+/******************************************************************************
+ *
* Function AVRC_UnitCmd
*
* Description Send a UNIT INFO command to the peer device. This
diff --git a/system/stack/l2cap/l2c_ble.cc b/system/stack/l2cap/l2c_ble.cc
index 5ab208c..0314b6e 100755
--- a/system/stack/l2cap/l2c_ble.cc
+++ b/system/stack/l2cap/l2c_ble.cc
@@ -715,22 +715,28 @@
for (int i = 0; i < p_lcb->pending_ecoc_conn_cnt; i++) {
uint16_t cid = p_lcb->pending_ecoc_connection_cids[i];
STREAM_TO_UINT16(rcid, p);
- /* if duplicated remote cid then disconnect original channel
- * and current channel by sending event to upper layer */
- temp_p_ccb = l2cu_find_ccb_by_remote_cid(p_lcb, rcid);
- if (temp_p_ccb != nullptr) {
- L2CAP_TRACE_ERROR(
- "Already Allocated Destination cid. "
- "rcid = %d "
- "send peer_disc_req", rcid);
- l2cu_send_peer_disc_req(temp_p_ccb);
+ if (rcid != 0) {
+ /* If remote cid is duplicated then disconnect original channel
+ * and current channel by sending event to upper layer
+ */
+ temp_p_ccb = l2cu_find_ccb_by_remote_cid(p_lcb, rcid);
+ if (temp_p_ccb != nullptr) {
+ L2CAP_TRACE_ERROR(
+ "Already Allocated Destination cid. "
+ "rcid = %d "
+ "send peer_disc_req",
+ rcid);
- temp_p_ccb = l2cu_find_ccb_by_cid(p_lcb, cid);
- con_info.l2cap_result = L2CAP_LE_RESULT_UNACCEPTABLE_PARAMETERS;
- l2c_csm_execute(temp_p_ccb, L2CEVT_L2CAP_CREDIT_BASED_CONNECT_RSP_NEG,
- &con_info);
- continue;
+ l2cu_send_peer_disc_req(temp_p_ccb);
+
+ temp_p_ccb = l2cu_find_ccb_by_cid(p_lcb, cid);
+ con_info.l2cap_result = L2CAP_LE_RESULT_UNACCEPTABLE_PARAMETERS;
+ l2c_csm_execute(temp_p_ccb,
+ L2CEVT_L2CAP_CREDIT_BASED_CONNECT_RSP_NEG,
+ &con_info);
+ continue;
+ }
}
temp_p_ccb = l2cu_find_ccb_by_cid(p_lcb, cid);
diff --git a/system/stack/sdp/sdp_server.cc b/system/stack/sdp/sdp_server.cc
index a486fb1..2ab2784 100644
--- a/system/stack/sdp/sdp_server.cc
+++ b/system/stack/sdp/sdp_server.cc
@@ -23,20 +23,21 @@
*
******************************************************************************/
+#include <base/logging.h>
#include <log/log.h>
#include <string.h> // memcpy
#include <cstdint>
+#include "btif/include/btif_config.h"
#include "device/include/interop.h"
#include "osi/include/allocator.h"
+#include "stack/include/avrc_api.h"
#include "stack/include/avrc_defs.h"
#include "stack/include/bt_hdr.h"
#include "stack/include/sdp_api.h"
#include "stack/sdp/sdpint.h"
-#include <base/logging.h>
-
/* Maximum number of bytes to reserve out of SDP MTU for response data */
#define SDP_MAX_SERVICE_RSPHDR_LEN 12
#define SDP_MAX_SERVATTR_RSPHDR_LEN 10
@@ -400,12 +401,22 @@
p_ccb->cont_info.attr_offset = 0;
}
+ bool is_service_avrc_target = false;
+ const tSDP_ATTRIBUTE* p_attr_service_id;
+ p_attr_service_id = sdp_db_find_attr_in_rec(
+ p_rec, ATTR_ID_SERVICE_CLASS_ID_LIST, ATTR_ID_SERVICE_CLASS_ID_LIST);
+ if (p_attr_service_id) {
+ is_service_avrc_target = sdpu_is_service_id_avrc_target(p_attr_service_id);
+ }
/* Search for attributes that match the list given to us */
for (xx = p_ccb->cont_info.next_attr_index; xx < attr_seq.num_attr; xx++) {
p_attr = sdp_db_find_attr_in_rec(p_rec, attr_seq.attr_entry[xx].start,
attr_seq.attr_entry[xx].end);
if (p_attr) {
+ if (is_service_avrc_target) {
+ sdpu_set_avrc_target_version(p_attr, &(p_ccb->device_address));
+ }
/* Check if attribute fits. Assume 3-byte value type/length */
rem_len = max_list_len - (int16_t)(p_rsp - &p_ccb->rsp_list[0]);
@@ -554,7 +565,6 @@
const tSDP_RECORD* p_rec;
tSDP_ATTR_SEQ attr_seq, attr_seq_sav;
const tSDP_ATTRIBUTE* p_attr;
- tSDP_ATTRIBUTE attr_sav;
bool maxxed_out = false, is_cont = false;
uint8_t* p_seq_start;
uint16_t seq_len, attr_len;
@@ -647,24 +657,22 @@
p_rsp += 3;
}
+ bool is_service_avrc_target = false;
+ const tSDP_ATTRIBUTE* p_attr_service_id;
+ p_attr_service_id = sdp_db_find_attr_in_rec(
+ p_rec, ATTR_ID_SERVICE_CLASS_ID_LIST, ATTR_ID_SERVICE_CLASS_ID_LIST);
+ if (p_attr_service_id) {
+ is_service_avrc_target =
+ sdpu_is_service_id_avrc_target(p_attr_service_id);
+ }
/* Get a list of handles that match the UUIDs given to us */
for (xx = p_ccb->cont_info.next_attr_index; xx < attr_seq.num_attr; xx++) {
p_attr = sdp_db_find_attr_in_rec(p_rec, attr_seq.attr_entry[xx].start,
attr_seq.attr_entry[xx].end);
if (p_attr) {
- // Check if the attribute contain AVRCP profile description list
- uint16_t avrcp_version = sdpu_is_avrcp_profile_description_list(p_attr);
- if (avrcp_version > AVRC_REV_1_4 &&
- interop_match_addr(INTEROP_AVRCP_1_4_ONLY,
- &(p_ccb->device_address))) {
- SDP_TRACE_DEBUG(
- "%s, device=%s is only accept AVRCP 1.4, reply AVRCP 1.4 "
- "instead.",
- __func__, p_ccb->device_address.ToString().c_str());
- memcpy(&attr_sav, p_attr, sizeof(tSDP_ATTRIBUTE));
- attr_sav.value_ptr[attr_sav.len - 1] = 0x04;
- p_attr = &attr_sav;
+ if (is_service_avrc_target) {
+ sdpu_set_avrc_target_version(p_attr, &(p_ccb->device_address));
}
/* Check if attribute fits. Assume 3-byte value type/length */
rem_len = max_list_len - (int16_t)(p_rsp - &p_ccb->rsp_list[0]);
diff --git a/system/stack/sdp/sdp_utils.cc b/system/stack/sdp/sdp_utils.cc
index 761883e..e919298 100644
--- a/system/stack/sdp/sdp_utils.cc
+++ b/system/stack/sdp/sdp_utils.cc
@@ -16,6 +16,8 @@
*
******************************************************************************/
+#define LOG_TAG "SDP_Utils"
+
/******************************************************************************
*
* This file contains SDP utility functions
@@ -28,12 +30,17 @@
#include <array>
#include <cstdint>
#include <cstring>
+#include <ostream>
#include <type_traits>
#include <utility>
#include <vector>
#include "btif/include/btif_config.h"
+#include "device/include/interop.h"
#include "osi/include/allocator.h"
+#include "osi/include/log.h"
+#include "osi/include/properties.h"
+#include "stack/include/avrc_api.h"
#include "stack/include/avrc_defs.h"
#include "stack/include/bt_hdr.h"
#include "stack/include/sdp_api.h"
@@ -1202,3 +1209,136 @@
return 0;
}
}
+/*******************************************************************************
+ *
+ * Function sdpu_is_service_id_avrc_target
+ *
+ * Description This function is to check if attirbute is A/V Remote Control
+ * Target
+ *
+ * p_attr: attribute to be checked
+ *
+ * Returns true if service id of attirbute is A/V Remote Control
+ * Target, else false
+ *
+ ******************************************************************************/
+bool sdpu_is_service_id_avrc_target(const tSDP_ATTRIBUTE* p_attr) {
+ if (p_attr->id != ATTR_ID_SERVICE_CLASS_ID_LIST || p_attr->len != 3) {
+ return false;
+ }
+
+ uint8_t* p_uuid = p_attr->value_ptr + 1;
+ // check UUID of A/V Remote Control Target
+ if (p_uuid[0] != 0x11 || p_uuid[1] != 0xc) {
+ return false;
+ }
+
+ return true;
+}
+/*******************************************************************************
+ *
+ * Function spdu_is_avrcp_version_valid
+ *
+ * Description Check avrcp version is valid
+ *
+ * version: the avrcp version to check
+ *
+ * Returns true if avrcp version is valid, else false
+ *
+ ******************************************************************************/
+bool spdu_is_avrcp_version_valid(const uint16_t version) {
+ return version == AVRC_REV_1_0 || version == AVRC_REV_1_3 ||
+ version == AVRC_REV_1_4 || version == AVRC_REV_1_5 ||
+ version == AVRC_REV_1_6;
+}
+/*******************************************************************************
+ *
+ * Function sdpu_set_avrc_target_version
+ *
+ * Description This function is to set AVRCP version of A/V Remote Control
+ * Target according to IOP table and cached Bluetooth config
+ *
+ * p_attr: attribute to be modified
+ * bdaddr: for searching IOP table and BT config
+ *
+ *
+ * Returns true if service id of attirbute is A/V Remote Control
+ * Target, else false
+ *
+ ******************************************************************************/
+void sdpu_set_avrc_target_version(const tSDP_ATTRIBUTE* p_attr,
+ const RawAddress* bdaddr) {
+ // Check attribute is AVRCP profile description list and get AVRC Target
+ // version
+ uint16_t avrcp_version = sdpu_is_avrcp_profile_description_list(p_attr);
+ if (avrcp_version == 0) {
+ LOG_INFO("Not AVRCP version attribute or version not valid for device %s",
+ bdaddr->ToString().c_str());
+ return;
+ }
+
+ // Some remote devices will have interoperation issue when receive AVRCP
+ // version 1.5. If those devices are in IOP database and our version high than
+ // 1.4, we reply version 1.4 to them.
+ if (avrcp_version > AVRC_REV_1_4 &&
+ interop_match_addr(INTEROP_AVRCP_1_4_ONLY, bdaddr)) {
+ LOG_INFO(
+ "device=%s is in IOP database. "
+ "Reply AVRC Target version 1.4 instead of %x.",
+ bdaddr->ToString().c_str(), avrcp_version);
+ uint8_t* p_version = p_attr->value_ptr + 6;
+ UINT16_TO_BE_FIELD(p_version, AVRC_REV_1_4);
+ return;
+ }
+
+ // Dynamic ACRCP version. If our version high than remote device's version,
+ // reply version same as its. Otherwise, reply default version.
+ if (!osi_property_get_bool(AVRC_DYNAMIC_AVRCP_ENABLE_PROPERTY, true)) {
+ LOG_INFO(
+ "Dynamic AVRCP version feature is not enabled, skipping this method");
+ return;
+ }
+
+ // Read the remote device's AVRC Controller version from local storage
+ uint16_t cached_version = 0;
+ size_t version_value_size = btif_config_get_bin_length(
+ bdaddr->ToString(), AVRCP_CONTROLLER_VERSION_CONFIG_KEY);
+ if (version_value_size != sizeof(cached_version)) {
+ LOG_ERROR(
+ "cached value len wrong, bdaddr=%s. Len is %zu but should be %zu.",
+ bdaddr->ToString().c_str(), version_value_size, sizeof(cached_version));
+ return;
+ }
+
+ if (!btif_config_get_bin(bdaddr->ToString(),
+ AVRCP_CONTROLLER_VERSION_CONFIG_KEY,
+ (uint8_t*)&cached_version, &version_value_size)) {
+ LOG_INFO(
+ "no cached AVRC Controller version for %s. "
+ "Reply default AVRC Target version %x.",
+ bdaddr->ToString().c_str(), avrcp_version);
+ return;
+ }
+
+ if (!spdu_is_avrcp_version_valid(cached_version)) {
+ LOG_ERROR(
+ "cached AVRC Controller version %x of %s is not valid. "
+ "Reply default AVRC Target version %x.",
+ cached_version, bdaddr->ToString().c_str(), avrcp_version);
+ return;
+ }
+
+ if (avrcp_version > cached_version) {
+ LOG_INFO(
+ "read cached AVRC Controller version %x of %s. "
+ "Reply AVRC Target version %x.",
+ cached_version, bdaddr->ToString().c_str(), cached_version);
+ uint8_t* p_version = p_attr->value_ptr + 6;
+ UINT16_TO_BE_FIELD(p_version, cached_version);
+ } else {
+ LOG_INFO(
+ "read cached AVRC Controller version %x of %s. "
+ "Reply default AVRC Target version %x.",
+ cached_version, bdaddr->ToString().c_str(), avrcp_version);
+ }
+}
diff --git a/system/stack/sdp/sdpint.h b/system/stack/sdp/sdpint.h
index 74816dd..7b25bbc 100644
--- a/system/stack/sdp/sdpint.h
+++ b/system/stack/sdp/sdpint.h
@@ -232,6 +232,10 @@
uint16_t len, uint16_t* offset);
extern uint16_t sdpu_is_avrcp_profile_description_list(
const tSDP_ATTRIBUTE* p_attr);
+extern bool sdpu_is_service_id_avrc_target(const tSDP_ATTRIBUTE* p_attr);
+extern bool spdu_is_avrcp_version_valid(const uint16_t version);
+extern void sdpu_set_avrc_target_version(const tSDP_ATTRIBUTE* p_attr,
+ const RawAddress* bdaddr);
/* Functions provided by sdp_db.cc
*/
diff --git a/system/stack/test/common/mock_btif_config.cc b/system/stack/test/common/mock_btif_config.cc
new file mode 100644
index 0000000..08fc80d
--- /dev/null
+++ b/system/stack/test/common/mock_btif_config.cc
@@ -0,0 +1,37 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ ******************************************************************************/
+
+#include "mock_btif_config.h"
+
+static bluetooth::manager::MockBtifConfigInterface* btif_config_interface =
+ nullptr;
+
+void bluetooth::manager::SetMockBtifConfigInterface(
+ MockBtifConfigInterface* mock_btif_config_interface) {
+ btif_config_interface = mock_btif_config_interface;
+}
+
+bool btif_config_get_bin(const std::string& section, const std::string& key,
+ uint8_t* value, size_t* length) {
+ return btif_config_interface->GetBin(section, key, value, length);
+}
+
+size_t btif_config_get_bin_length(const std::string& section,
+ const std::string& key) {
+ return btif_config_interface->GetBinLength(section, key);
+}
diff --git a/system/stack/test/common/mock_btif_config.h b/system/stack/test/common/mock_btif_config.h
new file mode 100644
index 0000000..9f11c43
--- /dev/null
+++ b/system/stack/test/common/mock_btif_config.h
@@ -0,0 +1,54 @@
+/******************************************************************************
+ *
+ * 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.
+ *
+ ******************************************************************************/
+#pragma once
+
+#include <gmock/gmock.h>
+
+#include <cstddef>
+
+namespace bluetooth {
+namespace manager {
+
+class BtifConfigInterface {
+ public:
+ virtual bool GetBin(const std::string& section, const std::string& key,
+ uint8_t* value, size_t* length) = 0;
+ virtual size_t GetBinLength(const std::string& section,
+ const std::string& key) = 0;
+ virtual ~BtifConfigInterface() = default;
+};
+
+class MockBtifConfigInterface : public BtifConfigInterface {
+ public:
+ MOCK_METHOD4(GetBin, bool(const std::string& section, const std::string& key,
+ uint8_t* value, size_t* length));
+ MOCK_METHOD2(GetBinLength,
+ size_t(const std::string& section, const std::string& key));
+};
+
+/**
+ * Set the {@link MockBtifConfigInterface} for testing
+ *
+ * @param mock_btif_config_interface pointer to mock btif config interface,
+ * could be null
+ */
+void SetMockBtifConfigInterface(
+ MockBtifConfigInterface* mock_btif_config_interface);
+
+} // namespace manager
+} // namespace bluetooth
diff --git a/system/stack/test/eatt/eatt_test.cc b/system/stack/test/eatt/eatt_test.cc
index 3eda92c..32c5848 100644
--- a/system/stack/test/eatt/eatt_test.cc
+++ b/system/stack/test/eatt/eatt_test.cc
@@ -185,13 +185,15 @@
TEST_F(EattTest, IncomingEattConnectionByUnknownDevice) {
std::vector<uint16_t> incoming_cids{71, 72, 73, 74, 75};
- EXPECT_CALL(l2cap_interface_,
- ConnectCreditBasedRsp(test_address, 1, incoming_cids,
- L2CAP_CONN_NO_RESOURCES, _))
+ EXPECT_CALL(
+ l2cap_interface_,
+ ConnectCreditBasedRsp(test_address, 1, incoming_cids, L2CAP_CONN_OK, _))
.WillOnce(Return(true));
l2cap_app_info_.pL2CA_CreditBasedConnectInd_Cb(
test_address, incoming_cids, BT_PSM_EATT, EATT_MIN_MTU_MPS, 1);
+
+ DisconnectEattDevice(incoming_cids);
}
TEST_F(EattTest, IncomingEattConnectionByKnownDevice) {
diff --git a/system/stack/test/gatt/gatt_sr_test.cc b/system/stack/test/gatt/gatt_sr_test.cc
index 913cf61..7e6ab31 100644
--- a/system/stack/test/gatt/gatt_sr_test.cc
+++ b/system/stack/test/gatt/gatt_sr_test.cc
@@ -65,8 +65,8 @@
}
} // namespace connection_manager
-BT_HDR* attp_build_sr_msg(tGATT_TCB& tcb, uint8_t op_code,
- tGATT_SR_MSG* p_msg) {
+BT_HDR* attp_build_sr_msg(tGATT_TCB& tcb, uint8_t op_code, tGATT_SR_MSG* p_msg,
+ uint16_t payload_size) {
test_state_.attp_build_sr_msg.op_code_ = op_code;
return nullptr;
}
diff --git a/system/stack/test/gatt/mock_gatt_utils_ref.cc b/system/stack/test/gatt/mock_gatt_utils_ref.cc
index 1c96dd4..cb01fd6 100644
--- a/system/stack/test/gatt/mock_gatt_utils_ref.cc
+++ b/system/stack/test/gatt/mock_gatt_utils_ref.cc
@@ -35,8 +35,8 @@
} // namespace connection_manager
/** stack/gatt/att_protocol.cc */
-BT_HDR* attp_build_sr_msg(tGATT_TCB& tcb, uint8_t op_code,
- tGATT_SR_MSG* p_msg) {
+BT_HDR* attp_build_sr_msg(tGATT_TCB& tcb, uint8_t op_code, tGATT_SR_MSG* p_msg,
+ uint16_t payload_size) {
return nullptr;
}
tGATT_STATUS attp_send_cl_confirmation_msg(tGATT_TCB& tcb, uint16_t cid) {
diff --git a/system/stack/test/stack_sdp_utils_test.cc b/system/stack/test/stack_sdp_utils_test.cc
new file mode 100644
index 0000000..200a429
--- /dev/null
+++ b/system/stack/test/stack_sdp_utils_test.cc
@@ -0,0 +1,228 @@
+/*
+ * 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.
+ */
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <cstddef>
+
+#include "bt_types.h"
+#include "device/include/interop.h"
+#include "mock_btif_config.h"
+#include "stack/include/avrc_defs.h"
+#include "stack/include/sdp_api.h"
+#include "stack/sdp/sdpint.h"
+
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArrayArgument;
+
+// Global trace level referred in the code under test
+uint8_t appl_trace_level = BT_TRACE_LEVEL_VERBOSE;
+
+extern "C" void LogMsg(uint32_t trace_set_mask, const char* fmt_str, ...) {}
+
+namespace {
+// convenience mock
+class IopMock {
+ public:
+ MOCK_METHOD2(InteropMatchAddr,
+ bool(const interop_feature_t, const RawAddress*));
+};
+
+std::unique_ptr<IopMock> localIopMock;
+} // namespace
+
+bool interop_match_addr(const interop_feature_t feature,
+ const RawAddress* addr) {
+ return localIopMock->InteropMatchAddr(feature, addr);
+}
+
+bool osi_property_get_bool(const char* key, bool default_value) { return true; }
+
+uint8_t avrc_value[8] = {
+ ((DATA_ELE_SEQ_DESC_TYPE << 3) | SIZE_IN_NEXT_BYTE), // data_element
+ 6, // data_len
+ ((UUID_DESC_TYPE << 3) | SIZE_TWO_BYTES), // uuid_element
+ 0, // uuid
+ 0, // uuid
+ ((UINT_DESC_TYPE << 3) | SIZE_TWO_BYTES), // version_element
+ 0, // version
+ 0 // version
+};
+tSDP_ATTRIBUTE avrcp_attr = {
+ .len = 0,
+ .value_ptr = (uint8_t*)(&avrc_value),
+ .id = 0,
+ .type = 0,
+};
+
+void set_avrcp_attr(uint32_t len, uint16_t id, uint16_t uuid,
+ uint16_t version) {
+ UINT16_TO_BE_FIELD(avrc_value + 3, uuid);
+ UINT16_TO_BE_FIELD(avrc_value + 6, version);
+ avrcp_attr.len = len;
+ avrcp_attr.id = id;
+}
+
+uint16_t get_avrc_target_version(tSDP_ATTRIBUTE* p_attr) {
+ uint8_t* p_version = p_attr->value_ptr + 6;
+ uint16_t version =
+ (((uint16_t)(*(p_version))) << 8) + ((uint16_t)(*((p_version) + 1)));
+ return version;
+}
+
+class StackSdpUtilsTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ bluetooth::manager::SetMockBtifConfigInterface(&btif_config_interface_);
+ localIopMock = std::make_unique<IopMock>();
+ set_avrcp_attr(8, ATTR_ID_BT_PROFILE_DESC_LIST,
+ UUID_SERVCLASS_AV_REMOTE_CONTROL, AVRC_REV_1_5);
+ }
+
+ void TearDown() override {
+ bluetooth::manager::SetMockBtifConfigInterface(nullptr);
+ localIopMock.reset();
+ }
+ bluetooth::manager::MockBtifConfigInterface btif_config_interface_;
+};
+
+TEST_F(StackSdpUtilsTest, sdpu_set_avrc_target_version_device_in_iop_table) {
+ RawAddress bdaddr;
+ EXPECT_CALL(*localIopMock, InteropMatchAddr(INTEROP_AVRCP_1_4_ONLY, &bdaddr))
+ .WillOnce(Return(true));
+ sdpu_set_avrc_target_version(&avrcp_attr, &bdaddr);
+ ASSERT_EQ(get_avrc_target_version(&avrcp_attr), AVRC_REV_1_4);
+}
+
+TEST_F(StackSdpUtilsTest, sdpu_set_avrc_target_version_wrong_len) {
+ RawAddress bdaddr;
+ set_avrcp_attr(5, ATTR_ID_BT_PROFILE_DESC_LIST,
+ UUID_SERVCLASS_AV_REMOTE_CONTROL, AVRC_REV_1_5);
+ sdpu_set_avrc_target_version(&avrcp_attr, &bdaddr);
+ ASSERT_EQ(get_avrc_target_version(&avrcp_attr), AVRC_REV_1_5);
+}
+
+TEST_F(StackSdpUtilsTest, sdpu_set_avrc_target_version_wrong_attribute_id) {
+ RawAddress bdaddr;
+ set_avrcp_attr(8, ATTR_ID_SERVICE_CLASS_ID_LIST,
+ UUID_SERVCLASS_AV_REMOTE_CONTROL, AVRC_REV_1_5);
+ sdpu_set_avrc_target_version(&avrcp_attr, &bdaddr);
+ ASSERT_EQ(get_avrc_target_version(&avrcp_attr), AVRC_REV_1_5);
+}
+
+TEST_F(StackSdpUtilsTest, sdpu_set_avrc_target_version_wrong_uuid) {
+ RawAddress bdaddr;
+ set_avrcp_attr(8, ATTR_ID_BT_PROFILE_DESC_LIST, UUID_SERVCLASS_AUDIO_SOURCE,
+ AVRC_REV_1_5);
+ sdpu_set_avrc_target_version(&avrcp_attr, &bdaddr);
+ ASSERT_EQ(get_avrc_target_version(&avrcp_attr), AVRC_REV_1_5);
+}
+
+// device's controller version older than our target version
+TEST_F(StackSdpUtilsTest, sdpu_set_avrc_target_version_device_older_version) {
+ RawAddress bdaddr;
+ uint8_t config_0104[2] = {0x04, 0x01};
+ EXPECT_CALL(*localIopMock, InteropMatchAddr(INTEROP_AVRCP_1_4_ONLY, &bdaddr))
+ .WillOnce(Return(false));
+ EXPECT_CALL(btif_config_interface_, GetBinLength(bdaddr.ToString(), _))
+ .WillOnce(Return(2));
+ EXPECT_CALL(btif_config_interface_, GetBin(bdaddr.ToString(), _, _, _))
+ .WillOnce(DoAll(SetArrayArgument<2>(config_0104, config_0104 + 2),
+ Return(true)));
+ sdpu_set_avrc_target_version(&avrcp_attr, &bdaddr);
+ ASSERT_EQ(get_avrc_target_version(&avrcp_attr), AVRC_REV_1_4);
+}
+
+// device's controller version same as our target version
+TEST_F(StackSdpUtilsTest, sdpu_set_avrc_target_version_device_same_version) {
+ RawAddress bdaddr;
+ uint8_t config_0105[2] = {0x05, 0x01};
+ EXPECT_CALL(*localIopMock, InteropMatchAddr(INTEROP_AVRCP_1_4_ONLY, &bdaddr))
+ .WillOnce(Return(false));
+ EXPECT_CALL(btif_config_interface_, GetBinLength(bdaddr.ToString(), _))
+ .WillOnce(Return(2));
+ EXPECT_CALL(btif_config_interface_, GetBin(bdaddr.ToString(), _, _, _))
+ .WillOnce(DoAll(SetArrayArgument<2>(config_0105, config_0105 + 2),
+ Return(true)));
+ sdpu_set_avrc_target_version(&avrcp_attr, &bdaddr);
+ ASSERT_EQ(get_avrc_target_version(&avrcp_attr), AVRC_REV_1_5);
+}
+
+// device's controller version higher than our target version
+TEST_F(StackSdpUtilsTest, sdpu_set_avrc_target_version_device_newer_version) {
+ RawAddress bdaddr;
+ uint8_t config_0106[2] = {0x06, 0x01};
+ EXPECT_CALL(*localIopMock, InteropMatchAddr(INTEROP_AVRCP_1_4_ONLY, &bdaddr))
+ .WillOnce(Return(false));
+ EXPECT_CALL(btif_config_interface_, GetBinLength(bdaddr.ToString(), _))
+ .WillOnce(Return(2));
+ EXPECT_CALL(btif_config_interface_, GetBin(bdaddr.ToString(), _, _, _))
+ .WillOnce(DoAll(SetArrayArgument<2>(config_0106, config_0106 + 2),
+ Return(true)));
+ sdpu_set_avrc_target_version(&avrcp_attr, &bdaddr);
+ ASSERT_EQ(get_avrc_target_version(&avrcp_attr), AVRC_REV_1_5);
+}
+
+// cannot read device's controller version from bt_config
+TEST_F(StackSdpUtilsTest, sdpu_set_avrc_target_version_no_config_value) {
+ RawAddress bdaddr;
+ EXPECT_CALL(*localIopMock, InteropMatchAddr(INTEROP_AVRCP_1_4_ONLY, &bdaddr))
+ .WillOnce(Return(false));
+ EXPECT_CALL(btif_config_interface_, GetBinLength(bdaddr.ToString(), _))
+ .WillOnce(Return(0));
+ sdpu_set_avrc_target_version(&avrcp_attr, &bdaddr);
+ ASSERT_EQ(get_avrc_target_version(&avrcp_attr), AVRC_REV_1_5);
+}
+
+// read device's controller version from bt_config return only 1 byte
+TEST_F(StackSdpUtilsTest, sdpu_set_avrc_target_version_config_value_1_byte) {
+ RawAddress bdaddr;
+ EXPECT_CALL(*localIopMock, InteropMatchAddr(INTEROP_AVRCP_1_4_ONLY, &bdaddr))
+ .WillOnce(Return(false));
+ EXPECT_CALL(btif_config_interface_, GetBinLength(bdaddr.ToString(), _))
+ .WillOnce(Return(1));
+ sdpu_set_avrc_target_version(&avrcp_attr, &bdaddr);
+ ASSERT_EQ(get_avrc_target_version(&avrcp_attr), AVRC_REV_1_5);
+}
+
+// read device's controller version from bt_config return 3 bytes
+TEST_F(StackSdpUtilsTest, sdpu_set_avrc_target_version_config_value_3_bytes) {
+ RawAddress bdaddr;
+ EXPECT_CALL(*localIopMock, InteropMatchAddr(INTEROP_AVRCP_1_4_ONLY, &bdaddr))
+ .WillOnce(Return(false));
+ EXPECT_CALL(btif_config_interface_, GetBinLength(bdaddr.ToString(), _))
+ .WillOnce(Return(3));
+ sdpu_set_avrc_target_version(&avrcp_attr, &bdaddr);
+ ASSERT_EQ(get_avrc_target_version(&avrcp_attr), AVRC_REV_1_5);
+}
+
+// cached controller version is not valid
+TEST_F(StackSdpUtilsTest, sdpu_set_avrc_target_version_config_value_not_valid) {
+ RawAddress bdaddr;
+ uint8_t config_not_valid[2] = {0x12, 0x34};
+ EXPECT_CALL(*localIopMock, InteropMatchAddr(INTEROP_AVRCP_1_4_ONLY, &bdaddr))
+ .WillOnce(Return(false));
+ EXPECT_CALL(btif_config_interface_, GetBinLength(bdaddr.ToString(), _))
+ .WillOnce(Return(2));
+ EXPECT_CALL(btif_config_interface_, GetBin(bdaddr.ToString(), _, _, _))
+ .WillOnce(
+ DoAll(SetArrayArgument<2>(config_not_valid, config_not_valid + 2),
+ Return(true)));
+ sdpu_set_avrc_target_version(&avrcp_attr, &bdaddr);
+ ASSERT_EQ(get_avrc_target_version(&avrcp_attr), AVRC_REV_1_5);
+}
diff --git a/system/test/mock/mock_stack_avrc_api.cc b/system/test/mock/mock_stack_avrc_api.cc
index 4d86f4a..c9e58a9 100644
--- a/system/test/mock/mock_stack_avrc_api.cc
+++ b/system/test/mock/mock_stack_avrc_api.cc
@@ -66,6 +66,9 @@
mock_function_count_map[__func__]++;
return 0;
}
+void AVRC_SaveControllerVersion(const RawAddress& bdaddr, uint16_t version) {
+ mock_function_count_map[__func__]++;
+}
uint16_t AVRC_PassCmd(uint8_t handle, uint8_t label, tAVRC_MSG_PASS* p_msg) {
mock_function_count_map[__func__]++;
return 0;