summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--android/app/jni/com_android_bluetooth_gatt.cpp7
-rwxr-xr-xandroid/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java8
-rw-r--r--android/app/src/com/android/bluetooth/gatt/GattNativeInterface.java635
-rw-r--r--android/app/src/com/android/bluetooth/gatt/GattObjectsFactory.java63
-rw-r--r--android/app/src/com/android/bluetooth/gatt/GattService.java202
-rw-r--r--android/app/src/com/android/bluetooth/le_audio/LeAudioService.java6
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java23
-rw-r--r--android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java6
-rw-r--r--android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanActivity.java5
-rw-r--r--android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java27
-rw-r--r--android/pandora/mmi2grpc/mmi2grpc/gatt.py250
-rw-r--r--android/pandora/server/configs/PtsBotTest.xml1
-rw-r--r--android/pandora/server/configs/pts_bot_tests_config.json39
-rw-r--r--android/pandora/server/proto/pandora/gatt.proto54
-rw-r--r--android/pandora/server/src/com/android/pandora/Gatt.kt124
-rw-r--r--android/pandora/server/src/com/android/pandora/GattInstance.kt95
-rw-r--r--framework/java/android/bluetooth/BluetoothAdapter.java46
-rw-r--r--framework/java/android/bluetooth/BluetoothDevice.java26
-rw-r--r--framework/java/android/bluetooth/BluetoothMap.java29
-rw-r--r--framework/java/android/bluetooth/BluetoothSap.java29
-rw-r--r--service/Android.bp18
-rw-r--r--service/java/com/android/server/bluetooth/BluetoothDeviceConfigChangeTracker.java85
-rw-r--r--service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java44
-rw-r--r--service/java/com/android/server/bluetooth/BluetoothNotificationManager.java7
-rw-r--r--service/tests/Android.bp2
-rw-r--r--service/tests/src/com/android/server/BluetoothDeviceConfigChangeTrackerTest.java167
-rw-r--r--system/bta/Android.bp4
-rw-r--r--system/bta/ag/bta_ag_act.cc2
-rw-r--r--system/bta/ag/bta_ag_sdp.cc2
-rw-r--r--system/bta/csis/csis_client.cc13
-rw-r--r--system/bta/le_audio/audio_set_configurations.json72
-rw-r--r--system/bta/le_audio/audio_set_scenarios.json15
-rw-r--r--system/bta/le_audio/broadcaster/broadcaster.cc128
-rw-r--r--system/bta/le_audio/broadcaster/broadcaster_test.cc107
-rw-r--r--system/bta/le_audio/broadcaster/mock_state_machine.h1
-rw-r--r--system/bta/le_audio/client.cc286
-rw-r--r--system/bta/le_audio/devices.cc53
-rw-r--r--system/bta/le_audio/devices.h24
-rw-r--r--system/bta/le_audio/devices_test.cc137
-rw-r--r--system/bta/le_audio/le_audio_client_test.cc49
-rw-r--r--system/bta/le_audio/le_audio_types.cc69
-rw-r--r--system/bta/le_audio/le_audio_types.h6
-rw-r--r--system/bta/le_audio/le_audio_utils.cc169
-rw-r--r--system/bta/le_audio/le_audio_utils.h36
-rw-r--r--system/bta/le_audio/metrics_collector.cc29
-rw-r--r--system/bta/le_audio/metrics_collector.h15
-rw-r--r--system/bta/le_audio/metrics_collector_linux.cc2
-rw-r--r--system/bta/le_audio/metrics_collector_test.cc17
-rw-r--r--system/bta/le_audio/mock_state_machine.h8
-rw-r--r--system/bta/le_audio/state_machine.cc61
-rw-r--r--system/bta/le_audio/state_machine.h8
-rw-r--r--system/bta/le_audio/state_machine_test.cc100
-rw-r--r--system/bta/vc/vc.cc2
-rw-r--r--system/btif/src/btif_dm.cc11
-rw-r--r--system/common/metrics.cc7
-rw-r--r--system/common/metrics.h2
-rw-r--r--system/common/metrics_linux.cc2
-rw-r--r--system/embdrv/aptx/README.md1
-rw-r--r--system/embdrv/aptx/aptxhd/README.md1
-rw-r--r--system/gd/Android.bp2
-rw-r--r--system/gd/rust/linux/mgmt/Cargo.toml2
-rw-r--r--system/gd/rust/linux/mgmt/src/state_machine.rs4
-rw-r--r--system/gd/rust/linux/utils/Cargo.toml (renamed from system/gd/rust/linux/socket/Cargo.toml)3
-rw-r--r--system/gd/rust/linux/utils/src/lib.rs7
-rw-r--r--system/gd/rust/linux/utils/src/socket.rs (renamed from system/gd/rust/linux/socket/src/lib.rs)3
-rw-r--r--system/gd/rust/linux/utils/src/uinput.rs199
-rw-r--r--system/stack/btm/btm_sco_hci.cc5
-rw-r--r--system/test/mock/mock_common_metrics.cc4
-rw-r--r--tools/pdl/Android.bp37
-rwxr-xr-xtools/pdl/scripts/generate_python_backend.py351
-rw-r--r--tools/pdl/scripts/pdl/core.py52
-rw-r--r--tools/pdl/tests/canonical/be_test_vectors.json29
-rw-r--r--tools/pdl/tests/canonical/le_test_file.pdl38
-rw-r--r--tools/pdl/tests/canonical/le_test_vectors.json71
-rw-r--r--tools/pdl/tests/custom_types.py4
-rw-r--r--tools/pdl/tests/python_generator_test.py83
76 files changed, 3577 insertions, 754 deletions
diff --git a/android/app/jni/com_android_bluetooth_gatt.cpp b/android/app/jni/com_android_bluetooth_gatt.cpp
index 963eb56f74..bd46bf5fd4 100644
--- a/android/app/jni/com_android_bluetooth_gatt.cpp
+++ b/android/app/jni/com_android_bluetooth_gatt.cpp
@@ -2441,7 +2441,7 @@ static JNINativeMethod sScanMethods[] = {
(void*)gattSetScanParametersNative},
};
-// JNI functions defined in GattService class.
+// JNI functions defined in GattNativeInterface class.
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void*)classInitNative},
{"initializeNative", "()V", (void*)initializeNative},
@@ -2526,7 +2526,8 @@ int register_com_android_bluetooth_gatt(JNIEnv* env) {
env, "com/android/bluetooth/gatt/PeriodicScanManager",
sPeriodicScanMethods, NELEM(sPeriodicScanMethods));
return register_success &
- jniRegisterNativeMethods(env, "com/android/bluetooth/gatt/GattService",
- sMethods, NELEM(sMethods));
+ jniRegisterNativeMethods(
+ env, "com/android/bluetooth/gatt/GattNativeInterface", sMethods,
+ NELEM(sMethods));
}
} // namespace android
diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java
index b320b8a2c8..4b2ca367ef 100755
--- a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java
+++ b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java
@@ -772,9 +772,11 @@ public class BassClientStateMachine extends StateMachine {
log("processBroadcastReceiverState: characteristic:" + characteristic);
BluetoothLeBroadcastReceiveState recvState = parseBroadcastReceiverState(
receiverState);
- if (recvState == null || recvState.getSourceId() == -1) {
- log("Null recvState or processBroadcastReceiverState: invalid index: "
- + recvState.getSourceId());
+ if (recvState == null) {
+ log("processBroadcastReceiverState: Null recvState");
+ return;
+ } else if (recvState.getSourceId() == -1) {
+ log("processBroadcastReceiverState: invalid index: " + recvState.getSourceId());
return;
}
BluetoothLeBroadcastReceiveState oldRecvState =
diff --git a/android/app/src/com/android/bluetooth/gatt/GattNativeInterface.java b/android/app/src/com/android/bluetooth/gatt/GattNativeInterface.java
new file mode 100644
index 0000000000..8786890a71
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/gatt/GattNativeInterface.java
@@ -0,0 +1,635 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.gatt;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * GATT Profile Native Interface to/from JNI.
+ */
+public class GattNativeInterface {
+ private static final String TAG = GattNativeInterface.class.getSimpleName();
+
+ static {
+ classInitNative();
+ }
+
+ private static GattNativeInterface sInterface;
+ private static final Object INSTANCE_LOCK = new Object();
+
+ private GattService mGattService;
+
+ private GattNativeInterface() {}
+
+ GattService getGattService() {
+ return mGattService;
+ }
+
+ /**
+ * This class is a singleton because native library should only be loaded once
+ *
+ * @return default instance
+ */
+ public static GattNativeInterface getInstance() {
+ synchronized (INSTANCE_LOCK) {
+ if (sInterface == null) {
+ sInterface = new GattNativeInterface();
+ }
+ }
+ return sInterface;
+ }
+
+
+ /* Callbacks */
+
+ void onScanResult(int eventType, int addressType, String address, int primaryPhy,
+ int secondaryPhy, int advertisingSid, int txPower, int rssi, int periodicAdvInt,
+ byte[] advData, String originalAddress) {
+ getGattService().onScanResult(eventType, addressType, address, primaryPhy, secondaryPhy,
+ advertisingSid, txPower, rssi, periodicAdvInt, advData, originalAddress);
+ }
+
+ void onScannerRegistered(int status, int scannerId, long uuidLsb, long uuidMsb)
+ throws RemoteException {
+ getGattService().onScannerRegistered(status, scannerId, uuidLsb, uuidMsb);
+ }
+
+ void onClientRegistered(int status, int clientIf, long uuidLsb, long uuidMsb)
+ throws RemoteException {
+ getGattService().onClientRegistered(status, clientIf, uuidLsb, uuidMsb);
+ }
+
+ void onConnected(int clientIf, int connId, int status, String address) throws RemoteException {
+ getGattService().onConnected(clientIf, connId, status, address);
+ }
+
+ void onDisconnected(int clientIf, int connId, int status, String address)
+ throws RemoteException {
+ getGattService().onDisconnected(clientIf, connId, status, address);
+ }
+
+ void onClientPhyUpdate(int connId, int txPhy, int rxPhy, int status) throws RemoteException {
+ getGattService().onClientPhyUpdate(connId, txPhy, rxPhy, status);
+ }
+
+ void onClientPhyRead(int clientIf, String address, int txPhy, int rxPhy, int status)
+ throws RemoteException {
+ getGattService().onClientPhyRead(clientIf, address, txPhy, rxPhy, status);
+ }
+
+ void onClientConnUpdate(int connId, int interval, int latency, int timeout, int status)
+ throws RemoteException {
+ getGattService().onClientConnUpdate(connId, interval, latency, timeout, status);
+ }
+
+ void onServiceChanged(int connId) throws RemoteException {
+ getGattService().onServiceChanged(connId);
+ }
+
+ void onServerPhyUpdate(int connId, int txPhy, int rxPhy, int status) throws RemoteException {
+ getGattService().onServerPhyUpdate(connId, txPhy, rxPhy, status);
+ }
+
+ void onServerPhyRead(int serverIf, String address, int txPhy, int rxPhy, int status)
+ throws RemoteException {
+ getGattService().onServerPhyRead(serverIf, address, txPhy, rxPhy, status);
+ }
+
+ void onServerConnUpdate(int connId, int interval, int latency, int timeout, int status)
+ throws RemoteException {
+ getGattService().onServerConnUpdate(connId, interval, latency, timeout, status);
+ }
+
+ void onSearchCompleted(int connId, int status) throws RemoteException {
+ getGattService().onSearchCompleted(connId, status);
+ }
+
+ GattDbElement getSampleGattDbElement() {
+ return getGattService().getSampleGattDbElement();
+ }
+
+ void onGetGattDb(int connId, ArrayList<GattDbElement> db) throws RemoteException {
+ getGattService().onGetGattDb(connId, db);
+ }
+
+ void onRegisterForNotifications(int connId, int status, int registered, int handle) {
+ getGattService().onRegisterForNotifications(connId, status, registered, handle);
+ }
+
+ void onNotify(int connId, String address, int handle, boolean isNotify, byte[] data)
+ throws RemoteException {
+ getGattService().onNotify(connId, address, handle, isNotify, data);
+ }
+
+ void onReadCharacteristic(int connId, int status, int handle, byte[] data)
+ throws RemoteException {
+ getGattService().onReadCharacteristic(connId, status, handle, data);
+ }
+
+ void onWriteCharacteristic(int connId, int status, int handle, byte[] data)
+ throws RemoteException {
+ getGattService().onWriteCharacteristic(connId, status, handle, data);
+ }
+
+ void onExecuteCompleted(int connId, int status) throws RemoteException {
+ getGattService().onExecuteCompleted(connId, status);
+ }
+
+ void onReadDescriptor(int connId, int status, int handle, byte[] data) throws RemoteException {
+ getGattService().onReadDescriptor(connId, status, handle, data);
+ }
+
+ void onWriteDescriptor(int connId, int status, int handle, byte[] data) throws RemoteException {
+ getGattService().onWriteDescriptor(connId, status, handle, data);
+ }
+
+ void onReadRemoteRssi(int clientIf, String address, int rssi, int status)
+ throws RemoteException {
+ getGattService().onReadRemoteRssi(clientIf, address, rssi, status);
+ }
+
+ void onScanFilterEnableDisabled(int action, int status, int clientIf) {
+ getGattService().onScanFilterEnableDisabled(action, status, clientIf);
+ }
+
+ void onScanFilterParamsConfigured(int action, int status, int clientIf, int availableSpace) {
+ getGattService().onScanFilterParamsConfigured(action, status, clientIf, availableSpace);
+ }
+
+ void onScanFilterConfig(int action, int status, int clientIf, int filterType,
+ int availableSpace) {
+ getGattService().onScanFilterConfig(action, status, clientIf, filterType, availableSpace);
+ }
+
+ void onBatchScanStorageConfigured(int status, int clientIf) {
+ getGattService().onBatchScanStorageConfigured(status, clientIf);
+ }
+
+ void onBatchScanStartStopped(int startStopAction, int status, int clientIf) {
+ getGattService().onBatchScanStartStopped(startStopAction, status, clientIf);
+ }
+
+ void onBatchScanReports(int status, int scannerId, int reportType, int numRecords,
+ byte[] recordData) throws RemoteException {
+ getGattService().onBatchScanReports(status, scannerId, reportType, numRecords, recordData);
+ }
+
+ void onBatchScanThresholdCrossed(int clientIf) {
+ getGattService().onBatchScanThresholdCrossed(clientIf);
+ }
+
+ AdvtFilterOnFoundOnLostInfo createOnTrackAdvFoundLostObject(int clientIf, int advPktLen,
+ byte[] advPkt, int scanRspLen, byte[] scanRsp, int filtIndex, int advState,
+ int advInfoPresent, String address, int addrType, int txPower, int rssiValue,
+ int timeStamp) {
+ return getGattService().createOnTrackAdvFoundLostObject(clientIf, advPktLen, advPkt,
+ scanRspLen, scanRsp, filtIndex, advState, advInfoPresent, address, addrType,
+ txPower, rssiValue, timeStamp);
+ }
+
+ void onTrackAdvFoundLost(AdvtFilterOnFoundOnLostInfo trackingInfo) throws RemoteException {
+ getGattService().onTrackAdvFoundLost(trackingInfo);
+ }
+
+ void onScanParamSetupCompleted(int status, int scannerId) throws RemoteException {
+ getGattService().onScanParamSetupCompleted(status, scannerId);
+ }
+
+ void onConfigureMTU(int connId, int status, int mtu) throws RemoteException {
+ getGattService().onConfigureMTU(connId, status, mtu);
+ }
+
+ void onClientCongestion(int connId, boolean congested) throws RemoteException {
+ getGattService().onClientCongestion(connId, congested);
+ }
+
+ /* Server callbacks */
+
+ void onServerRegistered(int status, int serverIf, long uuidLsb, long uuidMsb)
+ throws RemoteException {
+ getGattService().onServerRegistered(status, serverIf, uuidLsb, uuidMsb);
+ }
+
+ void onServiceAdded(int status, int serverIf, List<GattDbElement> service)
+ throws RemoteException {
+ getGattService().onServiceAdded(status, serverIf, service);
+ }
+
+ void onServiceStopped(int status, int serverIf, int srvcHandle) throws RemoteException {
+ getGattService().onServiceStopped(status, serverIf, srvcHandle);
+ }
+
+ void onServiceDeleted(int status, int serverIf, int srvcHandle) {
+ getGattService().onServiceDeleted(status, serverIf, srvcHandle);
+ }
+
+ void onClientConnected(String address, boolean connected, int connId, int serverIf)
+ throws RemoteException {
+ getGattService().onClientConnected(address, connected, connId, serverIf);
+ }
+
+ void onServerReadCharacteristic(String address, int connId, int transId, int handle, int offset,
+ boolean isLong) throws RemoteException {
+ getGattService().onServerReadCharacteristic(address, connId, transId, handle, offset,
+ isLong);
+ }
+
+ void onServerReadDescriptor(String address, int connId, int transId, int handle, int offset,
+ boolean isLong) throws RemoteException {
+ getGattService().onServerReadDescriptor(address, connId, transId, handle, offset, isLong);
+ }
+
+ void onServerWriteCharacteristic(String address, int connId, int transId, int handle,
+ int offset, int length, boolean needRsp, boolean isPrep, byte[] data)
+ throws RemoteException {
+ getGattService().onServerWriteCharacteristic(address, connId, transId, handle, offset,
+ length, needRsp, isPrep, data);
+ }
+
+ void onServerWriteDescriptor(String address, int connId, int transId, int handle, int offset,
+ int length, boolean needRsp, boolean isPrep, byte[] data) throws RemoteException {
+ getGattService().onServerWriteDescriptor(address, connId, transId, handle, offset, length,
+ needRsp, isPrep, data);
+ }
+
+ void onExecuteWrite(String address, int connId, int transId, int execWrite)
+ throws RemoteException {
+ getGattService().onExecuteWrite(address, connId, transId, execWrite);
+ }
+
+ void onResponseSendCompleted(int status, int attrHandle) {
+ getGattService().onResponseSendCompleted(status, attrHandle);
+ }
+
+ void onNotificationSent(int connId, int status) throws RemoteException {
+ getGattService().onNotificationSent(connId, status);
+ }
+
+ void onServerCongestion(int connId, boolean congested) throws RemoteException {
+ getGattService().onServerCongestion(connId, congested);
+ }
+
+ void onMtuChanged(int connId, int mtu) throws RemoteException {
+ getGattService().onMtuChanged(connId, mtu);
+ }
+
+ /* Native methods */
+ private static native void classInitNative();
+ private native void initializeNative();
+ private native void cleanupNative();
+ private native int gattClientGetDeviceTypeNative(String address);
+ private native void gattClientRegisterAppNative(long appUuidLsb, long appUuidMsb,
+ boolean eattSupport);
+ private native void gattClientUnregisterAppNative(int clientIf);
+ private native void gattClientConnectNative(int clientIf, String address, boolean isDirect,
+ int transport, boolean opportunistic, int initiatingPhys);
+ private native void gattClientDisconnectNative(int clientIf, String address, int connId);
+ private native void gattClientSetPreferredPhyNative(int clientIf, String address, int txPhy,
+ int rxPhy, int phyOptions);
+ private native void gattClientReadPhyNative(int clientIf, String address);
+ private native void gattClientRefreshNative(int clientIf, String address);
+ private native void gattClientSearchServiceNative(int connId, boolean searchAll,
+ long serviceUuidLsb, long serviceUuidMsb);
+ private native void gattClientDiscoverServiceByUuidNative(int connId, long serviceUuidLsb,
+ long serviceUuidMsb);
+ private native void gattClientGetGattDbNative(int connId);
+ private native void gattClientReadCharacteristicNative(int connId, int handle, int authReq);
+ private native void gattClientReadUsingCharacteristicUuidNative(int connId, long uuidMsb,
+ long uuidLsb, int sHandle, int eHandle, int authReq);
+ private native void gattClientReadDescriptorNative(int connId, int handle, int authReq);
+ private native void gattClientWriteCharacteristicNative(int connId, int handle, int writeType,
+ int authReq, byte[] value);
+ private native void gattClientWriteDescriptorNative(int connId, int handle, int authReq,
+ byte[] value);
+ private native void gattClientExecuteWriteNative(int connId, boolean execute);
+ private native void gattClientRegisterForNotificationsNative(int clientIf, String address,
+ int handle, boolean enable);
+ private native void gattClientReadRemoteRssiNative(int clientIf, String address);
+ private native void gattClientConfigureMTUNative(int connId, int mtu);
+ private native void gattConnectionParameterUpdateNative(int clientIf, String address,
+ int minInterval, int maxInterval, int latency, int timeout, int minConnectionEventLen,
+ int maxConnectionEventLen);
+ private native void gattServerRegisterAppNative(long appUuidLsb, long appUuidMsb,
+ boolean eattSupport);
+ private native void gattServerUnregisterAppNative(int serverIf);
+ private native void gattServerConnectNative(int serverIf, String address, boolean isDirect,
+ int transport);
+ private native void gattServerDisconnectNative(int serverIf, String address, int connId);
+ private native void gattServerSetPreferredPhyNative(int clientIf, String address, int txPhy,
+ int rxPhy, int phyOptions);
+ private native void gattServerReadPhyNative(int clientIf, String address);
+ private native void gattServerAddServiceNative(int serverIf, List<GattDbElement> service);
+ private native void gattServerStopServiceNative(int serverIf, int svcHandle);
+ private native void gattServerDeleteServiceNative(int serverIf, int svcHandle);
+ private native void gattServerSendIndicationNative(int serverIf, int attrHandle, int connId,
+ byte[] val);
+ private native void gattServerSendNotificationNative(int serverIf, int attrHandle, int connId,
+ byte[] val);
+ private native void gattServerSendResponseNative(int serverIf, int connId, int transId,
+ int status, int handle, int offset, byte[] val, int authReq);
+ private native void gattTestNative(int command, long uuid1Lsb, long uuid1Msb, String bda1,
+ int p1, int p2, int p3, int p4, int p5);
+
+ /**
+ * Initialize the native interface and native components
+ */
+ public void init(GattService gattService) {
+ mGattService = gattService;
+ initializeNative();
+ }
+
+ /**
+ * Clean up the native interface and native components
+ */
+ public void cleanup() {
+ cleanupNative();
+ mGattService = null;
+ }
+
+ /**
+ * Get the type of Bluetooth device
+ *
+ * @param address address of the Bluetooth device
+ * @return type of Bluetooth device 0 for BR/EDR, 1 for BLE, 2 for DUAL mode (To be confirmed)
+ */
+ public int gattClientGetDeviceType(String address) {
+ return gattClientGetDeviceTypeNative(address);
+ }
+
+ /**
+ * Register the given client
+ * It will invoke {@link #onClientRegistered(int, int, long, long)}.
+ */
+ public void gattClientRegisterApp(long appUuidLsb, long appUuidMsb, boolean eattSupport) {
+ gattClientRegisterAppNative(appUuidLsb, appUuidMsb, eattSupport);
+ }
+
+ /**
+ * Unregister the client
+ */
+ public void gattClientUnregisterApp(int clientIf) {
+ gattClientUnregisterAppNative(clientIf);
+ }
+
+ /**
+ * Connect to the remote Gatt server
+ * @see {@link BluetoothDevice#connectGatt} for parameters.
+ */
+ public void gattClientConnect(int clientIf, String address, boolean isDirect, int transport,
+ boolean opportunistic, int initiatingPhys) {
+ gattClientConnectNative(clientIf, address, isDirect, transport, opportunistic,
+ initiatingPhys);
+ }
+
+ /**
+ * Disconnect from the remote Gatt server
+ */
+ public void gattClientDisconnect(int clientIf, String address, int connId) {
+ gattClientDisconnectNative(clientIf, address, connId);
+ }
+
+ /**
+ * Set the preferred connection PHY for the client
+ */
+ public void gattClientSetPreferredPhy(int clientIf, String address, int txPhy,
+ int rxPhy, int phyOptions) {
+ gattClientSetPreferredPhyNative(clientIf, address, txPhy, rxPhy, phyOptions);
+ }
+
+ /**
+ * Read the current transmitter PHY and receiver PHY of the client
+ */
+ public void gattClientReadPhy(int clientIf, String address) {
+ gattClientReadPhyNative(clientIf, address);
+ }
+
+ /**
+ * Clear the internal cache and force a refresh of the services from the remote device
+ */
+ public void gattClientRefresh(int clientIf, String address) {
+ gattClientRefreshNative(clientIf, address);
+ }
+
+ /**
+ * Discover GATT services
+ */
+ public void gattClientSearchService(int connId, boolean searchAll, long serviceUuidLsb,
+ long serviceUuidMsb) {
+ gattClientSearchServiceNative(connId, searchAll, serviceUuidLsb, serviceUuidMsb);
+ }
+
+ /**
+ * Discover the GATT service by the given UUID
+ */
+ public void gattClientDiscoverServiceByUuid(int connId, long serviceUuidLsb,
+ long serviceUuidMsb) {
+ gattClientDiscoverServiceByUuidNative(connId, serviceUuidLsb, serviceUuidMsb);
+ }
+
+ /**
+ * Get GATT DB of the remote device
+ */
+ public void gattClientGetGattDb(int connId) {
+ gattClientGetGattDbNative(connId);
+ }
+
+ /**
+ * Read a characteristic by the given handle
+ */
+ public void gattClientReadCharacteristic(int connId, int handle, int authReq) {
+ gattClientReadCharacteristicNative(connId, handle, authReq);
+ }
+
+
+ /**
+ * Read a characteristic by the given UUID
+ */
+ public void gattClientReadUsingCharacteristicUuid(int connId, long uuidMsb,
+ long uuidLsb, int sHandle, int eHandle, int authReq) {
+ gattClientReadUsingCharacteristicUuidNative(connId, uuidMsb, uuidLsb, sHandle, eHandle,
+ authReq);
+ }
+
+ /**
+ * Read a descriptor by the given handle
+ */
+ public void gattClientReadDescriptor(int connId, int handle, int authReq) {
+ gattClientReadDescriptorNative(connId, handle, authReq);
+ }
+
+ /**
+ * Write a characteristic by the given handle
+ */
+ public void gattClientWriteCharacteristic(int connId, int handle, int writeType,
+ int authReq, byte[] value) {
+ gattClientWriteCharacteristicNative(connId, handle, writeType, authReq, value);
+ }
+
+ /**
+ * Write a descriptor by the given handle
+ */
+ public void gattClientWriteDescriptor(int connId, int handle, int authReq,
+ byte[] value) {
+ gattClientWriteDescriptorNative(connId, handle, authReq, value);
+ }
+
+ /**
+ * Execute a reliable write transaction
+ * @param connId
+ * @param execute
+ */
+ public void gattClientExecuteWrite(int connId, boolean execute) {
+ gattClientExecuteWriteNative(connId, execute);
+ }
+
+ /**
+ * Register notification for the characteristic
+ */
+ public void gattClientRegisterForNotifications(int clientIf, String address,
+ int handle, boolean enable) {
+ gattClientRegisterForNotificationsNative(clientIf, address, handle, enable);
+ }
+
+ /**
+ * Read the RSSI for a connected remote device
+ * @param clientIf
+ * @param address
+ */
+ public void gattClientReadRemoteRssi(int clientIf, String address) {
+ gattClientReadRemoteRssiNative(clientIf, address);
+ }
+
+ /**
+ * Configure MTU size used for the connection
+ */
+ public void gattClientConfigureMTU(int connId, int mtu) {
+ gattClientConfigureMTUNative(connId, mtu);
+ }
+
+ /**
+ * Update connection parameter.
+ */
+ public void gattConnectionParameterUpdate(int clientIf, String address,
+ int minInterval, int maxInterval, int latency, int timeout, int minConnectionEventLen,
+ int maxConnectionEventLen) {
+ gattConnectionParameterUpdateNative(clientIf, address, minInterval, maxInterval, latency,
+ timeout, minConnectionEventLen, maxConnectionEventLen);
+ }
+
+ /**
+ * Register GATT server
+ */
+ public void gattServerRegisterApp(long appUuidLsb, long appUuidMsb, boolean eattSupport) {
+ gattServerRegisterAppNative(appUuidLsb, appUuidMsb, eattSupport);
+ }
+
+ /**
+ * Unregister GATT server
+ */
+ public void gattServerUnregisterApp(int serverIf) {
+ gattServerUnregisterAppNative(serverIf);
+ }
+
+ /**
+ * Connect to a remote device as a GATT server role
+ */
+ public void gattServerConnect(int serverIf, String address, boolean isDirect,
+ int transport) {
+ gattServerConnectNative(serverIf, address, isDirect, transport);
+ }
+
+ /**
+ * Disconnects from a remote device as a GATT server role
+ */
+ public void gattServerDisconnect(int serverIf, String address, int connId) {
+ gattServerDisconnectNative(serverIf, address, connId);
+ }
+
+ /**
+ * Set the preferred connection PHY as a GATT server role
+ */
+ public void gattServerSetPreferredPhy(int clientIf, String address, int txPhy,
+ int rxPhy, int phyOptions) {
+ gattServerSetPreferredPhyNative(clientIf, address, txPhy, rxPhy, phyOptions);
+ }
+
+ /**
+ * Read the current transmitter PHY and receiver PHY of the connection
+ */
+ public void gattServerReadPhy(int clientIf, String address) {
+ gattServerReadPhyNative(clientIf, address);
+ }
+
+ /**
+ * Add a service to the list of services to be hosted.
+ */
+ public void gattServerAddService(int serverIf, List<GattDbElement> service) {
+ gattServerAddServiceNative(serverIf, service);
+ }
+
+ /**
+ * Stop a service
+ */
+ public void gattServerStopService(int serverIf, int svcHandle) {
+ gattServerStopServiceNative(serverIf, svcHandle);
+ }
+
+ /**
+ * Removes a service from the list of services to be provided
+ */
+ public void gattServerDeleteService(int serverIf, int svcHandle) {
+ gattServerDeleteServiceNative(serverIf, svcHandle);
+ }
+
+ /**
+ * Send an indication of the characteristic
+ */
+ public void gattServerSendIndication(int serverIf, int attrHandle, int connId,
+ byte[] val) {
+ gattServerSendIndicationNative(serverIf, attrHandle, connId, val);
+ }
+
+ /**
+ * Send a notification of the characteristic
+ */
+ public void gattServerSendNotification(int serverIf, int attrHandle, int connId,
+ byte[] val) {
+ gattServerSendNotificationNative(serverIf, attrHandle, connId, val);
+ }
+
+ /**
+ * Send a response as a GATT server role
+ */
+ public void gattServerSendResponse(int serverIf, int connId, int transId,
+ int status, int handle, int offset, byte[] val, int authReq) {
+ gattServerSendResponseNative(serverIf, connId, transId, status, handle, offset, val,
+ authReq);
+ }
+
+ /**
+ * Send a test command
+ */
+ public void gattTest(int command, long uuid1Lsb, long uuid1Msb, String bda1,
+ int p1, int p2, int p3, int p4, int p5) {
+ gattTestNative(command, uuid1Lsb, uuid1Msb, bda1, p1, p2, p3, p4, p5);
+ }
+}
+
diff --git a/android/app/src/com/android/bluetooth/gatt/GattObjectsFactory.java b/android/app/src/com/android/bluetooth/gatt/GattObjectsFactory.java
new file mode 100644
index 0000000000..85e54eada9
--- /dev/null
+++ b/android/app/src/com/android/bluetooth/gatt/GattObjectsFactory.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.bluetooth.gatt;
+
+import android.util.Log;
+
+import com.android.bluetooth.Utils;
+/**
+ * Factory class for object initialization to help with unit testing
+ */
+public class GattObjectsFactory {
+ private static final String TAG = GattObjectsFactory.class.getSimpleName();
+ private static GattObjectsFactory sInstance;
+ private static final Object INSTANCE_LOCK = new Object();
+
+ private GattObjectsFactory() {
+ }
+
+ /**
+ * Get the singleton instance of object factory
+ *
+ * @return the singleton instance, guaranteed not null
+ */
+ public static GattObjectsFactory getInstance() {
+ synchronized (INSTANCE_LOCK) {
+ if (sInstance == null) {
+ sInstance = new GattObjectsFactory();
+ }
+ }
+ return sInstance;
+ }
+
+ /**
+ * Allow unit tests to substitute GattObjectsFactory with a test instance
+ *
+ * @param objectsFactory a test instance of the GattObjectsFactory
+ */
+ static void setInstanceForTesting(GattObjectsFactory objectsFactory) {
+ Utils.enforceInstrumentationTestMode();
+ synchronized (INSTANCE_LOCK) {
+ Log.d(TAG, "setInstanceForTesting(), set to " + objectsFactory);
+ sInstance = objectsFactory;
+ }
+ }
+
+ public GattNativeInterface getNativeInterface() {
+ return GattNativeInterface.getInstance();
+ }
+}
diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java
index 12d3bf1dd4..0d4cfa95b9 100644
--- a/android/app/src/com/android/bluetooth/gatt/GattService.java
+++ b/android/app/src/com/android/bluetooth/gatt/GattService.java
@@ -301,9 +301,7 @@ public class GattService extends ProfileService {
*/
private Set<String> mReliableQueue = new HashSet<String>();
- static {
- classInitNative();
- }
+ private GattNativeInterface mNativeInterface;
@Override
protected IProfileServiceBinder initBinder() {
@@ -319,7 +317,8 @@ public class GattService extends ProfileService {
Settings.Global.putInt(
getContentResolver(), "bluetooth_sanitized_exposure_notification_supported", 1);
- initializeNative();
+ mNativeInterface = GattObjectsFactory.getInstance().getNativeInterface();
+ mNativeInterface.init(this);
mAdapterService = AdapterService.getAdapterService();
mBluetoothAdapterProxy = BluetoothAdapterProxy.getInstance();
mCompanionManager = getSystemService(CompanionDeviceManager.class);
@@ -348,15 +347,8 @@ public class GattService extends ProfileService {
mServerMap.clear();
mHandleMap.clear();
mReliableQueue.clear();
- if (mAdvertiseManager != null) {
- mAdvertiseManager.cleanup();
- }
- if (mScanManager != null) {
- mScanManager.cleanup();
- }
- if (mPeriodicScanManager != null) {
- mPeriodicScanManager.cleanup();
- }
+ cleanup();
+
return true;
}
@@ -365,7 +357,10 @@ public class GattService extends ProfileService {
if (DBG) {
Log.d(TAG, "cleanup()");
}
- cleanupNative();
+ if (mNativeInterface != null) {
+ mNativeInterface.cleanup();
+ mNativeInterface = null;
+ }
if (mAdvertiseManager != null) {
mAdvertiseManager.cleanup();
}
@@ -2212,7 +2207,7 @@ public class GattService extends ProfileService {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
- gattClientGetGattDbNative(connId);
+ mNativeInterface.gattClientGetGattDb(connId);
}
});
t.start();
@@ -3380,7 +3375,8 @@ public class GattService extends ProfileService {
Log.d(TAG, "registerClient() - UUID=" + uuid);
}
mClientMap.add(uuid, null, callback, null, this);
- gattClientRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits(), eatt_support);
+ mNativeInterface.gattClientRegisterApp(uuid.getLeastSignificantBits(),
+ uuid.getMostSignificantBits(), eatt_support);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3394,7 +3390,7 @@ public class GattService extends ProfileService {
Log.d(TAG, "unregisterClient() - clientIf=" + clientIf);
}
mClientMap.remove(clientIf);
- gattClientUnregisterAppNative(clientIf);
+ mNativeInterface.gattClientUnregisterApp(clientIf);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3413,7 +3409,8 @@ public class GattService extends ProfileService {
statsLogGattConnectionStateChange(
BluetoothProfile.GATT, address, clientIf,
BluetoothProtoEnums.CONNECTION_STATE_CONNECTING);
- gattClientConnectNative(clientIf, address, isDirect, transport, opportunistic, phy);
+ mNativeInterface.gattClientConnect(clientIf, address, isDirect, transport, opportunistic,
+ phy);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3430,7 +3427,7 @@ public class GattService extends ProfileService {
statsLogGattConnectionStateChange(
BluetoothProfile.GATT, address, clientIf,
BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING);
- gattClientDisconnectNative(clientIf, address, connId != null ? connId : 0);
+ mNativeInterface.gattClientDisconnect(clientIf, address, connId != null ? connId : 0);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3452,7 +3449,7 @@ public class GattService extends ProfileService {
if (DBG) {
Log.d(TAG, "clientSetPreferredPhy() - address=" + address + ", connId=" + connId);
}
- gattClientSetPreferredPhyNative(clientIf, address, txPhy, rxPhy, phyOptions);
+ mNativeInterface.gattClientSetPreferredPhy(clientIf, address, txPhy, rxPhy, phyOptions);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3473,7 +3470,7 @@ public class GattService extends ProfileService {
if (DBG) {
Log.d(TAG, "clientReadPhy() - address=" + address + ", connId=" + connId);
}
- gattClientReadPhyNative(clientIf, address);
+ mNativeInterface.gattClientReadPhy(clientIf, address);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3523,7 +3520,7 @@ public class GattService extends ProfileService {
if (DBG) {
Log.d(TAG, "refreshDevice() - address=" + address);
}
- gattClientRefreshNative(clientIf, address);
+ mNativeInterface.gattClientRefresh(clientIf, address);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3539,7 +3536,7 @@ public class GattService extends ProfileService {
}
if (connId != null) {
- gattClientSearchServiceNative(connId, true, 0, 0);
+ mNativeInterface.gattClientSearchService(connId, true, 0, 0);
} else {
Log.e(TAG, "discoverServices() - No connection for " + address + "...");
}
@@ -3555,7 +3552,7 @@ public class GattService extends ProfileService {
Integer connId = mClientMap.connIdByAddress(clientIf, address);
if (connId != null) {
- gattClientDiscoverServiceByUuidNative(connId, uuid.getLeastSignificantBits(),
+ mNativeInterface.gattClientDiscoverServiceByUuid(connId, uuid.getLeastSignificantBits(),
uuid.getMostSignificantBits());
} else {
Log.e(TAG, "discoverServiceByUuid() - No connection for " + address + "...");
@@ -3592,7 +3589,7 @@ public class GattService extends ProfileService {
return;
}
- gattClientReadCharacteristicNative(connId, handle, authReq);
+ mNativeInterface.gattClientReadCharacteristic(connId, handle, authReq);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3625,8 +3622,9 @@ public class GattService extends ProfileService {
return;
}
- gattClientReadUsingCharacteristicUuidNative(connId, uuid.getLeastSignificantBits(),
- uuid.getMostSignificantBits(), startHandle, endHandle, authReq);
+ mNativeInterface.gattClientReadUsingCharacteristicUuid(connId,
+ uuid.getLeastSignificantBits(), uuid.getMostSignificantBits(), startHandle,
+ endHandle, authReq);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3669,7 +3667,7 @@ public class GattService extends ProfileService {
atomicBoolean.set(false);
}
- gattClientWriteCharacteristicNative(connId, handle, writeType, authReq, value);
+ mNativeInterface.gattClientWriteCharacteristic(connId, handle, writeType, authReq, value);
return BluetoothStatusCodes.SUCCESS;
}
@@ -3703,7 +3701,7 @@ public class GattService extends ProfileService {
return;
}
- gattClientReadDescriptorNative(connId, handle, authReq);
+ mNativeInterface.gattClientReadDescriptor(connId, handle, authReq);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3724,7 +3722,7 @@ public class GattService extends ProfileService {
}
permissionCheck(connId, handle);
- gattClientWriteDescriptorNative(connId, handle, authReq, value);
+ mNativeInterface.gattClientWriteDescriptor(connId, handle, authReq, value);
return BluetoothStatusCodes.SUCCESS;
}
@@ -3756,7 +3754,7 @@ public class GattService extends ProfileService {
Integer connId = mClientMap.connIdByAddress(clientIf, address);
if (connId != null) {
- gattClientExecuteWriteNative(connId, execute);
+ mNativeInterface.gattClientExecuteWrite(connId, execute);
}
}
@@ -3790,7 +3788,7 @@ public class GattService extends ProfileService {
return;
}
- gattClientRegisterForNotificationsNative(clientIf, address, handle, enable);
+ mNativeInterface.gattClientRegisterForNotifications(clientIf, address, handle, enable);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3803,7 +3801,7 @@ public class GattService extends ProfileService {
if (DBG) {
Log.d(TAG, "readRemoteRssi() - address=" + address);
}
- gattClientReadRemoteRssiNative(clientIf, address);
+ mNativeInterface.gattClientReadRemoteRssi(clientIf, address);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3818,7 +3816,7 @@ public class GattService extends ProfileService {
}
Integer connId = mClientMap.connIdByAddress(clientIf, address);
if (connId != null) {
- gattClientConfigureMTUNative(connId, mtu);
+ mNativeInterface.gattClientConfigureMTU(connId, mtu);
} else {
Log.e(TAG, "configureMTU() - No connection for " + address + "...");
}
@@ -3868,8 +3866,8 @@ public class GattService extends ProfileService {
Log.d(TAG, "connectionParameterUpdate() - address=" + address + "params="
+ connectionPriority + " interval=" + minInterval + "/" + maxInterval);
}
- gattConnectionParameterUpdateNative(clientIf, address, minInterval, maxInterval, latency,
- timeout, 0, 0);
+ mNativeInterface.gattConnectionParameterUpdate(clientIf, address, minInterval, maxInterval,
+ latency, timeout, 0, 0);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -3890,7 +3888,7 @@ public class GattService extends ProfileService {
}
- gattConnectionParameterUpdateNative(clientIf, address, minInterval, maxInterval,
+ mNativeInterface.gattConnectionParameterUpdate(clientIf, address, minInterval, maxInterval,
peripheralLatency, supervisionTimeout,
minConnectionEventLen, maxConnectionEventLen);
}
@@ -4199,7 +4197,8 @@ public class GattService extends ProfileService {
Log.d(TAG, "registerServer() - UUID=" + uuid);
}
mServerMap.add(uuid, null, callback, null, this);
- gattServerRegisterAppNative(uuid.getLeastSignificantBits(), uuid.getMostSignificantBits(), eatt_support);
+ mNativeInterface.gattServerRegisterApp(uuid.getLeastSignificantBits(),
+ uuid.getMostSignificantBits(), eatt_support);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -4216,7 +4215,7 @@ public class GattService extends ProfileService {
deleteServices(serverIf);
mServerMap.remove(serverIf);
- gattServerUnregisterAppNative(serverIf);
+ mNativeInterface.gattServerUnregisterApp(serverIf);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -4230,7 +4229,7 @@ public class GattService extends ProfileService {
if (DBG) {
Log.d(TAG, "serverConnect() - address=" + address);
}
- gattServerConnectNative(serverIf, address, isDirect, transport);
+ mNativeInterface.gattServerConnect(serverIf, address, isDirect, transport);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -4245,7 +4244,7 @@ public class GattService extends ProfileService {
Log.d(TAG, "serverDisconnect() - address=" + address + ", connId=" + connId);
}
- gattServerDisconnectNative(serverIf, address, connId != null ? connId : 0);
+ mNativeInterface.gattServerDisconnect(serverIf, address, connId != null ? connId : 0);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -4267,7 +4266,7 @@ public class GattService extends ProfileService {
if (DBG) {
Log.d(TAG, "serverSetPreferredPhy() - address=" + address + ", connId=" + connId);
}
- gattServerSetPreferredPhyNative(serverIf, address, txPhy, rxPhy, phyOptions);
+ mNativeInterface.gattServerSetPreferredPhy(serverIf, address, txPhy, rxPhy, phyOptions);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -4288,7 +4287,7 @@ public class GattService extends ProfileService {
if (DBG) {
Log.d(TAG, "serverReadPhy() - address=" + address + ", connId=" + connId);
}
- gattServerReadPhyNative(serverIf, address);
+ mNativeInterface.gattServerReadPhy(serverIf, address);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -4335,7 +4334,7 @@ public class GattService extends ProfileService {
}
}
- gattServerAddServiceNative(serverIf, db);
+ mNativeInterface.gattServerAddService(serverIf, db);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -4349,7 +4348,7 @@ public class GattService extends ProfileService {
Log.d(TAG, "removeService() - handle=" + handle);
}
- gattServerDeleteServiceNative(serverIf, handle);
+ mNativeInterface.gattServerDeleteService(serverIf, handle);
}
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@@ -4384,7 +4383,7 @@ public class GattService extends ProfileService {
}
Integer connId = mServerMap.connIdByAddress(serverIf, address);
- gattServerSendResponseNative(serverIf, connId != null ? connId : 0, requestId,
+ mNativeInterface.gattServerSendResponse(serverIf, connId != null ? connId : 0, requestId,
(byte) status, handle, offset, value, (byte) 0);
mHandleMap.deleteRequest(requestId);
}
@@ -4407,9 +4406,9 @@ public class GattService extends ProfileService {
}
if (confirm) {
- gattServerSendIndicationNative(serverIf, handle, connId, value);
+ mNativeInterface.gattServerSendIndication(serverIf, handle, connId, value);
} else {
- gattServerSendNotificationNative(serverIf, handle, connId, value);
+ mNativeInterface.gattServerSendNotification(serverIf, handle, connId, value);
}
return BluetoothStatusCodes.SUCCESS;
@@ -4457,7 +4456,7 @@ public class GattService extends ProfileService {
}
private int getDeviceType(BluetoothDevice device) {
- int type = gattClientGetDeviceTypeNative(device.getAddress());
+ int type = mNativeInterface.gattClientGetDeviceType(device.getAddress());
if (DBG) {
Log.d(TAG, "getDeviceType() - device=" + device + ", type=" + type);
}
@@ -4596,7 +4595,7 @@ public class GattService extends ProfileService {
continue;
}
- gattServerStopServiceNative(serverIf, entry.handle);
+ mNativeInterface.gattServerStopService(serverIf, entry.handle);
return;
}
}
@@ -4622,7 +4621,7 @@ public class GattService extends ProfileService {
/* Now actually delete the services.... */
for (Integer handle : handleList) {
- gattServerDeleteServiceNative(serverIf, handle);
+ mNativeInterface.gattServerDeleteService(serverIf, handle);
}
}
@@ -4712,111 +4711,16 @@ public class GattService extends ProfileService {
/**************************************************************************
* GATT Test functions
*************************************************************************/
-
void gattTestCommand(int command, UUID uuid1, String bda1, int p1, int p2, int p3, int p4,
int p5) {
if (bda1 == null) {
bda1 = "00:00:00:00:00:00";
}
if (uuid1 != null) {
- gattTestNative(command, uuid1.getLeastSignificantBits(), uuid1.getMostSignificantBits(),
- bda1, p1, p2, p3, p4, p5);
+ mNativeInterface.gattTest(command, uuid1.getLeastSignificantBits(),
+ uuid1.getMostSignificantBits(), bda1, p1, p2, p3, p4, p5);
} else {
- gattTestNative(command, 0, 0, bda1, p1, p2, p3, p4, p5);
+ mNativeInterface.gattTest(command, 0, 0, bda1, p1, p2, p3, p4, p5);
}
}
-
- private native void gattTestNative(int command, long uuid1Lsb, long uuid1Msb, String bda1,
- int p1, int p2, int p3, int p4, int p5);
-
- /**************************************************************************
- * Native functions prototypes
- *************************************************************************/
-
- private static native void classInitNative();
-
- private native void initializeNative();
-
- private native void cleanupNative();
-
- private native int gattClientGetDeviceTypeNative(String address);
-
- private native void gattClientRegisterAppNative(long appUuidLsb, long appUuidMsb, boolean eatt_support);
-
- private native void gattClientUnregisterAppNative(int clientIf);
-
- private native void gattClientConnectNative(int clientIf, String address, boolean isDirect,
- int transport, boolean opportunistic, int initiatingPhys);
-
- private native void gattClientDisconnectNative(int clientIf, String address, int connId);
-
- private native void gattClientSetPreferredPhyNative(int clientIf, String address, int txPhy,
- int rxPhy, int phyOptions);
-
- private native void gattClientReadPhyNative(int clientIf, String address);
-
- private native void gattClientRefreshNative(int clientIf, String address);
-
- private native void gattClientSearchServiceNative(int connId, boolean searchAll,
- long serviceUuidLsb, long serviceUuidMsb);
-
- private native void gattClientDiscoverServiceByUuidNative(int connId, long serviceUuidLsb,
- long serviceUuidMsb);
-
- private native void gattClientGetGattDbNative(int connId);
-
- private native void gattClientReadCharacteristicNative(int connId, int handle, int authReq);
-
- private native void gattClientReadUsingCharacteristicUuidNative(int connId, long uuidMsb,
- long uuidLsb, int sHandle, int eHandle, int authReq);
-
- private native void gattClientReadDescriptorNative(int connId, int handle, int authReq);
-
- private native void gattClientWriteCharacteristicNative(int connId, int handle, int writeType,
- int authReq, byte[] value);
-
- private native void gattClientWriteDescriptorNative(int connId, int handle, int authReq,
- byte[] value);
-
- private native void gattClientExecuteWriteNative(int connId, boolean execute);
-
- private native void gattClientRegisterForNotificationsNative(int clientIf, String address,
- int handle, boolean enable);
-
- private native void gattClientReadRemoteRssiNative(int clientIf, String address);
-
- private native void gattClientConfigureMTUNative(int connId, int mtu);
-
- private native void gattConnectionParameterUpdateNative(int clientIf, String address,
- int minInterval, int maxInterval, int latency, int timeout, int minConnectionEventLen,
- int maxConnectionEventLen);
-
- private native void gattServerRegisterAppNative(long appUuidLsb, long appUuidMsb, boolean eatt_support);
-
- private native void gattServerUnregisterAppNative(int serverIf);
-
- private native void gattServerConnectNative(int serverIf, String address, boolean isDirect,
- int transport);
-
- private native void gattServerDisconnectNative(int serverIf, String address, int connId);
-
- private native void gattServerSetPreferredPhyNative(int clientIf, String address, int txPhy,
- int rxPhy, int phyOptions);
-
- private native void gattServerReadPhyNative(int clientIf, String address);
-
- private native void gattServerAddServiceNative(int serverIf, List<GattDbElement> service);
-
- private native void gattServerStopServiceNative(int serverIf, int svcHandle);
-
- private native void gattServerDeleteServiceNative(int serverIf, int svcHandle);
-
- private native void gattServerSendIndicationNative(int serverIf, int attrHandle, int connId,
- byte[] val);
-
- private native void gattServerSendNotificationNative(int serverIf, int attrHandle, int connId,
- byte[] val);
-
- private native void gattServerSendResponseNative(int serverIf, int connId, int transId,
- int status, int handle, int offset, byte[] val, int authReq);
}
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 76f1c3f004..a60987ccf2 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -633,7 +633,11 @@ public class LeAudioService extends ProfileService {
private Integer getAudioDirectionsFromActiveContextsMap(Integer activeContexts) {
Integer supportedAudioDirections = 0;
- if ((activeContexts & mContextSupportingInputAudio) != 0) {
+ if (((activeContexts & mContextSupportingInputAudio) != 0)
+ || (Utils.isPtsTestMode()
+ && (activeContexts
+ & (BluetoothLeAudio.CONTEXT_TYPE_RINGTONE
+ | BluetoothLeAudio.CONTEXT_TYPE_MEDIA)) != 0)) {
supportedAudioDirections |= AUDIO_DIRECTION_INPUT_BIT;
}
if ((activeContexts & mContextSupportingOutputAudio) != 0) {
diff --git a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
index 90a1a560c4..9856fb4d02 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/gatt/GattServiceTest.java
@@ -2,6 +2,10 @@ package com.android.bluetooth.gatt;
import static org.mockito.Mockito.*;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
@@ -22,6 +26,10 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
* Test cases for {@link GattService}.
@@ -30,12 +38,17 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidJUnit4.class)
public class GattServiceTest {
private static final int TIMES_UP_AND_DOWN = 3;
+ private static final int TIMEOUT_MS = 5_000;
private Context mTargetContext;
private GattService mService;
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
+ private BluetoothAdapter mAdapter;
@Mock private AdapterService mAdapterService;
+ @Mock private GattObjectsFactory mFactory;
+ @Mock private GattNativeInterface mNativeInterface;
+ private BluetoothDevice mCurrentDevice;
@Before
public void setUp() throws Exception {
@@ -44,6 +57,12 @@ public class GattServiceTest {
MockitoAnnotations.initMocks(this);
TestUtils.setAdapterService(mAdapterService);
doReturn(true).when(mAdapterService).isStartedProfile(anyString());
+
+ GattObjectsFactory.setInstanceForTesting(mFactory);
+ doReturn(mNativeInterface).when(mFactory).getNativeInterface();
+
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+
TestUtils.startService(mServiceRule, GattService.class);
mService = GattService.getGattService();
Assert.assertNotNull(mService);
@@ -59,11 +78,13 @@ public class GattServiceTest {
mService = GattService.getGattService();
Assert.assertNull(mService);
TestUtils.clearAdapterService(mAdapterService);
+ GattObjectsFactory.setInstanceForTesting(null);
}
@Test
public void testInitialize() {
- Assert.assertNotNull(GattService.getGattService());
+ Assert.assertEquals(mService, GattService.getGattService());
+ verify(mNativeInterface).init(eq(mService));
}
@Test
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java
index 589eceaa93..2cac893bfd 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BluetoothProxy.java
@@ -416,10 +416,6 @@ public class BluetoothProxy {
LeAudioDeviceStateWrapper valid_device = valid_device_opt.get();
LeAudioDeviceStateWrapper.BassData svc_data = valid_device.bassData;
- // TODO: Is the receiver_id same with BluetoothLeBroadcastReceiveState.getSourceId()?
- // If not, find getSourceId() usages and fix the issues.
-// rstate.receiver_id = intent.getIntExtra(
-// BluetoothBroadcastAudioScan.EXTRA_BASS_RECEIVER_ID, -1);
/**
* From "Introducing-Bluetooth-LE-Audio-book" 8.6.3.1:
*
@@ -435,8 +431,6 @@ public class BluetoothProxy {
*/
/**
- * From BluetoothBroadcastAudioScan.EXTRA_BASS_RECEIVER_ID:
- *
* Broadcast receiver's endpoint identifier.
*/
synchronized(this) {
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanActivity.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanActivity.java
index 0b2eb4c809..2296d21693 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanActivity.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/BroadcastScanActivity.java
@@ -42,11 +42,6 @@ import java.util.List;
public class BroadcastScanActivity extends AppCompatActivity {
- // Integer key used for sending/receiving receiver ID.
- public static final String EXTRA_BASS_RECEIVER_ID = "receiver_id";
-
- private static final int BIS_ALL = 0xFFFFFFFF;
-
private BluetoothDevice device;
private BroadcastScanViewModel mViewModel;
private BroadcastItemsAdapter adapter;
diff --git a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java
index 6d188d9dc6..4be87caae6 100644
--- a/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java
+++ b/android/leaudio/app/src/main/java/com/android/bluetooth/leaudio/LeAudioRecycleViewAdapter.java
@@ -26,8 +26,6 @@ import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_I
import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED;
import static android.bluetooth.BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCINFO_REQUEST;
-import static com.android.bluetooth.leaudio.BroadcastScanActivity.EXTRA_BASS_RECEIVER_ID;
-
import android.animation.ObjectAnimator;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothHapClient;
@@ -1810,27 +1808,27 @@ public class LeAudioRecycleViewAdapter
alert.setTitle("Scan and add a source or remove the currently set one.");
BluetoothDevice device = devices.get(ViewHolder.this.getAdapterPosition()).device;
- if (bassReceiverIdSpinner.getSelectedItem() == null) {
- Toast.makeText(view.getContext(), "Not available",
- Toast.LENGTH_SHORT).show();
- return;
+ int receiver_id = -1;
+ if (bassReceiverIdSpinner.getSelectedItem() != null) {
+ receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
}
- int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
alert.setPositiveButton("Scan", (dialog, whichButton) -> {
// Scan for new announcements
Intent intent = new Intent(this.itemView.getContext(), BroadcastScanActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
- intent.putExtra(EXTRA_BASS_RECEIVER_ID, receiver_id);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, devices.get(ViewHolder.this.getAdapterPosition()).device);
parent.startActivityForResult(intent, 666);
});
alert.setNeutralButton("Cancel", (dialog, whichButton) -> {
// Do nothing
});
- alert.setNegativeButton("Remove", (dialog, whichButton) -> {
- bassInteractionListener.onRemoveSourceReq(device, receiver_id);
- });
+ if (receiver_id != -1) {
+ final int remove_receiver_id = receiver_id;
+ alert.setNegativeButton("Remove", (dialog, whichButton) -> {
+ bassInteractionListener.onRemoveSourceReq(device, remove_receiver_id);
+ });
+ }
alert.show();
} else if (bassReceiverStateText.getText().equals(res.getString(R.string.broadcast_state_code_required))) {
@@ -1887,16 +1885,9 @@ public class LeAudioRecycleViewAdapter
AlertDialog.Builder alert = new AlertDialog.Builder(itemView.getContext());
alert.setTitle("Retry broadcast audio announcement scan?");
- if (bassReceiverIdSpinner.getSelectedItem() == null) {
- Toast.makeText(view.getContext(), "Not available",
- Toast.LENGTH_SHORT).show();
- return;
- }
- int receiver_id = Integer.parseInt(bassReceiverIdSpinner.getSelectedItem().toString());
alert.setPositiveButton("Yes", (dialog, whichButton) -> {
// Scan for new announcements
Intent intent = new Intent(view.getContext(), BroadcastScanActivity.class);
- intent.putExtra(EXTRA_BASS_RECEIVER_ID, receiver_id);
intent.putExtra(BluetoothDevice.EXTRA_DEVICE, devices.get(ViewHolder.this.getAdapterPosition()).device);
parent.startActivityForResult(intent, 666);
});
diff --git a/android/pandora/mmi2grpc/mmi2grpc/gatt.py b/android/pandora/mmi2grpc/mmi2grpc/gatt.py
index f997494e0a..ba933213f7 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/gatt.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/gatt.py
@@ -13,12 +13,14 @@
# limitations under the License.
import re
+import sys
from mmi2grpc._helpers import assert_description
from mmi2grpc._proxy import ProfileProxy
from pandora.gatt_grpc import GATT
from pandora.host_grpc import Host
+from pandora.gatt_pb2 import AttStatusCode
# Tests that need GATT cache cleared before discovering services.
NEEDS_CACHE_CLEARED = {
@@ -37,6 +39,7 @@ class GATTProxy(ProfileProxy):
self.services = None
self.characteristics = None
self.descriptors = None
+ self.read_value = None
@assert_description
def MMI_IUT_INITIATE_CONNECTION(self, test, pts_addr: bytes, **kwargs):
@@ -69,6 +72,7 @@ class GATTProxy(ProfileProxy):
self.services = None
self.characteristics = None
self.descriptors = None
+ self.read_value = None
return "OK"
@assert_description
@@ -229,7 +233,7 @@ class GATTProxy(ProfileProxy):
assert self.connection is not None
assert self.services is not None
for service in self.services:
- assert len(service.included_services) is 0
+ assert len(service.included_services) == 0
return "OK"
def MMI_CONFIRM_INCLUDE_SERVICE(self, description: str, **kwargs):
@@ -262,8 +266,9 @@ class GATTProxy(ProfileProxy):
stringHandleToInt(all_matches[i + 1]),\
formatUuid(all_matches[i + 3])):
found_services += 1
- assert found_services == (len(all_matches) / 4)
- return "OK"
+ if found_services == (len(all_matches) / 4):
+ return "Yes"
+ return "No"
def MMI_IUT_DISCOVER_SERVICE_UUID(self, description: str, **kwargs):
"""
@@ -405,6 +410,245 @@ class GATTProxy(ProfileProxy):
return "Yes"
return "No"
+ def MMI_IUT_SEND_READ_CHARACTERISTIC_HANDLE(self, description: str, **kwargs):
+ """
+ Please send read characteristic handle = 'XXXX'O to the PTS.
+ Description: Verify that the Implementation Under Test (IUT) can send
+ Read characteristic.
+ """
+
+ assert self.connection is not None
+ handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0])
+ self.read_value = self.gatt.ReadCharacteristicFromHandle(\
+ connection=self.connection, handle=handle)
+ return "OK"
+
+ @assert_description
+ def MMI_IUT_CONFIRM_READ_INVALID_HANDLE(self, **kwargs):
+ """
+ Please confirm IUT received Invalid handle error. Click Yes if IUT
+ received it, otherwise click No.
+
+ Description: Verify that the
+ Implementation Under Test (IUT) indicate Invalid handle error when read
+ a characteristic.
+ """
+
+ assert self.read_value is not None
+ if self.read_value.status == AttStatusCode.INVALID_HANDLE:
+ return "Yes"
+ return "No"
+
+ @assert_description
+ def MMI_IUT_CONFIRM_READ_NOT_PERMITTED(self, **kwargs):
+ """
+ Please confirm IUT received read is not permitted error. Click Yes if
+ IUT received it, otherwise click No.
+
+ Description: Verify that the
+ Implementation Under Test (IUT) indicate read is not permitted error
+ when read a characteristic.
+ """
+
+ assert self.read_value is not None
+ # Android read error doesn't return an error code so we have to also
+ # compare to the generic error code here.
+ if self.read_value.status == AttStatusCode.READ_NOT_PERMITTED or\
+ self.read_value.status == AttStatusCode.UNKNOWN_ERROR:
+ return "Yes"
+ return "No"
+
+ @assert_description
+ def MMI_IUT_CONFIRM_READ_AUTHENTICATION(self, **kwargs):
+ """
+ Please confirm IUT received authentication error. Click Yes if IUT
+ received it, otherwise click No.
+
+ Description: Verify that the
+ Implementation Under Test (IUT) indicate authentication error when read
+ a characteristic.
+ """
+
+ assert self.read_value is not None
+ if self.read_value.status == AttStatusCode.INSUFFICIENT_AUTHENTICATION:
+ return "Yes"
+ return "No"
+
+ def MMI_IUT_SEND_READ_CHARACTERISTIC_UUID(self, description: str, **kwargs):
+ """
+ Please send read using characteristic UUID = 'XXXX'O handle range =
+ 'XXXX'O to 'XXXX'O to the PTS.
+
+ Description: Verify that the
+ Implementation Under Test (IUT) can send Read characteristic by UUID.
+ """
+
+ assert self.connection is not None
+ matches = re.findall("'([a0-Z9]*)'O", description)
+ self.read_value = self.gatt.ReadCharacteristicFromUuid(\
+ connection=self.connection, uuid=formatUuid(matches[0]),\
+ start_handle=stringHandleToInt(matches[1]),\
+ end_handle=stringHandleToInt(matches[2]))
+ return "OK"
+
+ @assert_description
+ def MMI_IUT_CONFIRM_ATTRIBUTE_NOT_FOUND(self, **kwargs):
+ """
+ Please confirm IUT received attribute not found error. Click Yes if IUT
+ received it, otherwise click No.
+
+ Description: Verify that the
+ Implementation Under Test (IUT) indicate attribute not found error when
+ read a characteristic.
+ """
+
+ assert self.read_value is not None
+ # Android read error doesn't return an error code so we have to also
+ # compare to the generic error code here.
+ if self.read_value.status == AttStatusCode.ATTRIBUTE_NOT_FOUND or\
+ self.read_value.status == AttStatusCode.UNKNOWN_ERROR:
+ return "Yes"
+ return "No"
+
+ def MMI_IUT_SEND_READ_GREATER_OFFSET(self, description: str, **kwargs):
+ """
+ Please send read to handle = 'XXXX'O and offset greater than 'XXXX'O to
+ the PTS.
+
+ Description: Verify that the Implementation Under Test (IUT)
+ can send Read with invalid offset.
+ """
+
+ # Android handles the read offset internally, so we just do read with handle here.
+ # Unfortunately for testing, this will always work.
+ assert self.connection is not None
+ handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0])
+ self.read_value = self.gatt.ReadCharacteristicFromHandle(\
+ connection=self.connection, handle=handle)
+ return "OK"
+
+ @assert_description
+ def MMI_IUT_CONFIRM_READ_INVALID_OFFSET(self, **kwargs):
+ """
+ Please confirm IUT received Invalid offset error. Click Yes if IUT
+ received it, otherwise click No.
+
+ Description: Verify that the
+ Implementation Under Test (IUT) indicate Invalid offset error when read
+ a characteristic.
+ """
+
+ # Android handles read offset internally, so we can't read with wrong offset.
+ return "Yes"
+
+ @assert_description
+ def MMI_IUT_CONFIRM_READ_APPLICATION(self, **kwargs):
+ """
+ Please confirm IUT received Application error. Click Yes if IUT received
+ it, otherwise click No.
+
+ Description: Verify that the Implementation
+ Under Test (IUT) indicate Application error when read a characteristic.
+ """
+
+ assert self.read_value is not None
+ if self.read_value.status == AttStatusCode.APPLICATION_ERROR:
+ return "Yes"
+ return "No"
+
+ def MMI_IUT_CONFIRM_READ_CHARACTERISTIC_VALUE(self, description: str, **kwargs):
+ """
+ Please confirm IUT received characteristic value='XX'O in random
+ selected adopted database. Click Yes if IUT received it, otherwise click
+ No.
+
+ Description: Verify that the Implementation Under Test (IUT) can
+ send Read characteristic to PTS random select adopted database.
+ """
+
+ assert self.read_value is not None
+ characteristic_value = bytes.fromhex(re.findall("'([a0-Z9]*)'O", description)[0])
+ if characteristic_value[0] in self.read_value.value:
+ return "Yes"
+ return "No"
+
+ def MMI_IUT_READ_BY_TYPE_UUID(self, description: str, **kwargs):
+ """
+ Please send read by type characteristic UUID = 'XXXX'O to the PTS.
+ Description: Verify that the Implementation Under Test (IUT) can send
+ Read characteristic.
+ """
+
+ assert self.connection is not None
+ matches = re.findall("'([a0-Z9]*)'O", description)
+ self.read_value = self.gatt.ReadCharacteristicFromUuid(\
+ connection=self.connection, uuid=formatUuid(matches[0]),\
+ start_handle=0x0001,\
+ end_handle=0xffff)
+ return "OK"
+
+ def MMI_IUT_READ_BY_TYPE_UUID_ALT(self, description: str, **kwargs):
+ """
+ Please send read by type characteristic UUID =
+ 'XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX-XXXX'O to the PTS.
+
+ Description:
+ Verify that the Implementation Under Test (IUT) can send Read
+ characteristic.
+ """
+
+ assert self.connection is not None
+ uuid = formatUuid(re.findall("'([a0-Z9-]*)'O", description)[0])
+ self.read_value = self.gatt.ReadCharacteristicFromUuid(\
+ connection=self.connection, uuid=uuid, start_handle=0x0001, end_handle=0xffff)
+ return "OK"
+
+ def MMI_IUT_CONFIRM_READ_HANDLE_VALUE(self, description: str, **kwargs):
+ """
+ Please confirm IUT Handle='XX'O characteristic
+ value='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'O in random
+ selected adopted database. Click Yes if it matches the IUT, otherwise
+ click No.
+
+ Description: Verify that the Implementation Under Test (IUT)
+ can send Read long characteristic to PTS random select adopted database.
+ """
+
+ assert self.read_value is not None
+ bytes_value = bytes.fromhex(re.search("value='(.*)'O", description)[1])
+ if self.read_value.value == bytes_value:
+ return "Yes"
+ return "No"
+
+ def MMI_IUT_SEND_READ_DESCIPTOR_HANDLE(self, description: str, **kwargs):
+ """
+ Please send read characteristic descriptor handle = 'XXXX'O to the PTS.
+ Description: Verify that the Implementation Under Test (IUT) can send
+ Read characteristic descriptor.
+ """
+
+ assert self.connection is not None
+ handle = stringHandleToInt(re.findall("'([a0-Z9]*)'O", description)[0])
+ self.read_value = self.gatt.ReadCharacteristicDescriptorFromHandle(\
+ connection=self.connection, handle=handle)
+ return "OK"
+
+ def MMI_IUT_CONFIRM_READ_DESCRIPTOR_VALUE(self, description: str, **kwargs):
+ """
+ Please confirm IUT received Descriptor value='XXXXXXXX'O in random
+ selected adopted database. Click Yes if IUT received it, otherwise click
+ No.
+
+ Description: Verify that the Implementation Under Test (IUT) can
+ send Read Descriptor to PTS random select adopted database.
+ """
+
+ assert self.read_value is not None
+ bytes_value = bytes.fromhex(re.search("value='(.*)'O", description)[1])
+ if self.read_value.value == bytes_value:
+ return "Yes"
+ return "No"
+
common_uuid = "0000XXXX-0000-1000-8000-00805f9b34fb"
diff --git a/android/pandora/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml
index 930980f100..fd51c13577 100644
--- a/android/pandora/server/configs/PtsBotTest.xml
+++ b/android/pandora/server/configs/PtsBotTest.xml
@@ -25,6 +25,7 @@
<option name="profile" value="AVRCP" />
<option name="profile" value="GATT/CL/GAC" />
<option name="profile" value="GATT/CL/GAD" />
+ <option name="profile" value="GATT/CL/GAR" />
<option name="profile" value="HFP/AG/DIS" />
<option name="profile" value="HFP/AG/HFI" />
<option name="profile" value="HFP/AG/SLC" />
diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json
index 0db8d14133..ba1e8d45e4 100644
--- a/android/pandora/server/configs/pts_bot_tests_config.json
+++ b/android/pandora/server/configs/pts_bot_tests_config.json
@@ -15,7 +15,6 @@
"AVDTP/SRC/ACP/SIG/SMG/BI-26-C",
"AVDTP/SRC/ACP/SIG/SMG/BI-33-C",
"AVDTP/SRC/ACP/SIG/SMG/BV-06-C",
- "AVDTP/SRC/ACP/SIG/SMG/BV-08-C",
"AVDTP/SRC/ACP/SIG/SMG/BV-10-C",
"AVDTP/SRC/ACP/SIG/SMG/BV-12-C",
"AVDTP/SRC/ACP/SIG/SMG/BV-16-C",
@@ -59,12 +58,18 @@
"GATT/CL/GAD/BV-06-C",
"GATT/CL/GAD/BV-07-C",
"GATT/CL/GAD/BV-08-C",
- "HFP/AG/DIS/BV-01-I",
- "HFP/AG/HFI/BI-03-I",
- "HFP/AG/HFI/BV-02-I",
- "HFP/AG/SLC/BV-09-I",
- "HFP/AG/SLC/BV-10-I",
- "HFP/AG/TCA/BV-04-I",
+ "GATT/CL/GAR/BI-01-C",
+ "GATT/CL/GAR/BI-02-C",
+ "GATT/CL/GAR/BI-06-C",
+ "GATT/CL/GAR/BI-07-C",
+ "GATT/CL/GAR/BI-12-C",
+ "GATT/CL/GAR/BI-13-C",
+ "GATT/CL/GAR/BI-14-C",
+ "GATT/CL/GAR/BI-35-C",
+ "GATT/CL/GAR/BV-01-C",
+ "GATT/CL/GAR/BV-04-C",
+ "GATT/CL/GAR/BV-06-C",
+ "GATT/CL/GAR/BV-07-C",
"SDP/SR/BRW/BV-02-C",
"SDP/SR/SA/BI-01-C",
"SDP/SR/SA/BI-02-C",
@@ -101,7 +106,6 @@
"SM/CEN/EKS/BI-01-C",
"SM/CEN/EKS/BV-01-C",
"SM/CEN/JW/BV-05-C",
- "SM/CEN/KDU/BI-01-C",
"SM/CEN/KDU/BV-04-C",
"SM/CEN/KDU/BV-05-C",
"SM/CEN/KDU/BI-02-C",
@@ -123,6 +127,7 @@
"A2DP/SRC/SYN/BV-02-I",
"AVDTP/SRC/ACP/SIG/SMG/BI-11-C",
"AVDTP/SRC/ACP/SIG/SMG/BI-23-C",
+ "AVDTP/SRC/ACP/SIG/SMG/BV-08-C",
"AVDTP/SRC/ACP/SIG/SMG/BV-14-C",
"AVDTP/SRC/ACP/SIG/SMG/ESR05/BV-14-C",
"AVDTP/SRC/INT/SIG/SMG/BI-35-C",
@@ -182,6 +187,16 @@
"AVRCP/CT/CRC/BV-01-I",
"AVRCP/TG/CRC/BV-02-I",
"AVRCP/CT/CEC/BV-01-I",
+ "GATT/CL/GAR/BI-04-C",
+ "GATT/CL/GAR/BI-05-C",
+ "GATT/CL/GAR/BI-10-C",
+ "GATT/CL/GAR/BI-11-C",
+ "GATT/CL/GAR/BI-16-C",
+ "GATT/CL/GAR/BI-17-C",
+ "GATT/CL/GAR/BV-03-C",
+ "HFP/AG/DIS/BV-01-I",
+ "HFP/AG/HFI/BI-03-I",
+ "HFP/AG/HFI/BV-02-I",
"HFP/AG/SLC/BV-01-C",
"HFP/AG/SLC/BV-02-C",
"HFP/AG/SLC/BV-03-C",
@@ -189,12 +204,16 @@
"HFP/AG/SLC/BV-05-I",
"HFP/AG/SLC/BV-06-I",
"HFP/AG/SLC/BV-07-I",
+ "HFP/AG/SLC/BV-09-I",
+ "HFP/AG/SLC/BV-10-I",
"HFP/AG/TCA/BV-01-I",
"HFP/AG/TCA/BV-02-I",
"HFP/AG/TCA/BV-03-I",
+ "HFP/AG/TCA/BV-04-I",
"HFP/AG/TCA/BV-05-I",
"SM/CEN/JW/BI-01-C",
- "SM/CEN/JW/BI-04-C"
+ "SM/CEN/JW/BI-04-C",
+ "SM/CEN/KDU/BI-01-C"
],
"ics": {
"TSPC_4.0HCI_1a_2": true,
@@ -1219,4 +1238,4 @@
"SPP": {},
"SUM ICS": {}
}
-} \ No newline at end of file
+}
diff --git a/android/pandora/server/proto/pandora/gatt.proto b/android/pandora/server/proto/pandora/gatt.proto
index 0f8c8f5217..23e3f5725e 100644
--- a/android/pandora/server/proto/pandora/gatt.proto
+++ b/android/pandora/server/proto/pandora/gatt.proto
@@ -25,6 +25,26 @@ service GATT {
// Clears DUT GATT cache.
rpc ClearCache(ClearCacheRequest) returns (google.protobuf.Empty);
+
+ // Reads characteristic with given handle.
+ rpc ReadCharacteristicFromHandle(ReadCharacteristicRequest) returns (ReadCharacteristicResponse);
+
+ // Reads characteristic with given uuid, start and end handles.
+ rpc ReadCharacteristicFromUuid(ReadCharacteristicFromUuidRequest) returns (ReadCharacteristicResponse);
+
+ // Reads characteristic with given descriptor handle.
+ rpc ReadCharacteristicDescriptorFromHandle(ReadCharacteristicDescriptorRequest) returns (ReadCharacteristicDescriptorResponse);
+}
+
+enum AttStatusCode {
+ SUCCESS = 0x00;
+ UNKNOWN_ERROR = 0x101;
+ INVALID_HANDLE = 0x01;
+ READ_NOT_PERMITTED = 0x02;
+ INSUFFICIENT_AUTHENTICATION = 0x05;
+ INVALID_OFFSET = 0x07;
+ ATTRIBUTE_NOT_FOUND = 0x0A;
+ APPLICATION_ERROR = 0x80;
}
// A message representing a GATT service.
@@ -58,7 +78,7 @@ message ExchangeMTURequest {
int32 mtu = 2;
}
-// Request for the `writeCharacteristicFromHandle` rpc.
+// Request for the `WriteCharacteristicFromHandle` rpc.
message WriteCharacteristicRequest {
Connection connection = 1;
uint32 handle = 2;
@@ -95,3 +115,35 @@ message DiscoverServicesSdpResponse {
message ClearCacheRequest {
Connection connection = 1;
}
+
+// Request for the `ReadCharacteristicFromHandle` rpc.
+message ReadCharacteristicRequest {
+ Connection connection = 1;
+ uint32 handle = 2;
+}
+
+// Request for the `ReadCharacteristicFromUuid` rpc.
+message ReadCharacteristicFromUuidRequest {
+ Connection connection = 1;
+ string uuid = 2;
+ uint32 start_handle = 3;
+ uint32 end_handle = 4;
+}
+
+// Response for the `ReadCharacteristicFromHandle` and `ReadCharacteristicFromUuid` rpc.
+message ReadCharacteristicResponse {
+ bytes value = 1;
+ AttStatusCode status = 2;
+}
+
+// Request for the `ReadCharacteristicDescriptorFromHandle` rpc.
+message ReadCharacteristicDescriptorRequest {
+ Connection connection = 1;
+ uint32 handle = 2;
+}
+
+// Response for the `ReadCharacteristicDescriptorFromHandle` rpc.
+message ReadCharacteristicDescriptorResponse {
+ bytes value = 1;
+ AttStatusCode status = 2;
+}
diff --git a/android/pandora/server/src/com/android/pandora/Gatt.kt b/android/pandora/server/src/com/android/pandora/Gatt.kt
index 1a48c1e41f..a22f28d06b 100644
--- a/android/pandora/server/src/com/android/pandora/Gatt.kt
+++ b/android/pandora/server/src/com/android/pandora/Gatt.kt
@@ -18,16 +18,20 @@ package com.android.pandora
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattService
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothStatusCodes
+import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import com.google.protobuf.Empty
+import com.google.protobuf.ByteString
import io.grpc.Status
import io.grpc.stub.StreamObserver
@@ -74,9 +78,9 @@ class Gatt(private val context: Context) : GATTImplBase() {
responseObserver: StreamObserver<Empty>) {
grpcUnary<Empty>(mScope, responseObserver) {
val mtu = request.mtu
- val addr = request.connection.cookie.toByteArray().decodeToString()
- if (!GattInstance.get(addr).mGatt.requestMtu(mtu)) {
- Log.e(TAG, "Error on requesting MTU for $addr")
+ Log.i(TAG, "exchangeMTU MTU=$mtu")
+ if (!GattInstance.get(request.connection.cookie).mGatt.requestMtu(mtu)) {
+ Log.e(TAG, "Error on requesting MTU $mtu")
throw Status.UNKNOWN.asException()
}
Empty.getDefaultInstance()
@@ -86,16 +90,15 @@ class Gatt(private val context: Context) : GATTImplBase() {
override fun writeCharacteristicFromHandle(request: WriteCharacteristicRequest,
responseObserver: StreamObserver<Empty>) {
grpcUnary<Empty>(mScope, responseObserver) {
- val addr = request.connection.cookie.toByteArray().decodeToString()
- val gattInstance = GattInstance.get(addr)
+ val gattInstance = GattInstance.get(request.connection.cookie)
val characteristic: BluetoothGattCharacteristic? =
getCharacteristicWithHandle(request.handle, gattInstance)
if (characteristic != null) {
+ Log.i(TAG, "writeCharacteristicFromHandle handle=${request.handle}")
gattInstance.mGatt.writeCharacteristic(characteristic,
request.value.toByteArray(), BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT)
} else {
- Log.e(TAG,
- "Error while writing characteristic for $gattInstance")
+ Log.e(TAG, "Characteristic handle ${request.handle} not found.")
throw Status.UNKNOWN.asException()
}
Empty.getDefaultInstance()
@@ -105,12 +108,13 @@ class Gatt(private val context: Context) : GATTImplBase() {
override fun discoverServiceByUuid(request: DiscoverServiceByUuidRequest,
responseObserver: StreamObserver<DiscoverServicesResponse>) {
grpcUnary<DiscoverServicesResponse>(mScope, responseObserver) {
- val addr = request.connection.cookie.toByteArray().decodeToString()
- val gattInstance = GattInstance.get(addr)
+ val gattInstance = GattInstance.get(request.connection.cookie)
+ Log.i(TAG, "discoverServiceByUuid uuid=${request.uuid}")
// In some cases, GATT starts a discovery immediately after being connected, so
// we need to wait until the service discovery is finished to be able to discover again.
// This takes between 20s and 28s, and there is no way to know if the service is busy or not.
- delay(30000L)
+ // Delay was originally 30s, but due to flakyness increased to 32s.
+ delay(32000L)
check(gattInstance.mGatt.discoverServiceByUuid(UUID.fromString(request.uuid)))
// BluetoothGatt#discoverServiceByUuid does not trigger any callback and does not return
// any service, the API was made for PTS testing only.
@@ -121,8 +125,8 @@ class Gatt(private val context: Context) : GATTImplBase() {
override fun discoverServices(request: DiscoverServicesRequest,
responseObserver: StreamObserver<DiscoverServicesResponse>) {
grpcUnary<DiscoverServicesResponse>(mScope, responseObserver) {
- val addr = request.connection.cookie.toByteArray().decodeToString()
- val gattInstance = GattInstance.get(addr)
+ Log.i(TAG, "discoverServices")
+ val gattInstance = GattInstance.get(request.connection.cookie)
check(gattInstance.mGatt.discoverServices())
gattInstance.waitForDiscoveryEnd()
DiscoverServicesResponse.newBuilder()
@@ -133,6 +137,7 @@ class Gatt(private val context: Context) : GATTImplBase() {
override fun discoverServicesSdp(request: DiscoverServicesSdpRequest,
responseObserver: StreamObserver<DiscoverServicesSdpResponse>) {
grpcUnary<DiscoverServicesSdpResponse>(mScope, responseObserver) {
+ Log.i(TAG, "discoverServicesSdp")
val bluetoothDevice = request.address.toBluetoothDevice(mBluetoothAdapter)
check(bluetoothDevice.fetchUuidsWithSdp())
flow
@@ -151,23 +156,72 @@ class Gatt(private val context: Context) : GATTImplBase() {
override fun clearCache(request: ClearCacheRequest,
responseObserver: StreamObserver<Empty>) {
grpcUnary<Empty>(mScope, responseObserver) {
- val addr = request.connection.cookie.toByteArray().decodeToString()
- val gattInstance = GattInstance.get(addr)
+ Log.i(TAG, "clearCache")
+ val gattInstance = GattInstance.get(request.connection.cookie)
check(gattInstance.mGatt.refresh())
Empty.getDefaultInstance()
}
}
+ override fun readCharacteristicFromHandle(request: ReadCharacteristicRequest,
+ responseObserver: StreamObserver<ReadCharacteristicResponse>) {
+ grpcUnary<ReadCharacteristicResponse>(mScope, responseObserver) {
+ Log.i(TAG, "readCharacteristicFromHandle handle=${request.handle}")
+ val gattInstance = GattInstance.get(request.connection.cookie)
+ val characteristic: BluetoothGattCharacteristic? =
+ getCharacteristicWithHandle(request.handle, gattInstance)
+ val readValue: GattInstance.GattInstanceValueRead?
+ checkNotNull(characteristic) {
+ "Characteristic handle ${request.handle} not found."
+ }
+ readValue = gattInstance.readCharacteristicBlocking(characteristic)
+ ReadCharacteristicResponse.newBuilder()
+ .setStatus(AttStatusCode.forNumber(readValue.status))
+ .setValue(ByteString.copyFrom(readValue.value)).build()
+ }
+ }
+
+ override fun readCharacteristicFromUuid(request: ReadCharacteristicFromUuidRequest,
+ responseObserver: StreamObserver<ReadCharacteristicResponse>) {
+ grpcUnary<ReadCharacteristicResponse>(mScope, responseObserver) {
+ Log.i(TAG, "readCharacteristicFromUuid uuid=${request.uuid}")
+ val gattInstance = GattInstance.get(request.connection.cookie)
+ tryDiscoverServices(gattInstance)
+ val readValue = gattInstance.readCharacteristicUuidBlocking(UUID.fromString(request.uuid),
+ request.startHandle, request.endHandle)
+ ReadCharacteristicResponse.newBuilder()
+ .setStatus(AttStatusCode.forNumber(readValue.status))
+ .setValue(ByteString.copyFrom(readValue.value)).build()
+ }
+ }
+
+ override fun readCharacteristicDescriptorFromHandle(request: ReadCharacteristicDescriptorRequest,
+ responseObserver: StreamObserver<ReadCharacteristicDescriptorResponse>) {
+ grpcUnary<ReadCharacteristicDescriptorResponse>(mScope, responseObserver) {
+ Log.i(TAG, "readCharacteristicDescriptorFromHandle handle=${request.handle}")
+ val gattInstance = GattInstance.get(request.connection.cookie)
+ val descriptor: BluetoothGattDescriptor? =
+ getDescriptorWithHandle(request.handle, gattInstance)
+ val readValue: GattInstance.GattInstanceValueRead?
+ checkNotNull(descriptor) {
+ "Descriptor handle ${request.handle} not found."
+ }
+ readValue = gattInstance.readDescriptorBlocking(descriptor)
+ ReadCharacteristicDescriptorResponse.newBuilder()
+ .setStatus(AttStatusCode.forNumber(readValue.status))
+ .setValue(ByteString.copyFrom(readValue.value)).build()
+ }
+ }
+
+ /**
+ * Discovers services, then returns characteristic with given handle.
+ * BluetoothGatt API is package-private so we have to redefine it here.
+ */
private suspend fun getCharacteristicWithHandle(handle: Int,
gattInstance: GattInstance): BluetoothGattCharacteristic? {
- if (!gattInstance.servicesDiscovered() && !gattInstance.mGatt.discoverServices()) {
- Log.e(TAG, "Error on discovering services for $gattInstance")
- throw Status.UNKNOWN.asException()
- } else {
- gattInstance.waitForDiscoveryEnd()
- }
+ tryDiscoverServices(gattInstance)
for (service: BluetoothGattService in gattInstance.mGatt.services.orEmpty()) {
- for (characteristic : BluetoothGattCharacteristic in service.characteristics) {
+ for (characteristic: BluetoothGattCharacteristic in service.characteristics) {
if (characteristic.instanceId == handle) {
return characteristic
}
@@ -177,6 +231,25 @@ class Gatt(private val context: Context) : GATTImplBase() {
}
/**
+ * Discovers services, then returns descriptor with given handle.
+ * BluetoothGatt API is package-private so we have to redefine it here.
+ */
+ private suspend fun getDescriptorWithHandle(handle: Int,
+ gattInstance: GattInstance): BluetoothGattDescriptor? {
+ tryDiscoverServices(gattInstance)
+ for (service: BluetoothGattService in gattInstance.mGatt.services.orEmpty()) {
+ for (characteristic: BluetoothGattCharacteristic in service.characteristics) {
+ for (descriptor: BluetoothGattDescriptor in characteristic.descriptors) {
+ if (descriptor.getInstanceId() == handle) {
+ return descriptor
+ }
+ }
+ }
+ }
+ return null
+ }
+
+ /**
* Generates a list of GattService from a list of BluetoothGattService.
*/
private fun generateServicesList(servicesList: List<BluetoothGattService>, dpth: Int)
@@ -227,4 +300,13 @@ class Gatt(private val context: Context) : GATTImplBase() {
}
return newDescriptorsList
}
+
+ private suspend fun tryDiscoverServices(gattInstance: GattInstance) {
+ if (!gattInstance.servicesDiscovered() && !gattInstance.mGatt.discoverServices()) {
+ Log.e(TAG, "Error on discovering services for $gattInstance")
+ throw Status.UNKNOWN.asException()
+ } else {
+ gattInstance.waitForDiscoveryEnd()
+ }
+ }
} \ No newline at end of file
diff --git a/android/pandora/server/src/com/android/pandora/GattInstance.kt b/android/pandora/server/src/com/android/pandora/GattInstance.kt
index 69edf290fb..93c8799d78 100644
--- a/android/pandora/server/src/com/android/pandora/GattInstance.kt
+++ b/android/pandora/server/src/com/android/pandora/GattInstance.kt
@@ -19,14 +19,21 @@ package com.android.pandora
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCallback
+import android.bluetooth.BluetoothGattCharacteristic
+import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothProfile
import android.content.Context
import android.util.Log
+import com.google.protobuf.ByteString
+
+import java.util.UUID
+
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.withTimeoutOrNull
/**
* GattInstance extends and simplifies Android GATT APIs without re-implementing them.
@@ -38,6 +45,17 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte
private var mServiceDiscovered = MutableStateFlow(false)
private var mConnectionState = MutableStateFlow(BluetoothProfile.STATE_DISCONNECTED)
+ private var mValueRead = MutableStateFlow(false)
+
+ /**
+ * Wrapper for characteristic and descriptor reading.
+ * Uuid, startHandle and endHandle are used to compare with the callback returned object.
+ * Value and status can be read once the read has been done.
+ */
+ class GattInstanceValueRead(var uuid: UUID?, var startHandle: Int, var endHandle: Int,
+ var value: ByteArray?, var status: Int) {}
+ private var mGattInstanceValueRead = GattInstanceValueRead(
+ null, 0, 0, byteArrayOf(), BluetoothGatt.GATT_FAILURE)
companion object GattManager {
val gattInstances: MutableMap<String, GattInstance> = mutableMapOf<String, GattInstance>()
@@ -48,6 +66,13 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte
}
return instance
}
+ fun get(address: ByteString): GattInstance {
+ val instance = gattInstances.get(address.toByteArray().decodeToString())
+ requireNotNull(instance) {
+ "Unable to find GATT instance for $address"
+ }
+ return instance
+ }
}
private val mCallback = object : BluetoothGattCallback() {
@@ -66,6 +91,30 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte
mServiceDiscovered.value = true
}
}
+
+ override fun onCharacteristicRead(bluetoothGatt: BluetoothGatt,
+ characteristic: BluetoothGattCharacteristic, value: ByteArray, status: Int) {
+ Log.i(TAG, "onCharacteristicRead, status: $status")
+ if (characteristic.getUuid() == mGattInstanceValueRead.uuid
+ && characteristic.getInstanceId() >= mGattInstanceValueRead.startHandle
+ && characteristic.getInstanceId() <= mGattInstanceValueRead.endHandle) {
+ mGattInstanceValueRead.value = value
+ mGattInstanceValueRead.status = status
+ mValueRead.value = true
+ }
+ }
+
+ override fun onDescriptorRead(bluetoothGatt: BluetoothGatt,
+ descriptor: BluetoothGattDescriptor, status: Int, value: ByteArray) {
+ Log.i(TAG, "onDescriptorRead, status: $status")
+ if (descriptor.getUuid() == mGattInstanceValueRead.uuid
+ && descriptor.getInstanceId() >= mGattInstanceValueRead.startHandle
+ && descriptor.getInstanceId() <= mGattInstanceValueRead.endHandle) {
+ mGattInstanceValueRead.value = value
+ mGattInstanceValueRead.status = status
+ mValueRead.value = true
+ }
+ }
}
init {
@@ -118,6 +167,52 @@ class GattInstance(val mDevice: BluetoothDevice, val mTransport: Int, val mConte
}
}
+ public suspend fun waitForValueReadEnd() {
+ if (mValueRead.value != true) {
+ mValueRead.first { it == true }
+ }
+ mValueRead.value = false
+ }
+
+ public suspend fun readCharacteristicBlocking(
+ characteristic: BluetoothGattCharacteristic): GattInstanceValueRead {
+ // Init mGattInstanceValueRead with characteristic values.
+ mGattInstanceValueRead = GattInstanceValueRead(
+ characteristic.getUuid(), characteristic.getInstanceId(), characteristic.getInstanceId(),
+ byteArrayOf(), BluetoothGatt.GATT_FAILURE)
+ if (mGatt.readCharacteristic(characteristic)){
+ waitForValueReadEnd()
+ }
+ return mGattInstanceValueRead
+ }
+
+ public suspend fun readCharacteristicUuidBlocking(
+ uuid: UUID, startHandle: Int, endHandle: Int): GattInstanceValueRead {
+ // Init mGattInstanceValueRead with characteristic values.
+ mGattInstanceValueRead = GattInstanceValueRead(
+ uuid, startHandle, endHandle, byteArrayOf(), BluetoothGatt.GATT_FAILURE)
+ if (mGatt.readUsingCharacteristicUuid(uuid, startHandle, endHandle)){
+ // We have to timeout here as one test will try to read on an inexistant
+ // characteristic. We don't discover services when reading by uuid so we
+ // can't check if the characteristic exists beforehand. PTS is also waiting
+ // for the read to happen so we have to read anyway.
+ withTimeoutOrNull(1000L) { waitForValueReadEnd() }
+ }
+ return mGattInstanceValueRead
+ }
+
+ public suspend fun readDescriptorBlocking(
+ descriptor: BluetoothGattDescriptor): GattInstanceValueRead {
+ // Init mGattInstanceValueRead with descriptor values.
+ mGattInstanceValueRead = GattInstanceValueRead(
+ descriptor.getUuid(), descriptor.getInstanceId(), descriptor.getInstanceId(),
+ byteArrayOf(), BluetoothGatt.GATT_FAILURE)
+ if (mGatt.readDescriptor(descriptor)){
+ waitForValueReadEnd()
+ }
+ return mGattInstanceValueRead
+ }
+
public fun disconnectInstance() {
require(isConnected()) {
"Trying to disconnect an already disconnected device $mDevice"
diff --git a/framework/java/android/bluetooth/BluetoothAdapter.java b/framework/java/android/bluetooth/BluetoothAdapter.java
index dcd2989fe1..043880cc13 100644
--- a/framework/java/android/bluetooth/BluetoothAdapter.java
+++ b/framework/java/android/bluetooth/BluetoothAdapter.java
@@ -1269,7 +1269,7 @@ public final class BluetoothAdapter {
IpcDataCache.invalidateCache(IpcDataCache.MODULE_BLUETOOTH, api);
}
- private final IpcDataCache.QueryHandler<IBluetooth, Integer> mBluetoothGetStateQuery =
+ private static final IpcDataCache.QueryHandler<IBluetooth, Integer> sBluetoothGetStateQuery =
new IpcDataCache.QueryHandler<>() {
@RequiresLegacyBluetoothPermission
@RequiresNoPermission
@@ -1288,13 +1288,13 @@ public final class BluetoothAdapter {
private static final String GET_STATE_API = "BluetoothAdapter_getState";
- private final IpcDataCache<IBluetooth, Integer> mBluetoothGetStateCache =
- new BluetoothCache<>(GET_STATE_API, mBluetoothGetStateQuery);
+ private static final IpcDataCache<IBluetooth, Integer> sBluetoothGetStateCache =
+ new BluetoothCache<>(GET_STATE_API, sBluetoothGetStateQuery);
/** @hide */
@RequiresNoPermission
public void disableBluetoothGetStateCache() {
- mBluetoothGetStateCache.disableForCurrentProcess();
+ sBluetoothGetStateCache.disableForCurrentProcess();
}
/** @hide */
@@ -1310,7 +1310,7 @@ public final class BluetoothAdapter {
mServiceLock.readLock().lock();
try {
if (mService != null) {
- return mBluetoothGetStateCache.query(mService);
+ return sBluetoothGetStateCache.query(mService);
}
} catch (RuntimeException e) {
if (!(e.getCause() instanceof TimeoutException)
@@ -2422,7 +2422,7 @@ public final class BluetoothAdapter {
}
}
- private final IpcDataCache.QueryHandler<IBluetooth, Boolean> mBluetoothFilteringQuery =
+ private static final IpcDataCache.QueryHandler<IBluetooth, Boolean> sBluetoothFilteringQuery =
new IpcDataCache.QueryHandler<>() {
@RequiresLegacyBluetoothPermission
@RequiresNoPermission
@@ -2439,13 +2439,13 @@ public final class BluetoothAdapter {
private static final String FILTERING_API = "BluetoothAdapter_isOffloadedFilteringSupported";
- private final IpcDataCache<IBluetooth, Boolean> mBluetoothFilteringCache =
- new BluetoothCache<>(FILTERING_API, mBluetoothFilteringQuery);
+ private static final IpcDataCache<IBluetooth, Boolean> sBluetoothFilteringCache =
+ new BluetoothCache<>(FILTERING_API, sBluetoothFilteringQuery);
/** @hide */
@RequiresNoPermission
public void disableIsOffloadedFilteringSupportedCache() {
- mBluetoothFilteringCache.disableForCurrentProcess();
+ sBluetoothFilteringCache.disableForCurrentProcess();
}
/** @hide */
@@ -2466,7 +2466,7 @@ public final class BluetoothAdapter {
}
mServiceLock.readLock().lock();
try {
- if (mService != null) return mBluetoothFilteringCache.query(mService);
+ if (mService != null) return sBluetoothFilteringCache.query(mService);
} catch (RuntimeException e) {
if (!(e.getCause() instanceof TimeoutException)
&& !(e.getCause() instanceof RemoteException)) {
@@ -2982,8 +2982,8 @@ public final class BluetoothAdapter {
return supportedProfiles;
}
- private final IpcDataCache.QueryHandler<IBluetooth, Integer>
- mBluetoothGetAdapterConnectionStateQuery = new IpcDataCache.QueryHandler<>() {
+ private static final IpcDataCache.QueryHandler<IBluetooth, Integer>
+ sBluetoothGetAdapterConnectionStateQuery = new IpcDataCache.QueryHandler<>() {
@RequiresLegacyBluetoothPermission
@RequiresNoPermission
@Override
@@ -3002,13 +3002,14 @@ public final class BluetoothAdapter {
private static final String GET_CONNECTION_API = "BluetoothAdapter_getConnectionState";
- private final IpcDataCache<IBluetooth, Integer> mBluetoothGetAdapterConnectionStateCache =
- new BluetoothCache<>(GET_CONNECTION_API, mBluetoothGetAdapterConnectionStateQuery);
+ private static final IpcDataCache<IBluetooth, Integer>
+ sBluetoothGetAdapterConnectionStateCache = new BluetoothCache<>(GET_CONNECTION_API,
+ sBluetoothGetAdapterConnectionStateQuery);
/** @hide */
@RequiresNoPermission
public void disableGetAdapterConnectionStateCache() {
- mBluetoothGetAdapterConnectionStateCache.disableForCurrentProcess();
+ sBluetoothGetAdapterConnectionStateCache.disableForCurrentProcess();
}
/** @hide */
@@ -3035,7 +3036,7 @@ public final class BluetoothAdapter {
}
mServiceLock.readLock().lock();
try {
- if (mService != null) return mBluetoothGetAdapterConnectionStateCache.query(mService);
+ if (mService != null) return sBluetoothGetAdapterConnectionStateCache.query(mService);
} catch (RuntimeException e) {
if (!(e.getCause() instanceof TimeoutException)
&& !(e.getCause() instanceof RemoteException)) {
@@ -3048,8 +3049,8 @@ public final class BluetoothAdapter {
return STATE_DISCONNECTED;
}
- private final IpcDataCache.QueryHandler<Pair<IBluetooth, Integer>, Integer>
- mBluetoothProfileQuery = new IpcDataCache.QueryHandler<>() {
+ private static final IpcDataCache.QueryHandler<Pair<IBluetooth, Integer>, Integer>
+ sBluetoothProfileQuery = new IpcDataCache.QueryHandler<>() {
@RequiresNoPermission
@Override
public Integer apply(Pair<IBluetooth, Integer> pairQuery) {
@@ -3067,15 +3068,16 @@ public final class BluetoothAdapter {
private static final String PROFILE_API = "BluetoothAdapter_getProfileConnectionState";
- private final IpcDataCache<Pair<IBluetooth, Integer>, Integer> mGetProfileConnectionStateCache =
- new BluetoothCache<>(PROFILE_API, mBluetoothProfileQuery);
+ private static final IpcDataCache<Pair<IBluetooth, Integer>, Integer>
+ sGetProfileConnectionStateCache = new BluetoothCache<>(PROFILE_API,
+ sBluetoothProfileQuery);
/**
* @hide
*/
@RequiresNoPermission
public void disableGetProfileConnectionStateCache() {
- mGetProfileConnectionStateCache.disableForCurrentProcess();
+ sGetProfileConnectionStateCache.disableForCurrentProcess();
}
/**
@@ -3104,7 +3106,7 @@ public final class BluetoothAdapter {
mServiceLock.readLock().lock();
try {
if (mService != null) {
- return mGetProfileConnectionStateCache.query(new Pair<>(mService, profile));
+ return sGetProfileConnectionStateCache.query(new Pair<>(mService, profile));
}
} catch (RuntimeException e) {
if (!(e.getCause() instanceof TimeoutException)
diff --git a/framework/java/android/bluetooth/BluetoothDevice.java b/framework/java/android/bluetooth/BluetoothDevice.java
index 5c91c9480b..aa60fd1273 100644
--- a/framework/java/android/bluetooth/BluetoothDevice.java
+++ b/framework/java/android/bluetooth/BluetoothDevice.java
@@ -1903,21 +1903,25 @@ public final class BluetoothDevice implements Parcelable, Attributable {
IpcDataCache.invalidateCache(IpcDataCache.MODULE_BLUETOOTH, api);
}
- private final IpcDataCache.QueryHandler<Pair<IBluetooth, BluetoothDevice>, Integer>
- mBluetoothBondQuery = new IpcDataCache.QueryHandler<>() {
+ private static final IpcDataCache
+ .QueryHandler<Pair<IBluetooth, Pair<AttributionSource, BluetoothDevice>>, Integer>
+ sBluetoothBondQuery = new IpcDataCache.QueryHandler<>() {
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@Override
- public Integer apply(Pair<IBluetooth, BluetoothDevice> pairQuery) {
+ public Integer apply(Pair<IBluetooth,
+ Pair<AttributionSource, BluetoothDevice>> pairQuery) {
+ IBluetooth service = pairQuery.first;
+ AttributionSource source = pairQuery.second.first;
+ BluetoothDevice device = pairQuery.second.second;
if (DBG) {
- log("getConnectionState(" + pairQuery.second.getAnonymizedAddress()
- + ") uncached");
+ log("getBondState(" + device.getAnonymizedAddress() + ") uncached");
}
try {
final SynchronousResultReceiver<Integer> recv =
SynchronousResultReceiver.get();
- pairQuery.first.getBondState(pairQuery.second, mAttributionSource, recv);
+ service.getBondState(device, source, recv);
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(BOND_NONE);
} catch (RemoteException | TimeoutException e) {
throw new RuntimeException(e);
@@ -1927,12 +1931,13 @@ public final class BluetoothDevice implements Parcelable, Attributable {
private static final String GET_BOND_STATE_API = "BluetoothDevice_getBondState";
- private final BluetoothCache<Pair<IBluetooth, BluetoothDevice>, Integer> mBluetoothBondCache =
- new BluetoothCache<>(GET_BOND_STATE_API, mBluetoothBondQuery);
+ private static final
+ BluetoothCache<Pair<IBluetooth, Pair<AttributionSource, BluetoothDevice>>, Integer>
+ sBluetoothBondCache = new BluetoothCache<>(GET_BOND_STATE_API, sBluetoothBondQuery);
/** @hide */
public void disableBluetoothGetBondStateCache() {
- mBluetoothBondCache.disableForCurrentProcess();
+ sBluetoothBondCache.disableForCurrentProcess();
}
/** @hide */
@@ -1961,7 +1966,8 @@ public final class BluetoothDevice implements Parcelable, Attributable {
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else {
try {
- return mBluetoothBondCache.query(new Pair<>(service, BluetoothDevice.this));
+ return sBluetoothBondCache.query(
+ new Pair<>(service, new Pair<>(mAttributionSource, BluetoothDevice.this)));
} catch (RuntimeException e) {
if (!(e.getCause() instanceof TimeoutException)
&& !(e.getCause() instanceof RemoteException)) {
diff --git a/framework/java/android/bluetooth/BluetoothMap.java b/framework/java/android/bluetooth/BluetoothMap.java
index 2d4bc568ca..9e606f9d2e 100644
--- a/framework/java/android/bluetooth/BluetoothMap.java
+++ b/framework/java/android/bluetooth/BluetoothMap.java
@@ -367,7 +367,7 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable {
/** @hide */
public void disableBluetoothGetConnectionStateCache() {
- mBluetoothConnectionCache.disableForCurrentProcess();
+ sBluetoothConnectionCache.disableForCurrentProcess();
}
/** @hide */
@@ -383,20 +383,23 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable {
IpcDataCache.invalidateCache(IpcDataCache.MODULE_BLUETOOTH, api);
}
- private final IpcDataCache.QueryHandler<Pair<IBluetoothMap, BluetoothDevice>, Integer>
- mBluetoothConnectionQuery = new IpcDataCache.QueryHandler<>() {
+ private static final IpcDataCache
+ .QueryHandler<Pair<IBluetoothMap, Pair<AttributionSource, BluetoothDevice>>, Integer>
+ sBluetoothConnectionQuery = new IpcDataCache.QueryHandler<>() {
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@Override
- public Integer apply(Pair<IBluetoothMap, BluetoothDevice> pairQuery) {
+ public Integer apply(Pair<IBluetoothMap,
+ Pair<AttributionSource, BluetoothDevice>> pairQuery) {
+ IBluetoothMap service = pairQuery.first;
+ AttributionSource source = pairQuery.second.first;
+ BluetoothDevice device = pairQuery.second.second;
if (DBG) {
- log("getConnectionState(" + pairQuery.second.getAnonymizedAddress()
- + ") uncached");
+ log("getConnectionState(" + device.getAnonymizedAddress() + ") uncached");
}
final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
try {
- pairQuery.first
- .getConnectionState(pairQuery.second, mAttributionSource, recv);
+ service.getConnectionState(device, source, recv);
return recv.awaitResultNoInterrupt(getSyncTimeout())
.getValue(BluetoothProfile.STATE_DISCONNECTED);
} catch (RemoteException | TimeoutException e) {
@@ -407,9 +410,10 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable {
private static final String GET_CONNECTION_STATE_API = "BluetoothMap_getConnectionState";
- private final BluetoothCache<Pair<IBluetoothMap, BluetoothDevice>, Integer>
- mBluetoothConnectionCache = new BluetoothCache<>(GET_CONNECTION_STATE_API,
- mBluetoothConnectionQuery);
+ private static final
+ BluetoothCache<Pair<IBluetoothMap, Pair<AttributionSource, BluetoothDevice>>, Integer>
+ sBluetoothConnectionCache = new BluetoothCache<>(GET_CONNECTION_STATE_API,
+ sBluetoothConnectionQuery);
/**
* Get connection state of device
@@ -427,7 +431,8 @@ public final class BluetoothMap implements BluetoothProfile, AutoCloseable {
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled() && isValidDevice(device)) {
try {
- return mBluetoothConnectionCache.query(new Pair<>(service, device));
+ return sBluetoothConnectionCache.query(
+ new Pair<>(service, new Pair<>(mAttributionSource, device)));
} catch (RuntimeException e) {
if (!(e.getCause() instanceof TimeoutException)
&& !(e.getCause() instanceof RemoteException)) {
diff --git a/framework/java/android/bluetooth/BluetoothSap.java b/framework/java/android/bluetooth/BluetoothSap.java
index 137c25b29e..774df33d79 100644
--- a/framework/java/android/bluetooth/BluetoothSap.java
+++ b/framework/java/android/bluetooth/BluetoothSap.java
@@ -364,7 +364,7 @@ public final class BluetoothSap implements BluetoothProfile, AutoCloseable {
/** @hide */
public void disableBluetoothGetConnectionStateCache() {
- mBluetoothConnectionCache.disableForCurrentProcess();
+ sBluetoothConnectionCache.disableForCurrentProcess();
}
/** @hide */
@@ -380,20 +380,23 @@ public final class BluetoothSap implements BluetoothProfile, AutoCloseable {
IpcDataCache.invalidateCache(IpcDataCache.MODULE_BLUETOOTH, api);
}
- private final IpcDataCache.QueryHandler<Pair<IBluetoothSap, BluetoothDevice>, Integer>
- mBluetoothConnectionQuery = new IpcDataCache.QueryHandler<>() {
+ private static final IpcDataCache
+ .QueryHandler<Pair<IBluetoothSap, Pair<AttributionSource, BluetoothDevice>>, Integer>
+ sBluetoothConnectionQuery = new IpcDataCache.QueryHandler<>() {
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
@Override
- public Integer apply(Pair<IBluetoothSap, BluetoothDevice> pairQuery) {
+ public Integer apply(Pair<IBluetoothSap,
+ Pair<AttributionSource, BluetoothDevice>> pairQuery) {
+ IBluetoothSap service = pairQuery.first;
+ AttributionSource source = pairQuery.second.first;
+ BluetoothDevice device = pairQuery.second.second;
if (DBG) {
- log("getConnectionState(" + pairQuery.second.getAnonymizedAddress()
- + ") uncached");
+ log("getConnectionState(" + device.getAnonymizedAddress() + ") uncached");
}
final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();
try {
- pairQuery.first
- .getConnectionState(pairQuery.second, mAttributionSource, recv);
+ service.getConnectionState(device, source, recv);
return recv.awaitResultNoInterrupt(getSyncTimeout())
.getValue(BluetoothProfile.STATE_DISCONNECTED);
} catch (RemoteException | TimeoutException e) {
@@ -404,9 +407,10 @@ public final class BluetoothSap implements BluetoothProfile, AutoCloseable {
private static final String GET_CONNECTION_STATE_API = "BluetoothSap_getConnectionState";
- private final BluetoothCache<Pair<IBluetoothSap, BluetoothDevice>, Integer>
- mBluetoothConnectionCache = new BluetoothCache<>(GET_CONNECTION_STATE_API,
- mBluetoothConnectionQuery);
+ private static final
+ BluetoothCache<Pair<IBluetoothSap, Pair<AttributionSource, BluetoothDevice>>, Integer>
+ sBluetoothConnectionCache = new BluetoothCache<>(GET_CONNECTION_STATE_API,
+ sBluetoothConnectionQuery);
/**
* Get connection state of device
@@ -424,7 +428,8 @@ public final class BluetoothSap implements BluetoothProfile, AutoCloseable {
if (DBG) log(Log.getStackTraceString(new Throwable()));
} else if (isEnabled() && isValidDevice(device)) {
try {
- return mBluetoothConnectionCache.query(new Pair<>(service, device));
+ return sBluetoothConnectionCache.query(
+ new Pair<>(service, new Pair<>(mAttributionSource, device)));
} catch (RuntimeException e) {
if (!(e.getCause() instanceof TimeoutException)
&& !(e.getCause() instanceof RemoteException)) {
diff --git a/service/Android.bp b/service/Android.bp
index 2a5e1adef4..c43ac23bfd 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -68,6 +68,7 @@ java_library {
"modules-utils-shell-command-handler",
"bluetooth-manager-service-proto-java-gen",
"bluetooth-proto-enums-java-gen",
+ "bluetooth-nano-protos",
],
apex_available: [
@@ -128,6 +129,23 @@ java_library {
min_sdk_version: "Tiramisu"
}
+java_library {
+ name: "bluetooth-nano-protos",
+ sdk_version: "system_current",
+ min_sdk_version: "Tiramisu",
+ proto: {
+ type: "nano",
+ },
+ srcs: [
+ ":system-messages-proto-src",
+ ],
+ libs: ["libprotobuf-java-nano"],
+ apex_available: [
+ "com.android.bluetooth",
+ ],
+ lint: { strict_updatability_linting: true },
+}
+
platform_compat_config
{
name: "bluetooth-compat-config",
diff --git a/service/java/com/android/server/bluetooth/BluetoothDeviceConfigChangeTracker.java b/service/java/com/android/server/bluetooth/BluetoothDeviceConfigChangeTracker.java
new file mode 100644
index 0000000000..a035338b4f
--- /dev/null
+++ b/service/java/com/android/server/bluetooth/BluetoothDeviceConfigChangeTracker.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.bluetooth;
+
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * The BluetoothDeviceConfigChangeTracker receives changes to the DeviceConfig for
+ * NAMESPACE_BLUETOOTH, and determines whether we should queue a restart, if any Bluetooth-related
+ * INIT_ flags have been changed.
+ *
+ * <p>The initialProperties should be fetched from the BLUETOOTH namespace in DeviceConfig
+ */
+public final class BluetoothDeviceConfigChangeTracker {
+ private static final String TAG = "BluetoothDeviceConfigChangeTracker";
+
+ private final HashMap<String, String> mCurrFlags;
+
+ public BluetoothDeviceConfigChangeTracker(Properties initialProperties) {
+ mCurrFlags = getFlags(initialProperties);
+ }
+
+ /**
+ * Updates the instance state tracking the latest init flag values, and determines whether an
+ * init flag has changed (requiring a restart at some point)
+ */
+ public boolean shouldRestartWhenPropertiesUpdated(Properties newProperties) {
+ if (!newProperties.getNamespace().equals(DeviceConfig.NAMESPACE_BLUETOOTH)) {
+ return false;
+ }
+ ArrayList<String> flags = new ArrayList<>();
+ for (String name : newProperties.getKeyset()) {
+ flags.add(name + "='" + newProperties.getString(name, "") + "'");
+ }
+ Log.d(TAG, "shouldRestartWhenPropertiesUpdated: " + String.join(",", flags));
+ boolean shouldRestart = false;
+ for (String name : newProperties.getKeyset()) {
+ if (!isInitFlag(name)) {
+ continue;
+ }
+ var oldValue = mCurrFlags.get(name);
+ var newValue = newProperties.getString(name, "");
+ if (newValue.equals(oldValue)) {
+ continue;
+ }
+ Log.d(TAG, "Property " + name + " changed from " + oldValue + " -> " + newValue);
+ mCurrFlags.put(name, newValue);
+ shouldRestart = true;
+ }
+ return shouldRestart;
+ }
+
+ private HashMap<String, String> getFlags(Properties initialProperties) {
+ var out = new HashMap();
+ for (var name : initialProperties.getKeyset()) {
+ if (isInitFlag(name)) {
+ out.put(name, initialProperties.getString(name, ""));
+ }
+ }
+ return out;
+ }
+
+ private Boolean isInitFlag(String flagName) {
+ return flagName.startsWith("INIT_");
+ }
+}
diff --git a/service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java b/service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java
index 0c6eac13e2..6b985a7bc5 100644
--- a/service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java
+++ b/service/java/com/android/server/bluetooth/BluetoothDeviceConfigListener.java
@@ -24,8 +24,6 @@ import android.provider.DeviceConfig;
import android.provider.Settings;
import android.util.Log;
-import java.util.ArrayList;
-
/**
* The BluetoothDeviceConfigListener handles system device config change callback and checks
* whether we need to inform BluetoothManagerService on this change.
@@ -38,11 +36,13 @@ import java.util.ArrayList;
public class BluetoothDeviceConfigListener {
private static final String TAG = "BluetoothDeviceConfigListener";
+ private static final int DEFAULT_APM_ENHANCEMENT = 0;
+ private static final int DEFAULT_BT_APM_STATE = 0;
+
private final BluetoothManagerService mService;
private final boolean mLogDebug;
private final Context mContext;
- private static final int DEFAULT_APM_ENHANCEMENT = 0;
- private static final int DEFAULT_BT_APM_STATE = 0;
+ private final BluetoothDeviceConfigChangeTracker mConfigChangeTracker;
private boolean mPrevApmEnhancement;
private boolean mPrevBtApmState;
@@ -56,6 +56,9 @@ public class BluetoothDeviceConfigListener {
APM_ENHANCEMENT, DEFAULT_APM_ENHANCEMENT) == 1;
mPrevBtApmState = Settings.Global.getInt(mContext.getContentResolver(),
BT_DEFAULT_APM_STATE, DEFAULT_BT_APM_STATE) == 1;
+ mConfigChangeTracker =
+ new BluetoothDeviceConfigChangeTracker(
+ DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BLUETOOTH));
DeviceConfig.addOnPropertiesChangedListener(
DeviceConfig.NAMESPACE_BLUETOOTH,
(Runnable r) -> r.run(),
@@ -65,19 +68,8 @@ public class BluetoothDeviceConfigListener {
private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigChangedListener =
new DeviceConfig.OnPropertiesChangedListener() {
@Override
- public void onPropertiesChanged(DeviceConfig.Properties properties) {
- if (!properties.getNamespace().equals(DeviceConfig.NAMESPACE_BLUETOOTH)) {
- return;
- }
- if (mLogDebug) {
- ArrayList<String> flags = new ArrayList<>();
- for (String name : properties.getKeyset()) {
- flags.add(name + "='" + properties.getString(name, "") + "'");
- }
- Log.d(TAG, "onPropertiesChanged: " + String.join(",", flags));
- }
-
- boolean apmEnhancement = properties.getBoolean(
+ public void onPropertiesChanged(DeviceConfig.Properties newProperties) {
+ boolean apmEnhancement = newProperties.getBoolean(
APM_ENHANCEMENT, mPrevApmEnhancement);
if (apmEnhancement != mPrevApmEnhancement) {
mPrevApmEnhancement = apmEnhancement;
@@ -85,24 +77,20 @@ public class BluetoothDeviceConfigListener {
APM_ENHANCEMENT, apmEnhancement ? 1 : 0);
}
- boolean btApmState = properties.getBoolean(
+ boolean btApmState = newProperties.getBoolean(
BT_DEFAULT_APM_STATE, mPrevBtApmState);
if (btApmState != mPrevBtApmState) {
mPrevBtApmState = btApmState;
Settings.Global.putInt(mContext.getContentResolver(),
BT_DEFAULT_APM_STATE, btApmState ? 1 : 0);
}
- boolean foundInit = false;
- for (String name : properties.getKeyset()) {
- if (name.startsWith("INIT_")) {
- foundInit = true;
- break;
- }
- }
- if (!foundInit) {
- return;
+
+ if (mConfigChangeTracker.shouldRestartWhenPropertiesUpdated(newProperties)) {
+ Log.d(TAG, "Properties changed, enqueuing restart");
+ mService.onInitFlagsChanged();
+ } else {
+ Log.d(TAG, "All properties unchanged, skipping restart");
}
- mService.onInitFlagsChanged();
}
};
}
diff --git a/service/java/com/android/server/bluetooth/BluetoothNotificationManager.java b/service/java/com/android/server/bluetooth/BluetoothNotificationManager.java
index d2abde092b..d6883824e4 100644
--- a/service/java/com/android/server/bluetooth/BluetoothNotificationManager.java
+++ b/service/java/com/android/server/bluetooth/BluetoothNotificationManager.java
@@ -26,6 +26,8 @@ import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.util.Log;
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+
import java.util.ArrayList;
import java.util.List;
@@ -38,9 +40,6 @@ public class BluetoothNotificationManager {
public static final String APM_NOTIFICATION_CHANNEL = "apm_notification_channel";
private static final String APM_NOTIFICATION_GROUP = "apm_notification_group";
- //TODO: b/239983569 remove hardcoded notification ID
- private static final int NOTE_BT_APM_NOTIFICATION = -1000007;
-
private final Context mContext;
private NotificationManager mNotificationManager;
@@ -137,7 +136,7 @@ public class BluetoothNotificationManager {
.setStyle(new Notification.BigTextStyle().bigText(message))
.setSmallIcon(android.R.drawable.stat_sys_data_bluetooth)
.build();
- notify(NOTE_BT_APM_NOTIFICATION, notification);
+ notify(SystemMessage.NOTE_BT_APM_NOTIFICATION, notification);
}
/**
diff --git a/service/tests/Android.bp b/service/tests/Android.bp
index 3b5e866262..3ac0253278 100644
--- a/service/tests/Android.bp
+++ b/service/tests/Android.bp
@@ -32,6 +32,8 @@ android_test {
"androidx.test.rules",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
+ "frameworks-base-testutils",
+ "truth-prebuilt",
// Statically link service-bluetooth-pre-jarjar since we want to test the working copy of
// service-uwb, not the on-device copy.
diff --git a/service/tests/src/com/android/server/BluetoothDeviceConfigChangeTrackerTest.java b/service/tests/src/com/android/server/BluetoothDeviceConfigChangeTrackerTest.java
new file mode 100644
index 0000000000..489a574168
--- /dev/null
+++ b/service/tests/src/com/android/server/BluetoothDeviceConfigChangeTrackerTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfig.Properties;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BluetoothDeviceConfigChangeTrackerTest {
+ @Test
+ public void testNoProperties() {
+ BluetoothDeviceConfigChangeTracker changeTracker =
+ new BluetoothDeviceConfigChangeTracker(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH).build());
+
+ boolean shouldRestart =
+ changeTracker.shouldRestartWhenPropertiesUpdated(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH).build());
+
+ assertThat(shouldRestart).isFalse();
+ }
+
+ @Test
+ public void testNewFlag() {
+ BluetoothDeviceConfigChangeTracker changeTracker =
+ new BluetoothDeviceConfigChangeTracker(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH)
+ .setString("INIT_a", "true")
+ .build());
+
+ boolean shouldRestart =
+ changeTracker.shouldRestartWhenPropertiesUpdated(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH)
+ .setString("INIT_a", "true")
+ .setString("INIT_b", "true")
+ .build());
+
+ assertThat(shouldRestart).isTrue();
+ }
+
+ @Test
+ public void testChangedFlag() {
+ BluetoothDeviceConfigChangeTracker changeTracker =
+ new BluetoothDeviceConfigChangeTracker(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH)
+ .setString("INIT_a", "true")
+ .build());
+
+ boolean shouldRestart =
+ changeTracker.shouldRestartWhenPropertiesUpdated(
+ new Properties.Builder("another_namespace")
+ .setString("INIT_a", "false")
+ .build());
+
+ assertThat(shouldRestart).isTrue();
+ }
+
+ @Test
+ public void testUnchangedInitFlag() {
+ BluetoothDeviceConfigChangeTracker changeTracker =
+ new BluetoothDeviceConfigChangeTracker(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH)
+ .setString("INIT_a", "true")
+ .build());
+
+ boolean shouldRestart =
+ changeTracker.shouldRestartWhenPropertiesUpdated(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH)
+ .setString("INIT_a", "true")
+ .build());
+
+ assertThat(shouldRestart).isFalse();
+ }
+
+ @Test
+ public void testRepeatedChangeInitFlag() {
+ BluetoothDeviceConfigChangeTracker changeTracker =
+ new BluetoothDeviceConfigChangeTracker(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH).build());
+
+ changeTracker.shouldRestartWhenPropertiesUpdated(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH)
+ .setString("INIT_a", "true")
+ .build());
+
+ boolean shouldRestart =
+ changeTracker.shouldRestartWhenPropertiesUpdated(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH)
+ .setString("INIT_a", "true")
+ .build());
+
+ assertThat(shouldRestart).isFalse();
+ }
+
+ @Test
+ public void testWrongNamespace() {
+ BluetoothDeviceConfigChangeTracker changeTracker =
+ new BluetoothDeviceConfigChangeTracker(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH).build());
+
+ boolean shouldRestart =
+ changeTracker.shouldRestartWhenPropertiesUpdated(
+ new Properties.Builder("another_namespace")
+ .setString("INIT_a", "true")
+ .build());
+
+ assertThat(shouldRestart).isFalse();
+ }
+
+ @Test
+ public void testSkipProperty() {
+ BluetoothDeviceConfigChangeTracker changeTracker =
+ new BluetoothDeviceConfigChangeTracker(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH)
+ .setString("INIT_a", "true")
+ .setString("INIT_b", "false")
+ .build());
+
+ boolean shouldRestart =
+ changeTracker.shouldRestartWhenPropertiesUpdated(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH)
+ .setString("INIT_b", "false")
+ .build());
+
+ assertThat(shouldRestart).isFalse();
+ }
+
+ @Test
+ public void testNonInitFlag() {
+ BluetoothDeviceConfigChangeTracker changeTracker =
+ new BluetoothDeviceConfigChangeTracker(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH)
+ .setString("a", "true")
+ .build());
+
+ boolean shouldRestart =
+ changeTracker.shouldRestartWhenPropertiesUpdated(
+ new Properties.Builder(DeviceConfig.NAMESPACE_BLUETOOTH)
+ .setString("a", "false")
+ .build());
+
+ assertThat(shouldRestart).isFalse();
+ }
+}
diff --git a/system/bta/Android.bp b/system/bta/Android.bp
index f9640eb173..a24166a980 100644
--- a/system/bta/Android.bp
+++ b/system/bta/Android.bp
@@ -99,6 +99,7 @@ cc_library_static {
"le_audio/state_machine.cc",
"le_audio/client_parser.cc",
"le_audio/client_audio.cc",
+ "le_audio/le_audio_utils.cc",
"le_audio/le_audio_set_configuration_provider.cc",
"le_audio/le_audio_set_configuration_provider_json.cc",
"le_audio/le_audio_types.cc",
@@ -629,6 +630,7 @@ cc_test {
"le_audio/client_parser.cc",
"le_audio/content_control_id_keeper.cc",
"le_audio/devices.cc",
+ "le_audio/le_audio_utils.cc",
"le_audio/le_audio_client_test.cc",
"le_audio/le_audio_set_configuration_provider_json.cc",
"le_audio/le_audio_types.cc",
@@ -772,7 +774,9 @@ cc_test {
"le_audio/broadcaster/mock_state_machine.cc",
"le_audio/content_control_id_keeper.cc",
"le_audio/client_audio.cc",
+ "le_audio/le_audio_utils.cc",
"le_audio/le_audio_types.cc",
+ "le_audio/metrics_collector_linux.cc",
"le_audio/mock_iso_manager.cc",
"test/common/mock_controller.cc",
"le_audio/mock_codec_manager.cc",
diff --git a/system/bta/ag/bta_ag_act.cc b/system/bta/ag/bta_ag_act.cc
index 1ba68bcc44..1fbf17e9bb 100644
--- a/system/bta/ag/bta_ag_act.cc
+++ b/system/bta/ag/bta_ag_act.cc
@@ -471,7 +471,7 @@ void bta_ag_rfc_open(tBTA_AG_SCB* p_scb, const tBTA_AG_DATA& data) {
bool sdp_wbs_support = p_scb->peer_sdp_features & BTA_AG_FEAT_WBS_SUPPORT;
if (!p_scb->received_at_bac && sdp_wbs_support) {
p_scb->codec_updated = true;
- p_scb->peer_codecs = BTM_SCO_CODEC_CVSD & BTM_SCO_CODEC_MSBC;
+ p_scb->peer_codecs = BTM_SCO_CODEC_CVSD | BTM_SCO_CODEC_MSBC;
p_scb->sco_codec = UUID_CODEC_MSBC;
}
} else {
diff --git a/system/bta/ag/bta_ag_sdp.cc b/system/bta/ag/bta_ag_sdp.cc
index bb39a43226..01dd35303f 100644
--- a/system/bta/ag/bta_ag_sdp.cc
+++ b/system/bta/ag/bta_ag_sdp.cc
@@ -369,7 +369,7 @@ bool bta_ag_sdp_find_attr(tBTA_AG_SCB* p_scb, tBTA_SERVICE_MASK service) {
// 2. But do not send required AT+BAC command
// Will assume mSBC is enabled and try codec negotiation by default
p_scb->codec_updated = true;
- p_scb->peer_codecs = BTM_SCO_CODEC_CVSD & BTM_SCO_CODEC_MSBC;
+ p_scb->peer_codecs = BTM_SCO_CODEC_CVSD | BTM_SCO_CODEC_MSBC;
p_scb->sco_codec = UUID_CODEC_MSBC;
}
if (sdp_features != p_scb->peer_sdp_features) {
diff --git a/system/bta/csis/csis_client.cc b/system/bta/csis/csis_client.cc
index 9ae6450cdc..2e1d0d5d1f 100644
--- a/system/bta/csis/csis_client.cc
+++ b/system/bta/csis/csis_client.cc
@@ -1224,7 +1224,7 @@ class CsisClientImpl : public CsisClient {
auto csis_device = FindDeviceByAddress(result->bd_addr);
if (csis_device) {
- DLOG(INFO) << __func__ << " Drop same device .." << result->bd_addr;
+ LOG_DEBUG("Drop known device %s", result->bd_addr.ToString().c_str());
return;
}
@@ -1235,8 +1235,15 @@ class CsisClientImpl : public CsisClient {
for (auto& group : csis_groups_) {
for (auto& rsi : all_rsi) {
if (group->IsRsiMatching(rsi)) {
- DLOG(INFO) << " Found set member in background scan "
- << result->bd_addr;
+ LOG_INFO("Device %s match to group id %d",
+ result->bd_addr.ToString().c_str(), group->GetGroupId());
+ if (group->GetDesiredSize() > 0 &&
+ (group->GetCurrentSize() == group->GetDesiredSize())) {
+ LOG_WARN(
+ "Group is already completed. Some other device use same SIRK");
+ break;
+ }
+
callbacks_->OnSetMemberAvailable(result->bd_addr,
group->GetGroupId());
break;
diff --git a/system/bta/le_audio/audio_set_configurations.json b/system/bta/le_audio/audio_set_configurations.json
index 4adda2edd4..b887ef4e3c 100644
--- a/system/bta/le_audio/audio_set_configurations.json
+++ b/system/bta/le_audio/audio_set_configurations.json
@@ -168,6 +168,11 @@
"qos_config_name": ["QoS_Config_16_1_2"]
},
{
+ "name": "DualDev_OneChanMonoSnk_16_2_Server_Preferred",
+ "codec_config_name": "DualDev_OneChanMonoSnk_16_2",
+ "qos_config_name": ["QoS_Config_Server_Preferred"]
+ },
+ {
"name": "SingleDev_OneChanMonoSnk_16_2_Server_Preferred",
"codec_config_name": "SingleDev_OneChanMonoSnk_16_2",
"qos_config_name": ["QoS_Config_Server_Preferred"]
@@ -1694,6 +1699,73 @@
]
},
{
+ "name": "DualDev_OneChanMonoSnk_16_2",
+ "subconfigurations": [
+ {
+ "device_cnt": 2,
+ "ase_cnt": 2,
+ "direction": "SINK",
+ "configuration_strategy": "MONO_ONE_CIS_PER_DEVICE",
+ "codec_id": {
+ "coding_format": 6,
+ "vendor_company_id": 0,
+ "vendor_codec_id": 0
+ },
+ "codec_configuration": [
+ {
+ "name": "sampling_frequency",
+ "type": 1,
+ "compound_value": {
+ "value": [
+ 3
+ ]
+ }
+ },
+ {
+ "name": "frame_duration",
+ "type": 2,
+ "compound_value": {
+ "value": [
+ 1
+ ]
+ }
+ },
+ {
+ "name": "audio_channel_allocation",
+ "type": 3,
+ "compound_value": {
+ "value": [
+ 1,
+ 0,
+ 0,
+ 0
+ ]
+ }
+ },
+ {
+ "name": "octets_per_codec_frame",
+ "type": 4,
+ "compound_value": {
+ "value": [
+ 40,
+ 0
+ ]
+ }
+ },
+ {
+ "name": "codec_frame_blocks_per_sdu",
+ "type": 5,
+ "compound_value": {
+ "value": [
+ 1
+ ]
+ }
+ }
+ ]
+ }
+ ]
+ },
+ {
"name": "SingleDev_OneChanMonoSnk_16_2",
"subconfigurations": [
{
diff --git a/system/bta/le_audio/audio_set_scenarios.json b/system/bta/le_audio/audio_set_scenarios.json
index 58ad4f532b..d170ad1fbb 100644
--- a/system/bta/le_audio/audio_set_scenarios.json
+++ b/system/bta/le_audio/audio_set_scenarios.json
@@ -37,7 +37,10 @@
"SingleDev_OneChanMonoSnk_16_2_Server_Preferred",
"SingleDev_OneChanMonoSnk_16_2_1",
"SingleDev_OneChanMonoSnk_16_1_Server_Preferred",
- "SingleDev_OneChanMonoSnk_16_1_1"
+ "SingleDev_OneChanMonoSnk_16_1_1",
+ "DualDev_OneChanMonoSrc_16_2_Server_Preferred",
+ "SingleDev_OneChanStereoSrc_16_2_Server_Preferred",
+ "SingleDev_OneChanMonoSrc_16_2_Server_Preferred"
]
},
{
@@ -102,7 +105,10 @@
"SingleDev_OneChanMonoSrc_16_2_Server_Preferred",
"SingleDev_OneChanMonoSrc_16_1_Server_Preferred",
"VND_SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_32khz_Server_Prefered_1",
- "VND_SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_32khz_60oct_R3_L22_1"
+ "VND_SingleDev_TwoChanStereoSnk_TwoChanStereoSrc_32khz_60oct_R3_L22_1",
+ "DualDev_OneChanMonoSnk_16_2_Server_Preferred",
+ "SingleDev_OneChanStereoSnk_16_2_Server_Preferred",
+ "SingleDev_OneChanMonoSnk_16_2_Server_Preferred"
]
},
{
@@ -175,7 +181,10 @@
"VND_SingleDev_TwoChanStereoSnk_48khz_100octs_Server_Preferred_1",
"VND_SingleDev_TwoChanStereoSnk_48khz_100octs_R15_L70_1",
"VND_SingleDev_OneChanStereoSnk_48khz_100octs_Server_Preferred_1",
- "VND_SingleDev_OneChanStereoSnk_48khz_100octs_R15_L70_1"
+ "VND_SingleDev_OneChanStereoSnk_48khz_100octs_R15_L70_1",
+ "DualDev_OneChanMonoSrc_16_2_Server_Preferred",
+ "SingleDev_OneChanStereoSrc_16_2_Server_Preferred",
+ "SingleDev_OneChanMonoSrc_16_2_Server_Preferred"
]
},
{
diff --git a/system/bta/le_audio/broadcaster/broadcaster.cc b/system/bta/le_audio/broadcaster/broadcaster.cc
index 605f485aab..3b0a7fcec9 100644
--- a/system/bta/le_audio/broadcaster/broadcaster.cc
+++ b/system/bta/le_audio/broadcaster/broadcaster.cc
@@ -20,8 +20,9 @@
#include "bta/include/bta_le_audio_api.h"
#include "bta/include/bta_le_audio_broadcaster_api.h"
#include "bta/le_audio/broadcaster/state_machine.h"
-#include "bta/le_audio/content_control_id_keeper.h"
#include "bta/le_audio/le_audio_types.h"
+#include "bta/le_audio/le_audio_utils.h"
+#include "bta/le_audio/metrics_collector.h"
#include "device/include/controller.h"
#include "embdrv/lc3/include/lc3.h"
#include "gd/common/strings.h"
@@ -38,7 +39,6 @@ using bluetooth::hci::iso_manager::BigCallbacks;
using bluetooth::le_audio::BasicAudioAnnouncementData;
using bluetooth::le_audio::BroadcastId;
using le_audio::CodecManager;
-using le_audio::ContentControlIdKeeper;
using le_audio::broadcaster::BigConfig;
using le_audio::broadcaster::BroadcastCodecWrapper;
using le_audio::broadcaster::BroadcastQosConfig;
@@ -49,6 +49,8 @@ using le_audio::types::CodecLocation;
using le_audio::types::kLeAudioCodingFormatLC3;
using le_audio::types::LeAudioContextType;
using le_audio::types::LeAudioLtvMap;
+using le_audio::utils::GetAllCcids;
+using le_audio::utils::GetAllowedAudioContextsFromSourceMetadata;
namespace {
class LeAudioBroadcasterImpl;
@@ -165,6 +167,80 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
return announcement;
}
+ void UpdateStreamingContextTypeOnAllSubgroups(uint16_t context_type_map) {
+ LOG_DEBUG("%s context_type_map=%d", __func__, context_type_map);
+
+ auto ccids = GetAllCcids(context_type_map);
+ if (ccids.empty()) {
+ LOG_WARN("%s No content providers available for context_type_map=%d.",
+ __func__, context_type_map);
+ }
+
+ std::vector<uint8_t> stream_context_vec(2);
+ auto pp = stream_context_vec.data();
+ UINT16_TO_STREAM(pp, context_type_map);
+
+ for (auto const& kv_it : broadcasts_) {
+ auto& broadcast = kv_it.second;
+ if (broadcast->GetState() == BroadcastStateMachine::State::STREAMING) {
+ auto announcement = broadcast->GetBroadcastAnnouncement();
+ bool broadcast_update = false;
+
+ // Replace context type and CCID list
+ for (auto& subgroup : announcement.subgroup_configs) {
+ auto subgroup_ltv = LeAudioLtvMap(subgroup.metadata);
+ bool subgroup_update = false;
+
+ auto existing_context = subgroup_ltv.Find(
+ le_audio::types::kLeAudioMetadataTypeStreamingAudioContext);
+ if (existing_context) {
+ if (memcmp(stream_context_vec.data(), existing_context->data(),
+ existing_context->size()) != 0) {
+ subgroup_ltv.Add(
+ le_audio::types::kLeAudioMetadataTypeStreamingAudioContext,
+ stream_context_vec);
+ subgroup_update = true;
+ }
+ } else {
+ subgroup_ltv.Add(
+ le_audio::types::kLeAudioMetadataTypeStreamingAudioContext,
+ stream_context_vec);
+ subgroup_update = true;
+ }
+
+ auto existing_ccid_list =
+ subgroup_ltv.Find(le_audio::types::kLeAudioMetadataTypeCcidList);
+ if (existing_ccid_list) {
+ if (ccids.empty()) {
+ subgroup_ltv.Remove(
+ le_audio::types::kLeAudioMetadataTypeCcidList);
+ subgroup_update = true;
+
+ } else if (!std::is_permutation(ccids.begin(), ccids.end(),
+ existing_ccid_list->begin())) {
+ subgroup_ltv.Add(le_audio::types::kLeAudioMetadataTypeCcidList,
+ ccids);
+ subgroup_update = true;
+ }
+ } else if (!ccids.empty()) {
+ subgroup_ltv.Add(le_audio::types::kLeAudioMetadataTypeCcidList,
+ ccids);
+ subgroup_update = true;
+ }
+
+ if (subgroup_update) {
+ subgroup.metadata = subgroup_ltv.Values();
+ broadcast_update = true;
+ }
+ }
+
+ if (broadcast_update) {
+ broadcast->UpdateBroadcastAnnouncement(std::move(announcement));
+ }
+ }
+ }
+ }
+
void UpdateMetadata(uint32_t broadcast_id,
std::vector<uint8_t> metadata) override {
if (broadcasts_.count(broadcast_id) == 0) {
@@ -185,6 +261,22 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
return;
}
+ uint16_t context_type =
+ static_cast<std::underlying_type<LeAudioContextType>::type>(
+ LeAudioContextType::MEDIA);
+ auto stream_context_vec =
+ ltv.Find(le_audio::types::kLeAudioMetadataTypeStreamingAudioContext);
+ if (stream_context_vec) {
+ auto pp = stream_context_vec.value().data();
+ STREAM_TO_UINT16(context_type, pp);
+ }
+
+ // Append the CCID list
+ auto ccid_vec = GetAllCcids(context_type);
+ if (!ccid_vec.empty()) {
+ ltv.Add(le_audio::types::kLeAudioMetadataTypeCcidList, ccid_vec);
+ }
+
BasicAudioAnnouncementData announcement =
prepareAnnouncement(codec_config, std::move(ltv));
@@ -219,12 +311,9 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
}
// Append the CCID list
- // TODO: We currently support only one context (and CCID) at a time for both
- // Unicast and broadcast.
- auto ccid = ContentControlIdKeeper::GetInstance()->GetCcid(context_type);
- if (ccid != -1) {
- ltv.Add(le_audio::types::kLeAudioMetadataTypeCcidList,
- {static_cast<uint8_t>(ccid)});
+ auto ccid_vec = GetAllCcids(context_type);
+ if (!ccid_vec.empty()) {
+ ltv.Add(le_audio::types::kLeAudioMetadataTypeCcidList, ccid_vec);
}
if (CodecManager::GetInstance()->GetCodecLocation() ==
@@ -329,6 +418,7 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::START, nullptr);
+ le_audio::MetricsCollector::Get()->OnBroadcastStateChanged(true);
} else {
LOG_ERROR("No such broadcast_id=%d", broadcast_id);
}
@@ -346,6 +436,7 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
broadcasts_[broadcast_id]->SetMuted(true);
broadcasts_[broadcast_id]->ProcessMessage(
BroadcastStateMachine::Message::STOP, nullptr);
+ le_audio::MetricsCollector::Get()->OnBroadcastStateChanged(false);
}
void DestroyAudioBroadcast(uint32_t broadcast_id) override {
@@ -744,11 +835,24 @@ class LeAudioBroadcasterImpl : public LeAudioBroadcaster, public BigCallbacks {
const source_metadata_t& source_metadata) override {
LOG_INFO();
if (!instance) return;
+
+ /* TODO: Should we take supported contexts from ASCS? */
+ auto supported_context_types = le_audio::types::kLeAudioContextAllTypes;
+ auto contexts = GetAllowedAudioContextsFromSourceMetadata(
+ source_metadata,
+ static_cast<std::underlying_type<LeAudioContextType>::type>(
+ supported_context_types));
+ if (contexts.any()) {
+ /* NOTICE: We probably don't want to change the stream configuration
+ * on each metadata change, so just update the context type metadata.
+ * Since we are not able to identify individual track streams and
+ * they are all mixed inside a single data stream, we will update
+ * the metadata of all BIS subgroups with the same combined context.
+ */
+ instance->UpdateStreamingContextTypeOnAllSubgroups(contexts.to_ulong());
+ }
+
do_update_metadata_promise.set_value();
- /* TODO: We probably don't want to change stream type or update the
- * advertized metadata on each call. We should rather make sure we get
- * only a single content audio stream from the media frameworks.
- */
}
private:
diff --git a/system/bta/le_audio/broadcaster/broadcaster_test.cc b/system/bta/le_audio/broadcaster/broadcaster_test.cc
index 8aa914797c..a566cad2b8 100644
--- a/system/bta/le_audio/broadcaster/broadcaster_test.cc
+++ b/system/bta/le_audio/broadcaster/broadcaster_test.cc
@@ -17,6 +17,7 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <hardware/audio.h>
#include <chrono>
@@ -30,6 +31,8 @@
#include "device/include/controller.h"
#include "stack/include/btm_iso_api.h"
+using namespace std::chrono_literals;
+
using le_audio::types::LeAudioContextType;
using testing::_;
@@ -108,6 +111,7 @@ static void cleanup_message_loop_thread() {
namespace le_audio {
namespace broadcaster {
namespace {
+static constexpr uint8_t default_ccid = 0xDE;
static constexpr auto default_context =
static_cast<std::underlying_type<LeAudioContextType>::type>(
LeAudioContextType::ALERTS);
@@ -203,6 +207,7 @@ class BroadcasterTest : public Test {
base::Bind([]() -> bool { return true; }));
ContentControlIdKeeper::GetInstance()->Start();
+ ContentControlIdKeeper::GetInstance()->SetCcid(0x0004, media_ccid);
/* Simulate random generator */
uint8_t random[] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
@@ -405,12 +410,29 @@ TEST_F(BroadcasterTest, GetBroadcastAllStates) {
TEST_F(BroadcasterTest, UpdateMetadata) {
auto broadcast_id = InstantiateBroadcast();
-
+ std::vector<uint8_t> ccid_list;
EXPECT_CALL(*MockBroadcastStateMachine::GetLastInstance(),
UpdateBroadcastAnnouncement)
- .Times(1);
+ .WillOnce(
+ [&](bluetooth::le_audio::BasicAudioAnnouncementData announcement) {
+ for (auto subgroup : announcement.subgroup_configs) {
+ if (subgroup.metadata.count(
+ types::kLeAudioMetadataTypeCcidList)) {
+ ccid_list =
+ subgroup.metadata.at(types::kLeAudioMetadataTypeCcidList);
+ break;
+ }
+ }
+ });
+
+ ContentControlIdKeeper::GetInstance()->SetCcid(0x0400, default_ccid);
LeAudioBroadcaster::Get()->UpdateMetadata(
- broadcast_id, std::vector<uint8_t>({0x02, 0x01, 0x02}));
+ broadcast_id,
+ std::vector<uint8_t>({0x02, 0x01, 0x02, 0x03, 0x02, 0x04, 0x04}));
+
+ ASSERT_EQ(2u, ccid_list.size());
+ ASSERT_NE(0, std::count(ccid_list.begin(), ccid_list.end(), media_ccid));
+ ASSERT_NE(0, std::count(ccid_list.begin(), ccid_list.end(), default_ccid));
}
static BasicAudioAnnouncementData prepareAnnouncement(
@@ -444,10 +466,87 @@ static BasicAudioAnnouncementData prepareAnnouncement(
return announcement;
}
+TEST_F(BroadcasterTest, UpdateMetadataFromAudioTrackMetadata) {
+ ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+ auto broadcast_id = InstantiateBroadcast();
+
+ LeAudioClientAudioSinkReceiver* audio_receiver;
+ EXPECT_CALL(*mock_audio_source_, Start)
+ .WillOnce(DoAll(SaveArg<1>(&audio_receiver), Return(true)));
+
+ LeAudioBroadcaster::Get()->StartAudioBroadcast(broadcast_id);
+ ASSERT_NE(audio_receiver, nullptr);
+
+ auto sm = MockBroadcastStateMachine::GetLastInstance();
+ std::vector<uint8_t> ccid_list;
+ std::vector<uint8_t> context_types_map;
+ EXPECT_CALL(*sm, UpdateBroadcastAnnouncement)
+ .WillOnce(
+ [&](bluetooth::le_audio::BasicAudioAnnouncementData announcement) {
+ for (auto subgroup : announcement.subgroup_configs) {
+ if (subgroup.metadata.count(
+ types::kLeAudioMetadataTypeCcidList)) {
+ ccid_list =
+ subgroup.metadata.at(types::kLeAudioMetadataTypeCcidList);
+ }
+ if (subgroup.metadata.count(
+ types::kLeAudioMetadataTypeStreamingAudioContext)) {
+ context_types_map = subgroup.metadata.at(
+ types::kLeAudioMetadataTypeStreamingAudioContext);
+ }
+ }
+ });
+
+ std::map<uint8_t, std::vector<uint8_t>> meta = {};
+ BroadcastCodecWrapper codec_config(
+ {.coding_format = le_audio::types::kLeAudioCodingFormatLC3,
+ .vendor_company_id = le_audio::types::kLeAudioVendorCompanyIdUndefined,
+ .vendor_codec_id = le_audio::types::kLeAudioVendorCodecIdUndefined},
+ {.num_channels = LeAudioCodecConfiguration::kChannelNumberMono,
+ .sample_rate = LeAudioCodecConfiguration::kSampleRate16000,
+ .bits_per_sample = LeAudioCodecConfiguration::kBitsPerSample16,
+ .data_interval_us = LeAudioCodecConfiguration::kInterval10000Us},
+ 32000, 40);
+ auto announcement = prepareAnnouncement(codec_config, meta);
+
+ ON_CALL(*sm, GetBroadcastAnnouncement())
+ .WillByDefault(ReturnRef(announcement));
+
+ std::promise<void> do_update_metadata_promise;
+ struct playback_track_metadata tracks_[] = {
+ {AUDIO_USAGE_GAME, AUDIO_CONTENT_TYPE_SONIFICATION, 0},
+ {AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, 0},
+ {AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING, AUDIO_CONTENT_TYPE_SPEECH,
+ 0},
+ {AUDIO_USAGE_UNKNOWN, AUDIO_CONTENT_TYPE_UNKNOWN, 0}};
+
+ source_metadata_t multitrack_source_metadata = {.track_count = 4,
+ .tracks = &tracks_[0]};
+
+ auto do_update_metadata_future = do_update_metadata_promise.get_future();
+ audio_receiver->OnAudioMetadataUpdate(std::move(do_update_metadata_promise),
+ multitrack_source_metadata);
+ do_update_metadata_future.wait_for(3s);
+
+ // Verify ccid
+ ASSERT_NE(ccid_list.size(), 0u);
+ ASSERT_TRUE(std::find(ccid_list.begin(), ccid_list.end(), media_ccid) !=
+ ccid_list.end());
+
+ // Verify context type
+ ASSERT_NE(context_types_map.size(), 0u);
+ uint16_t context_type;
+ auto pp = context_types_map.data();
+ STREAM_TO_UINT16(context_type, pp);
+ ASSERT_NE(context_type &
+ static_cast<std::underlying_type<LeAudioContextType>::type>(
+ LeAudioContextType::MEDIA | LeAudioContextType::GAME),
+ 0);
+}
+
TEST_F(BroadcasterTest, GetMetadata) {
auto broadcast_id = InstantiateBroadcast();
bluetooth::le_audio::BroadcastMetadata metadata;
- // bluetooth::le_audio::BasicAudioAnnouncementData announcement;
static const uint8_t test_adv_sid = 0x14;
std::optional<bluetooth::le_audio::BroadcastCode> test_broadcast_code =
diff --git a/system/bta/le_audio/broadcaster/mock_state_machine.h b/system/bta/le_audio/broadcaster/mock_state_machine.h
index 3b1532c21d..eb346736b4 100644
--- a/system/bta/le_audio/broadcaster/mock_state_machine.h
+++ b/system/bta/le_audio/broadcaster/mock_state_machine.h
@@ -131,6 +131,7 @@ class MockBroadcastStateMachine
std::optional<le_audio::broadcaster::BigConfig> big_config_ = std::nullopt;
le_audio::broadcaster::BroadcastStateMachineConfig cfg;
le_audio::broadcaster::IBroadcastStateMachineCallbacks* cb;
+ void SetExpectedState(BroadcastStateMachine::State state) { SetState(state); }
void SetExpectedResult(bool result) { result_ = result; }
void SetExpectedBigConfig(
std::optional<le_audio::broadcaster::BigConfig> big_cfg) {
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index 73da1fdf88..09cb59d8f5 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -40,6 +40,7 @@
#include "gd/common/strings.h"
#include "le_audio_set_configuration_provider.h"
#include "le_audio_types.h"
+#include "le_audio_utils.h"
#include "metrics_collector.h"
#include "osi/include/log.h"
#include "osi/include/osi.h"
@@ -76,6 +77,9 @@ using le_audio::types::AudioStreamDataPathState;
using le_audio::types::hdl_pair;
using le_audio::types::kDefaultScanDurationS;
using le_audio::types::LeAudioContextType;
+using le_audio::utils::AudioContentToLeAudioContext;
+using le_audio::utils::GetAllCcids;
+using le_audio::utils::GetAllowedAudioContextsFromSourceMetadata;
using le_audio::client_parser::ascs::
kCtpResponseCodeInvalidConfigurationParameterValue;
@@ -141,72 +145,6 @@ std::ostream& operator<<(std::ostream& os, const AudioState& audio_state) {
return os;
}
-static std::string usageToString(audio_usage_t usage) {
- switch (usage) {
- case AUDIO_USAGE_UNKNOWN:
- return "USAGE_UNKNOWN";
- case AUDIO_USAGE_MEDIA:
- return "USAGE_MEDIA";
- case AUDIO_USAGE_VOICE_COMMUNICATION:
- return "USAGE_VOICE_COMMUNICATION";
- case AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING:
- return "USAGE_VOICE_COMMUNICATION_SIGNALLING";
- case AUDIO_USAGE_ALARM:
- return "USAGE_ALARM";
- case AUDIO_USAGE_NOTIFICATION:
- return "USAGE_NOTIFICATION";
- case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
- return "USAGE_NOTIFICATION_TELEPHONY_RINGTONE";
- case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
- return "USAGE_NOTIFICATION_COMMUNICATION_REQUEST";
- case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
- return "USAGE_NOTIFICATION_COMMUNICATION_INSTANT";
- case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
- return "USAGE_NOTIFICATION_COMMUNICATION_DELAYED";
- case AUDIO_USAGE_NOTIFICATION_EVENT:
- return "USAGE_NOTIFICATION_EVENT";
- case AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY:
- return "USAGE_ASSISTANCE_ACCESSIBILITY";
- case AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
- return "USAGE_ASSISTANCE_NAVIGATION_GUIDANCE";
- case AUDIO_USAGE_ASSISTANCE_SONIFICATION:
- return "USAGE_ASSISTANCE_SONIFICATION";
- case AUDIO_USAGE_GAME:
- return "USAGE_GAME";
- case AUDIO_USAGE_ASSISTANT:
- return "USAGE_ASSISTANT";
- case AUDIO_USAGE_CALL_ASSISTANT:
- return "USAGE_CALL_ASSISTANT";
- case AUDIO_USAGE_EMERGENCY:
- return "USAGE_EMERGENCY";
- case AUDIO_USAGE_SAFETY:
- return "USAGE_SAFETY";
- case AUDIO_USAGE_VEHICLE_STATUS:
- return "USAGE_VEHICLE_STATUS";
- case AUDIO_USAGE_ANNOUNCEMENT:
- return "USAGE_ANNOUNCEMENT";
- default:
- return "unknown usage ";
- }
-}
-
-static std::string contentTypeToString(audio_content_type_t content_type) {
- switch (content_type) {
- case AUDIO_CONTENT_TYPE_UNKNOWN:
- return "CONTENT_TYPE_UNKNOWN";
- case AUDIO_CONTENT_TYPE_SPEECH:
- return "CONTENT_TYPE_SPEECH";
- case AUDIO_CONTENT_TYPE_MUSIC:
- return "CONTENT_TYPE_MUSIC";
- case AUDIO_CONTENT_TYPE_MOVIE:
- return "CONTENT_TYPE_MOVIE";
- case AUDIO_CONTENT_TYPE_SONIFICATION:
- return "CONTENT_TYPE_SONIFICATION";
- default:
- return "unknown content type ";
- }
-}
-
namespace {
void le_audio_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data);
@@ -281,7 +219,10 @@ class LeAudioClientImpl : public LeAudioClient {
: gatt_if_(0),
callbacks_(callbacks_),
active_group_id_(bluetooth::groups::kGroupUnknown),
- current_context_type_(LeAudioContextType::MEDIA),
+ configuration_context_type_(LeAudioContextType::MEDIA),
+ metadata_context_types_(
+ static_cast<std::underlying_type<LeAudioContextType>::type>(
+ LeAudioContextType::MEDIA)),
stream_setup_start_timestamp_(0),
stream_setup_end_timestamp_(0),
audio_receiver_state_(AudioState::IDLE),
@@ -401,10 +342,6 @@ class LeAudioClientImpl : public LeAudioClient {
group_remove_node(group, address);
}
- int GetCcid(uint16_t context_type) {
- return ContentControlIdKeeper::GetInstance()->GetCcid(context_type);
- }
-
/* This callback happens if kLeAudioDeviceSetStateTimeoutMs timeout happens
* during transition from origin to target state
*/
@@ -681,7 +618,8 @@ class LeAudioClientImpl : public LeAudioClient {
group_remove_node(group, address, true);
}
- bool InternalGroupStream(const int group_id, const uint16_t context_type) {
+ bool GroupStream(const int group_id, const uint16_t context_type,
+ AudioContexts metadata_context_type) {
LeAudioDeviceGroup* group = aseGroups_.FindById(group_id);
auto final_context_type = context_type;
@@ -719,7 +657,7 @@ class LeAudioClientImpl : public LeAudioClient {
bool result = groupStateMachine_->StartStream(
group, static_cast<LeAudioContextType>(final_context_type),
- GetCcid(final_context_type));
+ metadata_context_type, GetAllCcids(metadata_context_type));
if (result)
stream_setup_start_timestamp_ =
bluetooth::common::time_get_os_boottime_us();
@@ -728,7 +666,7 @@ class LeAudioClientImpl : public LeAudioClient {
}
void GroupStream(const int group_id, const uint16_t context_type) override {
- InternalGroupStream(group_id, context_type);
+ GroupStream(group_id, context_type, context_type);
}
void GroupSuspend(const int group_id) override {
@@ -2683,7 +2621,8 @@ class LeAudioClientImpl : public LeAudioClient {
void Dump(int fd) {
dprintf(fd, " Active group: %d\n", active_group_id_);
- dprintf(fd, " current content type: 0x%08hx\n", current_context_type_);
+ dprintf(fd, " configuration content type: 0x%08hx\n",
+ configuration_context_type_);
dprintf(
fd, " stream setup time if started: %d ms\n",
(int)((stream_setup_end_timestamp_ - stream_setup_start_timestamp_) /
@@ -2776,7 +2715,7 @@ class LeAudioClientImpl : public LeAudioClient {
LOG_INFO(" Session reconfiguration needed group: %d for context type: %s",
group->group_id_, ToString(context_type).c_str());
- current_context_type_ = context_type;
+ configuration_context_type_ = context_type;
return AudioReconfigurationResult::RECONFIGURATION_NEEDED;
}
@@ -2784,8 +2723,9 @@ class LeAudioClientImpl : public LeAudioClient {
if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
return true;
}
- return InternalGroupStream(active_group_id_,
- static_cast<uint16_t>(current_context_type_));
+ return GroupStream(active_group_id_,
+ static_cast<uint16_t>(configuration_context_type_),
+ metadata_context_types_);
}
void OnAudioSuspend() {
@@ -2864,9 +2804,10 @@ class LeAudioClientImpl : public LeAudioClient {
/* Check if the device resume is expected */
if (!group->GetCodecConfigurationByDirection(
- current_context_type_, le_audio::types::kLeAudioDirectionSink)) {
+ configuration_context_type_,
+ le_audio::types::kLeAudioDirectionSink)) {
LOG(ERROR) << __func__ << ", invalid resume request for context type: "
- << loghex(static_cast<int>(current_context_type_));
+ << loghex(static_cast<int>(configuration_context_type_));
leAudioClientAudioSource->CancelStreamingRequest();
return;
}
@@ -2874,8 +2815,8 @@ class LeAudioClientImpl : public LeAudioClient {
DLOG(INFO) << __func__ << " active_group_id: " << active_group_id_ << "\n"
<< " audio_receiver_state: " << audio_receiver_state_ << "\n"
<< " audio_sender_state: " << audio_sender_state_ << "\n"
- << " current_context_type_: "
- << static_cast<int>(current_context_type_) << "\n"
+ << " configuration_context_type_: "
+ << static_cast<int>(configuration_context_type_) << "\n"
<< " group " << (group ? " exist " : " does not exist ") << "\n";
switch (audio_sender_state_) {
@@ -2940,7 +2881,7 @@ class LeAudioClientImpl : public LeAudioClient {
alarm_cancel(suspend_timeout_);
leAudioClientAudioSource->ConfirmStreamingRequest();
le_audio::MetricsCollector::Get()->OnStreamStarted(
- active_group_id_, current_context_type_);
+ active_group_id_, configuration_context_type_);
break;
case AudioState::RELEASING:
/* Keep wainting. After release is done, Audio Hal will be notified
@@ -2989,9 +2930,9 @@ class LeAudioClientImpl : public LeAudioClient {
<< " audio_sender_state_: " << audio_sender_state_;
}
- bool IsAudioSourceAvailableForCurrentContentType() {
- if (current_context_type_ == LeAudioContextType::CONVERSATIONAL ||
- current_context_type_ == LeAudioContextType::VOICEASSISTANTS) {
+ bool IsAudioSourceAvailableForCurrentConfiguration() {
+ if (configuration_context_type_ == LeAudioContextType::CONVERSATIONAL ||
+ configuration_context_type_ == LeAudioContextType::VOICEASSISTANTS) {
return true;
}
@@ -3014,9 +2955,10 @@ class LeAudioClientImpl : public LeAudioClient {
/* Check if the device resume is expected */
if (!group->GetCodecConfigurationByDirection(
- current_context_type_, le_audio::types::kLeAudioDirectionSource)) {
+ configuration_context_type_,
+ le_audio::types::kLeAudioDirectionSource)) {
LOG(ERROR) << __func__ << ", invalid resume request for context type: "
- << loghex(static_cast<int>(current_context_type_));
+ << loghex(static_cast<int>(configuration_context_type_));
leAudioClientAudioSink->CancelStreamingRequest();
return;
}
@@ -3024,8 +2966,8 @@ class LeAudioClientImpl : public LeAudioClient {
DLOG(INFO) << __func__ << " active_group_id: " << active_group_id_ << "\n"
<< " audio_receiver_state: " << audio_receiver_state_ << "\n"
<< " audio_sender_state: " << audio_sender_state_ << "\n"
- << " current_context_type_: "
- << static_cast<int>(current_context_type_) << "\n"
+ << " configuration_context_type_: "
+ << static_cast<int>(configuration_context_type_) << "\n"
<< " group " << (group ? " exist " : " does not exist ") << "\n";
switch (audio_receiver_state_) {
@@ -3049,7 +2991,7 @@ class LeAudioClientImpl : public LeAudioClient {
*/
if (group->GetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
- if (!IsAudioSourceAvailableForCurrentContentType()) {
+ if (!IsAudioSourceAvailableForCurrentConfiguration()) {
StopStreamIfNeeded(group, LeAudioContextType::VOICEASSISTANTS);
break;
}
@@ -3105,61 +3047,48 @@ class LeAudioClientImpl : public LeAudioClient {
}
}
- LeAudioContextType AudioContentToLeAudioContext(
- 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_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:
- return LeAudioContextType::NOTIFICATIONS;
- case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
- return LeAudioContextType::RINGTONE;
- case AUDIO_USAGE_ALARM:
- return LeAudioContextType::ALERTS;
- case AUDIO_USAGE_EMERGENCY:
- return LeAudioContextType::EMERGENCYALARM;
- case AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
- return LeAudioContextType::INSTRUCTIONAL;
- default:
- break;
+ LeAudioContextType ChooseConfigurationContextType(
+ le_audio::types::AudioContexts available_contexts) {
+ if (available_contexts.none()) {
+ LOG_WARN(" invalid/unknown context, using 'UNSPECIFIED'");
+ return LeAudioContextType::UNSPECIFIED;
}
- return LeAudioContextType::MEDIA;
- }
+ using T = std::underlying_type<LeAudioContextType>::type;
- LeAudioContextType ChooseContextType(
- std::vector<LeAudioContextType>& available_contents) {
/* 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())
+ if ((available_contexts &
+ AudioContexts(static_cast<T>(LeAudioContextType::CONVERSATIONAL)))
+ .any())
return LeAudioContextType::CONVERSATIONAL;
- iter = find(available_contents.begin(), available_contents.end(),
- LeAudioContextType::RINGTONE);
- if (iter != available_contents.end()) return LeAudioContextType::RINGTONE;
+ if ((available_contexts &
+ AudioContexts(static_cast<T>(LeAudioContextType::GAME)))
+ .any())
+ return LeAudioContextType::GAME;
- iter = find(available_contents.begin(), available_contents.end(),
- LeAudioContextType::GAME);
- if (iter != available_contents.end()) return LeAudioContextType::GAME;
+ if ((available_contexts &
+ AudioContexts(static_cast<T>(LeAudioContextType::RINGTONE)))
+ .any())
+ return LeAudioContextType::RINGTONE;
- iter = find(available_contents.begin(), available_contents.end(),
- LeAudioContextType::MEDIA);
- if (iter != available_contents.end()) return LeAudioContextType::MEDIA;
+ if ((available_contexts &
+ AudioContexts(static_cast<T>(LeAudioContextType::MEDIA)))
+ .any())
+ return LeAudioContextType::MEDIA;
/*TODO do something smarter here */
- return available_contents[0];
+ /* Get context for the first non-zero bit */
+ uint16_t context_type = 0b1;
+ while (available_contexts != 0b1) {
+ available_contexts = available_contexts >> 1;
+ context_type = context_type << 1;
+ }
+
+ if (context_type < static_cast<T>(LeAudioContextType::RFU)) {
+ return LeAudioContextType(context_type);
+ }
+ return LeAudioContextType::UNSPECIFIED;
}
bool StopStreamIfNeeded(LeAudioDeviceGroup* group,
@@ -3194,9 +3123,6 @@ class LeAudioClientImpl : public LeAudioClient {
}
void OnAudioMetadataUpdate(const source_metadata_t& source_metadata) {
- 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;
@@ -3211,52 +3137,21 @@ class LeAudioClientImpl : public LeAudioClient {
bool is_group_streaming =
(group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
- std::vector<LeAudioContextType> contexts;
+ metadata_context_types_ = GetAllowedAudioContextsFromSourceMetadata(
+ source_metadata, group->GetActiveContexts());
- auto supported_context_type = group->GetActiveContexts();
+ auto new_configuration_context =
+ ChooseConfigurationContextType(metadata_context_types_);
+ LOG_DEBUG("new_configuration_context_type: %s",
+ ToString(new_configuration_context).c_str());
- while (track_count) {
- if (tracks->content_type == 0 && tracks->usage == 0) {
- --track_count;
- ++tracks;
- continue;
- }
-
- LOG_INFO("%s: usage=%s(%d), content_type=%s(%d), gain=%f", __func__,
- usageToString(tracks->usage).c_str(), tracks->usage,
- contentTypeToString(tracks->content_type).c_str(),
- tracks->content_type, tracks->gain);
-
- 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()) {
- LOG_WARN(" invalid/unknown metadata update");
- return;
- }
-
- auto new_context = ChooseContextType(contexts);
- LOG_DEBUG("new_context_type: %s", ToString(new_context).c_str());
-
- if (new_context == current_context_type_) {
+ if (new_configuration_context == configuration_context_type_) {
LOG_INFO("Context did not changed.");
return;
}
- current_context_type_ = new_context;
- if (StopStreamIfNeeded(group, new_context)) {
+ configuration_context_type_ = new_configuration_context;
+ if (StopStreamIfNeeded(group, new_configuration_context)) {
return;
}
@@ -3264,7 +3159,9 @@ class LeAudioClientImpl : public LeAudioClient {
/* Configuration is the same for new context, just will do update
* metadata of stream
*/
- GroupStream(active_group_id_, static_cast<uint16_t>(new_context));
+ GroupStream(active_group_id_,
+ static_cast<uint16_t>(new_configuration_context),
+ metadata_context_types_);
}
}
@@ -3300,25 +3197,35 @@ class LeAudioClientImpl : public LeAudioClient {
*/
if (is_audio_source_invalid ||
!group->IsContextSupported(LeAudioContextType::VOICEASSISTANTS) ||
- IsAudioSourceAvailableForCurrentContentType()) {
+ IsAudioSourceAvailableForCurrentConfiguration()) {
return;
}
auto new_context = LeAudioContextType::VOICEASSISTANTS;
+ /* Add the new_context to the metadata */
+ metadata_context_types_ =
+ metadata_context_types_.to_ulong() | static_cast<uint16_t>(new_context);
+
if (StopStreamIfNeeded(group, new_context)) return;
if (group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
+ /* Add the new_context to the metadata */
+ metadata_context_types_ = metadata_context_types_.to_ulong() |
+ static_cast<uint16_t>(new_context);
+
/* Configuration is the same for new context, just will do update
- * metadata of stream
+ * metadata of stream. Consider using separate metadata for each
+ * direction.
*/
- GroupStream(active_group_id_, static_cast<uint16_t>(new_context));
+ GroupStream(active_group_id_, static_cast<uint16_t>(new_context),
+ metadata_context_types_);
}
/* Audio sessions are not resumed yet and not streaming, let's pick voice
* assistant as possible current context type.
*/
- current_context_type_ = new_context;
+ configuration_context_type_ = new_context;
}
static void OnGattReadRspStatic(uint16_t conn_id, tGATT_STATUS status,
@@ -3555,7 +3462,7 @@ class LeAudioClientImpl : public LeAudioClient {
stream_setup_end_timestamp_ =
bluetooth::common::time_get_os_boottime_us();
le_audio::MetricsCollector::Get()->OnStreamStarted(
- active_group_id_, current_context_type_);
+ active_group_id_, configuration_context_type_);
break;
case GroupStreamStatus::SUSPENDED:
stream_setup_end_timestamp_ = 0;
@@ -3584,10 +3491,8 @@ class LeAudioClientImpl : public LeAudioClient {
if (group && group->IsPendingConfiguration()) {
SuspendedForReconfiguration();
if (groupStateMachine_->ConfigureStream(
- group, current_context_type_,
- GetCcid(static_cast<
- std::underlying_type<LeAudioContextType>::type>(
- current_context_type_)))) {
+ group, configuration_context_type_, metadata_context_types_,
+ GetAllCcids(metadata_context_types_))) {
/* If configuration succeed wait for new status. */
return;
}
@@ -3620,7 +3525,8 @@ class LeAudioClientImpl : public LeAudioClient {
LeAudioDeviceGroups aseGroups_;
LeAudioGroupStateMachine* groupStateMachine_;
int active_group_id_;
- LeAudioContextType current_context_type_;
+ LeAudioContextType configuration_context_type_;
+ AudioContexts metadata_context_types_;
uint64_t stream_setup_start_timestamp_;
uint64_t stream_setup_end_timestamp_;
diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc
index 67c03985b8..1ed37f5e61 100644
--- a/system/bta/le_audio/devices.cc
+++ b/system/bta/le_audio/devices.cc
@@ -894,6 +894,15 @@ types::LeAudioConfigurationStrategy LeAudioDeviceGroup::GetGroupStrategy(void) {
return types::LeAudioConfigurationStrategy::STEREO_ONE_CIS_PER_DEVICE;
}
+int LeAudioDeviceGroup::GetAseCount(uint8_t direction) {
+ int result = 0;
+ for (const auto& device_iter : leAudioDevices_) {
+ result += device_iter.lock()->GetAseCount(direction);
+ }
+
+ return result;
+}
+
void LeAudioDeviceGroup::CigGenerateCisIds(
types::LeAudioContextType context_type) {
LOG_INFO("Group %p, group_id: %d, context_type: %s", this, group_id_,
@@ -910,7 +919,9 @@ void LeAudioDeviceGroup::CigGenerateCisIds(
uint8_t cis_count_bidir = 0;
uint8_t cis_count_unidir_sink = 0;
uint8_t cis_count_unidir_source = 0;
- get_cis_count(confs, GetGroupStrategy(), &cis_count_bidir,
+ get_cis_count(confs, GetGroupStrategy(),
+ GetAseCount(types::kLeAudioDirectionSink),
+ GetAseCount(types::kLeAudioDirectionSource), &cis_count_bidir,
&cis_count_unidir_sink, &cis_count_unidir_source);
uint8_t idx = 0;
@@ -1397,7 +1408,8 @@ bool LeAudioDevice::ConfigureAses(
uint8_t* number_of_already_active_group_ase,
types::AudioLocations& group_snk_audio_locations,
types::AudioLocations& group_src_audio_locations, bool reuse_cis_id,
- int ccid) {
+ AudioContexts metadata_context_type,
+ const std::vector<uint8_t>& ccid_list) {
struct ase* ase = GetFirstInactiveAse(ent.direction, reuse_cis_id);
if (!ase) return false;
@@ -1453,7 +1465,7 @@ bool LeAudioDevice::ConfigureAses(
ase->retrans_nb = ent.qos.retransmission_number;
ase->max_transport_latency = ent.qos.max_transport_latency;
- ase->metadata = GetMetadata(context_type, ccid);
+ ase->metadata = GetMetadata(metadata_context_type, ccid_list);
DLOG(INFO) << __func__ << " device=" << address_
<< ", activated ASE id=" << +ase->id
@@ -1474,7 +1486,8 @@ bool LeAudioDevice::ConfigureAses(
*/
bool LeAudioDeviceGroup::ConfigureAses(
const set_configurations::AudioSetConfiguration* audio_set_conf,
- types::LeAudioContextType context_type, int ccid) {
+ types::LeAudioContextType context_type, AudioContexts metadata_context_type,
+ const std::vector<uint8_t>& ccid_list) {
if (!set_configurations::check_if_may_cover_scenario(
audio_set_conf, NumOfConnected(context_type)))
return false;
@@ -1523,7 +1536,8 @@ bool LeAudioDeviceGroup::ConfigureAses(
if (!device->ConfigureAses(ent, context_type, &active_ase_num,
group_snk_audio_locations,
- group_src_audio_locations, reuse_cis_id, ccid))
+ group_src_audio_locations, reuse_cis_id,
+ metadata_context_type, ccid_list))
continue;
required_device_cnt--;
@@ -1611,10 +1625,10 @@ bool LeAudioDeviceGroup::IsContextSupported(
}
bool LeAudioDeviceGroup::IsMetadataChanged(
- types::LeAudioContextType context_type, int ccid) {
+ types::AudioContexts context_type, const std::vector<uint8_t>& ccid_list) {
for (auto* leAudioDevice = GetFirstActiveDevice(); leAudioDevice;
leAudioDevice = GetNextActiveDevice(leAudioDevice)) {
- if (leAudioDevice->IsMetadataChanged(context_type, ccid)) return true;
+ if (leAudioDevice->IsMetadataChanged(context_type, ccid_list)) return true;
}
return false;
@@ -1786,7 +1800,9 @@ LeAudioDeviceGroup::FindFirstSupportedConfiguration(
/* This method should choose aproperiate ASEs to be active and set a cached
* configuration for codec and qos.
*/
-bool LeAudioDeviceGroup::Configure(LeAudioContextType context_type, int ccid) {
+bool LeAudioDeviceGroup::Configure(LeAudioContextType context_type,
+ AudioContexts metadata_context_type,
+ std::vector<uint8_t> ccid_list) {
const set_configurations::AudioSetConfiguration* conf =
active_context_to_configuration_map[context_type];
@@ -1801,7 +1817,7 @@ bool LeAudioDeviceGroup::Configure(LeAudioContextType context_type, int ccid) {
DLOG(INFO) << __func__ << " setting context type: " << int(context_type);
- if (!ConfigureAses(conf, context_type, ccid)) {
+ if (!ConfigureAses(conf, context_type, metadata_context_type, ccid_list)) {
LOG(ERROR) << __func__ << ", requested pick ASE config context type: "
<< loghex(static_cast<uint16_t>(context_type))
<< ", is in mismatch with cached active contexts";
@@ -1928,6 +1944,12 @@ struct ase* LeAudioDevice::GetAseByValHandle(uint16_t val_hdl) {
return (iter == ases_.end()) ? nullptr : &(*iter);
}
+int LeAudioDevice::GetAseCount(uint8_t direction) {
+ return std::count_if(ases_.begin(), ases_.end(), [direction](const auto& a) {
+ return a.direction == direction;
+ });
+}
+
struct ase* LeAudioDevice::GetFirstInactiveAseWithState(uint8_t direction,
AseState state) {
auto iter = std::find_if(
@@ -2390,21 +2412,22 @@ void LeAudioDevice::DeactivateAllAses(void) {
}
}
-std::vector<uint8_t> LeAudioDevice::GetMetadata(LeAudioContextType context_type,
- int ccid) {
+std::vector<uint8_t> LeAudioDevice::GetMetadata(
+ AudioContexts context_type, const std::vector<uint8_t>& ccid_list) {
std::vector<uint8_t> metadata;
AppendMetadataLtvEntryForStreamingContext(metadata, context_type);
- AppendMetadataLtvEntryForCcidList(metadata, ccid);
+ AppendMetadataLtvEntryForCcidList(metadata, ccid_list);
return std::move(metadata);
}
-bool LeAudioDevice::IsMetadataChanged(types::LeAudioContextType context_type,
- int ccid) {
+bool LeAudioDevice::IsMetadataChanged(AudioContexts context_type,
+ const std::vector<uint8_t>& ccid_list) {
for (auto* ase = this->GetFirstActiveAse(); ase;
ase = this->GetNextActiveAse(ase)) {
- if (this->GetMetadata(context_type, ccid) != ase->metadata) return true;
+ if (this->GetMetadata(context_type, ccid_list) != ase->metadata)
+ return true;
}
return false;
diff --git a/system/bta/le_audio/devices.h b/system/bta/le_audio/devices.h
index 79980094ca..885f013026 100644
--- a/system/bta/le_audio/devices.h
+++ b/system/bta/le_audio/devices.h
@@ -104,6 +104,7 @@ class LeAudioDevice {
void RegisterPACs(std::vector<struct types::acs_ac_record>* apr_db,
std::vector<struct types::acs_ac_record>* apr);
struct types::ase* GetAseByValHandle(uint16_t val_hdl);
+ int GetAseCount(uint8_t direction);
struct types::ase* GetFirstActiveAse(void);
struct types::ase* GetFirstActiveAseByDirection(uint8_t direction);
struct types::ase* GetNextActiveAseWithSameDirection(
@@ -139,7 +140,8 @@ class LeAudioDevice {
uint8_t* number_of_already_active_group_ase,
types::AudioLocations& group_snk_audio_locations,
types::AudioLocations& group_src_audio_locations,
- bool reconnect = false, int ccid = -1);
+ bool reconnect, types::AudioContexts metadata_context_type,
+ const std::vector<uint8_t>& ccid_list);
void SetSupportedContexts(types::AudioContexts snk_contexts,
types::AudioContexts src_contexts);
types::AudioContexts GetAvailableContexts(void);
@@ -149,9 +151,10 @@ class LeAudioDevice {
bool ActivateConfiguredAses(types::LeAudioContextType context_type);
void Dump(int fd);
void DisconnectAcl(void);
- std::vector<uint8_t> GetMetadata(types::LeAudioContextType context_type,
- int ccid);
- bool IsMetadataChanged(types::LeAudioContextType context_type, int ccid);
+ std::vector<uint8_t> GetMetadata(types::AudioContexts context_type,
+ const std::vector<uint8_t>& ccid_list);
+ bool IsMetadataChanged(types::AudioContexts context_type,
+ const std::vector<uint8_t>& ccid_list);
private:
types::AudioContexts avail_snk_contexts_;
@@ -233,6 +236,7 @@ class LeAudioDeviceGroup {
LeAudioDevice* GetFirstDeviceWithActiveContext(
types::LeAudioContextType context_type);
le_audio::types::LeAudioConfigurationStrategy GetGroupStrategy(void);
+ int GetAseCount(uint8_t direction);
LeAudioDevice* GetNextDevice(LeAudioDevice* leAudioDevice);
LeAudioDevice* GetNextDeviceWithActiveContext(
LeAudioDevice* leAudioDevice, types::LeAudioContextType context_type);
@@ -255,7 +259,9 @@ class LeAudioDeviceGroup {
void CigAssignCisConnHandlesToAses(LeAudioDevice* leAudioDevice);
void CigAssignCisConnHandlesToAses(void);
void CigUnassignCis(LeAudioDevice* leAudioDevice);
- bool Configure(types::LeAudioContextType context_type, int ccid = 1);
+ bool Configure(types::LeAudioContextType context_type,
+ types::AudioContexts metadata_context_type,
+ std::vector<uint8_t> ccid_list = {});
bool SetContextType(types::LeAudioContextType context_type);
types::LeAudioContextType GetContextType(void);
uint32_t GetSduInterval(uint8_t direction);
@@ -287,8 +293,8 @@ class LeAudioDeviceGroup {
std::optional<LeAudioCodecConfiguration> GetCodecConfigurationByDirection(
types::LeAudioContextType group_context_type, uint8_t direction);
bool IsContextSupported(types::LeAudioContextType group_context_type);
- bool IsMetadataChanged(types::LeAudioContextType group_context_type,
- int ccid);
+ bool IsMetadataChanged(types::AudioContexts group_context_type,
+ const std::vector<uint8_t>& ccid_list);
void CreateStreamVectorForOffloader(uint8_t direction);
void StreamOffloaderUpdated(uint8_t direction);
@@ -327,7 +333,9 @@ class LeAudioDeviceGroup {
FindFirstSupportedConfiguration(types::LeAudioContextType context_type);
bool ConfigureAses(
const set_configurations::AudioSetConfiguration* audio_set_conf,
- types::LeAudioContextType context_type, int ccid = 1);
+ types::LeAudioContextType context_type,
+ types::AudioContexts metadata_context_type,
+ const std::vector<uint8_t>& ccid_list);
bool IsConfigurationSupported(
const set_configurations::AudioSetConfiguration* audio_set_configuration,
types::LeAudioContextType context_type);
diff --git a/system/bta/le_audio/devices_test.cc b/system/bta/le_audio/devices_test.cc
index 6b48fdca61..ac6fa2f884 100644
--- a/system/bta/le_audio/devices_test.cc
+++ b/system/bta/le_audio/devices_test.cc
@@ -388,8 +388,8 @@ struct TestGroupAseConfigurationData {
uint8_t audio_channel_counts_snk;
uint8_t audio_channel_counts_src;
- uint8_t active_channel_num_snk;
- uint8_t active_channel_num_src;
+ uint8_t expected_active_channel_num_snk;
+ uint8_t expected_active_channel_num_src;
};
class LeAudioAseConfigurationTest : public Test {
@@ -458,9 +458,9 @@ class LeAudioAseConfigurationTest : public Test {
uint8_t active_channel_num_snk = 0;
uint8_t active_channel_num_src = 0;
- bool have_active_ase =
- data.active_channel_num_snk + data.active_channel_num_src;
- ASSERT_EQ(have_active_ase, data.device->HaveActiveAse());
+ bool expected_active_ase = data.expected_active_channel_num_snk +
+ data.expected_active_channel_num_src;
+ ASSERT_EQ(expected_active_ase, data.device->HaveActiveAse());
for (ase* ase = data.device->GetFirstActiveAse(); ase;
ase = data.device->GetNextActiveAse(ase)) {
@@ -472,8 +472,8 @@ class LeAudioAseConfigurationTest : public Test {
GetAudioChannelCounts(*ase->codec_config.audio_channel_allocation);
}
- ASSERT_EQ(data.active_channel_num_snk, active_channel_num_snk);
- ASSERT_EQ(data.active_channel_num_src, active_channel_num_src);
+ ASSERT_EQ(data.expected_active_channel_num_snk, active_channel_num_snk);
+ ASSERT_EQ(data.expected_active_channel_num_src, active_channel_num_src);
}
void SetCisInformationToActiveAse(void) {
@@ -497,8 +497,8 @@ class LeAudioAseConfigurationTest : public Test {
// the configuration should fail if there are no active ases expected
bool success_expected = data_size > 0;
for (int i = 0; i < data_size; i++) {
- success_expected &=
- (data[i].active_channel_num_snk + data[i].active_channel_num_src) > 0;
+ success_expected &= (data[i].expected_active_channel_num_snk +
+ data[i].expected_active_channel_num_src) > 0;
/* Prepare PAC's */
PublishedAudioCapabilitiesBuilder snk_pac_builder, src_pac_builder;
@@ -516,78 +516,101 @@ class LeAudioAseConfigurationTest : public Test {
/* Stimulate update of active context map */
group_->UpdateActiveContextsMap(static_cast<uint16_t>(context_type));
- ASSERT_EQ(success_expected, group_->Configure(context_type));
+ ASSERT_EQ(
+ success_expected,
+ group_->Configure(context_type,
+ AudioContexts(static_cast<uint16_t>(context_type))));
for (int i = 0; i < data_size; i++) {
TestGroupAseConfigurationVerdict(data[i]);
}
}
+ int getNumOfAses(LeAudioDevice* device, uint8_t direction) {
+ return std::count_if(
+ device->ases_.begin(), device->ases_.end(),
+ [direction](auto& a) { return a.direction == direction; });
+ }
+
void TestGroupAseConfiguration(LeAudioContextType context_type,
TestGroupAseConfigurationData* data,
uint8_t data_size) {
const auto* configurations =
::le_audio::AudioSetConfigurationProvider::Get()->GetConfigurations(
context_type);
+
+ int num_of_matching_configurations = 0;
+ bool success_expected = data_size > 0;
for (const auto& audio_set_conf : *configurations) {
+ bool interesting_configuration = true;
// 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();
+ /* Let's go thru devices in the group and configure them*/
for (int i = 0; i < data_size; i++) {
- success_expected &= (data[i].active_channel_num_snk +
- data[i].active_channel_num_src) > 0;
-
- /* Prepare PAC's */
- /* Note this test requires that reach TwoStereoChan configuration
- * version has similar version for OneStereoChan (both SingleDev,
- * DualDev). This is just how the test is created and this limitation
- * should be removed b/230107540
- */
+ success_expected &= (data[i].expected_active_channel_num_snk +
+ data[i].expected_active_channel_num_src) > 0;
+ int num_of_ase_snk_per_dev = 0;
+ int num_of_ase_src_per_dev = 0;
+
+ /* Prepare PAC's for each device. Also make sure configuration is in our
+ * interest to test */
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;
+ /* We are interested in the configurations which contains exact number
+ * of devices and number of ases is same the number of expected ases
+ * to active
+ */
+ if (entry.device_cnt != data_size) {
+ interesting_configuration = false;
}
+
if (entry.direction == kLeAudioDirectionSink) {
- snk_ases_cnt += entry.ase_cnt;
+ num_of_ase_snk_per_dev = entry.ase_cnt / data_size;
snk_pac_builder.Add(entry.codec, data[i].audio_channel_counts_snk);
} else {
+ num_of_ase_src_per_dev = entry.ase_cnt / data_size;
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;
+ data[i].device->snk_pacs_ = snk_pac_builder.Get();
+ data[i].device->src_pacs_ = src_pac_builder.Get();
}
- if (not_matching_scenario) break;
+ /* Make sure configuration can satisfy number of expected active ASEs*/
+ if (num_of_ase_snk_per_dev != data[i].expected_active_channel_num_snk) {
+ interesting_configuration = false;
+ }
- data[i].device->snk_pacs_ = snk_pac_builder.Get();
- data[i].device->src_pacs_ = src_pac_builder.Get();
+ if (num_of_ase_src_per_dev != data[i].expected_active_channel_num_src) {
+ interesting_configuration = false;
+ }
}
-
- 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));
-
- for (int i = 0; i < data_size; i++) {
- TestGroupAseConfigurationVerdict(data[i]);
+ auto configuration_result = group_->Configure(
+ context_type, AudioContexts(static_cast<uint16_t>(context_type)));
+
+ /* In case of configuration #ase is same as the one we expected to be
+ * activated verify, ASEs are actually active */
+ if (interesting_configuration) {
+ ASSERT_TRUE(configuration_result);
+ num_of_matching_configurations++;
+ /* Check if each of the devices has activated ASEs as expected */
+ for (int i = 0; i < data_size; i++) {
+ TestGroupAseConfigurationVerdict(data[i]);
+ }
}
-
group_->Deactivate();
TestAsesInactive();
}
+
+ if (success_expected) {
+ ASSERT_TRUE((num_of_matching_configurations > 0));
+ } else {
+ ASSERT_TRUE(num_of_matching_configurations == 0);
+ }
}
void TestAsesActive(LeAudioCodecId codec_id, uint8_t sampling_frequency,
@@ -678,7 +701,10 @@ class LeAudioAseConfigurationTest : public Test {
/* Stimulate update of active context map */
group_->UpdateActiveContextsMap(
static_cast<uint16_t>(context_type));
- ASSERT_EQ(success_expected, group_->Configure(context_type));
+ ASSERT_EQ(success_expected,
+ group_->Configure(
+ context_type,
+ AudioContexts(static_cast<uint16_t>(context_type))));
if (success_expected) {
TestAsesActive(LeAudioCodecIdLc3, sampling_frequency,
frame_duration, octets_per_frame);
@@ -703,12 +729,12 @@ TEST_F(LeAudioAseConfigurationTest, test_mono_speaker_ringtone) {
LeAudioDevice* mono_speaker = AddTestDevice(1, 1);
TestGroupAseConfigurationData data(
{mono_speaker, kLeAudioCodecLC3ChannelCountSingleChannel,
- kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0});
+ kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1});
TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1);
}
-TEST_F(LeAudioAseConfigurationTest, test_mono_speaker_conversional) {
+TEST_F(LeAudioAseConfigurationTest, test_mono_speaker_conversational) {
LeAudioDevice* mono_speaker = AddTestDevice(1, 0);
TestGroupAseConfigurationData data({mono_speaker,
kLeAudioCodecLC3ChannelCountSingleChannel,
@@ -730,7 +756,7 @@ TEST_F(LeAudioAseConfigurationTest, test_bounded_headphones_ringtone) {
LeAudioDevice* bounded_headphones = AddTestDevice(2, 1);
TestGroupAseConfigurationData data(
{bounded_headphones, kLeAudioCodecLC3ChannelCountTwoChannel,
- kLeAudioCodecLC3ChannelCountSingleChannel, 2, 0});
+ kLeAudioCodecLC3ChannelCountSingleChannel, 2, 1});
TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1);
}
@@ -757,7 +783,7 @@ TEST_F(LeAudioAseConfigurationTest, test_bounded_headset_ringtone) {
LeAudioDevice* bounded_headset = AddTestDevice(2, 1);
TestGroupAseConfigurationData data(
{bounded_headset, kLeAudioCodecLC3ChannelCountTwoChannel,
- kLeAudioCodecLC3ChannelCountSingleChannel, 2, 0});
+ kLeAudioCodecLC3ChannelCountSingleChannel, 2, 1});
TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1);
}
@@ -785,9 +811,9 @@ TEST_F(LeAudioAseConfigurationTest, test_earbuds_ringtone) {
LeAudioDevice* right = AddTestDevice(1, 1);
TestGroupAseConfigurationData data[] = {
{left, kLeAudioCodecLC3ChannelCountSingleChannel,
- kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0},
+ kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1},
{right, kLeAudioCodecLC3ChannelCountSingleChannel,
- kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0}};
+ kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1}};
TestGroupAseConfiguration(LeAudioContextType::RINGTONE, data, 2);
}
@@ -820,7 +846,7 @@ TEST_F(LeAudioAseConfigurationTest, test_handsfree_ringtone) {
LeAudioDevice* handsfree = AddTestDevice(1, 1);
TestGroupAseConfigurationData data(
{handsfree, kLeAudioCodecLC3ChannelCountSingleChannel,
- kLeAudioCodecLC3ChannelCountSingleChannel, 1, 0});
+ kLeAudioCodecLC3ChannelCountSingleChannel, 1, 1});
TestGroupAseConfiguration(LeAudioContextType::RINGTONE, &data, 1);
}
@@ -898,7 +924,9 @@ TEST_F(LeAudioAseConfigurationTest, test_unsupported_codec) {
device->snk_pacs_ = pac_builder.Get();
device->src_pacs_ = pac_builder.Get();
- ASSERT_FALSE(group_->Configure(LeAudioContextType::RINGTONE));
+ ASSERT_FALSE(group_->Configure(
+ LeAudioContextType::RINGTONE,
+ AudioContexts(static_cast<uint16_t>(LeAudioContextType::RINGTONE))));
TestAsesInactive();
}
@@ -936,11 +964,12 @@ TEST_F(LeAudioAseConfigurationTest, test_reconnection_media) {
*ase->codec_config.audio_channel_allocation;
/* Get entry for the sink direction and use it to set configuration */
+ std::vector<uint8_t> ccid_list;
for (auto& ent : configuration->confs) {
if (ent.direction == ::le_audio::types::kLeAudioDirectionSink) {
left->ConfigureAses(ent, group_->GetCurrentContextType(),
&number_of_active_ases, group_snk_audio_location,
- group_src_audio_location);
+ group_src_audio_location, false, 0, ccid_list);
}
}
diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc
index f77f7bf952..8a7bd0139d 100644
--- a/system/bta/le_audio/le_audio_client_test.cc
+++ b/system/bta/le_audio/le_audio_client_test.cc
@@ -550,10 +550,11 @@ class UnicastTestNoInit : public Test {
ON_CALL(mock_state_machine_, Initialize(_))
.WillByDefault(SaveArg<0>(&state_machine_callbacks_));
- ON_CALL(mock_state_machine_, ConfigureStream(_, _, _))
+ ON_CALL(mock_state_machine_, ConfigureStream(_, _, _, _))
.WillByDefault([this](LeAudioDeviceGroup* group,
types::LeAudioContextType context_type,
- int ccid) {
+ types::AudioContexts metadata_context_type,
+ std::vector<uint8_t> ccid_list) {
bool isReconfiguration = group->IsPendingConfiguration();
/* This shall be called only for user reconfiguration */
@@ -570,7 +571,8 @@ class UnicastTestNoInit : public Test {
group->CigClearCis();
/* end */
- if (!group->Configure(context_type, ccid)) {
+ if (!group->Configure(context_type, metadata_context_type,
+ ccid_list)) {
LOG_ERROR("Could not configure ASEs for group %d content type %d",
group->group_id_, int(context_type));
@@ -615,7 +617,8 @@ class UnicastTestNoInit : public Test {
return false;
}
- group->Configure(group->GetContextType());
+ group->Configure(group->GetContextType(),
+ static_cast<uint16_t>(group->GetContextType()), {});
if (!group->CigAssignCisIds(leAudioDevice)) return false;
group->CigAssignCisConnHandlesToAses(leAudioDevice);
@@ -693,10 +696,11 @@ class UnicastTestNoInit : public Test {
return true;
});
- ON_CALL(mock_state_machine_, StartStream(_, _, _))
+ ON_CALL(mock_state_machine_, StartStream(_, _, _, _))
.WillByDefault([this](LeAudioDeviceGroup* group,
types::LeAudioContextType context_type,
- int ccid) {
+ types::AudioContexts metadata_context_type,
+ std::vector<uint8_t> ccid_list) {
if (group->GetState() ==
types::AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
if (group->GetContextType() != context_type) {
@@ -717,7 +721,8 @@ class UnicastTestNoInit : public Test {
group->CigClearCis();
/* end */
- if (!group->Configure(context_type, ccid)) {
+ if (!group->Configure(context_type, metadata_context_type,
+ ccid_list)) {
LOG(ERROR) << __func__ << ", failed to set ASE configuration";
return false;
}
@@ -2870,11 +2875,11 @@ TEST_F(UnicastTest, RemoveWhileStreaming) {
ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown);
// Start streaming
- uint8_t cis_count_out = 1;
- uint8_t cis_count_in = 0;
+ constexpr uint8_t cis_count_out = 1;
+ constexpr uint8_t cis_count_in = 0;
- int gmcs_ccid = 1;
- int gtbs_ccid = 2;
+ constexpr int gmcs_ccid = 1;
+ constexpr int gtbs_ccid = 2;
// Audio sessions are started only when device gets active
EXPECT_CALL(*mock_unicast_audio_source_, Start(_, _)).Times(1);
@@ -2883,7 +2888,8 @@ TEST_F(UnicastTest, RemoveWhileStreaming) {
LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */);
LeAudioClient::Get()->GroupSetActive(group_id);
- EXPECT_CALL(mock_state_machine_, StartStream(_, _, gmcs_ccid)).Times(1);
+ EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, {{gmcs_ccid}}))
+ .Times(1);
StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);
@@ -3139,7 +3145,7 @@ TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchSimple) {
// Start streaming with reconfiguration from default media stream setup
EXPECT_CALL(
mock_state_machine_,
- StartStream(_, le_audio::types::LeAudioContextType::NOTIFICATIONS, _))
+ StartStream(_, le_audio::types::LeAudioContextType::NOTIFICATIONS, _, _))
.Times(1);
StartStreaming(AUDIO_USAGE_NOTIFICATION, AUDIO_CONTENT_TYPE_UNKNOWN,
@@ -3154,7 +3160,7 @@ TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchSimple) {
EXPECT_CALL(*mock_unicast_audio_source_, Stop).Times(0);
EXPECT_CALL(*mock_unicast_audio_source_, Start).Times(0);
EXPECT_CALL(mock_state_machine_,
- StartStream(_, le_audio::types::LeAudioContextType::ALERTS, _))
+ StartStream(_, le_audio::types::LeAudioContextType::ALERTS, _, _))
.Times(1);
UpdateMetadata(AUDIO_USAGE_ALARM, AUDIO_CONTENT_TYPE_UNKNOWN);
Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
@@ -3167,7 +3173,7 @@ TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchSimple) {
EXPECT_CALL(
mock_state_machine_,
- StartStream(_, le_audio::types::LeAudioContextType::EMERGENCYALARM, _))
+ StartStream(_, le_audio::types::LeAudioContextType::EMERGENCYALARM, _, _))
.Times(1);
UpdateMetadata(AUDIO_USAGE_EMERGENCY, AUDIO_CONTENT_TYPE_UNKNOWN);
Mock::VerifyAndClearExpectations(mock_unicast_audio_source_);
@@ -3199,8 +3205,8 @@ TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchReconfigure) {
codec_spec_conf::kLeAudioLocationFrontRight, group_size,
group_id, 2 /* rank*/, true /*connect_through_csis*/);
- int gmcs_ccid = 1;
- int gtbs_ccid = 2;
+ constexpr int gmcs_ccid = 1;
+ constexpr int gtbs_ccid = 2;
// Start streaming MEDIA
EXPECT_CALL(*mock_unicast_audio_source_, Start(_, _)).Times(1);
@@ -3209,7 +3215,8 @@ TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchReconfigure) {
LeAudioClient::Get()->SetCcidInformation(gtbs_ccid, 2 /* Phone */);
LeAudioClient::Get()->GroupSetActive(group_id);
- EXPECT_CALL(mock_state_machine_, StartStream(_, _, gmcs_ccid)).Times(1);
+ EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, {{gmcs_ccid}}))
+ .Times(1);
StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id);
Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
@@ -3227,7 +3234,8 @@ TEST_F(UnicastTest, TwoEarbudsStreamingContextSwitchReconfigure) {
fake_osi_alarm_set_on_mloop_.cb(fake_osi_alarm_set_on_mloop_.data);
Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
- EXPECT_CALL(mock_state_machine_, StartStream(_, _, gtbs_ccid)).Times(1);
+ EXPECT_CALL(mock_state_machine_, StartStream(_, _, _, {{gtbs_ccid}}))
+ .Times(1);
StartStreaming(AUDIO_USAGE_VOICE_COMMUNICATION, AUDIO_CONTENT_TYPE_SPEECH,
group_id);
@@ -3470,7 +3478,8 @@ TEST_F(UnicastTest, MicrophoneAttachToCurrentMediaScenario) {
EXPECT_CALL(
mock_state_machine_,
- StartStream(_, le_audio::types::LeAudioContextType::VOICEASSISTANTS, _))
+ StartStream(_, le_audio::types::LeAudioContextType::VOICEASSISTANTS, _,
+ _))
.Times(1);
Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
Mock::VerifyAndClearExpectations(mock_unicast_audio_source_);
diff --git a/system/bta/le_audio/le_audio_types.cc b/system/bta/le_audio/le_audio_types.cc
index 5979d73afc..8920052c28 100644
--- a/system/bta/le_audio/le_audio_types.cc
+++ b/system/bta/le_audio/le_audio_types.cc
@@ -71,8 +71,12 @@ static uint8_t min_req_devices_cnt(
void get_cis_count(const AudioSetConfigurations* audio_set_confs,
types::LeAudioConfigurationStrategy strategy,
+ int group_ase_snk_cnt, int group_ase_src_count,
uint8_t* cis_count_bidir, uint8_t* cis_count_unidir_sink,
uint8_t* cis_count_unidir_source) {
+ LOG_INFO(" strategy %d, sink ases: %d, source ases %d",
+ static_cast<int>(strategy), group_ase_snk_cnt, group_ase_src_count);
+
for (auto audio_set_conf : *audio_set_confs) {
std::pair<uint8_t /* sink */, uint8_t /* source */> snk_src_pair(0, 0);
uint8_t bidir_count = 0;
@@ -83,8 +87,8 @@ void get_cis_count(const AudioSetConfigurations* audio_set_confs,
bool stategy_mismatch = false;
for (auto ent : (*audio_set_conf).confs) {
if (ent.strategy != strategy) {
- LOG_INFO("Strategy does not match (%d != %d)- skip this configuration",
- static_cast<int>(ent.strategy), static_cast<int>(strategy));
+ LOG_DEBUG("Strategy does not match (%d != %d)- skip this configuration",
+ static_cast<int>(ent.strategy), static_cast<int>(strategy));
stategy_mismatch = true;
break;
}
@@ -100,6 +104,42 @@ void get_cis_count(const AudioSetConfigurations* audio_set_confs,
continue;
}
+ /* Before we start adding another CISes, ignore scenarios which cannot
+ * satisfied because of the number of ases
+ */
+
+ if (group_ase_snk_cnt == 0 && snk_src_pair.first > 0) {
+ LOG_DEBUG("Group does not have sink ASEs");
+ continue;
+ }
+
+ if (group_ase_src_count == 0 && snk_src_pair.second > 0) {
+ LOG_DEBUG("Group does not have source ASEs");
+ continue;
+ }
+
+ /* Configuration list is set in the prioritized order.
+ * it might happen that more prio configuration can be supported
+ * and is already taken into account.
+ * Now let's try to ignore ortogonal configuration which would just
+ * increase our demant on number of CISes but will never happen
+ */
+
+ if (snk_src_pair.first == 0 &&
+ (*cis_count_unidir_sink > 0 || *cis_count_bidir > 0)) {
+ LOG_DEBUG(
+ "More prio configuration using sink ASEs has been taken into "
+ "account");
+ continue;
+ }
+ if (snk_src_pair.second == 0 &&
+ (*cis_count_unidir_source > 0 || *cis_count_bidir > 0)) {
+ LOG_DEBUG(
+ "More prio configuration using source ASEs has been taken into "
+ "account");
+ continue;
+ }
+
bidir_count = std::min(snk_src_pair.first, snk_src_pair.second);
unidir_sink_count = ((snk_src_pair.first - bidir_count) > 0)
? (snk_src_pair.first - bidir_count)
@@ -479,24 +519,21 @@ std::string LeAudioLtvMap::ToString() const {
} // namespace types
void AppendMetadataLtvEntryForCcidList(std::vector<uint8_t>& metadata,
- int ccid) {
- if (ccid < 0) return;
-
- std::vector<uint8_t> ccid_ltv_entry;
- std::vector<uint8_t> ccid_value = {static_cast<uint8_t>(ccid)};
+ const std::vector<uint8_t>& ccid_list) {
+ if (ccid_list.size() == 0) {
+ LOG_WARN("Empty CCID list.");
+ return;
+ }
- ccid_ltv_entry.push_back(
- static_cast<uint8_t>(types::kLeAudioMetadataTypeLen + ccid_value.size()));
- ccid_ltv_entry.push_back(
- static_cast<uint8_t>(types::kLeAudioMetadataTypeCcidList));
- ccid_ltv_entry.insert(ccid_ltv_entry.end(), ccid_value.begin(),
- ccid_value.end());
+ metadata.push_back(
+ static_cast<uint8_t>(types::kLeAudioMetadataTypeLen + ccid_list.size()));
+ metadata.push_back(static_cast<uint8_t>(types::kLeAudioMetadataTypeCcidList));
- metadata.insert(metadata.end(), ccid_ltv_entry.begin(), ccid_ltv_entry.end());
+ metadata.insert(metadata.end(), ccid_list.begin(), ccid_list.end());
}
void AppendMetadataLtvEntryForStreamingContext(
- std::vector<uint8_t>& metadata, LeAudioContextType context_type) {
+ std::vector<uint8_t>& metadata, types::AudioContexts context_type) {
std::vector<uint8_t> streaming_context_ltv_entry;
streaming_context_ltv_entry.resize(
@@ -510,7 +547,7 @@ void AppendMetadataLtvEntryForStreamingContext(
UINT8_TO_STREAM(streaming_context_ltv_entry_buf,
types::kLeAudioMetadataTypeStreamingAudioContext);
UINT16_TO_STREAM(streaming_context_ltv_entry_buf,
- static_cast<uint16_t>(context_type));
+ static_cast<uint16_t>(context_type.to_ulong()));
metadata.insert(metadata.end(), streaming_context_ltv_entry.begin(),
streaming_context_ltv_entry.end());
diff --git a/system/bta/le_audio/le_audio_types.h b/system/bta/le_audio/le_audio_types.h
index 30e52a00d8..fd198e19b3 100644
--- a/system/bta/le_audio/le_audio_types.h
+++ b/system/bta/le_audio/le_audio_types.h
@@ -414,6 +414,7 @@ class LeAudioLtvMap {
void Add(uint8_t type, std::vector<uint8_t> value) {
values.insert_or_assign(type, std::move(value));
}
+ void Remove(uint8_t type) { values.erase(type); }
bool IsEmpty() const { return values.empty(); }
void Clear() { values.clear(); }
size_t Size() const { return values.size(); }
@@ -668,6 +669,7 @@ static constexpr uint32_t kChannelAllocationStereo =
/* Declarations */
void get_cis_count(const AudioSetConfigurations* audio_set_configurations,
types::LeAudioConfigurationStrategy strategy,
+ int group_ase_snk_cnt, int group_ase_src_count,
uint8_t* cis_count_bidir, uint8_t* cis_count_unidir_sink,
uint8_t* cis_count_unidir_source);
bool check_if_may_cover_scenario(
@@ -721,9 +723,9 @@ struct stream_configuration {
};
void AppendMetadataLtvEntryForCcidList(std::vector<uint8_t>& metadata,
- int ccid);
+ const std::vector<uint8_t>& ccid_list);
void AppendMetadataLtvEntryForStreamingContext(
- std::vector<uint8_t>& metadata, types::LeAudioContextType context_type);
+ std::vector<uint8_t>& metadata, types::AudioContexts context_type);
uint8_t GetMaxCodecFramesPerSduFromPac(const types::acs_ac_record* pac_record);
uint32_t AdjustAllocationForOffloader(uint32_t allocation);
} // namespace le_audio \ No newline at end of file
diff --git a/system/bta/le_audio/le_audio_utils.cc b/system/bta/le_audio/le_audio_utils.cc
new file mode 100644
index 0000000000..ff83cc8784
--- /dev/null
+++ b/system/bta/le_audio/le_audio_utils.cc
@@ -0,0 +1,169 @@
+/*
+ * 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 "le_audio_utils.h"
+
+#include "bta/le_audio/content_control_id_keeper.h"
+#include "gd/common/strings.h"
+#include "le_audio_types.h"
+#include "osi/include/log.h"
+
+using le_audio::types::AudioContexts;
+using le_audio::types::LeAudioContextType;
+
+namespace le_audio {
+namespace utils {
+LeAudioContextType AudioContentToLeAudioContext(
+ 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_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:
+ return LeAudioContextType::NOTIFICATIONS;
+ case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
+ return LeAudioContextType::RINGTONE;
+ case AUDIO_USAGE_ALARM:
+ return LeAudioContextType::ALERTS;
+ case AUDIO_USAGE_EMERGENCY:
+ return LeAudioContextType::EMERGENCYALARM;
+ case AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+ return LeAudioContextType::INSTRUCTIONAL;
+ default:
+ break;
+ }
+
+ return LeAudioContextType::MEDIA;
+}
+
+static std::string usageToString(audio_usage_t usage) {
+ switch (usage) {
+ case AUDIO_USAGE_UNKNOWN:
+ return "USAGE_UNKNOWN";
+ case AUDIO_USAGE_MEDIA:
+ return "USAGE_MEDIA";
+ case AUDIO_USAGE_VOICE_COMMUNICATION:
+ return "USAGE_VOICE_COMMUNICATION";
+ case AUDIO_USAGE_VOICE_COMMUNICATION_SIGNALLING:
+ return "USAGE_VOICE_COMMUNICATION_SIGNALLING";
+ case AUDIO_USAGE_ALARM:
+ return "USAGE_ALARM";
+ case AUDIO_USAGE_NOTIFICATION:
+ return "USAGE_NOTIFICATION";
+ case AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE:
+ return "USAGE_NOTIFICATION_TELEPHONY_RINGTONE";
+ case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+ return "USAGE_NOTIFICATION_COMMUNICATION_REQUEST";
+ case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+ return "USAGE_NOTIFICATION_COMMUNICATION_INSTANT";
+ case AUDIO_USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+ return "USAGE_NOTIFICATION_COMMUNICATION_DELAYED";
+ case AUDIO_USAGE_NOTIFICATION_EVENT:
+ return "USAGE_NOTIFICATION_EVENT";
+ case AUDIO_USAGE_ASSISTANCE_ACCESSIBILITY:
+ return "USAGE_ASSISTANCE_ACCESSIBILITY";
+ case AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
+ return "USAGE_ASSISTANCE_NAVIGATION_GUIDANCE";
+ case AUDIO_USAGE_ASSISTANCE_SONIFICATION:
+ return "USAGE_ASSISTANCE_SONIFICATION";
+ case AUDIO_USAGE_GAME:
+ return "USAGE_GAME";
+ case AUDIO_USAGE_ASSISTANT:
+ return "USAGE_ASSISTANT";
+ case AUDIO_USAGE_CALL_ASSISTANT:
+ return "USAGE_CALL_ASSISTANT";
+ case AUDIO_USAGE_EMERGENCY:
+ return "USAGE_EMERGENCY";
+ case AUDIO_USAGE_SAFETY:
+ return "USAGE_SAFETY";
+ case AUDIO_USAGE_VEHICLE_STATUS:
+ return "USAGE_VEHICLE_STATUS";
+ case AUDIO_USAGE_ANNOUNCEMENT:
+ return "USAGE_ANNOUNCEMENT";
+ default:
+ return "unknown usage ";
+ }
+}
+
+static std::string contentTypeToString(audio_content_type_t content_type) {
+ switch (content_type) {
+ case AUDIO_CONTENT_TYPE_UNKNOWN:
+ return "CONTENT_TYPE_UNKNOWN";
+ case AUDIO_CONTENT_TYPE_SPEECH:
+ return "CONTENT_TYPE_SPEECH";
+ case AUDIO_CONTENT_TYPE_MUSIC:
+ return "CONTENT_TYPE_MUSIC";
+ case AUDIO_CONTENT_TYPE_MOVIE:
+ return "CONTENT_TYPE_MOVIE";
+ case AUDIO_CONTENT_TYPE_SONIFICATION:
+ return "CONTENT_TYPE_SONIFICATION";
+ default:
+ return "unknown content type ";
+ }
+}
+
+AudioContexts GetAllowedAudioContextsFromSourceMetadata(
+ const source_metadata_t& source_metadata, AudioContexts allowed_contexts) {
+ AudioContexts track_contexts(0);
+ for (auto idx = 0u; idx < source_metadata.track_count; ++idx) {
+ auto& track = source_metadata.tracks[idx];
+ if (track.content_type == 0 && track.usage == 0) continue;
+
+ LOG_INFO("%s: usage=%s(%d), content_type=%s(%d), gain=%f", __func__,
+ usageToString(track.usage).c_str(), track.usage,
+ contentTypeToString(track.content_type).c_str(),
+ track.content_type, track.gain);
+
+ track_contexts |= AudioContexts(
+ static_cast<std::underlying_type<LeAudioContextType>::type>(
+ AudioContentToLeAudioContext(track.content_type, track.usage)));
+ }
+ track_contexts &= allowed_contexts;
+ LOG_INFO("%s: allowed context=%lu", __func__, track_contexts.to_ulong());
+
+ return track_contexts;
+}
+
+std::vector<uint8_t> GetAllCcids(std::bitset<16> context_types) {
+ auto ccid_keeper = ContentControlIdKeeper::GetInstance();
+ std::vector<uint8_t> ccid_vec;
+
+ std::size_t bit_pos = 0;
+ while (bit_pos < context_types.size()) {
+ if (context_types.test(bit_pos)) {
+ auto ccid = ccid_keeper->GetCcid(static_cast<uint16_t>(0b1 << bit_pos));
+ if (ccid != -1) {
+ ccid_vec.push_back(static_cast<uint8_t>(ccid));
+ }
+ }
+ ++bit_pos;
+ }
+
+ return ccid_vec;
+}
+
+} // namespace utils
+} // namespace le_audio
diff --git a/system/bta/le_audio/le_audio_utils.h b/system/bta/le_audio/le_audio_utils.h
new file mode 100644
index 0000000000..6596c97323
--- /dev/null
+++ b/system/bta/le_audio/le_audio_utils.h
@@ -0,0 +1,36 @@
+/*
+ * 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 <hardware/audio.h>
+
+#include <bitset>
+#include <vector>
+
+#include "le_audio_types.h"
+
+namespace le_audio {
+namespace utils {
+types::LeAudioContextType AudioContentToLeAudioContext(
+ audio_content_type_t content_type, audio_usage_t usage);
+types::AudioContexts GetAllowedAudioContextsFromSourceMetadata(
+ const source_metadata_t& source_metadata,
+ types::AudioContexts allowed_contexts);
+std::vector<uint8_t> GetAllCcids(std::bitset<16> context_types);
+
+} // namespace utils
+} // namespace le_audio
diff --git a/system/bta/le_audio/metrics_collector.cc b/system/bta/le_audio/metrics_collector.cc
index 4a00d2da84..aed66c006e 100644
--- a/system/bta/le_audio/metrics_collector.cc
+++ b/system/bta/le_audio/metrics_collector.cc
@@ -16,7 +16,6 @@
#include "metrics_collector.h"
-#include <chrono>
#include <memory>
#include <vector>
@@ -24,17 +23,15 @@
namespace le_audio {
-using ClockTimePoint =
- std::chrono::time_point<std::chrono::high_resolution_clock>;
using bluetooth::le_audio::ConnectionState;
using le_audio::types::LeAudioContextType;
-const static ClockTimePoint kInvalidTimePoint{};
+const static metrics::ClockTimePoint kInvalidTimePoint{};
MetricsCollector* MetricsCollector::instance = nullptr;
-inline int64_t get_timedelta_nanos(const ClockTimePoint& t1,
- const ClockTimePoint& t2) {
+inline int64_t get_timedelta_nanos(const metrics::ClockTimePoint& t1,
+ const metrics::ClockTimePoint& t2) {
if (t1 == kInvalidTimePoint || t2 == kInvalidTimePoint) {
return -1;
}
@@ -79,9 +76,9 @@ inline int32_t to_atom_context_type(const LeAudioContextType stack_type) {
class DeviceMetrics {
public:
RawAddress address_;
- ClockTimePoint connecting_timepoint_ = kInvalidTimePoint;
- ClockTimePoint connected_timepoint_ = kInvalidTimePoint;
- ClockTimePoint disconnected_timepoint_ = kInvalidTimePoint;
+ metrics::ClockTimePoint connecting_timepoint_ = kInvalidTimePoint;
+ metrics::ClockTimePoint connected_timepoint_ = kInvalidTimePoint;
+ metrics::ClockTimePoint disconnected_timepoint_ = kInvalidTimePoint;
int32_t connection_status_ = 0;
int32_t disconnection_status_ = 0;
@@ -114,7 +111,7 @@ class GroupMetricsImpl : public GroupMetrics {
int32_t group_size_;
std::vector<std::unique_ptr<DeviceMetrics>> device_metrics_;
std::unordered_map<RawAddress, DeviceMetrics*> opened_devices_;
- ClockTimePoint beginning_timepoint_;
+ metrics::ClockTimePoint beginning_timepoint_;
std::vector<int64_t> streaming_offset_nanos_;
std::vector<int64_t> streaming_duration_nanos_;
std::vector<int32_t> streaming_context_type_;
@@ -283,6 +280,18 @@ void MetricsCollector::OnStreamEnded(int32_t group_id) {
}
}
+void MetricsCollector::OnBroadcastStateChanged(bool started) {
+ if (started) {
+ broadcast_beginning_timepoint_ = std::chrono::high_resolution_clock::now();
+ } else {
+ auto broadcast_ending_timepoint_ =
+ std::chrono::high_resolution_clock::now();
+ bluetooth::common::LogLeAudioBroadcastSessionReported(get_timedelta_nanos(
+ broadcast_beginning_timepoint_, broadcast_ending_timepoint_));
+ broadcast_beginning_timepoint_ = kInvalidTimePoint;
+ }
+}
+
void MetricsCollector::Flush() {
LOG(INFO) << __func__;
for (auto& p : opened_groups_) {
diff --git a/system/bta/le_audio/metrics_collector.h b/system/bta/le_audio/metrics_collector.h
index f988364734..1e235dfdc4 100644
--- a/system/bta/le_audio/metrics_collector.h
+++ b/system/bta/le_audio/metrics_collector.h
@@ -18,6 +18,7 @@
#include <hardware/bt_le_audio.h>
+#include <chrono>
#include <cstdint>
#include <memory>
#include <unordered_map>
@@ -27,6 +28,11 @@
namespace le_audio {
+namespace metrics {
+using ClockTimePoint =
+ std::chrono::time_point<std::chrono::high_resolution_clock>;
+}
+
enum ConnectionStatus : int32_t {
UNKNOWN = 0,
SUCCESS = 1,
@@ -117,6 +123,13 @@ class MetricsCollector {
void OnStreamEnded(int32_t group_id);
/**
+ * When there is a change in Bluetooth LE Audio broadcast state
+ *
+ * @param started if broadcast streaming is started.
+ */
+ void OnBroadcastStateChanged(bool started);
+
+ /**
* Flush all log to statsd
*
* @param group_id Group ID of the associated stream.
@@ -131,6 +144,8 @@ class MetricsCollector {
std::unordered_map<int32_t, std::unique_ptr<GroupMetrics>> opened_groups_;
std::unordered_map<int32_t, int32_t> group_size_table_;
+
+ metrics::ClockTimePoint broadcast_beginning_timepoint_;
};
} // namespace le_audio
diff --git a/system/bta/le_audio/metrics_collector_linux.cc b/system/bta/le_audio/metrics_collector_linux.cc
index 2ac63a567b..cb36fbec2e 100644
--- a/system/bta/le_audio/metrics_collector_linux.cc
+++ b/system/bta/le_audio/metrics_collector_linux.cc
@@ -40,6 +40,8 @@ void MetricsCollector::OnStreamStarted(
void MetricsCollector::OnStreamEnded(int32_t group_id) {}
+void MetricsCollector::OnBroadcastStateChanged(bool started) {}
+
void MetricsCollector::Flush() {}
} // namespace le_audio
diff --git a/system/bta/le_audio/metrics_collector_test.cc b/system/bta/le_audio/metrics_collector_test.cc
index ee295d56d7..49c13bf05d 100644
--- a/system/bta/le_audio/metrics_collector_test.cc
+++ b/system/bta/le_audio/metrics_collector_test.cc
@@ -44,6 +44,7 @@ int log_count = 0;
int32_t last_group_size;
int32_t last_group_metric_id;
int64_t last_connection_duration_nanos;
+int64_t last_broadcast_duration_nanos;
std::vector<int64_t> last_device_connecting_offset_nanos;
std::vector<int64_t> last_device_connected_offset_nanos;
std::vector<int64_t> last_device_connection_duration_nanos;
@@ -84,6 +85,10 @@ void LogLeAudioConnectionSessionReported(
last_streaming_context_type = streaming_context_type;
}
+void LogLeAudioBroadcastSessionReported(int64_t duration_nanos) {
+ last_broadcast_duration_nanos = duration_nanos;
+}
+
} // namespace common
} // namespace bluetooth
@@ -111,6 +116,7 @@ class MetricsCollectorTest : public Test {
log_count = 0;
last_group_size = 0;
last_group_metric_id = 0;
+ last_broadcast_duration_nanos = 0;
last_connection_duration_nanos = 0;
last_device_connecting_offset_nanos = {};
last_device_connected_offset_nanos = {};
@@ -367,4 +373,15 @@ TEST_F(MetricsCollectorTest, StreamingSessions) {
static_cast<int32_t>(LeAudioMetricsContextType::COMMUNICATION));
}
+TEST_F(MetricsCollectorTest, BroadastSessions) {
+ last_broadcast_duration_nanos = 0;
+ collector->OnBroadcastStateChanged(true);
+ collector->OnBroadcastStateChanged(false);
+ ASSERT_GT(last_broadcast_duration_nanos, 0);
+ last_broadcast_duration_nanos = 0;
+ collector->OnBroadcastStateChanged(true);
+ collector->OnBroadcastStateChanged(false);
+ ASSERT_GT(last_broadcast_duration_nanos, 0);
+}
+
} // namespace le_audio \ No newline at end of file
diff --git a/system/bta/le_audio/mock_state_machine.h b/system/bta/le_audio/mock_state_machine.h
index 63e850b8e1..ac98e45161 100644
--- a/system/bta/le_audio/mock_state_machine.h
+++ b/system/bta/le_audio/mock_state_machine.h
@@ -25,7 +25,9 @@ class MockLeAudioGroupStateMachine : public le_audio::LeAudioGroupStateMachine {
public:
MOCK_METHOD((bool), StartStream,
(le_audio::LeAudioDeviceGroup * group,
- le_audio::types::LeAudioContextType context_type, int ccid),
+ le_audio::types::LeAudioContextType context_type,
+ le_audio::types::AudioContexts metadata_context_type,
+ std::vector<uint8_t> ccid_list),
(override));
MOCK_METHOD((bool), AttachToStream,
(le_audio::LeAudioDeviceGroup * group,
@@ -35,7 +37,9 @@ class MockLeAudioGroupStateMachine : public le_audio::LeAudioGroupStateMachine {
(override));
MOCK_METHOD((bool), ConfigureStream,
(le_audio::LeAudioDeviceGroup * group,
- le_audio::types::LeAudioContextType context_type, int ccid),
+ le_audio::types::LeAudioContextType context_type,
+ le_audio::types::AudioContexts metadata_context_type,
+ std::vector<uint8_t> ccid_list),
(override));
MOCK_METHOD((void), StopStream, (le_audio::LeAudioDeviceGroup * group),
(override));
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index 713be3ac68..b9e9528767 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -99,6 +99,7 @@ using le_audio::LeAudioGroupStateMachine;
using le_audio::types::ase;
using le_audio::types::AseState;
+using le_audio::types::AudioContexts;
using le_audio::types::AudioStreamDataPathState;
using le_audio::types::CodecLocation;
@@ -156,7 +157,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
bool StartStream(LeAudioDeviceGroup* group,
le_audio::types::LeAudioContextType context_type,
- int ccid) override {
+ AudioContexts metadata_context_type,
+ std::vector<uint8_t> ccid_list) override {
LOG_INFO(" current state: %s", ToString(group->GetState()).c_str());
switch (group->GetState()) {
@@ -177,7 +179,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
/* If configuration is needed */
FALLTHROUGH;
case AseState::BTA_LE_AUDIO_ASE_STATE_IDLE:
- if (!group->Configure(context_type, ccid)) {
+ if (!group->Configure(context_type, metadata_context_type, ccid_list)) {
LOG(ERROR) << __func__ << ", failed to set ASE configuration";
return false;
}
@@ -206,7 +208,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
/* This case just updates the metadata for the stream, in case
* stream configuration is satisfied
*/
- if (!group->IsMetadataChanged(context_type, ccid)) return true;
+ if (!group->IsMetadataChanged(metadata_context_type, ccid_list))
+ return true;
LeAudioDevice* leAudioDevice = group->GetFirstActiveDevice();
if (!leAudioDevice) {
@@ -214,7 +217,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
return false;
}
- PrepareAndSendUpdateMetadata(group, leAudioDevice, context_type, ccid);
+ PrepareAndSendUpdateMetadata(group, leAudioDevice,
+ metadata_context_type, ccid_list);
break;
}
@@ -229,7 +233,8 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
bool ConfigureStream(LeAudioDeviceGroup* group,
le_audio::types::LeAudioContextType context_type,
- int ccid) override {
+ AudioContexts metadata_context_type,
+ std::vector<uint8_t> ccid_list) override {
if (group->GetState() > AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED) {
LOG_ERROR(
"Stream should be stopped or in configured stream. Current state: %s",
@@ -239,7 +244,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
ReleaseCisIds(group);
- if (!group->Configure(context_type, ccid)) {
+ if (!group->Configure(context_type, metadata_context_type, ccid_list)) {
LOG_ERROR("Could not configure ASEs for group %d content type %d",
group->group_id_, int(context_type));
@@ -727,6 +732,12 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
AudioStreamDataPathState::DATA_PATH_ESTABLISHED) {
value |= bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput;
}
+
+ if (value == 0) {
+ LOG_INFO("Data path was not set. Nothing to do here.");
+ return;
+ }
+
IsoManager::GetInstance()->RemoveIsoDataPath(cis_conn_hdl, value);
}
@@ -737,6 +748,11 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
FreeLinkQualityReports(leAudioDevice);
+ /* If this is peer disconnecting CIS, make sure to clear data path */
+ if (event->reason != HCI_ERR_CONN_CAUSE_LOCAL_HOST) {
+ RemoveDataPathByCisHandle(leAudioDevice, event->cis_conn_hdl);
+ }
+
auto ases_pair = leAudioDevice->GetAsesByCisConnHdl(event->cis_conn_hdl);
if (ases_pair.sink) {
ases_pair.sink->data_path_state = AudioStreamDataPathState::CIS_ASSIGNED;
@@ -922,6 +938,17 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
*ase->codec_config.codec_frames_blocks_per_sdu);
}
+ if (stream_conf->sink_frame_duration_us == 0) {
+ stream_conf->sink_frame_duration_us =
+ ase->codec_config.GetFrameDurationUs();
+ } else {
+ ASSERT_LOG(stream_conf->sink_frame_duration_us ==
+ ase->codec_config.GetFrameDurationUs(),
+ "frame_duration_us: %d!=%d",
+ stream_conf->sink_frame_duration_us,
+ ase->codec_config.GetFrameDurationUs());
+ }
+
LOG_INFO(
" Added Sink Stream Configuration. CIS Connection Handle: %d"
", Audio Channel Allocation: %d"
@@ -981,6 +1008,17 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
*ase->codec_config.codec_frames_blocks_per_sdu);
}
+ if (stream_conf->source_frame_duration_us == 0) {
+ stream_conf->source_frame_duration_us =
+ ase->codec_config.GetFrameDurationUs();
+ } else {
+ ASSERT_LOG(stream_conf->source_frame_duration_us ==
+ ase->codec_config.GetFrameDurationUs(),
+ "frame_duration_us: %d!=%d",
+ stream_conf->source_frame_duration_us,
+ ase->codec_config.GetFrameDurationUs());
+ }
+
LOG_INFO(
" Added Source Stream Configuration. CIS Connection Handle: %d"
", Audio Channel Allocation: %d"
@@ -1888,17 +1926,18 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
GATT_WRITE_NO_RSP, NULL, NULL);
}
- void PrepareAndSendUpdateMetadata(
- LeAudioDeviceGroup* group, LeAudioDevice* leAudioDevice,
- le_audio::types::LeAudioContextType context_type, int ccid) {
+ void PrepareAndSendUpdateMetadata(LeAudioDeviceGroup* group,
+ LeAudioDevice* leAudioDevice,
+ le_audio::types::AudioContexts context_type,
+ const std::vector<uint8_t>& ccid_list) {
std::vector<struct le_audio::client_parser::ascs::ctp_update_metadata>
confs;
for (; leAudioDevice;
leAudioDevice = group->GetNextActiveDevice(leAudioDevice)) {
- if (!leAudioDevice->IsMetadataChanged(context_type, ccid)) continue;
+ if (!leAudioDevice->IsMetadataChanged(context_type, ccid_list)) continue;
- auto new_metadata = leAudioDevice->GetMetadata(context_type, ccid);
+ auto new_metadata = leAudioDevice->GetMetadata(context_type, ccid_list);
/* Request server to update ASEs with new metadata */
for (struct ase* ase = leAudioDevice->GetFirstActiveAse(); ase != nullptr;
diff --git a/system/bta/le_audio/state_machine.h b/system/bta/le_audio/state_machine.h
index 06b63fb0bf..d4e97c1d07 100644
--- a/system/bta/le_audio/state_machine.h
+++ b/system/bta/le_audio/state_machine.h
@@ -49,11 +49,13 @@ class LeAudioGroupStateMachine {
LeAudioDevice* leAudioDevice) = 0;
virtual bool StartStream(LeAudioDeviceGroup* group,
types::LeAudioContextType context_type,
- int ccid = -1) = 0;
+ types::AudioContexts metadata_context_type,
+ std::vector<uint8_t> ccid_list = {}) = 0;
virtual void SuspendStream(LeAudioDeviceGroup* group) = 0;
virtual bool ConfigureStream(LeAudioDeviceGroup* group,
- le_audio::types::LeAudioContextType context_type,
- int ccid = -1) = 0;
+ types::LeAudioContextType context_type,
+ types::AudioContexts metadata_context_type,
+ std::vector<uint8_t> ccid_list = {}) = 0;
virtual void StopStream(LeAudioDeviceGroup* group) = 0;
virtual void ProcessGattNotifEvent(uint8_t* value, uint16_t len,
struct types::ase* ase,
diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc
index 2fd0e7865a..da363cf357 100644
--- a/system/bta/le_audio/state_machine_test.cc
+++ b/system/bta/le_audio/state_machine_test.cc
@@ -1185,7 +1185,8 @@ TEST_F(StateMachineTest, testConfigureCodecSingle) {
InjectInitialIdleNotification(group);
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1226,7 +1227,8 @@ TEST_F(StateMachineTest, testConfigureCodecMulti) {
// Start the configuration and stream the content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1263,7 +1265,8 @@ TEST_F(StateMachineTest, testConfigureQosSingle) {
InjectInitialIdleNotification(group);
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1307,7 +1310,8 @@ TEST_F(StateMachineTest, testConfigureQosMultiple) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1351,7 +1355,8 @@ TEST_F(StateMachineTest, testStreamSingle) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1395,7 +1400,8 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1440,7 +1446,8 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1492,7 +1499,8 @@ TEST_F(StateMachineTest, testStreamMultipleConversational) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1543,7 +1551,8 @@ TEST_F(StateMachineTest, testStreamMultiple) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1586,7 +1595,8 @@ TEST_F(StateMachineTest, testDisableSingle) {
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1653,7 +1663,8 @@ TEST_F(StateMachineTest, testDisableMultiple) {
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1715,7 +1726,8 @@ TEST_F(StateMachineTest, testDisableBidirectional) {
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1762,7 +1774,8 @@ TEST_F(StateMachineTest, testReleaseSingle) {
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1833,7 +1846,8 @@ TEST_F(StateMachineTest, testReleaseCachingSingle) {
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1906,7 +1920,8 @@ TEST_F(StateMachineTest, testStreamCachingSingle) {
// Start the configuration and stream Ringtone content
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1921,7 +1936,8 @@ TEST_F(StateMachineTest, testStreamCachingSingle) {
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -1991,7 +2007,8 @@ TEST_F(StateMachineTest, testActivateStreamCachingSingle) {
// Start the configuration and stream Conversational content
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -2007,7 +2024,8 @@ TEST_F(StateMachineTest, testActivateStreamCachingSingle) {
// Start the configuration and stream Media content
context_type = kContextTypeMedia;
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -2054,7 +2072,8 @@ TEST_F(StateMachineTest, testReleaseMultiple) {
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -2110,7 +2129,8 @@ TEST_F(StateMachineTest, testReleaseBidirectional) {
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ types::AudioContexts(context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -2156,7 +2176,8 @@ TEST_F(StateMachineTest, testDisableAndReleaseBidirectional) {
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
// Suspend the stream
LeAudioGroupStateMachine::Get()->SuspendStream(group);
@@ -2260,7 +2281,8 @@ TEST_F(StateMachineTest, testAseAutonomousRelease) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Validate new GroupStreamStatus
EXPECT_CALL(mock_callbacks_,
@@ -2326,7 +2348,8 @@ TEST_F(StateMachineTest, testAseAutonomousRelease2Devices) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Check streaming will continue
EXPECT_CALL(mock_callbacks_,
@@ -2367,7 +2390,8 @@ TEST_F(StateMachineTest, testStateTransitionTimeoutOnIdleState) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Disconnect device
LeAudioGroupStateMachine::Get()->ProcessHciNotifAclDisconnected(
@@ -2398,7 +2422,8 @@ TEST_F(StateMachineTest, testStateTransitionTimeout) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Check if timeout is fired
EXPECT_CALL(mock_callbacks_, OnStateTransitionTimeout(leaudio_group_id));
@@ -2445,7 +2470,8 @@ TEST_F(StateMachineTest, testConfigureDataPathForHost) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
}
TEST_F(StateMachineTest, testConfigureDataPathForAdsp) {
const auto context_type = kContextTypeRingtone;
@@ -2483,7 +2509,8 @@ TEST_F(StateMachineTest, testConfigureDataPathForAdsp) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
}
TEST_F(StateMachineTest, testStreamConfigurationAdsp) {
@@ -2516,7 +2543,8 @@ TEST_F(StateMachineTest, testStreamConfigurationAdsp) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -2569,7 +2597,8 @@ TEST_F(StateMachineTest, testStreamConfigurationAdspNoDownMix) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
ASSERT_EQ(static_cast<int>(group->stream_conf.sink_offloader_streams.size()),
2);
@@ -2630,7 +2659,8 @@ TEST_F(StateMachineTest, testStreamConfigurationAdspDownMix) {
// Start the configuration and stream Media content
ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type)));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
ASSERT_EQ(static_cast<int>(group->stream_conf.sink_offloader_streams.size()),
2);
@@ -2715,7 +2745,8 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStream) {
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
- group, static_cast<types::LeAudioContextType>(context_type));
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
// Check if group has transitioned to a proper state
ASSERT_EQ(group->GetState(),
@@ -2752,12 +2783,14 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStream) {
source_num_of_active_ases++;
}
+ std::vector<uint8_t> ccid_list;
for (auto& ent : stream_conf->conf->confs) {
if (ent.direction == le_audio::types::kLeAudioDirectionSink) {
/* Sink*/
if (!lastDevice->ConfigureAses(
ent, group->GetCurrentContextType(), &sink_num_of_active_ases,
- sink_group_audio_locations, source_group_audio_locations, true)) {
+ sink_group_audio_locations, source_group_audio_locations, true, 0,
+ ccid_list)) {
FAIL() << __func__ << " Could not set sink configuration of "
<< stream_conf->conf->name;
}
@@ -2765,7 +2798,8 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStream) {
/* Source*/
if (!lastDevice->ConfigureAses(
ent, group->GetCurrentContextType(), &source_num_of_active_ases,
- sink_group_audio_locations, source_group_audio_locations, true)) {
+ sink_group_audio_locations, source_group_audio_locations, true, 0,
+ ccid_list)) {
FAIL() << __func__ << " Could not set source configuration of "
<< stream_conf->conf->name;
}
diff --git a/system/bta/vc/vc.cc b/system/bta/vc/vc.cc
index 7475e63e32..70f82e16f5 100644
--- a/system/bta/vc/vc.cc
+++ b/system/bta/vc/vc.cc
@@ -1051,7 +1051,7 @@ class VolumeControlImpl : public VolumeControl {
case BTA_GATTC_ENC_CMPL_CB_EVT: {
uint8_t encryption_status;
- if (!BTM_IsEncrypted(p_data->enc_cmpl.remote_bda, BT_TRANSPORT_LE)) {
+ if (BTM_IsEncrypted(p_data->enc_cmpl.remote_bda, BT_TRANSPORT_LE)) {
encryption_status = BTM_SUCCESS;
} else {
encryption_status = BTM_FAILED_ON_SECURITY;
diff --git a/system/btif/src/btif_dm.cc b/system/btif/src/btif_dm.cc
index 0d5a11b952..6718de7220 100644
--- a/system/btif/src/btif_dm.cc
+++ b/system/btif/src/btif_dm.cc
@@ -1314,6 +1314,13 @@ static void btif_dm_search_devices_evt(tBTA_DM_SEARCH_EVT event,
}
#if TARGET_FLOSS
+ std::vector<uint8_t> property_value;
+ for (auto uuid : uuid_iter->second) {
+ auto uuid_128bit = uuid.To128BitBE();
+ property_value.insert(property_value.end(), uuid_128bit.begin(),
+ uuid_128bit.end());
+ }
+
// Floss expects that EIR uuids are immediately reported when the
// device is found and doesn't wait for the pairing intent.
//
@@ -1321,8 +1328,8 @@ static void btif_dm_search_devices_evt(tBTA_DM_SEARCH_EVT event,
// existing UUIDs.
BTIF_STORAGE_FILL_PROPERTY(
&properties[num_properties], BT_PROPERTY_UUIDS,
- pairing_cb.num_eir_uuids * Uuid::kNumBytes128,
- pairing_cb.eir_uuids);
+ uuid_iter->second.size() * Uuid::kNumBytes128,
+ (void*)property_value.data());
num_properties++;
#endif
}
diff --git a/system/common/metrics.cc b/system/common/metrics.cc
index 0664186cac..baf838b0c4 100644
--- a/system/common/metrics.cc
+++ b/system/common/metrics.cc
@@ -962,6 +962,13 @@ void LogLeAudioConnectionSessionReported(
}
}
+void LogLeAudioBroadcastSessionReported(int64_t duration_nanos) {
+ int ret = stats_write(LE_AUDIO_BROADCAST_SESSION_REPORTED, duration_nanos);
+ if (ret < 0) {
+ LOG(WARNING) << __func__ << ": failed for duration=" << duration_nanos;
+ }
+}
+
} // namespace common
} // namespace bluetooth
diff --git a/system/common/metrics.h b/system/common/metrics.h
index 9d33e1b81f..a0a34b5945 100644
--- a/system/common/metrics.h
+++ b/system/common/metrics.h
@@ -518,6 +518,8 @@ void LogLeAudioConnectionSessionReported(
std::vector<int64_t>& streaming_duration_nanos,
std::vector<int32_t>& streaming_context_type);
+void LogLeAudioBroadcastSessionReported(int64_t duration_nanos);
+
} // namespace common
} // namespace bluetooth
diff --git a/system/common/metrics_linux.cc b/system/common/metrics_linux.cc
index fa8c6a0497..883a645e30 100644
--- a/system/common/metrics_linux.cc
+++ b/system/common/metrics_linux.cc
@@ -167,6 +167,8 @@ void LogLeAudioConnectionSessionReported(
std::vector<int64_t>& streaming_duration_nanos,
std::vector<int32_t>& streaming_context_type) {}
+void LogLeAudioBroadcastSessionReported(int64_t duration_nanos) {}
+
} // namespace common
} // namespace bluetooth
diff --git a/system/embdrv/aptx/README.md b/system/embdrv/aptx/README.md
new file mode 100644
index 0000000000..4b56c29dce
--- /dev/null
+++ b/system/embdrv/aptx/README.md
@@ -0,0 +1 @@
+# AptX Codec
diff --git a/system/embdrv/aptx/aptxhd/README.md b/system/embdrv/aptx/aptxhd/README.md
new file mode 100644
index 0000000000..703b1da518
--- /dev/null
+++ b/system/embdrv/aptx/aptxhd/README.md
@@ -0,0 +1 @@
+# AptX High Definition Codec
diff --git a/system/gd/Android.bp b/system/gd/Android.bp
index 1c6573aba9..722902e47e 100644
--- a/system/gd/Android.bp
+++ b/system/gd/Android.bp
@@ -59,7 +59,7 @@ cc_defaults {
name: "gd_clang_file_coverage",
target: {
glibc: {
- clang_cflags: [
+ cflags: [
"-fprofile-instr-generate",
"-fcoverage-mapping",
],
diff --git a/system/gd/rust/linux/mgmt/Cargo.toml b/system/gd/rust/linux/mgmt/Cargo.toml
index b4a8aa4b12..f16841d153 100644
--- a/system/gd/rust/linux/mgmt/Cargo.toml
+++ b/system/gd/rust/linux/mgmt/Cargo.toml
@@ -8,7 +8,7 @@ edition = "2018"
[dependencies]
bt_common = { path = "../../common" }
bt_topshim = { path = "../../topshim" }
-bt_socket = { path = "../socket" }
+bt_utils = { path = "../utils" }
btstack = { path = "../stack" }
# external deps
diff --git a/system/gd/rust/linux/mgmt/src/state_machine.rs b/system/gd/rust/linux/mgmt/src/state_machine.rs
index 4459f86c2b..d6228d103f 100644
--- a/system/gd/rust/linux/mgmt/src/state_machine.rs
+++ b/system/gd/rust/linux/mgmt/src/state_machine.rs
@@ -1,7 +1,9 @@
use crate::bluetooth_manager::BluetoothManager;
use crate::config_util;
use bt_common::time::Alarm;
-use bt_socket::{BtSocket, HciChannels, MgmtCommand, MgmtCommandResponse, MgmtEvent, HCI_DEV_NONE};
+use bt_utils::socket::{
+ BtSocket, HciChannels, MgmtCommand, MgmtCommandResponse, MgmtEvent, HCI_DEV_NONE,
+};
use log::{debug, error, info, warn};
use nix::sys::signal::{self, Signal};
diff --git a/system/gd/rust/linux/socket/Cargo.toml b/system/gd/rust/linux/utils/Cargo.toml
index 8ab039eefc..adbf949505 100644
--- a/system/gd/rust/linux/socket/Cargo.toml
+++ b/system/gd/rust/linux/utils/Cargo.toml
@@ -13,13 +13,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
[package]
-name = "bt_socket"
+name = "bt_utils"
version = "0.0.1"
edition = "2021"
[dependencies]
libc = "0.2"
log = "0.4.14"
+nix = "0.19"
num-derive = "0.3"
num-traits = "0.2"
diff --git a/system/gd/rust/linux/utils/src/lib.rs b/system/gd/rust/linux/utils/src/lib.rs
new file mode 100644
index 0000000000..734756cf46
--- /dev/null
+++ b/system/gd/rust/linux/utils/src/lib.rs
@@ -0,0 +1,7 @@
+//! Utilities
+
+#[macro_use]
+extern crate num_derive;
+
+pub mod socket;
+pub mod uinput;
diff --git a/system/gd/rust/linux/socket/src/lib.rs b/system/gd/rust/linux/utils/src/socket.rs
index 8c0c80e4b4..719bbcc36f 100644
--- a/system/gd/rust/linux/socket/src/lib.rs
+++ b/system/gd/rust/linux/utils/src/socket.rs
@@ -4,9 +4,6 @@
use std::mem;
use std::os::unix::io::{AsRawFd, RawFd};
-#[macro_use]
-extern crate num_derive;
-
use libc;
use log::debug;
use num_traits::cast::{FromPrimitive, ToPrimitive};
diff --git a/system/gd/rust/linux/utils/src/uinput.rs b/system/gd/rust/linux/utils/src/uinput.rs
new file mode 100644
index 0000000000..f274aadf73
--- /dev/null
+++ b/system/gd/rust/linux/utils/src/uinput.rs
@@ -0,0 +1,199 @@
+//! This library provides access to Linux uinput.
+
+use libc;
+use log::error;
+use nix;
+use std::ffi::CString;
+use std::mem;
+use std::slice;
+
+// Supported AVRCP Keys
+const AVC_PLAY: u8 = 0x44;
+const AVC_STOP: u8 = 0x45;
+const AVC_PAUSE: u8 = 0x46;
+const AVC_REWIND: u8 = 0x48;
+const AVC_FAST_FORWAED: u8 = 0x49;
+const AVC_FORWARD: u8 = 0x4B;
+const AVC_BACKWARD: u8 = 0x4C;
+
+// Supported uinput keys
+const KEY_PLAYPAUSE: libc::c_uint = 164;
+const KEY_STOPCD: libc::c_uint = 166;
+const KEY_REWIND: libc::c_uint = 168;
+const KEY_FASTFORWAED: libc::c_uint = 208;
+const KEY_NEXTSONG: libc::c_uint = 163;
+const KEY_PREVIOUSSONG: libc::c_uint = 165;
+
+// uinput setup constants
+const UINPUT_MAX_NAME_SIZE: usize = 80;
+const ABS_MAX: usize = 0x3F;
+const BUS_BLUETOOTH: u16 = 0x05;
+const UINPUT_IOCTL_BASE: char = 'U';
+
+const EV_SYN: libc::c_int = 0x00;
+const EV_KEY: libc::c_int = 0x01;
+const EV_REL: libc::c_int = 0x02;
+const EV_REP: libc::c_int = 0x14;
+
+const UI_DEV_CREATE: u64 = nix::request_code_none!(UINPUT_IOCTL_BASE, 1);
+const UI_DEV_DESTROY: u64 = nix::request_code_none!(UINPUT_IOCTL_BASE, 2);
+const UI_SET_EVBIT: u64 =
+ nix::request_code_write!(UINPUT_IOCTL_BASE, 100, mem::size_of::<libc::c_int>());
+const UI_SET_PHYS: u64 =
+ nix::request_code_write!(UINPUT_IOCTL_BASE, 108, mem::size_of::<libc::c_char>());
+const UI_SET_KEYBIT: u64 =
+ nix::request_code_write!(UINPUT_IOCTL_BASE, 101, mem::size_of::<libc::c_int>());
+
+// Conversion key map from AVRCP keys to uinput keys.
+#[allow(dead_code)]
+struct KeyMap {
+ avc: u8,
+ uinput: libc::c_uint,
+}
+
+const KEY_MAP: [KeyMap; 7] = [
+ KeyMap { avc: AVC_PLAY, uinput: KEY_PLAYPAUSE },
+ KeyMap { avc: AVC_STOP, uinput: KEY_STOPCD },
+ KeyMap { avc: AVC_PAUSE, uinput: KEY_PLAYPAUSE },
+ KeyMap { avc: AVC_REWIND, uinput: KEY_REWIND },
+ KeyMap { avc: AVC_FAST_FORWAED, uinput: KEY_FASTFORWAED },
+ KeyMap { avc: AVC_FORWARD, uinput: KEY_NEXTSONG },
+ KeyMap { avc: AVC_BACKWARD, uinput: KEY_PREVIOUSSONG },
+];
+
+#[repr(C, packed)]
+struct UInputId {
+ bustype: libc::c_ushort,
+ vendor: libc::c_ushort,
+ product: libc::c_ushort,
+ version: libc::c_ushort,
+}
+
+#[repr(C, packed)]
+struct UInputDev {
+ name: [libc::c_char; UINPUT_MAX_NAME_SIZE],
+ id: UInputId,
+ ff_effects_max: libc::c_int,
+ absmax: [libc::c_int; ABS_MAX + 1],
+ absmin: [libc::c_int; ABS_MAX + 1],
+ absfuzz: [libc::c_int; ABS_MAX + 1],
+ absflat: [libc::c_int; ABS_MAX + 1],
+}
+
+#[allow(dead_code)]
+impl UInputDev {
+ pub fn serialize(&mut self) -> &[u8] {
+ unsafe {
+ slice::from_raw_parts(
+ (self as *const UInputDev) as *const u8,
+ mem::size_of::<UInputDev>(),
+ )
+ }
+ }
+}
+
+/// A struct that holds the uinput object. It consists of a file descriptor fetched from the kernel
+/// and a device struct which contains the information required to construct an uinput device.
+pub struct UInput {
+ fd: i32,
+ device: UInputDev,
+}
+
+impl Drop for UInput {
+ fn drop(&mut self) {
+ self.close();
+ }
+}
+
+impl UInput {
+ /// Create a new UInput object.
+ pub fn new() -> Self {
+ UInput {
+ fd: -1,
+ device: UInputDev {
+ name: [0; UINPUT_MAX_NAME_SIZE],
+ id: UInputId { bustype: BUS_BLUETOOTH, vendor: 0, product: 0, version: 0 },
+ ff_effects_max: 0,
+ absmax: [0; ABS_MAX + 1],
+ absmin: [0; ABS_MAX + 1],
+ absfuzz: [0; ABS_MAX + 1],
+ absflat: [0; ABS_MAX + 1],
+ },
+ }
+ }
+
+ /// Return true if uinput is open and a valid fd is retrieved.
+ pub fn is_initialized(&self) -> bool {
+ self.fd >= 0
+ }
+
+ /// Initialize a uinput device with kernel.
+ #[allow(temporary_cstring_as_ptr)]
+ pub fn init(&mut self, mut name: String, addr: String) {
+ if self.is_initialized() {
+ return;
+ }
+
+ // Truncate the device name if over the max size allowed.
+ name.truncate(UINPUT_MAX_NAME_SIZE);
+ for (i, ch) in name.chars().enumerate() {
+ self.device.name[i] = ch as libc::c_char;
+ }
+
+ let mut fd = -1;
+
+ unsafe {
+ for path in ["/dev/uinput", "/dev/input/uinput", "/dev/misc/uinput"] {
+ fd = libc::open(CString::new(path).unwrap().as_ptr().cast(), libc::O_RDWR);
+ if fd >= 0 {
+ break;
+ }
+ }
+
+ if fd < -1 {
+ error!("Failed to open uinput: {}", std::io::Error::last_os_error());
+ return;
+ }
+
+ if libc::write(
+ fd,
+ self.device.serialize().as_ptr() as *const libc::c_void,
+ mem::size_of::<UInputDev>(),
+ ) < 0
+ {
+ error!("Can't write device information: {}", std::io::Error::last_os_error());
+ libc::close(fd);
+ return;
+ }
+
+ libc::ioctl(fd, UI_SET_EVBIT, EV_KEY);
+ libc::ioctl(fd, UI_SET_EVBIT, EV_REL);
+ libc::ioctl(fd, UI_SET_EVBIT, EV_REP);
+ libc::ioctl(fd, UI_SET_EVBIT, EV_SYN);
+ libc::ioctl(fd, UI_SET_PHYS, addr);
+
+ for key_map in KEY_MAP {
+ libc::ioctl(fd, UI_SET_KEYBIT, key_map.uinput);
+ }
+
+ if libc::ioctl(fd, UI_DEV_CREATE, 0) < 0 {
+ error!("Can't create uinput device: {}", std::io::Error::last_os_error());
+ libc::close(fd);
+ return;
+ }
+ }
+
+ self.fd = fd;
+ }
+
+ /// Close the uinput device with kernel if there is one.
+ pub fn close(&mut self) {
+ if self.is_initialized() {
+ unsafe {
+ libc::ioctl(self.fd, UI_DEV_DESTROY, 0);
+ libc::close(self.fd);
+ }
+ self.fd = -1;
+ }
+ }
+}
diff --git a/system/stack/btm/btm_sco_hci.cc b/system/stack/btm/btm_sco_hci.cc
index 7c0e16e7c7..8cebe18d66 100644
--- a/system/stack/btm/btm_sco_hci.cc
+++ b/system/stack/btm/btm_sco_hci.cc
@@ -134,7 +134,7 @@ static const uint8_t btm_msbc_zero_packet[] = {
static const uint8_t btm_msbc_zero_frames[BTM_MSBC_CODE_SIZE] = {0};
/* Define the structure that contains mSBC data */
-typedef struct {
+struct tBTM_MSBC_INFO {
size_t packet_size; /* SCO mSBC packet size supported by lower layer */
bool check_alignment; /* True to wait for mSBC packet to align */
size_t buf_size; /* The size of the buffer, determined by the packet_size. */
@@ -286,8 +286,7 @@ typedef struct {
return &msbc_encode_buf[encode_buf_ro];
}
-
-} tBTM_MSBC_INFO;
+};
static tBTM_MSBC_INFO* msbc_info = nullptr;
diff --git a/system/test/mock/mock_common_metrics.cc b/system/test/mock/mock_common_metrics.cc
index 766fec2747..cf71750734 100644
--- a/system/test/mock/mock_common_metrics.cc
+++ b/system/test/mock/mock_common_metrics.cc
@@ -219,5 +219,9 @@ void LogLeAudioConnectionSessionReported(
mock_function_count_map[__func__]++;
}
+void LogLeAudioBroadcastSessionReported(int64_t duration_nanos) {
+ mock_function_count_map[__func__]++;
+}
+
} // namespace common
} // namespace bluetooth
diff --git a/tools/pdl/Android.bp b/tools/pdl/Android.bp
index 4991c01946..80473ac6a2 100644
--- a/tools/pdl/Android.bp
+++ b/tools/pdl/Android.bp
@@ -82,18 +82,35 @@ rust_test_host {
],
}
+// Python generator.
+python_binary_host {
+ name: "pdl_python_generator",
+ main: "scripts/generate_python_backend.py",
+ srcs: [
+ "scripts/generate_python_backend.py",
+ "scripts/pdl/ast.py",
+ "scripts/pdl/core.py",
+ ]
+}
+
+// Defaults for PDL python backend generation.
+genrule_defaults {
+ name: "pdl_python_generator_defaults",
+ tools: [
+ ":pdl",
+ ":pdl_python_generator",
+ ],
+}
+
// Generate the python parser+serializer backend for the
// little endian test file located at tests/canonical/le_test_file.pdl.
genrule {
name: "pdl_python_generator_le_test_gen",
+ defaults: [ "pdl_python_generator_defaults" ],
cmd: "$(location :pdl) $(in) |" +
- " $(location scripts/generate_python_backend.py)" +
+ " $(location :pdl_python_generator)" +
" --output $(out) --custom-type-location tests.custom_types",
- tools: [ ":pdl" ],
tool_files: [
- "scripts/generate_python_backend.py",
- "scripts/pdl/core.py",
- "scripts/pdl/ast.py",
"tests/custom_types.py",
],
srcs: [
@@ -108,14 +125,11 @@ genrule {
// big endian test file located at tests/canonical/be_test_file.pdl.
genrule {
name: "pdl_python_generator_be_test_gen",
+ defaults: [ "pdl_python_generator_defaults" ],
cmd: "$(location :pdl) $(in) |" +
- " $(location scripts/generate_python_backend.py)" +
+ " $(location :pdl_python_generator)" +
" --output $(out) --custom-type-location tests.custom_types",
- tools: [ ":pdl" ],
tool_files: [
- "scripts/generate_python_backend.py",
- "scripts/pdl/core.py",
- "scripts/pdl/ast.py",
"tests/custom_types.py",
],
srcs: [
@@ -141,6 +155,9 @@ python_test_host {
"tests/canonical/le_test_vectors.json",
"tests/canonical/be_test_vectors.json",
],
+ libs: [
+ "typing_extensions",
+ ],
test_options: {
unit_test: true,
},
diff --git a/tools/pdl/scripts/generate_python_backend.py b/tools/pdl/scripts/generate_python_backend.py
index a9c6935f9e..34497a844a 100755
--- a/tools/pdl/scripts/generate_python_backend.py
+++ b/tools/pdl/scripts/generate_python_backend.py
@@ -39,7 +39,7 @@ def generate_prelude() -> str:
@dataclass
class Packet:
- payload: Optional[bytes] = field(repr=False)
+ payload: Optional[bytes] = field(repr=False, default_factory=bytes)
@classmethod
def parse_all(cls, span: bytes) -> 'Packet':
@@ -48,6 +48,10 @@ def generate_prelude() -> str:
raise Exception('Unexpected parsing remainder')
return packet
+ @property
+ def size(self) -> int:
+ pass
+
def show(self, prefix: str = ''):
print(f'{self.__class__.__name__}')
@@ -141,11 +145,11 @@ class FieldParser:
self.check_size_(str(self.offset))
self.code.extend(unchecked_code)
- def consume_span_(self) -> str:
+ def consume_span_(self, keep: int = 0) -> str:
"""Skip consumed span bytes."""
if self.offset > 0:
self.check_code_()
- self.append_(f'span = span[{self.offset}:]')
+ self.append_(f'span = span[{self.offset - keep}:]')
self.offset = 0
def parse_array_element_dynamic_(self, field: ast.ArrayField, span: str):
@@ -196,9 +200,7 @@ class FieldParser:
# Apply the size modifier.
if field.size_modifier and size:
- self.append_(f"{size} = {size} {field.size_modifier}")
- if field.size_modifier and count:
- self.append_(f"{count} = {count} {field.size_modifier}")
+ self.append_(f"{size} = {size} - {field.size_modifier}")
# The element width is not known, but the array full octet size
# is known by size field. Parse elements item by item as a vector.
@@ -288,7 +290,7 @@ class FieldParser:
value = "value_"
for shift, width, field in self.chunk:
- v = (value if len(self.chunk) == 1 else f"({value} >> {shift}) & {mask(width)}")
+ v = (value if len(self.chunk) == 1 and shift == 0 else f"({value} >> {shift}) & {mask(width)}")
if isinstance(field, ast.ScalarField):
self.unchecked_append_(f"fields['{field.id}'] = {v}")
@@ -349,14 +351,29 @@ class FieldParser:
def parse_payload_field_(self, field: Union[ast.BodyField, ast.PayloadField]):
"""Parse body and payload fields."""
- size = core.get_payload_field_size(field)
+ payload_size = core.get_payload_field_size(field)
offset_from_end = core.get_field_offset_from_end(field)
- self.consume_span_()
+
+ # If the payload is not byte aligned, do parse the bit fields
+ # that can be extracted, but do not consume the input bytes as
+ # they will also be included in the payload span.
+ if self.shift != 0:
+ if payload_size:
+ raise Exception("Unexpected payload size for non byte aligned payload")
+
+ rounded_size = int((self.shift + 7) / 8)
+ padding_bits = 8 * rounded_size - self.shift
+ self.parse_bit_field_(core.make_reserved_field(padding_bits))
+ self.consume_span_(rounded_size)
+ else:
+ self.consume_span_()
# The payload or body has a known size.
# Consume the payload and update the span in case
# fields are placed after the payload.
- if size:
+ if payload_size:
+ if getattr(field, 'size_modifier', None):
+ self.append_(f"{field.id}_size -= {field.size_modifier}")
self.check_size_(f'{field.id}_size')
self.append_(f"payload = span[:{field.id}_size]")
self.append_(f"span = span[{field.id}_size:]")
@@ -429,7 +446,7 @@ class FieldParser:
checksum_span = f'span[:-{offset_from_end}]'
if value_size > 1:
start = offset_from_end
- end = offset_from_start - value_size
+ end = offset_from_end - value_size
value = f"int.from_bytes(span[-{start}:-{end}], byteorder='{self.byteorder}')"
else:
value = f'span[-{offset_from_end}]'
@@ -479,23 +496,262 @@ class FieldParser:
self.consume_span_()
+@dataclass
+class FieldSerializer:
+ byteorder: str
+ shift: int = 0
+ value: List[str] = field(default_factory=lambda: [])
+ code: List[str] = field(default_factory=lambda: [])
+ indent: int = 0
+
+ def indent_(self):
+ self.indent += 1
+
+ def unindent_(self):
+ self.indent -= 1
+
+ def append_(self, line: str):
+ """Append field serializing code."""
+ lines = line.split('\n')
+ self.code.extend([' ' * self.indent + line for line in lines])
+
+ def extend_(self, value: str, length: int):
+ """Append data to the span being constructed."""
+ if length == 1:
+ self.append_(f"_span.append({value})")
+ else:
+ self.append_(f"_span.extend(int.to_bytes({value}, length={length}, byteorder='{self.byteorder}'))")
+
+ def serialize_array_element_(self, field: ast.ArrayField):
+ """Serialize a single array field element."""
+ if field.width is not None:
+ length = int(field.width / 8)
+ self.extend_('_elt', length)
+ elif isinstance(field.type, ast.EnumDeclaration):
+ length = int(field.type.width / 8)
+ self.extend_('_elt', length)
+ else:
+ self.append_("_span.extend(_elt.serialize())")
+
+ def serialize_array_field_(self, field: ast.ArrayField):
+ """Serialize the selected array field."""
+ if field.width == 8:
+ self.append_(f"_span.extend(self.{field.id})")
+ else:
+ self.append_(f"for _elt in self.{field.id}:")
+ self.indent_()
+ self.serialize_array_element_(field)
+ self.unindent_()
+
+ def serialize_bit_field_(self, field: ast.Field):
+ """Serialize the selected field as a bit field.
+ The field is added to the current chunk. When a byte boundary
+ is reached all saved fields are serialized together."""
+
+ # Add to current chunk.
+ width = core.get_field_size(field)
+ shift = self.shift
+
+ if isinstance(field, str):
+ self.value.append(f"({field} << {shift})")
+ elif isinstance(field, ast.ScalarField):
+ max_value = (1 << field.width) - 1
+ self.append_(f"if self.{field.id} > {max_value}:")
+ self.append_(f" print(f\"Invalid value for field {field.parent.id}::{field.id}:" +
+ f" {{self.{field.id}}} > {max_value}; the value will be truncated\")")
+ self.append_(f" self.{field.id} &= {max_value}")
+ self.value.append(f"(self.{field.id} << {shift})")
+ elif isinstance(field, ast.FixedField) and field.enum_id:
+ self.value.append(f"({field.enum_id}.{field.tag_id} << {shift})")
+ elif isinstance(field, ast.FixedField):
+ self.value.append(f"({field.value} << {shift})")
+ elif isinstance(field, ast.TypedefField):
+ self.value.append(f"(self.{field.id} << {shift})")
+
+ elif isinstance(field, ast.SizeField):
+ max_size = (1 << field.width) - 1
+ value_field = core.get_packet_field(field.parent, field.field_id)
+ size_modifier = ''
+
+ if getattr(value_field, 'size_modifier', None):
+ size_modifier = f' + {value_field.size_modifier}'
+
+ if isinstance(value_field, (ast.PayloadField, ast.BodyField)):
+ self.append_(f"_size = len(payload or self.payload or []){size_modifier}")
+ self.append_(f"if _size > {max_size}:")
+ self.append_(f" print(f\"Invalid length for payload field:" +
+ f" {{_size}} > {max_size}; the packet cannot be generated\")")
+ self.append_(f" raise Exception(\"Invalid payload length\")")
+ array_size = "_size"
+ elif isinstance(value_field, ast.ArrayField) and value_field.width:
+ array_size = f"(len(self.{value_field.id}) * {int(value_field.width / 8)})"
+ elif isinstance(value_field, ast.ArrayField):
+ self.append_(f"_size = sum([elt.size for elt in self.{value_field.id}]){size_modifier}")
+ array_size = "_size"
+ else:
+ raise Exception("Unsupported field type")
+ self.value.append(f"({array_size} << {shift})")
+
+ elif isinstance(field, ast.CountField):
+ max_count = (1 << field.width) - 1
+ self.append_(f"if len(self.{field.field_id}) > {max_count}:")
+ self.append_(f" print(f\"Invalid length for field {field.parent.id}::{field.field_id}:" +
+ f" {{len(self.{field.field_id})}} > {max_count}; the array will be truncated\")")
+ self.append_(f" del self.{field.field_id}[{max_count}:]")
+ self.value.append(f"(len(self.{field.field_id}) << {shift})")
+ elif isinstance(field, ast.ReservedField):
+ pass
+ else:
+ raise Exception(f'Unsupported bit field type {field.kind}')
+
+ # Check if a byte boundary is reached.
+ self.shift += width
+ if (self.shift % 8) == 0:
+ self.pack_bit_fields_()
+
+ def pack_bit_fields_(self):
+ """Pack serialized bit fields."""
+
+ # Should have an integral number of bytes now.
+ assert (self.shift % 8) == 0
+
+ # Generate the backing integer, and serialize it
+ # using the configured endiannes,
+ size = int(self.shift / 8)
+
+ if len(self.value) == 0:
+ self.append_(f"_span.extend([0] * {size})")
+ elif len(self.value) == 1:
+ self.extend_(self.value[0], size)
+ else:
+ self.append_(f"_value = (")
+ self.append_(" " + " |\n ".join(self.value))
+ self.append_(")")
+ self.extend_('_value', size)
+
+ # Reset state.
+ self.shift = 0
+ self.value = []
+
+ def serialize_typedef_field_(self, field: ast.TypedefField):
+ """Serialize a typedef field, to the exclusion of Enum fields."""
+
+ if self.shift != 0:
+ raise Exception('Typedef field does not start on an octet boundary')
+ if (isinstance(field.type, ast.StructDeclaration) and field.type.parent_id is not None):
+ raise Exception('Derived struct used in typedef field')
+
+ if isinstance(field.type, ast.ChecksumDeclaration):
+ size = int(field.type.width / 8)
+ self.append_(f"_checksum = {field.type.function}(_span[_checksum_start:])")
+ self.extend_('_checksum', size)
+ else:
+ self.append_(f"_span.extend(self.{field.id}.serialize())")
+
+ def serialize_padding_field_(self, field: ast.PaddingField):
+ """Serialize a padding field. The value is zero."""
+
+ if self.shift != 0:
+ raise Exception('Padding field does not start on an octet boundary')
+ self.append_(f"_span.extend([0] * {field.width})")
+
+ def serialize_payload_field_(self, field: Union[ast.BodyField, ast.PayloadField]):
+ """Serialize body and payload fields."""
+
+ if self.shift != 0 and self.byteorder == 'big':
+ raise Exception('Payload field does not start on an octet boundary')
+
+ if self.shift == 0:
+ self.append_(f"_span.extend(payload or self.payload or [])")
+ else:
+ # Supported case of packet inheritance;
+ # the incomplete fields are serialized into
+ # the payload, rather than separately.
+ # First extract the padding bits from the payload,
+ # then recombine them with the bit fields to be serialized.
+ rounded_size = int((self.shift + 7) / 8)
+ padding_bits = 8 * rounded_size - self.shift
+ self.append_(f"_payload = payload or self.payload or bytes()")
+ self.append_(f"if len(_payload) < {rounded_size}:")
+ self.append_(f" raise Exception(f\"Invalid length for payload field:" +
+ f" {{len(_payload)}} < {rounded_size}\")")
+ self.append_(
+ f"_padding = int.from_bytes(_payload[:{rounded_size}], byteorder='{self.byteorder}') >> {self.shift}")
+ self.value.append(f"(_padding << {self.shift})")
+ self.shift += padding_bits
+ self.pack_bit_fields_()
+ self.append_(f"_span.extend(_payload[{rounded_size}:])")
+
+ def serialize_checksum_field_(self, field: ast.ChecksumField):
+ """Generate a checksum check."""
+
+ self.append_("_checksum_start = len(_span)")
+
+ def serialize(self, field: ast.Field):
+ # Field has bit granularity.
+ # Append the field to the current chunk,
+ # check if a byte boundary was reached.
+ if core.is_bit_field(field):
+ self.serialize_bit_field_(field)
+
+ # Padding fields.
+ elif isinstance(field, ast.PaddingField):
+ self.serialize_padding_field_(field)
+
+ # Array fields.
+ elif isinstance(field, ast.ArrayField):
+ self.serialize_array_field_(field)
+
+ # Other typedef fields.
+ elif isinstance(field, ast.TypedefField):
+ self.serialize_typedef_field_(field)
+
+ # Payload and body fields.
+ elif isinstance(field, (ast.PayloadField, ast.BodyField)):
+ self.serialize_payload_field_(field)
+
+ # Checksum fields.
+ elif isinstance(field, ast.ChecksumField):
+ self.serialize_checksum_field_(field)
+
+ else:
+ raise Exception(f'Unimplemented field type {field.kind}')
+
+
def generate_toplevel_packet_serializer(packet: ast.Declaration) -> List[str]:
"""Generate the serialize() function for a toplevel Packet or Struct
declaration."""
- return ["pass"]
+
+ serializer = FieldSerializer(byteorder=packet.file.byteorder)
+ for f in packet.fields:
+ serializer.serialize(f)
+ return ['_span = bytearray()'] + serializer.code + ['return bytes(_span)']
def generate_derived_packet_serializer(packet: ast.Declaration) -> List[str]:
"""Generate the serialize() function for a derived Packet or Struct
declaration."""
- return ["pass"]
+
+ packet_shift = core.get_packet_shift(packet)
+ if packet_shift and packet.file.byteorder == 'big':
+ raise Exception(f"Big-endian packet {packet.id} has an unsupported body shift")
+
+ serializer = FieldSerializer(byteorder=packet.file.byteorder, shift=packet_shift)
+ for f in packet.fields:
+ serializer.serialize(f)
+ return ['_span = bytearray()'
+ ] + serializer.code + [f'return {packet.parent.id}.serialize(self, payload = bytes(_span))']
def generate_packet_parser(packet: ast.Declaration) -> List[str]:
"""Generate the parse() function for a toplevel Packet or Struct
declaration."""
- parser = FieldParser(byteorder=packet.file.byteorder)
+ packet_shift = core.get_packet_shift(packet)
+ if packet_shift and packet.file.byteorder == 'big':
+ raise Exception(f"Big-endian packet {packet.id} has an unsupported body shift")
+
+ parser = FieldParser(byteorder=packet.file.byteorder, shift=packet_shift)
for f in packet.fields:
parser.parse(f)
parser.done()
@@ -527,15 +783,39 @@ def generate_packet_parser(packet: ast.Declaration) -> List[str]:
return decl + parser.code + [f"return {packet.id}(**fields), span"]
-def generate_derived_packet_parser(packet: ast.Declaration) -> List[str]:
- """Generate the parse() function for a derived Packet or Struct
- declaration."""
- print(f"Parsing packet {packet.id}", file=sys.stderr)
- parser = FieldParser(byteorder=packet.file.byteorder)
+def generate_packet_size_getter(packet: ast.Declaration) -> List[str]:
+ constant_width = 0
+ variable_width = []
for f in packet.fields:
- parser.parse(f)
- parser.done()
- return parser.code + [f"return {packet.id}(**fields)"]
+ field_size = core.get_field_size(f)
+ if field_size is not None:
+ constant_width += field_size
+ elif isinstance(f, (ast.PayloadField, ast.BodyField)):
+ variable_width.append("len(self.payload)")
+ elif isinstance(f, ast.TypedefField):
+ variable_width.append(f"self.{f.id}.size")
+ elif isinstance(f, ast.ArrayField) and isinstance(f.type, (ast.StructDeclaration, ast.CustomFieldDeclaration)):
+ variable_width.append(f"sum([elt.size for elt in self.{f.id}])")
+ elif isinstance(f, ast.ArrayField) and isinstance(f.type, ast.EnumDeclaration):
+ variable_width.append(f"len(self.{f.id}) * {f.type.width}")
+ elif isinstance(f, ast.ArrayField):
+ variable_width.append(f"len(self.{f.id}) * {int(f.width / 8)}")
+ else:
+ raise Exception("Unsupported field type")
+
+ constant_width = int(constant_width / 8)
+ if len(variable_width) == 0:
+ return [f"return {constant_width}"]
+ elif len(variable_width) == 1 and constant_width:
+ return [f"return {variable_width[0]} + {constant_width}"]
+ elif len(variable_width) == 1:
+ return [f"return {variable_width[0]}"]
+ elif len(variable_width) > 1 and constant_width:
+ return ([f"return {constant_width} + ("] + " +\n ".join(variable_width).split("\n") + [")"])
+ elif len(variable_width) > 1:
+ return (["return ("] + " +\n ".join(variable_width).split("\n") + [")"])
+ else:
+ assert False
def generate_enum_declaration(decl: ast.EnumDeclaration) -> str:
@@ -562,15 +842,22 @@ def generate_packet_declaration(packet: ast.Declaration) -> str:
field_decls = []
for f in packet.fields:
if isinstance(f, ast.ScalarField):
- field_decls.append(f"{f.id}: int")
+ field_decls.append(f"{f.id}: int = 0")
elif isinstance(f, ast.TypedefField):
- field_decls.append(f"{f.id}: {f.type_id}")
+ if isinstance(f.type, ast.EnumDeclaration):
+ field_decls.append(f"{f.id}: {f.type_id} = {f.type_id}.{f.type.tags[0].id}")
+ elif isinstance(f.type, ast.ChecksumDeclaration):
+ field_decls.append(f"{f.id}: int = 0")
+ elif isinstance(f.type, (ast.StructDeclaration, ast.CustomFieldDeclaration)):
+ field_decls.append(f"{f.id}: {f.type_id} = field(default_factory={f.type_id})")
+ else:
+ raise Exception("Unsupported typedef field type")
elif isinstance(f, ast.ArrayField) and f.width == 8:
- field_decls.append(f"{f.id}: bytes")
+ field_decls.append(f"{f.id}: bytearray = field(default_factory=bytearray)")
elif isinstance(f, ast.ArrayField) and f.width:
- field_decls.append(f"{f.id}: List[int]")
+ field_decls.append(f"{f.id}: List[int] = field(default_factory=list)")
elif isinstance(f, ast.ArrayField) and f.type_id:
- field_decls.append(f"{f.id}: List[{f.type_id}]")
+ field_decls.append(f"{f.id}: List[{f.type_id}] = field(default_factory=list)")
if packet.parent_id:
parent_name = packet.parent_id
@@ -582,6 +869,7 @@ def generate_packet_declaration(packet: ast.Declaration) -> str:
serializer = generate_toplevel_packet_serializer(packet)
parser = generate_packet_parser(packet)
+ size = generate_packet_size_getter(packet)
return dedent("""\
@@ -593,15 +881,20 @@ def generate_packet_declaration(packet: ast.Declaration) -> str:
def parse({parent_fields}span: bytes) -> Tuple['{packet_name}', bytes]:
{parser}
- def serialize(self) -> bytes:
+ def serialize(self, payload: bytes = None) -> bytes:
{serializer}
+
+ @property
+ def size(self) -> int:
+ {size}
""").format(
packet_name=packet_name,
parent_name=parent_name,
parent_fields=parent_fields,
field_decls=indent(field_decls, 1),
parser=indent(parser, 2),
- serializer=indent(serializer, 2))
+ serializer=indent(serializer, 2),
+ size=indent(size, 2))
def generate_custom_field_declaration_check(decl: ast.CustomFieldDeclaration) -> str:
diff --git a/tools/pdl/scripts/pdl/core.py b/tools/pdl/scripts/pdl/core.py
index f7db265e8f..f6ad1f730c 100644
--- a/tools/pdl/scripts/pdl/core.py
+++ b/tools/pdl/scripts/pdl/core.py
@@ -54,6 +54,11 @@ def desugar(file: File):
file.group_scope = {}
+def make_reserved_field(width: int) -> ReservedField:
+ """Create a reserved field of specified width."""
+ return ReservedField(kind='reserved_field', loc=None, width=width)
+
+
def get_packet_field(packet: Union[PacketDeclaration, StructDeclaration], id: str) -> Optional[Field]:
"""Return the field with selected identifier declared in the provided
packet or its ancestors."""
@@ -70,6 +75,53 @@ def get_packet_field(packet: Union[PacketDeclaration, StructDeclaration], id: st
return None
+def get_packet_shift(packet: Union[PacketDeclaration, StructDeclaration]) -> int:
+ """Return the bit shift of the payload or body field in the parent packet.
+
+ When using packet derivation on bit fields, the body may be shifted.
+ The shift is handled statically in the implementation of child packets,
+ and the incomplete field is included in the body.
+ ```
+ packet Basic {
+ type: 1,
+ _body_
+ }
+ ```
+ """
+
+ # Traverse empty parents.
+ parent = packet.parent
+ while parent and len(parent.fields) == 1:
+ parent = parent.parent
+
+ if not parent:
+ return 0
+
+ shift = 0
+ for f in packet.parent.fields:
+ if isinstance(f, (BodyField, PayloadField)):
+ return 0 if (shift % 8) == 0 else shift
+ else:
+ # Fields that do not have a constant size are assumed to start
+ # on a byte boundary, and measure an integral number of bytes.
+ # Start the count over.
+ size = get_field_size(f)
+ shift = 0 if size is None else shift + size
+
+ # No payload or body in parent packet.
+ # Not raising an error, the generation will fail somewhere else.
+ return 0
+
+
+def get_packet_ancestor(
+ decl: Union[PacketDeclaration, StructDeclaration]) -> Union[PacketDeclaration, StructDeclaration]:
+ """Return the root ancestor of the selected packet or struct."""
+ if decl.parent_id is None:
+ return decl
+ else:
+ return get_packet_ancestor(decl.grammar.packet_scope[decl.parent_id])
+
+
def get_derived_packets(decl: Union[PacketDeclaration, StructDeclaration]
) -> List[Tuple[List[Constraint], Union[PacketDeclaration, StructDeclaration]]]:
"""Return the list of packets or structs that immediately derive from the
diff --git a/tools/pdl/tests/canonical/be_test_vectors.json b/tools/pdl/tests/canonical/be_test_vectors.json
index 24832a9a64..de0b2b6782 100644
--- a/tools/pdl/tests/canonical/be_test_vectors.json
+++ b/tools/pdl/tests/canonical/be_test_vectors.json
@@ -144,6 +144,29 @@
]
},
{
+ "packet": "Packet_Payload_Field_SizeModifier",
+ "tests": [
+ {
+ "packed": "02",
+ "unpacked": {
+ "payload": []
+ }
+ },
+ {
+ "packed": "070001020304",
+ "unpacked": {
+ "payload": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4
+ ]
+ }
+ }
+ ]
+ },
+ {
"packet": "Packet_Payload_Field_UnknownSize",
"tests": [
{
@@ -593,6 +616,7 @@
"packet": "ScalarParent",
"tests": [
{
+ "packet": "ScalarChild_A",
"packed": "0001da",
"unpacked": {
"a": 0,
@@ -600,6 +624,7 @@
}
},
{
+ "packet": "ScalarChild_B",
"packed": "0102dedc",
"unpacked": {
"a": 1,
@@ -607,6 +632,7 @@
}
},
{
+ "packet": "AliasedChild_A",
"packed": "0201d8",
"unpacked": {
"a": 2,
@@ -614,6 +640,7 @@
}
},
{
+ "packet": "AliasedChild_B",
"packed": "0302e70a",
"unpacked": {
"a": 3,
@@ -626,6 +653,7 @@
"packet": "EnumParent",
"tests": [
{
+ "packet": "EnumChild_A",
"packed": "aabb01dd",
"unpacked": {
"a": 43707,
@@ -633,6 +661,7 @@
}
},
{
+ "packet": "EnumChild_B",
"packed": "ccdd02def7",
"unpacked": {
"a": 52445,
diff --git a/tools/pdl/tests/canonical/le_test_file.pdl b/tools/pdl/tests/canonical/le_test_file.pdl
index c697b1b6dc..0c6b181c6b 100644
--- a/tools/pdl/tests/canonical/le_test_file.pdl
+++ b/tools/pdl/tests/canonical/le_test_file.pdl
@@ -50,6 +50,16 @@ packet EmptyParent : ScalarParent {
_payload_
}
+packet PartialParent5 {
+ a: 5,
+ _payload_
+}
+
+packet PartialParent12 {
+ a: 12,
+ _payload_
+}
+
// Packet bit fields
// The parser must be able to handle bit fields with scalar values
@@ -344,6 +354,34 @@ packet AliasedChild_B : EmptyParent (a = 3) {
c: 16,
}
+// The parser must handle inheritance of packets with payloads starting
+// on a shifted byte boundary, as long as the first fields of the child
+// complete the bit fields.
+packet PartialChild5_A : PartialParent5 (a = 0) {
+ b: 11,
+}
+
+// The parser must handle inheritance of packets with payloads starting
+// on a shifted byte boundary, as long as the first fields of the child
+// complete the bit fields.
+packet PartialChild5_B : PartialParent5 (a = 1) {
+ c: 27,
+}
+
+// The parser must handle inheritance of packets with payloads starting
+// on a shifted byte boundary, as long as the first fields of the child
+// complete the bit fields.
+packet PartialChild12_A : PartialParent12 (a = 2) {
+ d: 4,
+}
+
+// The parser must handle inheritance of packets with payloads starting
+// on a shifted byte boundary, as long as the first fields of the child
+// complete the bit fields.
+packet PartialChild12_B : PartialParent12 (a = 3) {
+ e: 20,
+}
+
// Struct bit fields
// The parser must be able to handle bit fields with scalar values
diff --git a/tools/pdl/tests/canonical/le_test_vectors.json b/tools/pdl/tests/canonical/le_test_vectors.json
index 83aadf85b6..8eff0837ac 100644
--- a/tools/pdl/tests/canonical/le_test_vectors.json
+++ b/tools/pdl/tests/canonical/le_test_vectors.json
@@ -144,6 +144,29 @@
]
},
{
+ "packet": "Packet_Payload_Field_SizeModifier",
+ "tests": [
+ {
+ "packed": "02",
+ "unpacked": {
+ "payload": []
+ }
+ },
+ {
+ "packed": "070001020304",
+ "unpacked": {
+ "payload": [
+ 0,
+ 1,
+ 2,
+ 3,
+ 4
+ ]
+ }
+ }
+ ]
+ },
+ {
"packet": "Packet_Payload_Field_UnknownSize",
"tests": [
{
@@ -593,6 +616,7 @@
"packet": "ScalarParent",
"tests": [
{
+ "packet": "ScalarChild_A",
"packed": "0001da",
"unpacked": {
"a": 0,
@@ -600,6 +624,7 @@
}
},
{
+ "packet": "ScalarChild_B",
"packed": "0102dcde",
"unpacked": {
"a": 1,
@@ -607,6 +632,7 @@
}
},
{
+ "packet": "AliasedChild_A",
"packed": "0201d8",
"unpacked": {
"a": 2,
@@ -614,6 +640,7 @@
}
},
{
+ "packet": "AliasedChild_B",
"packed": "03020ae7",
"unpacked": {
"a": 3,
@@ -623,9 +650,52 @@
]
},
{
+ "packet": "PartialParent5",
+ "tests": [
+ {
+ "packet": "PartialChild5_A",
+ "packed": "409d",
+ "unpacked": {
+ "a": 0,
+ "b": 1258
+ }
+ },
+ {
+ "packet": "PartialChild5_B",
+ "packed": "a1434b84",
+ "unpacked": {
+ "a": 1,
+ "c": 69360157
+ }
+ }
+ ]
+ },
+ {
+ "packet": "PartialParent12",
+ "tests": [
+ {
+ "packet": "PartialChild12_A",
+ "packed": "02c0",
+ "unpacked": {
+ "a": 2,
+ "d": 12
+ }
+ },
+ {
+ "packet": "PartialChild12_B",
+ "packed": "030035e9",
+ "unpacked": {
+ "a": 3,
+ "e": 955216
+ }
+ }
+ ]
+ },
+ {
"packet": "EnumParent",
"tests": [
{
+ "packet": "EnumChild_A",
"packed": "bbaa01dd",
"unpacked": {
"a": 43707,
@@ -633,6 +703,7 @@
}
},
{
+ "packet": "EnumChild_B",
"packed": "ddcc02f7de",
"unpacked": {
"a": 52445,
diff --git a/tools/pdl/tests/custom_types.py b/tools/pdl/tests/custom_types.py
index 115314f757..72484c62d2 100644
--- a/tools/pdl/tests/custom_types.py
+++ b/tools/pdl/tests/custom_types.py
@@ -5,7 +5,7 @@ from typing import Tuple
@dataclass
class SizedCustomField:
- def __init__(self, value: int):
+ def __init__(self, value: int = 0):
self.value = value
def parse(span: bytes) -> Tuple['SizedCustomField', bytes]:
@@ -23,7 +23,7 @@ class SizedCustomField:
@dataclass
class UnsizedCustomField:
- def __init__(self, value: int):
+ def __init__(self, value: int = 0):
self.value = value
def parse(span: bytes) -> Tuple['UnsizedCustomField', bytes]:
diff --git a/tools/pdl/tests/python_generator_test.py b/tools/pdl/tests/python_generator_test.py
index 6014e4baa0..720d887311 100644
--- a/tools/pdl/tests/python_generator_test.py
+++ b/tools/pdl/tests/python_generator_test.py
@@ -5,7 +5,11 @@
# Tests the generated python backend against standard PDL
# constructs, with matching input vectors.
+import dataclasses
+import enum
import json
+import typing
+import typing_extensions
import unittest
# (le|be)_pdl_test are the names of the modules generated from the canonical
@@ -18,7 +22,7 @@ import be_pdl_test
def match_object(self, left, right):
"""Recursively match a python class object against a reference
- json object."""
+ json object."""
if isinstance(right, int):
self.assertEqual(left, right)
elif isinstance(right, list):
@@ -31,6 +35,34 @@ def match_object(self, left, right):
match_object(self, getattr(left, k), v)
+def create_object(typ, value):
+ """Build an object of the selected type using the input value."""
+ if dataclasses.is_dataclass(typ):
+ field_types = dict([(f.name, f.type) for f in dataclasses.fields(typ)])
+ values = dict()
+ for (f, v) in value.items():
+ field_type = field_types[f]
+ values[f] = create_object(field_type, v)
+ return typ(**values)
+ elif typing_extensions.get_origin(typ) is list:
+ typ = typing_extensions.get_args(typ)[0]
+ return [create_object(typ, v) for v in value]
+ elif typing_extensions.get_origin(typ) is typing.Union:
+ # typing.Optional[int] expands to typing.Union[int, None]
+ typ = typing_extensions.get_args(typ)[0]
+ return create_object(typ, value) if value else None
+ elif typ is bytes:
+ return bytes(value)
+ elif typ is bytearray:
+ return bytearray(value)
+ elif issubclass(typ, enum.Enum):
+ return typ(value)
+ elif typ is int:
+ return value
+ else:
+ raise Exception(f"unsupported type annotation {typ}")
+
+
class PacketParserTest(unittest.TestCase):
"""Validate the generated parser against pre-generated test
vectors in canonical/(le|be)_test_vectors.json"""
@@ -66,7 +98,7 @@ class PacketParserTest(unittest.TestCase):
tests = item['tests']
with self.subTest(packet=packet):
# Retrieve the class object from the generated
- # module, in order to invoke the proper parse
+ # module, in order to invoke the proper constructor
# method for this test.
cls = getattr(be_pdl_test, packet)
for test in tests:
@@ -74,6 +106,51 @@ class PacketParserTest(unittest.TestCase):
match_object(self, result, test['unpacked'])
+class PacketSerializerTest(unittest.TestCase):
+ """Validate the generated serializer against pre-generated test
+ vectors in canonical/(le|be)_test_vectors.json"""
+
+ def testLittleEndian(self):
+ with open('tests/canonical/le_test_vectors.json') as f:
+ reference = json.load(f)
+
+ for item in reference:
+ # 'packet' is the name of the packet being tested,
+ # 'tests' lists input vectors that must match the
+ # selected packet.
+ packet = item['packet']
+ tests = item['tests']
+ with self.subTest(packet=packet):
+ # Retrieve the class object from the generated
+ # module, in order to invoke the proper constructor
+ # method for this test.
+ for test in tests:
+ cls = getattr(le_pdl_test, test.get('packet', packet))
+ obj = create_object(cls, test['unpacked'])
+ result = obj.serialize()
+ self.assertEqual(result, bytes.fromhex(test['packed']))
+
+ def testBigEndian(self):
+ with open('tests/canonical/be_test_vectors.json') as f:
+ reference = json.load(f)
+
+ for item in reference:
+ # 'packet' is the name of the packet being tested,
+ # 'tests' lists input vectors that must match the
+ # selected packet.
+ packet = item['packet']
+ tests = item['tests']
+ with self.subTest(packet=packet):
+ # Retrieve the class object from the generated
+ # module, in order to invoke the proper parse
+ # method for this test.
+ for test in tests:
+ cls = getattr(be_pdl_test, test.get('packet', packet))
+ obj = create_object(cls, test['unpacked'])
+ result = obj.serialize()
+ self.assertEqual(result, bytes.fromhex(test['packed']))
+
+
class CustomPacketParserTest(unittest.TestCase):
"""Manual testing for custom fields."""
@@ -104,4 +181,4 @@ class CustomPacketParserTest(unittest.TestCase):
if __name__ == '__main__':
- unittest.main(verbosity=2)
+ unittest.main(verbosity=3)