summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--android/app/Android.bp6
-rw-r--r--android/app/src/com/android/bluetooth/BluetoothMethodProxy.java (renamed from android/app/src/com/android/bluetooth/pbap/BluetoothPbapMethodProxy.java)17
-rw-r--r--[-rwxr-xr-x]android/app/src/com/android/bluetooth/bass_client/BaseData.java0
-rw-r--r--[-rwxr-xr-x]android/app/src/com/android/bluetooth/bass_client/BassClientService.java13
-rw-r--r--[-rwxr-xr-x]android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java5
-rw-r--r--[-rwxr-xr-x]android/app/src/com/android/bluetooth/bass_client/BassConstants.java7
-rw-r--r--[-rwxr-xr-x]android/app/src/com/android/bluetooth/bass_client/BassUtils.java82
-rw-r--r--[-rwxr-xr-x]android/app/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResult.java0
-rw-r--r--android/app/src/com/android/bluetooth/btservice/AdapterService.java16
-rw-r--r--android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java18
-rw-r--r--android/app/src/com/android/bluetooth/gatt/GattService.java23
-rw-r--r--android/app/src/com/android/bluetooth/hfp/HeadsetService.java5
-rw-r--r--android/app/src/com/android/bluetooth/le_audio/LeAudioService.java207
-rw-r--r--android/app/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java3
-rw-r--r--android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java4
-rw-r--r--android/app/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java3
-rw-r--r--android/app/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java5
-rw-r--r--android/app/src/com/android/bluetooth/pbap/BluetoothPbapSimVcardManager.java40
-rw-r--r--android/app/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java21
-rw-r--r--android/app/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java63
-rw-r--r--android/app/src/com/android/bluetooth/vc/VolumeControlService.java53
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java39
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoContactElementTest.java203
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoListingElementTest.java192
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposerTest.java8
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapObexServerTest.java9
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapSimVcardManagerTest.java7
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapVcardManagerTest.java8
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandlerTest.java252
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java96
-rw-r--r--android/pandora/.gitignore3
-rwxr-xr-xandroid/pandora/gen_cov.py322
-rwxr-xr-xandroid/pandora/mmi2grpc/_build/protoc-gen-custom_grpc75
-rw-r--r--android/pandora/mmi2grpc/mmi2grpc/__init__.py33
-rw-r--r--android/pandora/mmi2grpc/mmi2grpc/_rootcanal.py7
-rw-r--r--android/pandora/mmi2grpc/mmi2grpc/a2dp.py52
-rw-r--r--android/pandora/mmi2grpc/mmi2grpc/avrcp.py197
-rw-r--r--android/pandora/mmi2grpc/mmi2grpc/l2cap.py30
-rw-r--r--android/pandora/mmi2grpc/mmi2grpc/sm.py144
-rw-r--r--android/pandora/server/Android.bp1
-rw-r--r--android/pandora/server/configs/PtsBotTest.xml21
-rw-r--r--android/pandora/server/configs/PtsBotTestMts.xml7
-rw-r--r--android/pandora/server/configs/pts_bot_tests_config.json89
-rw-r--r--android/pandora/server/proto/pandora_experimental/gatt.proto22
-rw-r--r--android/pandora/server/proto/pandora_experimental/host.proto164
-rw-r--r--android/pandora/server/src/com/android/pandora/A2dpSink.kt128
-rw-r--r--android/pandora/server/src/com/android/pandora/Gatt.kt236
-rw-r--r--android/pandora/server/src/com/android/pandora/GattServerManager.kt53
-rw-r--r--android/pandora/server/src/com/android/pandora/Host.kt361
-rw-r--r--android/pandora/server/src/com/android/pandora/Security.kt154
-rw-r--r--android/pandora/server/src/com/android/pandora/Server.kt26
-rw-r--r--android/pandora/server/src/com/android/pandora/Utils.kt80
-rwxr-xr-xbuild.py14
-rw-r--r--system/audio_bluetooth_hw/Android.bp21
-rw-r--r--system/binder/android/bluetooth/IBluetoothLeAudio.aidl3
-rw-r--r--system/blueberry/facade/topshim/facade.proto5
-rw-r--r--system/blueberry/tests/topshim/adapter/adapter_test.py8
-rw-r--r--system/blueberry/tests/topshim/lib/adapter_client.py16
-rw-r--r--system/blueberry/tests/topshim/lib/async_closable.py50
-rw-r--r--system/blueberry/tests/topshim/lib/gatt_client.py6
-rw-r--r--system/blueberry/tests/topshim/lib/security_client.py71
-rw-r--r--system/blueberry/tests/topshim/lib/topshim_base_test.py50
-rw-r--r--system/blueberry/tests/topshim/lib/topshim_device.py92
-rw-r--r--system/blueberry/tests/topshim/power/suspend_test.py96
-rw-r--r--system/blueberry/tests/topshim/security/classic_security_test.py (renamed from system/blueberry/tests/topshim/security/security_test.py)9
-rw-r--r--system/blueberry/tests/topshim/topshim_test_runner.py4
-rw-r--r--system/bta/dm/bta_dm_act.cc21
-rw-r--r--system/bta/dm/bta_dm_api.cc14
-rw-r--r--system/bta/dm/bta_dm_int.h2
-rw-r--r--system/bta/gatt/bta_gattc_act.cc20
-rw-r--r--system/bta/gatt/bta_gattc_utils.cc2
-rw-r--r--system/bta/hf_client/bta_hf_client_rfc.cc1
-rw-r--r--system/bta/include/bta_api.h11
-rw-r--r--system/bta/le_audio/client.cc216
-rw-r--r--system/bta/le_audio/client_audio.h10
-rw-r--r--system/bta/le_audio/devices.cc46
-rw-r--r--system/bta/le_audio/devices.h20
-rw-r--r--system/bta/le_audio/devices_test.cc20
-rw-r--r--system/bta/le_audio/le_audio_client_test.cc145
-rw-r--r--system/bta/le_audio/le_audio_types.cc4
-rw-r--r--system/bta/le_audio/le_audio_types.h2
-rw-r--r--system/bta/le_audio/mock_iso_manager.cc4
-rw-r--r--system/bta/le_audio/mock_iso_manager.h2
-rw-r--r--system/bta/le_audio/state_machine.cc69
-rw-r--r--system/bta/le_audio/state_machine_test.cc239
-rw-r--r--system/bta/vc/vc.cc55
-rw-r--r--system/bta/vc/vc_test.cc33
-rw-r--r--system/btif/Android.bp5
-rw-r--r--system/btif/src/btif_a2dp_source.cc10
-rw-r--r--system/btif/src/btif_av.cc198
-rw-r--r--system/btif/src/btif_hf.cc20
-rw-r--r--system/btif/src/btif_storage.cc12
-rw-r--r--system/build/Android.bp26
-rw-r--r--system/conf/bt_stack.conf3
-rw-r--r--system/embdrv/lc3/Android.bp2
-rw-r--r--system/gd/Android.bp5
-rw-r--r--system/gd/common/byte_array_test.cc130
-rw-r--r--system/gd/common/list_map_test.cc9
-rw-r--r--system/gd/common/lru_cache_test.cc9
-rw-r--r--system/gd/dumpsys/Android.bp7
-rw-r--r--system/gd/dumpsys/bundler/Android.bp7
-rw-r--r--system/gd/dumpsys/bundler/bundler.cc2
-rw-r--r--system/gd/dumpsys/bundler/test.cc2
-rw-r--r--system/gd/hal/hci_hal_host.cc2
-rw-r--r--system/gd/hci/Android.bp1
-rw-r--r--system/gd/hci/acl_manager/classic_acl_connection_test.cc338
-rw-r--r--system/gd/hci/acl_manager/le_impl_test.cc705
-rw-r--r--system/gd/hci/hci_layer.cc1
-rw-r--r--system/gd/hci/hci_layer_unittest.cc357
-rw-r--r--system/gd/hci/le_address_manager.cc12
-rw-r--r--system/gd/metrics/chromeos/metrics.cc12
-rw-r--r--system/gd/metrics/chromeos/metrics_event.cc60
-rw-r--r--system/gd/metrics/chromeos/metrics_event.h14
-rw-r--r--system/gd/rust/common/src/init_flags.rs4
-rw-r--r--system/gd/rust/linux/client/src/command_handler.rs34
-rw-r--r--system/gd/rust/linux/client/src/dbus_iface.rs5
-rw-r--r--system/gd/rust/linux/service/Cargo.toml2
-rw-r--r--system/gd/rust/linux/service/src/iface_battery_manager.rs22
-rw-r--r--system/gd/rust/linux/service/src/iface_bluetooth.rs5
-rw-r--r--system/gd/rust/linux/service/src/main.rs69
-rw-r--r--system/gd/rust/linux/stack/src/battery_manager.rs123
-rw-r--r--system/gd/rust/linux/stack/src/battery_service.rs397
-rw-r--r--system/gd/rust/linux/stack/src/bluetooth.rs32
-rw-r--r--system/gd/rust/linux/stack/src/bluetooth_gatt.rs85
-rw-r--r--system/gd/rust/linux/stack/src/bluetooth_media.rs331
-rw-r--r--system/gd/rust/linux/stack/src/lib.rs28
-rw-r--r--system/gd/rust/linux/stack/src/uuid.rs13
-rw-r--r--system/gd/rust/linux/utils/src/adv_parser.rs88
-rw-r--r--system/gd/rust/packets/build.rs1
-rw-r--r--system/gd/rust/shim/src/init_flags.rs4
-rw-r--r--system/gd/rust/topshim/btav/btav_shim.cc23
-rw-r--r--system/gd/rust/topshim/btav/btav_shim.h8
-rw-r--r--system/gd/rust/topshim/facade/src/adapter_service.rs12
-rw-r--r--system/gd/rust/topshim/facade/src/main.rs5
-rw-r--r--system/gd/rust/topshim/facade/src/security_service.rs41
-rw-r--r--system/gd/rust/topshim/hfp/hfp_shim.cc4
-rw-r--r--system/gd/rust/topshim/hfp/hfp_shim.h4
-rw-r--r--system/gd/rust/topshim/src/btif.rs25
-rw-r--r--system/gd/rust/topshim/src/profiles/a2dp.rs52
-rw-r--r--system/gd/rust/topshim/src/profiles/avrcp.rs14
-rw-r--r--system/gd/rust/topshim/src/profiles/hfp.rs16
-rw-r--r--system/gd/rust/topshim/src/profiles/socket.rs4
-rw-r--r--system/gd/security/pairing_handler_le.h9
-rw-r--r--system/gd/storage/legacy_config_file.cc4
-rw-r--r--system/include/hardware/bt_le_audio.h1
-rw-r--r--system/internal_include/stack_config.h1
-rw-r--r--system/main/shim/btm_api.cc6
-rw-r--r--system/main/shim/btm_api.h9
-rw-r--r--system/main/shim/hci_layer.cc1
-rw-r--r--system/main/stack_config.cc7
-rw-r--r--system/profile/avrcp/tests/avrcp_device_fuzz/avrcp_device_fuzz.cc3
-rw-r--r--system/profile/avrcp/tests/avrcp_device_test.cc3
-rw-r--r--system/stack/avrc/avrc_pars_ct.cc27
-rw-r--r--system/stack/btm/btm_ble.cc13
-rw-r--r--system/stack/btm/btm_ble_gap.cc2
-rw-r--r--system/stack/btm/btm_iso.cc4
-rw-r--r--system/stack/btm/btm_iso_impl.h8
-rw-r--r--system/stack/eatt/eatt.cc2
-rw-r--r--system/stack/eatt/eatt.h3
-rw-r--r--system/stack/eatt/eatt_impl.h10
-rw-r--r--system/stack/gatt/gatt_attr.cc19
-rw-r--r--system/stack/gatt/gatt_cl.cc3
-rw-r--r--system/stack/include/btm_iso_api.h3
-rw-r--r--system/stack/l2cap/l2c_fcr.cc4
-rw-r--r--system/stack/sdp/sdp_main.cc7
-rw-r--r--system/stack/test/btm/stack_btm_test.cc3
-rw-r--r--system/stack/test/btm_iso_test.cc8
-rw-r--r--system/stack/test/common/mock_eatt.cc4
-rw-r--r--system/stack/test/common/mock_eatt.h2
-rw-r--r--system/stack/test/sdp/stack_sdp_test.cc2
-rw-r--r--system/stack/test/stack_avrcp_test.cc50
-rw-r--r--system/stack/test/stack_smp_test.cc3
-rw-r--r--system/test/common/stack_config.cc3
-rw-r--r--system/test/mock/mock_bta_dm_act.h9
-rw-r--r--system/test/mock/mock_main_shim_btm_api.cc5
-rw-r--r--system/test/mock/mock_stack_btm_iso.cc2
-rw-r--r--system/test/rootcanal/Android.bp6
-rwxr-xr-xsystem/tools/scripts/dump_le_audio.py344
-rw-r--r--system/vendor_libs/linux/interface/Android.bp10
-rw-r--r--tools/pdl/Android.bp2
-rw-r--r--tools/pdl/doc/reference.md8
-rwxr-xr-xtools/pdl/scripts/generate_python_backend.py20
-rw-r--r--tools/pdl/scripts/pdl/ast.py3
-rw-r--r--tools/pdl/scripts/pdl/core.py11
-rw-r--r--tools/pdl/scripts/pdl/parser.py4
-rw-r--r--tools/pdl/src/ast.rs2
-rw-r--r--tools/pdl/src/backends.rs4
-rw-r--r--tools/pdl/src/backends/json.rs9
-rw-r--r--tools/pdl/src/backends/rust.rs1028
-rw-r--r--tools/pdl/src/backends/rust/preamble.rs67
-rw-r--r--tools/pdl/src/backends/rust/types.rs49
-rw-r--r--tools/pdl/src/generator.rs685
-rw-r--r--tools/pdl/src/lint.rs154
-rw-r--r--tools/pdl/src/main.rs6
-rw-r--r--tools/pdl/src/parser.rs4
-rw-r--r--tools/pdl/tests/canonical/be_test_file.pdl55
-rw-r--r--tools/pdl/tests/canonical/be_test_vectors.json80
-rw-r--r--tools/pdl/tests/canonical/le_test_file.pdl55
-rw-r--r--tools/pdl/tests/canonical/le_test_vectors.json80
-rw-r--r--tools/pdl/tests/generated/packet_decl_complex_big_endian.rs155
-rw-r--r--tools/pdl/tests/generated/packet_decl_complex_little_endian.rs155
-rw-r--r--tools/pdl/tests/generated/packet_decl_simple_big_endian.rs2
-rw-r--r--tools/pdl/tests/generated/packet_decl_simple_little_endian.rs2
-rw-r--r--tools/rootcanal/Android.bp44
-rw-r--r--tools/rootcanal/lmp/build.rs1
-rw-r--r--tools/rootcanal/lmp/src/procedure/encryption.rs73
-rw-r--r--tools/rootcanal/lmp/src/procedure/features.rs29
-rw-r--r--tools/rootcanal/lmp/src/test/context.rs40
-rw-r--r--tools/rootcanal/lmp/test/ENC/BV-01-C.in32
-rw-r--r--tools/rootcanal/lmp/test/ENC/BV-05-C.in40
-rw-r--r--tools/rootcanal/lmp/test/ENC/BV-26-C.in32
-rw-r--r--tools/rootcanal/lmp/test/ENC/BV-34-C.in40
-rw-r--r--tools/rootcanal/model/controller/acl_connection.cc3
-rw-r--r--tools/rootcanal/model/controller/acl_connection.h3
-rw-r--r--tools/rootcanal/model/controller/acl_connection_handler.cc12
-rw-r--r--tools/rootcanal/model/controller/acl_connection_handler.h3
-rw-r--r--tools/rootcanal/model/controller/controller_properties.cc4
-rw-r--r--tools/rootcanal/model/controller/controller_properties.h12
-rw-r--r--tools/rootcanal/model/controller/dual_mode_controller.cc103
-rw-r--r--tools/rootcanal/model/controller/le_advertiser.cc27
-rw-r--r--tools/rootcanal/model/controller/le_advertiser.h13
-rw-r--r--tools/rootcanal/model/controller/link_layer_controller.cc653
-rw-r--r--tools/rootcanal/model/controller/link_layer_controller.h124
-rw-r--r--tools/rootcanal/packets/link_layer_packets.pdl1
-rw-r--r--tools/rootcanal/test/controller/le/le_add_device_to_filter_accept_list_test.cc104
-rw-r--r--tools/rootcanal/test/controller/le/le_add_device_to_resolving_list_test.cc128
-rw-r--r--tools/rootcanal/test/controller/le/le_clear_filter_accept_list_test.cc97
-rw-r--r--tools/rootcanal/test/controller/le/le_clear_resolving_list_test.cc90
-rw-r--r--tools/rootcanal/test/controller/le/le_remove_device_from_filter_accept_list_test.cc112
-rw-r--r--tools/rootcanal/test/controller/le/le_remove_device_from_resolving_list_test.cc109
-rw-r--r--tools/rootcanal/test/controller/le/le_set_address_resolution_enable_test.cc73
231 files changed, 10916 insertions, 2717 deletions
diff --git a/android/app/Android.bp b/android/app/Android.bp
index 5af8cfd487..2b1d388c8c 100644
--- a/android/app/Android.bp
+++ b/android/app/Android.bp
@@ -69,12 +69,6 @@ cc_library_shared {
"libbluetooth",
"libc++fs",
],
- cflags: [
- "-Wall",
- "-Werror",
- "-Wextra",
- "-Wno-unused-parameter",
- ],
sanitize: {
scs: true,
},
diff --git a/android/app/src/com/android/bluetooth/pbap/BluetoothPbapMethodProxy.java b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
index 8846e6020c..57104510f5 100644
--- a/android/app/src/com/android/bluetooth/pbap/BluetoothPbapMethodProxy.java
+++ b/android/app/src/com/android/bluetooth/BluetoothMethodProxy.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.bluetooth.pbap;
+package com.android.bluetooth;
import android.content.ContentResolver;
import android.content.Context;
@@ -22,7 +22,6 @@ import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
-import com.android.bluetooth.Utils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.obex.HeaderSet;
@@ -31,22 +30,22 @@ import java.io.IOException;
/**
* Proxy class for method calls to help with unit testing
*/
-public class BluetoothPbapMethodProxy {
- private static final String TAG = BluetoothPbapMethodProxy.class.getSimpleName();
- private static BluetoothPbapMethodProxy sInstance;
+public class BluetoothMethodProxy {
+ private static final String TAG = BluetoothMethodProxy.class.getSimpleName();
+ private static BluetoothMethodProxy sInstance;
private static final Object INSTANCE_LOCK = new Object();
- private BluetoothPbapMethodProxy() {}
+ private BluetoothMethodProxy() {}
/**
* Get the singleton instance of proxy
*
* @return the singleton instance, guaranteed not null
*/
- public static BluetoothPbapMethodProxy getInstance() {
+ public static BluetoothMethodProxy getInstance() {
synchronized (INSTANCE_LOCK) {
if (sInstance == null) {
- sInstance = new BluetoothPbapMethodProxy();
+ sInstance = new BluetoothMethodProxy();
}
}
return sInstance;
@@ -58,7 +57,7 @@ public class BluetoothPbapMethodProxy {
* @param proxy a test instance of the BluetoothPbapMethodCallProxy
*/
@VisibleForTesting
- public static void setInstanceForTesting(BluetoothPbapMethodProxy proxy) {
+ public static void setInstanceForTesting(BluetoothMethodProxy proxy) {
Utils.enforceInstrumentationTestMode();
synchronized (INSTANCE_LOCK) {
Log.d(TAG, "setInstanceForTesting(), set to " + proxy);
diff --git a/android/app/src/com/android/bluetooth/bass_client/BaseData.java b/android/app/src/com/android/bluetooth/bass_client/BaseData.java
index c76987ede1..c76987ede1 100755..100644
--- a/android/app/src/com/android/bluetooth/bass_client/BaseData.java
+++ b/android/app/src/com/android/bluetooth/bass_client/BaseData.java
diff --git a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java
index 9ba993aff5..c38ef94d8e 100755..100644
--- a/android/app/src/com/android/bluetooth/bass_client/BassClientService.java
+++ b/android/app/src/com/android/bluetooth/bass_client/BassClientService.java
@@ -92,7 +92,6 @@ public class BassClientService extends ProfileService {
private AdapterService mAdapterService;
private DatabaseManager mDatabaseManager;
private BluetoothAdapter mBluetoothAdapter = null;
- private BassUtils mBassUtils = null;
private Map<BluetoothDevice, BluetoothDevice> mActiveSourceMap;
/* Caching the PeriodicAdvertisementResult from Broadcast source */
/* This is stored at service so that each device state machine can access
@@ -279,7 +278,6 @@ public class BassClientService extends ProfileService {
registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
setBassClientService(this);
- mBassUtils = new BassUtils(this);
// Saving PSync stuff for future addition
mDeviceToSyncHandleMap = new HashMap<BluetoothDevice, Integer>();
mPeriodicAdvertisementResultMap = new HashMap<BluetoothDevice,
@@ -324,10 +322,6 @@ public class BassClientService extends ProfileService {
mActiveSourceMap.clear();
mActiveSourceMap = null;
}
- if (mBassUtils != null) {
- mBassUtils.cleanUp();
- mBassUtils = null;
- }
if (mPendingGroupOp != null) {
mPendingGroupOp.clear();
}
@@ -340,13 +334,6 @@ public class BassClientService extends ProfileService {
return super.onUnbind(intent);
}
- /**
- * getBassUtils
- */
- public BassUtils getBassUtils() {
- return mBassUtils;
- }
-
BluetoothDevice getDeviceForSyncHandle(int syncHandle) {
if (mDeviceToSyncHandleMap == null) {
return null;
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 bc14f6d3c7..cadf0e5a78 100755..100644
--- a/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java
+++ b/android/app/src/com/android/bluetooth/bass_client/BassClientStateMachine.java
@@ -178,7 +178,6 @@ public class BassClientStateMachine extends StateMachine {
private final Map<Integer, Boolean> mPendingRemove = new HashMap();
// Psync and PAST interfaces
private PeriodicAdvertisingManager mPeriodicAdvManager;
- private boolean mAutoAssist = false;
private boolean mAutoTriggered = false;
private boolean mNoStopScanOffload = false;
private boolean mDefNoPAS = false;
@@ -606,10 +605,6 @@ public class BassClientStateMachine extends StateMachine {
advHandle, mPeriodicAdvCallback);
} else {
Log.e(TAG, "There is no valid sync handle for this Source");
- if (mAutoAssist) {
- // Initiate Auto Assist procedure for this device
- mService.getBassUtils().triggerAutoAssist(recvState);
- }
}
}
} else if (state == BluetoothLeBroadcastReceiveState.PA_SYNC_STATE_SYNCHRONIZED
diff --git a/android/app/src/com/android/bluetooth/bass_client/BassConstants.java b/android/app/src/com/android/bluetooth/bass_client/BassConstants.java
index dcd6b5b5a4..6e0dd184b7 100755..100644
--- a/android/app/src/com/android/bluetooth/bass_client/BassConstants.java
+++ b/android/app/src/com/android/bluetooth/bass_client/BassConstants.java
@@ -37,12 +37,7 @@ public class BassConstants {
UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
public static final ParcelUuid BASIC_AUDIO_UUID =
ParcelUuid.fromString("00001851-0000-1000-8000-00805F9B34FB");
- public static final int AA_START_SCAN = 1;
- public static final int AA_SCAN_SUCCESS = 2;
- public static final int AA_SCAN_FAILURE = 3;
- public static final int AA_SCAN_TIMEOUT = 4;
- // timeout for internal scan
- public static final int AA_SCAN_TIMEOUT_MS = 1000;
+
public static final int INVALID_SYNC_HANDLE = -1;
public static final int INVALID_ADV_SID = -1;
public static final int INVALID_ADV_ADDRESS_TYPE = -1;
diff --git a/android/app/src/com/android/bluetooth/bass_client/BassUtils.java b/android/app/src/com/android/bluetooth/bass_client/BassUtils.java
index 42f88cad0c..6a31485cc5 100755..100644
--- a/android/app/src/com/android/bluetooth/bass_client/BassUtils.java
+++ b/android/app/src/com/android/bluetooth/bass_client/BassUtils.java
@@ -16,27 +16,12 @@
package com.android.bluetooth.bass_client;
-import android.annotation.NonNull;
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothLeBroadcastReceiveState;
-import android.bluetooth.IBluetoothLeBroadcastAssistantCallback;
-import android.bluetooth.le.BluetoothLeScanner;
-import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
-import android.bluetooth.le.ScanResult;
-import android.os.Handler;
-import android.os.Message;
import android.os.ParcelUuid;
import android.util.Log;
-import com.android.bluetooth.btservice.ServiceFactory;
-
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
/**
* Bass Utility functions
@@ -44,73 +29,6 @@ import java.util.Map;
class BassUtils {
private static final String TAG = "BassUtils";
- // Using ArrayList as KEY to hashmap. May be not risk
- // in this case as It is used to track the callback to cancel Scanning later
- private final Map<ArrayList<IBluetoothLeBroadcastAssistantCallback>, ScanCallback>
- mLeAudioSourceScanCallbacks =
- new HashMap<ArrayList<IBluetoothLeBroadcastAssistantCallback>, ScanCallback>();
- private final Map<BluetoothDevice, ScanCallback> mBassAutoAssist =
- new HashMap<BluetoothDevice, ScanCallback>();
-
- /*LE Scan related members*/
- private boolean mBroadcastersAround = false;
- private BluetoothAdapter mBluetoothAdapter = null;
- private BluetoothLeScanner mLeScanner = null;
- private BassClientService mService = null;
- private ServiceFactory mFactory = new ServiceFactory();
-
- BassUtils(BassClientService service) {
- mService = service;
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- mLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
- }
-
- void cleanUp() {
- if (mLeAudioSourceScanCallbacks != null) {
- mLeAudioSourceScanCallbacks.clear();
- }
- if (mBassAutoAssist != null) {
- mBassAutoAssist.clear();
- }
- }
-
- private final Handler mAutoAssistScanHandler =
- new Handler() {
- public void handleMessage(Message msg) {
- super.handleMessage(msg);
- switch (msg.what) {
- case BassConstants.AA_START_SCAN:
- Message m = obtainMessage(BassConstants.AA_SCAN_TIMEOUT);
- sendMessageDelayed(m, BassConstants.AA_SCAN_TIMEOUT_MS);
- mService.startSearchingForSources(null);
- break;
- case BassConstants.AA_SCAN_SUCCESS:
- // Able to find to desired desired Source Device
- ScanResult scanRes = (ScanResult) msg.obj;
- BluetoothDevice dev = scanRes.getDevice();
- mService.stopSearchingForSources();
- mService.selectSource(dev, scanRes, true);
- break;
- case BassConstants.AA_SCAN_FAILURE:
- // Not able to find the given source
- break;
- case BassConstants.AA_SCAN_TIMEOUT:
- mService.stopSearchingForSources();
- break;
- }
- }
- };
-
- @NonNull Handler getAutoAssistScanHandler() {
- return mAutoAssistScanHandler;
- }
-
- void triggerAutoAssist(BluetoothLeBroadcastReceiveState recvState) {
- Message msg = mAutoAssistScanHandler.obtainMessage(BassConstants.AA_START_SCAN);
- msg.obj = recvState.getSourceDevice();
- mAutoAssistScanHandler.sendMessage(msg);
- }
-
static boolean containUuid(List<ScanFilter> filters, ParcelUuid uuid) {
for (ScanFilter filter: filters) {
if (filter.getServiceUuid().equals(uuid)) {
diff --git a/android/app/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResult.java b/android/app/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResult.java
index 9d1aab0e5a..9d1aab0e5a 100755..100644
--- a/android/app/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResult.java
+++ b/android/app/src/com/android/bluetooth/bass_client/PeriodicAdvertisementResult.java
diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
index c3f04a1106..f3ad2ea140 100644
--- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java
+++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java
@@ -5247,6 +5247,7 @@ public class AdapterService extends Service {
}
// Boolean flags
+ private static final String SDP_SERIALIZATION_FLAG = "INIT_sdp_serialization";
private static final String GD_CORE_FLAG = "INIT_gd_core";
private static final String GD_ADVERTISING_FLAG = "INIT_gd_advertising";
private static final String GD_SCANNING_FLAG = "INIT_gd_scanning";
@@ -5256,7 +5257,8 @@ public class AdapterService extends Service {
private static final String GD_L2CAP_FLAG = "INIT_gd_l2cap";
private static final String GD_RUST_FLAG = "INIT_gd_rust";
private static final String GD_LINK_POLICY_FLAG = "INIT_gd_link_policy";
- private static final String GATT_ROBUST_CACHING_FLAG = "INIT_gatt_robust_caching";
+ private static final String GATT_ROBUST_CACHING_CLIENT_FLAG = "INIT_gatt_robust_caching_client";
+ private static final String GATT_ROBUST_CACHING_SERVER_FLAG = "INIT_gatt_robust_caching_server";
/**
* Logging flags logic (only applies to DEBUG and VERBOSE levels):
@@ -5285,6 +5287,10 @@ public class AdapterService extends Service {
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_CORE_FLAG, false)) {
initFlags.add(String.format("%s=%s", GD_CORE_FLAG, "true"));
}
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
+ SDP_SERIALIZATION_FLAG, true)) {
+ initFlags.add(String.format("%s=%s", SDP_SERIALIZATION_FLAG, "true"));
+ }
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH, GD_ADVERTISING_FLAG, false)) {
initFlags.add(String.format("%s=%s", GD_ADVERTISING_FLAG, "true"));
}
@@ -5311,8 +5317,12 @@ public class AdapterService extends Service {
initFlags.add(String.format("%s=%s", GD_LINK_POLICY_FLAG, "true"));
}
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
- GATT_ROBUST_CACHING_FLAG, false)) {
- initFlags.add(String.format("%s=%s", GATT_ROBUST_CACHING_FLAG, "true"));
+ GATT_ROBUST_CACHING_CLIENT_FLAG, true)) {
+ initFlags.add(String.format("%s=%s", GATT_ROBUST_CACHING_CLIENT_FLAG, "true"));
+ }
+ if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
+ GATT_ROBUST_CACHING_SERVER_FLAG, false)) {
+ initFlags.add(String.format("%s=%s", GATT_ROBUST_CACHING_SERVER_FLAG, "true"));
}
if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_BLUETOOTH,
LOGGING_DEBUG_ENABLED_FOR_ALL_FLAG, false)) {
diff --git a/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java b/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java
index f85dbbc4bf..172383e2f2 100644
--- a/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java
+++ b/android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java
@@ -598,6 +598,24 @@ public class CsipSetCoordinatorService extends ProfileService {
}
/**
+ * Get group ID for a given device and UUID
+ * @param device potential group member
+ * @param uuid profile context UUID
+ * @return group ID
+ */
+ public Integer getGroupId(BluetoothDevice device, ParcelUuid uuid) {
+ Map<Integer, Integer> device_groups =
+ mDeviceGroupIdRankMap.getOrDefault(device, new HashMap<>());
+ return mGroupIdToUuidMap.entrySet()
+ .stream()
+ .filter(e -> (device_groups.containsKey(e.getKey())
+ && e.getValue().equals(uuid)))
+ .map(Map.Entry::getKey)
+ .findFirst()
+ .orElse(IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID);
+ }
+
+ /**
* Get device's groups/
* @param device group member device
* @return map of group id and related uuids.
diff --git a/android/app/src/com/android/bluetooth/gatt/GattService.java b/android/app/src/com/android/bluetooth/gatt/GattService.java
index 0d4cfa95b9..b21a349867 100644
--- a/android/app/src/com/android/bluetooth/gatt/GattService.java
+++ b/android/app/src/com/android/bluetooth/gatt/GattService.java
@@ -258,9 +258,9 @@ public class GattService extends ProfileService {
/**
* HashMap used to synchronize writeCharacteristic calls mapping remote device address to
- * available permit (either 1 or 0).
+ * available permit (connectId or -1).
*/
- private final HashMap<String, AtomicBoolean> mPermits = new HashMap<>();
+ private final HashMap<String, Integer> mPermits = new HashMap<>();
private AdapterService mAdapterService;
private BluetoothAdapterProxy mBluetoothAdapterProxy;
@@ -2020,7 +2020,7 @@ public class GattService extends ProfileService {
synchronized (mPermits) {
Log.d(TAG, "onConnected() - adding permit for address="
+ address);
- mPermits.putIfAbsent(address, new AtomicBoolean(true));
+ mPermits.putIfAbsent(address, -1);
}
connectionState = BluetoothProtoEnums.CONNECTION_STATE_CONNECTED;
@@ -2052,6 +2052,13 @@ public class GattService extends ProfileService {
+ address);
mPermits.remove(address);
}
+ } else {
+ synchronized (mPermits) {
+ if (mPermits.get(address) == connId) {
+ Log.d(TAG, "onDisconnected() - set permit -1 for address=" + address);
+ mPermits.put(address, -1);
+ }
+ }
}
if (app != null) {
@@ -2357,7 +2364,7 @@ public class GattService extends ProfileService {
synchronized (mPermits) {
Log.d(TAG, "onWriteCharacteristic() - increasing permit for address="
+ address);
- mPermits.get(address).set(true);
+ mPermits.put(address, -1);
}
if (VDBG) {
@@ -3653,18 +3660,18 @@ public class GattService extends ProfileService {
Log.d(TAG, "writeCharacteristic() - trying to acquire permit.");
// Lock the thread until onCharacteristicWrite callback comes back.
synchronized (mPermits) {
- AtomicBoolean atomicBoolean = mPermits.get(address);
- if (atomicBoolean == null) {
+ Integer permit = mPermits.get(address);
+ if (permit == null) {
Log.d(TAG, "writeCharacteristic() - atomicBoolean uninitialized!");
return BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED;
}
- boolean success = atomicBoolean.get();
+ boolean success = (permit == -1);
if (!success) {
Log.d(TAG, "writeCharacteristic() - no permit available.");
return BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY;
}
- atomicBoolean.set(false);
+ mPermits.put(address, connId);
}
mNativeInterface.gattClientWriteCharacteristic(connId, handle, writeType, authReq, value);
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
index 0c40235e4b..98abf5b5a9 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -1399,8 +1399,9 @@ public class HeadsetService extends ProfileService {
LeAudioService leAudioService = mFactory.getLeAudioService();
if (leAudioService != null) {
Log.i(TAG, "Make sure there is no le audio device active.");
- leAudioService.setActiveDevice(null);
+ leAudioService.setInactiveForHfpHandover(mActiveDevice);
}
+
broadcastActiveDevice(mActiveDevice);
int connectStatus = connectAudio(mActiveDevice);
if (connectStatus != BluetoothStatusCodes.SUCCESS) {
@@ -1432,7 +1433,7 @@ public class HeadsetService extends ProfileService {
}
}
- int connectAudio() {
+ public int connectAudio() {
synchronized (mStateMachines) {
BluetoothDevice device = mActiveDevice;
if (device == null) {
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 6889d04226..729aaf1132 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -60,6 +60,7 @@ import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.bluetooth.hfp.HeadsetService;
import com.android.bluetooth.mcp.McpService;
import com.android.bluetooth.tbs.TbsGatt;
import com.android.bluetooth.vc.VolumeControlService;
@@ -92,6 +93,11 @@ public class LeAudioService extends ProfileService {
private static LeAudioService sLeAudioService;
/**
+ * Indicates group audio support for none direction
+ */
+ private static final int AUDIO_DIRECTION_NONE = 0x00;
+
+ /**
* Indicates group audio support for input direction
*/
private static final int AUDIO_DIRECTION_INPUT_BIT = 0x01;
@@ -101,11 +107,6 @@ public class LeAudioService extends ProfileService {
*/
private static final int AUDIO_DIRECTION_OUTPUT_BIT = 0x02;
- /*
- * Indicates no active contexts
- */
- private static final int ACTIVE_CONTEXTS_NONE = 0;
-
private AdapterService mAdapterService;
private DatabaseManager mDatabaseManager;
private HandlerThread mStateMachinesThread;
@@ -117,6 +118,7 @@ public class LeAudioService extends ProfileService {
LeAudioNativeInterface mLeAudioNativeInterface;
boolean mLeAudioNativeIsInitialized = false;
+ BluetoothDevice mHfpHandoverDevice = null;
LeAudioBroadcasterNativeInterface mLeAudioBroadcasterNativeInterface = null;
@VisibleForTesting
AudioManager mAudioManager;
@@ -135,14 +137,14 @@ public class LeAudioService extends ProfileService {
LeAudioGroupDescriptor() {
mIsConnected = false;
mIsActive = false;
- mActiveContexts = ACTIVE_CONTEXTS_NONE;
+ mDirection = AUDIO_DIRECTION_NONE;
mCodecStatus = null;
mLostLeadDeviceWhileStreaming = null;
}
public Boolean mIsConnected;
public Boolean mIsActive;
- public Integer mActiveContexts;
+ public Integer mDirection;
public BluetoothLeAudioCodecStatus mCodecStatus;
/* This can be non empty only for the streaming time */
BluetoothDevice mLostLeadDeviceWhileStreaming;
@@ -159,21 +161,6 @@ public class LeAudioService extends ProfileService {
private final Map<BluetoothDevice, Integer> mDeviceGroupIdMap = new ConcurrentHashMap<>();
private final Map<BluetoothDevice, Integer> mDeviceAudioLocationMap = new ConcurrentHashMap<>();
- private final int mContextSupportingInputAudio = BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL
- | BluetoothLeAudio.CONTEXT_TYPE_VOICE_ASSISTANTS;
-
- private final int mContextSupportingOutputAudio = BluetoothLeAudio.CONTEXT_TYPE_CONVERSATIONAL
- | BluetoothLeAudio.CONTEXT_TYPE_MEDIA
- | BluetoothLeAudio.CONTEXT_TYPE_GAME
- | BluetoothLeAudio.CONTEXT_TYPE_INSTRUCTIONAL
- | BluetoothLeAudio.CONTEXT_TYPE_VOICE_ASSISTANTS
- | BluetoothLeAudio.CONTEXT_TYPE_LIVE
- | BluetoothLeAudio.CONTEXT_TYPE_SOUND_EFFECTS
- | BluetoothLeAudio.CONTEXT_TYPE_NOTIFICATIONS
- | BluetoothLeAudio.CONTEXT_TYPE_RINGTONE
- | BluetoothLeAudio.CONTEXT_TYPE_ALERTS
- | BluetoothLeAudio.CONTEXT_TYPE_EMERGENCY_ALARM;
-
private BroadcastReceiver mBondStateChangedReceiver;
private BroadcastReceiver mConnectionStateChangedReceiver;
private BroadcastReceiver mMuteStateChangedReceiver;
@@ -318,8 +305,8 @@ public class LeAudioService extends ProfileService {
Integer group_id = entry.getKey();
if (descriptor.mIsActive) {
descriptor.mIsActive = false;
- updateActiveDevices(group_id, descriptor.mActiveContexts,
- ACTIVE_CONTEXTS_NONE, descriptor.mIsActive);
+ updateActiveDevices(group_id, descriptor.mDirection, AUDIO_DIRECTION_NONE,
+ descriptor.mIsActive);
break;
}
}
@@ -331,6 +318,7 @@ public class LeAudioService extends ProfileService {
mLeAudioNativeInterface.cleanup();
mLeAudioNativeInterface = null;
mLeAudioNativeIsInitialized = false;
+ mHfpHandoverDevice = null;
// Set the service and BLE devices as inactive
setLeAudioService(null);
@@ -412,7 +400,7 @@ public class LeAudioService extends ProfileService {
sLeAudioService = instance;
}
- private int getGroupVolume(int groupId) {
+ private int getAudioDeviceGroupVolume(int groupId) {
if (mVolumeControlService == null) {
mVolumeControlService = mServiceFactory.getVolumeControlService();
if (mVolumeControlService == null) {
@@ -421,7 +409,7 @@ public class LeAudioService extends ProfileService {
}
}
- return mVolumeControlService.getGroupVolume(groupId);
+ return mVolumeControlService.getAudioDeviceGroupVolume(groupId);
}
public boolean connect(BluetoothDevice device) {
@@ -621,32 +609,6 @@ public class LeAudioService extends ProfileService {
return result;
}
- /**
- * Get supported group audio direction from available context.
- *
- * @param activeContexts bitset of active context to be matched with possible audio direction
- * support.
- * @return matched possible audio direction support masked bitset
- * {@link #AUDIO_DIRECTION_INPUT_BIT} if input audio is supported
- * {@link #AUDIO_DIRECTION_OUTPUT_BIT} if output audio is supported
- */
- private Integer getAudioDirectionsFromActiveContextsMap(Integer activeContexts) {
- Integer supportedAudioDirections = 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) {
- supportedAudioDirections |= AUDIO_DIRECTION_OUTPUT_BIT;
- }
-
- return supportedAudioDirections;
- }
-
private Integer getActiveGroupId() {
synchronized (mGroupLock) {
for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) {
@@ -798,12 +760,7 @@ public class LeAudioService extends ProfileService {
}
private boolean updateActiveInDevice(BluetoothDevice device, Integer groupId,
- Integer oldActiveContexts, Integer newActiveContexts) {
- Integer oldSupportedAudioDirections =
- getAudioDirectionsFromActiveContextsMap(oldActiveContexts);
- Integer newSupportedAudioDirections =
- getAudioDirectionsFromActiveContextsMap(newActiveContexts);
-
+ Integer oldSupportedAudioDirections, Integer newSupportedAudioDirections) {
boolean oldSupportedByDeviceInput = (oldSupportedAudioDirections
& AUDIO_DIRECTION_INPUT_BIT) != 0;
boolean newSupportedByDeviceInput = (newSupportedAudioDirections
@@ -863,12 +820,7 @@ public class LeAudioService extends ProfileService {
}
private boolean updateActiveOutDevice(BluetoothDevice device, Integer groupId,
- Integer oldActiveContexts, Integer newActiveContexts) {
- Integer oldSupportedAudioDirections =
- getAudioDirectionsFromActiveContextsMap(oldActiveContexts);
- Integer newSupportedAudioDirections =
- getAudioDirectionsFromActiveContextsMap(newActiveContexts);
-
+ Integer oldSupportedAudioDirections, Integer newSupportedAudioDirections) {
boolean oldSupportedByDeviceOutput = (oldSupportedAudioDirections
& AUDIO_DIRECTION_OUTPUT_BIT) != 0;
boolean newSupportedByDeviceOutput = (newSupportedAudioDirections
@@ -923,7 +875,7 @@ public class LeAudioService extends ProfileService {
}
int volume = IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME;
if (mActiveAudioOutDevice != null) {
- volume = getGroupVolume(groupId);
+ volume = getAudioDeviceGroupVolume(groupId);
}
mAudioManager.handleBluetoothActiveDeviceChanged(mActiveAudioOutDevice,
@@ -938,13 +890,13 @@ public class LeAudioService extends ProfileService {
/**
* Report the active devices change to the active device manager and the media framework.
* @param groupId id of group which devices should be updated
- * @param newActiveContexts new active contexts for group of devices
- * @param oldActiveContexts old active contexts for group of devices
+ * @param newSupportedAudioDirections new supported audio directions for group of devices
+ * @param oldSupportedAudioDirections old supported audio directions for group of devices
* @param isActive if there is new active group
* @return true if group is active after change false otherwise.
*/
- private boolean updateActiveDevices(Integer groupId, Integer oldActiveContexts,
- Integer newActiveContexts, boolean isActive) {
+ private boolean updateActiveDevices(Integer groupId, Integer oldSupportedAudioDirections,
+ Integer newSupportedAudioDirections, boolean isActive) {
BluetoothDevice device = null;
if (isActive) {
@@ -952,9 +904,11 @@ public class LeAudioService extends ProfileService {
}
boolean outReplaced =
- updateActiveOutDevice(device, groupId, oldActiveContexts, newActiveContexts);
+ updateActiveOutDevice(device, groupId, oldSupportedAudioDirections,
+ newSupportedAudioDirections);
boolean inReplaced =
- updateActiveInDevice(device, groupId, oldActiveContexts, newActiveContexts);
+ updateActiveInDevice(device, groupId, oldSupportedAudioDirections,
+ newSupportedAudioDirections);
if (outReplaced || inReplaced) {
Intent intent = new Intent(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
@@ -1073,7 +1027,7 @@ public class LeAudioService extends ProfileService {
}
if (DBG) {
- Log.d(TAG, "connect(): " + device);
+ Log.d(TAG, "connect(): " + storedDevice);
}
synchronized (mStateMachines) {
@@ -1146,8 +1100,8 @@ public class LeAudioService extends ProfileService {
return;
}
- descriptor.mIsActive = updateActiveDevices(groupId,
- ACTIVE_CONTEXTS_NONE, descriptor.mActiveContexts, true);
+ descriptor.mIsActive = updateActiveDevices(groupId, AUDIO_DIRECTION_NONE,
+ descriptor.mDirection, true);
if (descriptor.mIsActive) {
notifyGroupStatusChanged(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
@@ -1164,8 +1118,8 @@ public class LeAudioService extends ProfileService {
}
descriptor.mIsActive = false;
- updateActiveDevices(groupId, descriptor.mActiveContexts,
- ACTIVE_CONTEXTS_NONE, descriptor.mIsActive);
+ updateActiveDevices(groupId, descriptor.mDirection, AUDIO_DIRECTION_NONE,
+ descriptor.mIsActive);
/* Clear lost devices */
if (DBG) Log.d(TAG, "Clear for group: " + groupId);
clearLostDevicesWhileStreaming(descriptor);
@@ -1173,6 +1127,36 @@ public class LeAudioService extends ProfileService {
}
}
+ private void handleGroupIdleDuringCall() {
+ if (mHfpHandoverDevice == null) {
+ if (DBG) {
+ Log.d(TAG, "There is no HFP handover");
+ }
+ return;
+ }
+ HeadsetService headsetService = mServiceFactory.getHeadsetService();
+ if (headsetService == null) {
+ if (DBG) {
+ Log.d(TAG, "There is no HFP service available");
+ }
+ return;
+ }
+
+ BluetoothDevice activeHfpDevice = headsetService.getActiveDevice();
+ if (activeHfpDevice == null) {
+ if (DBG) {
+ Log.d(TAG, "Make " + mHfpHandoverDevice + " active again ");
+ }
+ headsetService.setActiveDevice(mHfpHandoverDevice);
+ } else {
+ if (DBG) {
+ Log.d(TAG, "Connect audio to " + activeHfpDevice);
+ }
+ headsetService.connectAudio();
+ }
+ mHfpHandoverDevice = null;
+ }
+
// Suppressed since this is part of a local process
@SuppressLint("AndroidFrameworkRequiresPermission")
void messageFromNative(LeAudioStackEvent stackEvent) {
@@ -1215,8 +1199,13 @@ public class LeAudioService extends ProfileService {
break;
case LeAudioStackEvent.CONNECTION_STATE_CONNECTED:
case LeAudioStackEvent.CONNECTION_STATE_CONNECTING:
- if (descriptor != null) {
- if (DBG) Log.d(TAG, "Removing from lost devices : " + device);
+ if (descriptor != null
+ && Objects.equals(
+ descriptor.mLostLeadDeviceWhileStreaming,
+ device)) {
+ if (DBG) {
+ Log.d(TAG, "Removing from lost devices : " + device);
+ }
descriptor.mLostLeadDeviceWhileStreaming = null;
/* Try to connect other devices from the group */
connectSet(device);
@@ -1304,13 +1293,13 @@ public class LeAudioService extends ProfileService {
if (descriptor != null) {
if (descriptor.mIsActive) {
descriptor.mIsActive =
- updateActiveDevices(groupId, descriptor.mActiveContexts,
- available_contexts, descriptor.mIsActive);
+ updateActiveDevices(groupId, descriptor.mDirection, direction,
+ descriptor.mIsActive);
if (!descriptor.mIsActive) {
notifyGroupStatusChanged(groupId, BluetoothLeAudio.GROUP_STATUS_INACTIVE);
}
}
- descriptor.mActiveContexts = available_contexts;
+ descriptor.mDirection = direction;
} else {
Log.e(TAG, "no descriptors for group: " + groupId);
}
@@ -1338,6 +1327,10 @@ public class LeAudioService extends ProfileService {
handleGroupTransitToInactive(groupId);
break;
}
+ case LeAudioStackEvent.GROUP_STATUS_TURNED_IDLE_DURING_CALL: {
+ handleGroupIdleDuringCall();
+ break;
+ }
default:
break;
}
@@ -1627,8 +1620,8 @@ public class LeAudioService extends ProfileService {
descriptor.mIsActive = false;
/* Update audio framework */
updateActiveDevices(myGroupId,
- descriptor.mActiveContexts,
- descriptor.mActiveContexts,
+ descriptor.mDirection,
+ descriptor.mDirection,
descriptor.mIsActive);
return;
}
@@ -1636,8 +1629,8 @@ public class LeAudioService extends ProfileService {
if (descriptor.mIsActive) {
updateActiveDevices(myGroupId,
- descriptor.mActiveContexts,
- descriptor.mActiveContexts,
+ descriptor.mDirection,
+ descriptor.mDirection,
descriptor.mIsActive);
}
}
@@ -1747,6 +1740,20 @@ public class LeAudioService extends ProfileService {
}
/**
+ * Set Inactive by HFP during handover
+ */
+ public void setInactiveForHfpHandover(BluetoothDevice hfpHandoverDevice) {
+ if (!mLeAudioNativeIsInitialized) {
+ Log.e(TAG, "Le Audio not initialized properly.");
+ return;
+ }
+ if (getActiveGroupId() != LE_AUDIO_GROUP_ID_INVALID) {
+ mHfpHandoverDevice = hfpHandoverDevice;
+ setActiveDevice(null);
+ }
+ }
+
+ /**
* Set connection policy of the profile and connects it if connectionPolicy is
* {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED} or disconnects if connectionPolicy is
* {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
@@ -1877,6 +1884,13 @@ public class LeAudioService extends ProfileService {
}
private void notifyGroupNodeAdded(BluetoothDevice device, int groupId) {
+ if (mVolumeControlService == null) {
+ mVolumeControlService = mServiceFactory.getVolumeControlService();
+ }
+ if (mVolumeControlService != null) {
+ mVolumeControlService.handleGroupNodeAdded(groupId, device);
+ }
+
if (mLeAudioCallbacks != null) {
int n = mLeAudioCallbacks.beginBroadcast();
for (int i = 0; i < n; i++) {
@@ -2468,6 +2482,26 @@ public class LeAudioService extends ProfileService {
}
@Override
+ public void setInactiveForHfpHandover(BluetoothDevice hfpHandoverDevice,
+ AttributionSource source,
+ SynchronousResultReceiver receiver) {
+ try {
+ Objects.requireNonNull(source, "source cannot be null");
+ Objects.requireNonNull(receiver, "receiver cannot be null");
+
+ LeAudioService service = getService(source);
+ if (service == null) {
+ throw new IllegalStateException("service is null");
+ }
+ enforceBluetoothPrivilegedPermission(service);
+ service.setInactiveForHfpHandover(hfpHandoverDevice);
+ receiver.send(null);
+ } catch (RuntimeException e) {
+ receiver.propagateException(e);
+ }
+ }
+
+ @Override
public void groupRemoveNode(int groupId, BluetoothDevice device,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
@@ -2712,6 +2746,7 @@ public class LeAudioService extends ProfileService {
ProfileService.println(sb, " currentlyActiveGroupId: " + getActiveGroupId());
ProfileService.println(sb, " mActiveAudioOutDevice: " + mActiveAudioOutDevice);
ProfileService.println(sb, " mActiveAudioInDevice: " + mActiveAudioInDevice);
+ ProfileService.println(sb, " mHfpHandoverDevice:" + mHfpHandoverDevice);
for (Map.Entry<Integer, LeAudioGroupDescriptor> entry : mGroupDescriptors.entrySet()) {
LeAudioGroupDescriptor descriptor = entry.getValue();
@@ -2719,7 +2754,7 @@ public class LeAudioService extends ProfileService {
ProfileService.println(sb, " Group: " + groupId);
ProfileService.println(sb, " isActive: " + descriptor.mIsActive);
ProfileService.println(sb, " isConnected: " + descriptor.mIsConnected);
- ProfileService.println(sb, " mActiveContexts: " + descriptor.mActiveContexts);
+ ProfileService.println(sb, " mDirection: " + descriptor.mDirection);
ProfileService.println(sb, " group lead: " + getConnectedGroupLeadDevice(groupId));
ProfileService.println(sb, " first device: " + getFirstDeviceFromGroup(groupId));
ProfileService.println(sb, " lost lead device: "
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java
index 6ec748396c..3824dfe66c 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioStackEvent.java
@@ -55,6 +55,7 @@ public class LeAudioStackEvent {
static final int GROUP_STATUS_INACTIVE = 0;
static final int GROUP_STATUS_ACTIVE = 1;
+ static final int GROUP_STATUS_TURNED_IDLE_DURING_CALL = 2;
static final int GROUP_NODE_ADDED = 1;
static final int GROUP_NODE_REMOVED = 2;
@@ -192,6 +193,8 @@ public class LeAudioStackEvent {
return "GROUP_STATUS_ACTIVE";
case GROUP_STATUS_INACTIVE:
return "GROUP_STATUS_INACTIVE";
+ case GROUP_STATUS_TURNED_IDLE_DURING_CALL:
+ return "GROUP_STATUS_TURNED_IDLE_DURING_CALL";
default:
break;
}
diff --git a/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java b/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java
index d1ab4f7419..a3b11fc38b 100644
--- a/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java
+++ b/android/app/src/com/android/bluetooth/mcp/MediaControlGattService.java
@@ -454,9 +454,7 @@ public class MediaControlGattService implements MediaControlGattServiceInterface
}
private void onRejectedAuthorizationGattOperation(BluetoothDevice device, GattOpContext op) {
- if (VDBG) {
- Log.d(TAG, "onRejectedAuthorizationGattOperation device: " + device);
- }
+ Log.w(TAG, "onRejectedAuthorizationGattOperation device: " + device);
switch (op.mOperation) {
case READ_CHARACTERISTIC:
diff --git a/android/app/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java b/android/app/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java
index 3f37aac4a6..123f145f90 100644
--- a/android/app/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java
+++ b/android/app/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposer.java
@@ -24,6 +24,7 @@ import android.provider.CallLog.Calls;
import android.text.TextUtils;
import android.util.Log;
+import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.vcard.VCardBuilder;
@@ -107,7 +108,7 @@ public class BluetoothPbapCallLogComposer {
return false;
}
- mCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(
+ mCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
mContext.getContentResolver(), contentUri, projection, selection, selectionArgs,
sortOrder);
diff --git a/android/app/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java b/android/app/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
index 1353152067..e64772c759 100644
--- a/android/app/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
+++ b/android/app/src/com/android/bluetooth/pbap/BluetoothPbapObexServer.java
@@ -43,6 +43,7 @@ import android.provider.CallLog.Calls;
import android.text.TextUtils;
import android.util.Log;
+import com.android.bluetooth.BluetoothMethodProxy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.obex.ApplicationParameter;
import com.android.obex.HeaderSet;
@@ -242,7 +243,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {
private PbapStateMachine mStateMachine;
- private BluetoothPbapMethodProxy mPbapMethodProxy;
+ private BluetoothMethodProxy mPbapMethodProxy;
private enum ContactsType {
TYPE_PHONEBOOK , TYPE_SIM ;
@@ -272,7 +273,7 @@ public class BluetoothPbapObexServer extends ServerRequestHandler {
mVcardManager = new BluetoothPbapVcardManager(mContext);
mVcardSimManager = new BluetoothPbapSimVcardManager(mContext);
mStateMachine = stateMachine;
- mPbapMethodProxy = BluetoothPbapMethodProxy.getInstance();
+ mPbapMethodProxy = BluetoothMethodProxy.getInstance();
}
@Override
diff --git a/android/app/src/com/android/bluetooth/pbap/BluetoothPbapSimVcardManager.java b/android/app/src/com/android/bluetooth/pbap/BluetoothPbapSimVcardManager.java
index 5019169656..22eac64d69 100644
--- a/android/app/src/com/android/bluetooth/pbap/BluetoothPbapSimVcardManager.java
+++ b/android/app/src/com/android/bluetooth/pbap/BluetoothPbapSimVcardManager.java
@@ -15,41 +15,35 @@
*/
package com.android.bluetooth.pbap;
-import com.android.bluetooth.R;
-
import android.content.ContentResolver;
+import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
+import android.provider.ContactsContract.CommonDataKinds;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts;
+import android.text.TextUtils;
+import android.util.Log;
+import com.android.bluetooth.BluetoothMethodProxy;
+import com.android.bluetooth.R;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.obex.Operation;
+import com.android.obex.ResponseCodes;
+import com.android.obex.ServerOperation;
import com.android.vcard.VCardBuilder;
import com.android.vcard.VCardConfig;
-import com.android.vcard.VCardConstants;
import com.android.vcard.VCardUtils;
-import android.content.ContentValues;
-import android.provider.CallLog;
-import android.provider.CallLog.Calls;
-import android.text.TextUtils;
-import android.util.Log;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.CommonDataKinds;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
import java.util.Collections;
import java.util.Comparator;
-
-import com.android.obex.Operation;
-import com.android.obex.ResponseCodes;
-import com.android.obex.ServerOperation;
+import java.util.List;
/**
* VCard composer especially for Call Log used in Bluetooth.
@@ -119,7 +113,7 @@ public class BluetoothPbapSimVcardManager {
}
//checkpoint Figure out if we can apply selection, projection and sort order.
- mCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(mContentResolver,
+ mCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mContentResolver,
contentUri, SIM_PROJECTION, null, null, sortOrder);
if (mCursor == null) {
@@ -273,7 +267,7 @@ public class BluetoothPbapSimVcardManager {
int size = 0;
Cursor contactCursor = null;
try {
- contactCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(
+ contactCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
mContentResolver, SIM_URI, SIM_PROJECTION, null,null, null);
if (contactCursor != null) {
size = contactCursor.getCount();
@@ -293,7 +287,7 @@ public class BluetoothPbapSimVcardManager {
ArrayList<String> allnames = new ArrayList<String>();
Cursor contactCursor = null;
try {
- contactCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(
+ contactCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
mContentResolver, SIM_URI, SIM_PROJECTION, null,null,null);
if (contactCursor != null) {
for (contactCursor.moveToFirst(); !contactCursor.isAfterLast(); contactCursor
@@ -334,7 +328,7 @@ public class BluetoothPbapSimVcardManager {
Cursor contactCursor = null;
try {
- contactCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(
+ contactCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(
mContentResolver, SIM_URI, SIM_PROJECTION, null, null, null);
if (contactCursor != null) {
diff --git a/android/app/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java b/android/app/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
index 14e26470a9..658b82a4e5 100644
--- a/android/app/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
+++ b/android/app/src/com/android/bluetooth/pbap/BluetoothPbapVcardManager.java
@@ -51,6 +51,7 @@ import android.telephony.PhoneNumberUtils;
import android.text.TextUtils;
import android.util.Log;
+import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.R;
import com.android.bluetooth.util.DevicePolicyUtils;
import com.android.internal.annotations.VisibleForTesting;
@@ -182,7 +183,7 @@ public class BluetoothPbapVcardManager {
selectionClause = Phone.STARRED + " = 1";
}
try {
- contactCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contactCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
myUri, new String[]{Phone.CONTACT_ID}, selectionClause,
null, Phone.CONTACT_ID);
if (contactCursor == null) {
@@ -209,7 +210,7 @@ public class BluetoothPbapVcardManager {
int size = 0;
Cursor callCursor = null;
try {
- callCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(mResolver,
+ callCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
myUri, null, selection, null, CallLog.Calls.DEFAULT_SORT_ORDER);
if (callCursor != null) {
size = callCursor.getCount();
@@ -243,7 +244,7 @@ public class BluetoothPbapVcardManager {
Cursor callCursor = null;
ArrayList<String> list = new ArrayList<String>();
try {
- callCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(mResolver,
+ callCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
myUri, projection, selection, null, CALLLOG_SORT_ORDER);
if (callCursor != null) {
for (callCursor.moveToFirst(); !callCursor.isAfterLast(); callCursor.moveToNext()) {
@@ -295,7 +296,7 @@ public class BluetoothPbapVcardManager {
if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
orderBy = Phone.DISPLAY_NAME;
}
- contactCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contactCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
myUri, PHONES_CONTACTS_PROJECTION, null, null, orderBy);
if (contactCursor != null) {
appendDistinctNameIdList(nameList, mContext.getString(android.R.string.unknownName),
@@ -354,7 +355,7 @@ public class BluetoothPbapVcardManager {
final Uri myUri = DevicePolicyUtils.getEnterprisePhoneUri(mContext);
Cursor contactCursor = null;
try {
- contactCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contactCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
myUri, PHONES_CONTACTS_PROJECTION, null, null,
Phone.CONTACT_ID);
@@ -443,7 +444,7 @@ public class BluetoothPbapVcardManager {
}
try {
- contactCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contactCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
uri, projection, null, null, Phone.CONTACT_ID);
if (contactCursor != null) {
@@ -478,7 +479,7 @@ public class BluetoothPbapVcardManager {
long primaryVcMsb = 0;
ArrayList<String> list = new ArrayList<String>();
try {
- callCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(mResolver,
+ callCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
myUri, null, selection, null, null);
while (callCursor != null && callCursor.moveToNext()) {
count = count + 1;
@@ -522,7 +523,7 @@ public class BluetoothPbapVcardManager {
long endPointId = 0;
try {
// Need test to see if order by _ID is ok here, or by date?
- callsCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(mResolver,
+ callsCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
myUri, CALLLOG_PROJECTION, typeSelection, null,
CALLLOG_SORT_ORDER);
if (callsCursor != null) {
@@ -596,7 +597,7 @@ public class BluetoothPbapVcardManager {
}
try {
- contactCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contactCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
myUri, PHONES_CONTACTS_PROJECTION, selectionClause,
null, Phone.CONTACT_ID);
if (contactCursor != null) {
@@ -640,7 +641,7 @@ public class BluetoothPbapVcardManager {
if (orderByWhat == BluetoothPbapObexServer.ORDER_BY_ALPHABETICAL) {
orderBy = Phone.DISPLAY_NAME;
}
- contactCursor = BluetoothPbapMethodProxy.getInstance().contentResolverQuery(mResolver,
+ contactCursor = BluetoothMethodProxy.getInstance().contentResolverQuery(mResolver,
myUri, PHONES_CONTACTS_PROJECTION, null, null, orderBy);
} catch (CursorWindowAllocationException e) {
Log.e(TAG, "CursorWindowAllocationException while composing phonebook one vcard");
diff --git a/android/app/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java b/android/app/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
index 70621d9797..ce5ccc9ab1 100644
--- a/android/app/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
+++ b/android/app/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandler.java
@@ -17,7 +17,6 @@ package com.android.bluetooth.pbapclient;
import android.accounts.Account;
import android.accounts.AccountManager;
-import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.bluetooth.BluetoothUuid;
@@ -32,6 +31,7 @@ import android.util.Log;
import com.android.bluetooth.BluetoothObexTransport;
import com.android.bluetooth.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.obex.ClientSession;
import com.android.obex.HeaderSet;
import com.android.obex.ResponseCodes;
@@ -102,7 +102,9 @@ class PbapClientConnectionHandler extends Handler {
private static final long PBAP_REQUESTED_FIELDS =
PBAP_FILTER_VERSION | PBAP_FILTER_FN | PBAP_FILTER_N | PBAP_FILTER_PHOTO
| PBAP_FILTER_ADR | PBAP_FILTER_EMAIL | PBAP_FILTER_TEL | PBAP_FILTER_NICKNAME;
- private static final int L2CAP_INVALID_PSM = -1;
+
+ @VisibleForTesting
+ static final int L2CAP_INVALID_PSM = -1;
public static final String PB_PATH = "telecom/pb.vcf";
public static final String FAV_PATH = "telecom/fav.vcf";
@@ -126,7 +128,6 @@ class PbapClientConnectionHandler extends Handler {
private Account mAccount;
private AccountManager mAccountManager;
private BluetoothSocket mSocket;
- private final BluetoothAdapter mAdapter;
private final BluetoothDevice mDevice;
// PSE SDP Record for current device.
private SdpPseRecord mPseRec = null;
@@ -136,19 +137,6 @@ class PbapClientConnectionHandler extends Handler {
private final PbapClientStateMachine mPbapClientStateMachine;
private boolean mAccountCreated;
- PbapClientConnectionHandler(Looper looper, Context context, PbapClientStateMachine stateMachine,
- BluetoothDevice device) {
- super(looper);
- mAdapter = BluetoothAdapter.getDefaultAdapter();
- mDevice = device;
- mContext = context;
- mPbapClientStateMachine = stateMachine;
- mAuth = new BluetoothPbapObexAuthenticator(this);
- mAccountManager = AccountManager.get(mPbapClientStateMachine.getContext());
- mAccount =
- new Account(mDevice.getAddress(), mContext.getString(R.string.pbap_account_type));
- }
-
/**
* Constructs PCEConnectionHandler object
*
@@ -156,7 +144,6 @@ class PbapClientConnectionHandler extends Handler {
*/
PbapClientConnectionHandler(Builder pceHandlerbuild) {
super(pceHandlerbuild.mLooper);
- mAdapter = BluetoothAdapter.getDefaultAdapter();
mDevice = pceHandlerbuild.mDevice;
mContext = pceHandlerbuild.mContext;
mPbapClientStateMachine = pceHandlerbuild.mClientStateMachine;
@@ -252,8 +239,8 @@ class PbapClientConnectionHandler extends Handler {
if (DBG) {
Log.d(TAG, "Completing Disconnect");
}
- removeAccount(mAccount);
- removeCallLog(mAccount);
+ removeAccount();
+ removeCallLog();
mPbapClientStateMachine.sendMessage(PbapClientStateMachine.MSG_CONNECTION_CLOSED);
break;
@@ -286,9 +273,20 @@ class PbapClientConnectionHandler extends Handler {
return;
}
+ @VisibleForTesting
+ synchronized void setPseRecord(SdpPseRecord record) {
+ mPseRec = record;
+ }
+
+ @VisibleForTesting
+ synchronized BluetoothSocket getSocket() {
+ return mSocket;
+ }
+
/* Utilize SDP, if available, to create a socket connection over L2CAP, RFCOMM specified
* channel, or RFCOMM default channel. */
- private synchronized boolean connectSocket() {
+ @VisibleForTesting
+ synchronized boolean connectSocket() {
try {
/* Use BluetoothSocket to connect */
if (mPseRec == null) {
@@ -318,7 +316,8 @@ class PbapClientConnectionHandler extends Handler {
/* Connect an OBEX session over the already connected socket. First establish an OBEX Transport
* abstraction, then establish a Bluetooth Authenticator, and finally issue the connect call */
- private boolean connectObexSession() {
+ @VisibleForTesting
+ boolean connectObexSession() {
boolean connectionSuccessful = false;
try {
@@ -357,13 +356,13 @@ class PbapClientConnectionHandler extends Handler {
// Will get NPE if a null mSocket is passed to BluetoothObexTransport.
// mSocket can be set to null if an abort() --> closeSocket() was called between
// the calls to connectSocket() and connectObexSession().
- Log.w(TAG, "CONNECT Failure " + e.toString());
+ Log.w(TAG, "CONNECT Failure ", e);
closeSocket();
}
return connectionSuccessful;
}
- public void abort() {
+ void abort() {
// Perform forced cleanup, it is ok if the handler throws an exception this will free the
// handler to complete what it is doing and finish with cleanup.
closeSocket();
@@ -385,6 +384,7 @@ class PbapClientConnectionHandler extends Handler {
}
}
+ @VisibleForTesting
void downloadContacts(String path) {
try {
PhonebookPullRequest processor =
@@ -438,6 +438,7 @@ class PbapClientConnectionHandler extends Handler {
}
}
+ @VisibleForTesting
void downloadCallLog(String path, HashMap<String, Integer> callCounter) {
try {
BluetoothPbapRequestPullPhoneBook request =
@@ -453,7 +454,8 @@ class PbapClientConnectionHandler extends Handler {
}
}
- private boolean addAccount(Account account) {
+ @VisibleForTesting
+ boolean addAccount(Account account) {
if (mAccountManager.addAccountExplicitly(account, null, null)) {
if (DBG) {
Log.d(TAG, "Added account " + mAccount);
@@ -463,17 +465,19 @@ class PbapClientConnectionHandler extends Handler {
return false;
}
- private void removeAccount(Account account) {
- if (mAccountManager.removeAccountExplicitly(account)) {
+ @VisibleForTesting
+ void removeAccount() {
+ if (mAccountManager.removeAccountExplicitly(mAccount)) {
if (DBG) {
- Log.d(TAG, "Removed account " + account);
+ Log.d(TAG, "Removed account " + mAccount);
}
} else {
Log.e(TAG, "Failed to remove account " + mAccount);
}
}
- private void removeCallLog(Account account) {
+ @VisibleForTesting
+ void removeCallLog() {
try {
// need to check call table is exist ?
if (mContext.getContentResolver() == null) {
@@ -489,7 +493,8 @@ class PbapClientConnectionHandler extends Handler {
}
}
- private boolean isRepositorySupported(int mask) {
+ @VisibleForTesting
+ boolean isRepositorySupported(int mask) {
if (mPseRec == null) {
if (VDBG) Log.v(TAG, "No PBAP Server SDP Record");
return false;
diff --git a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
index 0b0fa265e3..d1e5fd0fe6 100644
--- a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
+++ b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
@@ -26,6 +26,7 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.BluetoothVolumeControl;
+import android.bluetooth.IBluetoothCsipSetCoordinator;
import android.bluetooth.IBluetoothLeAudio;
import android.bluetooth.IBluetoothVolumeControl;
import android.bluetooth.IBluetoothVolumeControlCallback;
@@ -47,6 +48,7 @@ import com.android.bluetooth.btservice.AdapterService;
import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.bluetooth.csip.CsipSetCoordinatorService;
import com.android.bluetooth.le_audio.LeAudioService;
import com.android.internal.annotations.VisibleForTesting;
import com.android.modules.utils.SynchronousResultReceiver;
@@ -193,7 +195,8 @@ public class VolumeControlService extends ProfileService {
private BroadcastReceiver mBondStateChangedReceiver;
private BroadcastReceiver mConnectionStateChangedReceiver;
- private final ServiceFactory mFactory = new ServiceFactory();
+ @VisibleForTesting
+ ServiceFactory mFactory = new ServiceFactory();
public static boolean isEnabled() {
return BluetoothProperties.isProfileVcpControllerEnabled().orElse(false);
@@ -586,6 +589,11 @@ public class VolumeControlService extends ProfileService {
* {@hide}
*/
public void setGroupVolume(int groupId, int volume) {
+ if (volume < 0) {
+ Log.w(TAG, "Tried to set invalid volume " + volume + ". Ignored.");
+ return;
+ }
+
mGroupVolumeCache.put(groupId, volume);
mVolumeControlNativeInterface.setGroupVolume(groupId, volume);
}
@@ -627,6 +635,29 @@ public class VolumeControlService extends ProfileService {
mVolumeControlNativeInterface.unmuteGroup(groupId);
}
+ /**
+ * {@hide}
+ */
+ public void handleGroupNodeAdded(int groupId, BluetoothDevice device) {
+ // Ignore disconnected device, its volume will be set once it connects
+ synchronized (mStateMachines) {
+ VolumeControlStateMachine sm = mStateMachines.get(device);
+ if (sm == null) {
+ return;
+ }
+ if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) {
+ return;
+ }
+ }
+
+ // If group volume has already changed, the new group member should set it
+ Integer groupVolume = mGroupVolumeCache.getOrDefault(groupId,
+ IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME);
+ if (groupVolume != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) {
+ mVolumeControlNativeInterface.setVolume(device, groupVolume);
+ }
+ }
+
void handleVolumeControlChanged(BluetoothDevice device, int groupId,
int volume, boolean mute, boolean isAutonomous) {
@@ -683,6 +714,15 @@ public class VolumeControlService extends ProfileService {
}
}
+ /**
+ * {@hide}
+ */
+ public int getAudioDeviceGroupVolume(int groupId) {
+ int volume = getGroupVolume(groupId);
+ if (volume == IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) return -1;
+ return getDeviceVolume(getBluetoothContextualVolumeStream(), volume);
+ }
+
int getDeviceVolume(int streamType, int bleVolume) {
int deviceMaxVolume = mAudioManager.getStreamMaxVolume(streamType);
@@ -960,6 +1000,17 @@ public class VolumeControlService extends ProfileService {
}
removeStateMachine(device);
}
+ } else if (toState == BluetoothProfile.STATE_CONNECTED) {
+ // Restore the group volume if it was changed while the device was not yet connected.
+ CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService();
+ Integer groupId = csipClient.getGroupId(device, BluetoothUuid.CAP);
+ if (groupId != IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID) {
+ Integer groupVolume = mGroupVolumeCache.getOrDefault(groupId,
+ IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME);
+ if (groupVolume != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) {
+ mVolumeControlNativeInterface.setVolume(device, groupVolume);
+ }
+ }
}
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
index b37ae31cbc..a11ee69e59 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/le_audio/LeAudioServiceTest.java
@@ -285,10 +285,9 @@ public class LeAudioServiceTest {
assertThat(intent).isNotNull();
assertThat(intent.getAction())
.isEqualTo(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
- assertThat(device).isEqualTo(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE));
- assertThat(newState).isEqualTo(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1));
- assertThat(prevState).isEqualTo(intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE,
- -1));
+ assertThat((BluetoothDevice)intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)).isEqualTo(device);
+ assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1)).isEqualTo(newState);
+ assertThat(intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1)).isEqualTo(prevState);
}
/**
@@ -701,31 +700,35 @@ public class LeAudioServiceTest {
// LeAudio stack event: CONNECTION_STATE_CONNECTING - state machine should be created
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTING,
BluetoothProfile.STATE_DISCONNECTED);
- assertThat(BluetoothProfile.STATE_CONNECTING)
- .isEqualTo(mService.getConnectionState(mLeftDevice));
+ assertThat(mService.getConnectionState(mLeftDevice))
+ .isEqualTo(BluetoothProfile.STATE_CONNECTING);
assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
// Device unbond - state machine is not removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+ verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
+ BluetoothProfile.STATE_CONNECTING);
// LeAudio stack event: CONNECTION_STATE_CONNECTED - state machine is not removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED);
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_CONNECTED,
- BluetoothProfile.STATE_CONNECTING);
- assertThat(BluetoothProfile.STATE_CONNECTED)
- .isEqualTo(mService.getConnectionState(mLeftDevice));
+ BluetoothProfile.STATE_DISCONNECTED);
+ assertThat(mService.getConnectionState(mLeftDevice))
+ .isEqualTo(BluetoothProfile.STATE_CONNECTED);
assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
// Device unbond - state machine is not removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+ verifyConnectionStateIntent(TIMEOUT_MS, mLeftDevice, BluetoothProfile.STATE_DISCONNECTING,
+ BluetoothProfile.STATE_CONNECTED);
+ assertThat(mService.getConnectionState(mLeftDevice))
+ .isEqualTo(BluetoothProfile.STATE_DISCONNECTING);
+ assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
// LeAudio stack event: CONNECTION_STATE_DISCONNECTING - state machine is not removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED);
- generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTING,
- BluetoothProfile.STATE_CONNECTED);
- assertThat(BluetoothProfile.STATE_DISCONNECTING)
- .isEqualTo(mService.getConnectionState(mLeftDevice));
- assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
+ assertThat(mService.getConnectionState(mLeftDevice))
+ .isEqualTo(BluetoothProfile.STATE_DISCONNECTING);
// Device unbond - state machine is not removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
@@ -734,8 +737,8 @@ public class LeAudioServiceTest {
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_BONDED);
generateConnectionMessageFromNative(mLeftDevice, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.STATE_DISCONNECTING);
- assertThat(BluetoothProfile.STATE_DISCONNECTED)
- .isEqualTo(mService.getConnectionState(mLeftDevice));
+ assertThat(mService.getConnectionState(mLeftDevice))
+ .isEqualTo(BluetoothProfile.STATE_DISCONNECTED);
assertThat(mService.getDevices().contains(mLeftDevice)).isTrue();
// Device unbond - state machine is removed
mService.bondStateChanged(mLeftDevice, BluetoothDevice.BOND_NONE);
@@ -1428,7 +1431,7 @@ public class LeAudioServiceTest {
//Add location support.
injectAudioConfChanged(groupId, availableContexts);
- doReturn(-1).when(mVolumeControlService).getGroupVolume(groupId);
+ doReturn(-1).when(mVolumeControlService).getAudioDeviceGroupVolume(groupId);
//Set group and device as active.
injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
@@ -1445,7 +1448,7 @@ public class LeAudioServiceTest {
verify(mAudioManager, times(1)).handleBluetoothActiveDeviceChanged(eq(null), any(),
any(BluetoothProfileConnectionInfo.class));
- doReturn(100).when(mVolumeControlService).getGroupVolume(groupId);
+ doReturn(100).when(mVolumeControlService).getAudioDeviceGroupVolume(groupId);
//Set back to active and check if last volume is restored.
injectGroupStatusChange(groupId, LeAudioStackEvent.GROUP_STATUS_ACTIVE);
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoContactElementTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoContactElementTest.java
new file mode 100644
index 0000000000..4e6a144e90
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoContactElementTest.java
@@ -0,0 +1,203 @@
+/*
+ * 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.map;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.SignedLongLong;
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.text.SimpleDateFormat;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothMapConvoContactElementTest {
+ private static final String TEST_UCI = "test_bt_uci";
+ private static final String TEST_NAME = "test_name";
+ private static final String TEST_DISPLAY_NAME = "test_display_name";
+ private static final String TEST_PRESENCE_STATUS = "test_presence_status";
+ private static final int TEST_PRESENCE_AVAILABILITY = 2;
+ private static final long TEST_LAST_ACTIVITY = 1;
+ private static final int TEST_CHAT_STATE = 2;
+ private static final int TEST_PRIORITY = 1;
+ private static final String TEST_BT_UID = "1111";
+
+ private final SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
+
+ @Mock
+ private MapContact mMapContact;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void constructorWithArguments() {
+ BluetoothMapConvoContactElement contactElement =
+ new BluetoothMapConvoContactElement(TEST_UCI, TEST_NAME, TEST_DISPLAY_NAME,
+ TEST_PRESENCE_STATUS, TEST_PRESENCE_AVAILABILITY, TEST_LAST_ACTIVITY,
+ TEST_CHAT_STATE, TEST_PRIORITY, TEST_BT_UID);
+
+ assertThat(contactElement.getContactId()).isEqualTo(TEST_UCI);
+ assertThat(contactElement.getName()).isEqualTo(TEST_NAME);
+ assertThat(contactElement.getDisplayName()).isEqualTo(TEST_DISPLAY_NAME);
+ assertThat(contactElement.getPresenceStatus()).isEqualTo(TEST_PRESENCE_STATUS);
+ assertThat(contactElement.getPresenceAvailability()).isEqualTo(TEST_PRESENCE_AVAILABILITY);
+ assertThat(contactElement.getLastActivityString()).isEqualTo(
+ format.format(TEST_LAST_ACTIVITY));
+ assertThat(contactElement.getChatState()).isEqualTo(TEST_CHAT_STATE);
+ assertThat(contactElement.getPriority()).isEqualTo(TEST_PRIORITY);
+ assertThat(contactElement.getBtUid()).isEqualTo(TEST_BT_UID);
+ }
+
+ @Test
+ public void createFromMapContact() {
+ final long id = 1111;
+ final SignedLongLong signedLongLong = new SignedLongLong(id, 0);
+ when(mMapContact.getId()).thenReturn(id);
+ when(mMapContact.getName()).thenReturn(TEST_DISPLAY_NAME);
+ BluetoothMapConvoContactElement contactElement =
+ BluetoothMapConvoContactElement.createFromMapContact(mMapContact, TEST_UCI);
+ assertThat(contactElement.getContactId()).isEqualTo(TEST_UCI);
+ assertThat(contactElement.getBtUid()).isEqualTo(signedLongLong.toHexString());
+ assertThat(contactElement.getDisplayName()).isEqualTo(TEST_DISPLAY_NAME);
+ }
+
+ @Test
+ public void settersAndGetters() throws Exception {
+ BluetoothMapConvoContactElement contactElement = new BluetoothMapConvoContactElement();
+ contactElement.setDisplayName(TEST_DISPLAY_NAME);
+ contactElement.setPresenceStatus(TEST_PRESENCE_STATUS);
+ contactElement.setPresenceAvailability(TEST_PRESENCE_AVAILABILITY);
+ contactElement.setPriority(TEST_PRIORITY);
+ contactElement.setName(TEST_NAME);
+ contactElement.setBtUid(SignedLongLong.fromString(TEST_BT_UID));
+ contactElement.setChatState(TEST_CHAT_STATE);
+ contactElement.setLastActivity(TEST_LAST_ACTIVITY);
+ contactElement.setContactId(TEST_UCI);
+
+ assertThat(contactElement.getContactId()).isEqualTo(TEST_UCI);
+ assertThat(contactElement.getName()).isEqualTo(TEST_NAME);
+ assertThat(contactElement.getDisplayName()).isEqualTo(TEST_DISPLAY_NAME);
+ assertThat(contactElement.getPresenceStatus()).isEqualTo(TEST_PRESENCE_STATUS);
+ assertThat(contactElement.getPresenceAvailability()).isEqualTo(TEST_PRESENCE_AVAILABILITY);
+ assertThat(contactElement.getLastActivityString()).isEqualTo(
+ format.format(TEST_LAST_ACTIVITY));
+ assertThat(contactElement.getChatState()).isEqualTo(TEST_CHAT_STATE);
+ assertThat(contactElement.getPriority()).isEqualTo(TEST_PRIORITY);
+ assertThat(contactElement.getBtUid()).isEqualTo(TEST_BT_UID);
+ }
+
+ @Test
+ public void encodeToXml_thenDecodeToInstance_returnsCorrectly() throws Exception {
+ BluetoothMapConvoContactElement contactElement = new
+ BluetoothMapConvoContactElement(TEST_UCI, TEST_NAME, TEST_DISPLAY_NAME,
+ TEST_PRESENCE_STATUS, TEST_PRESENCE_AVAILABILITY, TEST_LAST_ACTIVITY,
+ TEST_CHAT_STATE, TEST_PRIORITY, TEST_BT_UID);
+
+ final XmlSerializer serializer = new FastXmlSerializer();
+ final StringWriter writer = new StringWriter();
+
+ serializer.setOutput(writer);
+ serializer.startDocument("UTF-8", true);
+ contactElement.encode(serializer);
+ serializer.endDocument();
+
+ final XmlPullParserFactory parserFactory = XmlPullParserFactory.newInstance();
+ parserFactory.setNamespaceAware(true);
+ final XmlPullParser parser;
+ parser = parserFactory.newPullParser();
+
+ parser.setInput(new StringReader(writer.toString()));
+ parser.next();
+
+ BluetoothMapConvoContactElement contactElementFromXml =
+ BluetoothMapConvoContactElement.createFromXml(parser);
+
+ assertThat(contactElementFromXml.getContactId()).isEqualTo(TEST_UCI);
+ assertThat(contactElementFromXml.getName()).isEqualTo(TEST_NAME);
+ assertThat(contactElementFromXml.getDisplayName()).isEqualTo(TEST_DISPLAY_NAME);
+ assertThat(contactElementFromXml.getPresenceStatus()).isEqualTo(TEST_PRESENCE_STATUS);
+ assertThat(contactElementFromXml.getPresenceAvailability()).isEqualTo(
+ TEST_PRESENCE_AVAILABILITY);
+ assertThat(contactElementFromXml.getLastActivityString()).isEqualTo(
+ format.format(TEST_LAST_ACTIVITY));
+ assertThat(contactElementFromXml.getChatState()).isEqualTo(TEST_CHAT_STATE);
+ assertThat(contactElementFromXml.getPriority()).isEqualTo(TEST_PRIORITY);
+ assertThat(contactElementFromXml.getBtUid()).isEqualTo(TEST_BT_UID);
+ }
+
+ @Test
+ public void equalsWithSameValues_returnsTrue() {
+ BluetoothMapConvoContactElement contactElement =
+ new BluetoothMapConvoContactElement(TEST_UCI, TEST_NAME, TEST_DISPLAY_NAME,
+ TEST_PRESENCE_STATUS, TEST_PRESENCE_AVAILABILITY, TEST_LAST_ACTIVITY,
+ TEST_CHAT_STATE, TEST_PRIORITY, TEST_BT_UID);
+
+ BluetoothMapConvoContactElement contactElementEqual =
+ new BluetoothMapConvoContactElement(TEST_UCI, TEST_NAME, TEST_DISPLAY_NAME,
+ TEST_PRESENCE_STATUS, TEST_PRESENCE_AVAILABILITY, TEST_LAST_ACTIVITY,
+ TEST_CHAT_STATE, TEST_PRIORITY, TEST_BT_UID);
+
+ assertThat(contactElement).isEqualTo(contactElementEqual);
+ }
+
+ @Test
+ public void equalsWithDifferentPriority_returnsFalse() {
+ BluetoothMapConvoContactElement contactElement =
+ new BluetoothMapConvoContactElement(TEST_UCI, TEST_NAME, TEST_DISPLAY_NAME,
+ TEST_PRESENCE_STATUS, TEST_PRESENCE_AVAILABILITY, TEST_LAST_ACTIVITY,
+ TEST_CHAT_STATE, TEST_PRIORITY, TEST_BT_UID);
+
+ BluetoothMapConvoContactElement contactElementWithDifferentPriority =
+ new BluetoothMapConvoContactElement(TEST_UCI, TEST_NAME, TEST_DISPLAY_NAME,
+ TEST_PRESENCE_STATUS, TEST_PRESENCE_AVAILABILITY, TEST_LAST_ACTIVITY,
+ TEST_CHAT_STATE, /*priority=*/0, TEST_BT_UID);
+
+ assertThat(contactElement).isNotEqualTo(contactElementWithDifferentPriority);
+ }
+
+ @Test
+ public void compareTo_withSameValues_returnsZero() {
+ BluetoothMapConvoContactElement contactElement =
+ new BluetoothMapConvoContactElement(TEST_UCI, TEST_NAME, TEST_DISPLAY_NAME,
+ TEST_PRESENCE_STATUS, TEST_PRESENCE_AVAILABILITY, TEST_LAST_ACTIVITY,
+ TEST_CHAT_STATE, TEST_PRIORITY, TEST_BT_UID);
+
+ BluetoothMapConvoContactElement contactElementSameLastActivity =
+ new BluetoothMapConvoContactElement(TEST_UCI, TEST_NAME, TEST_DISPLAY_NAME,
+ TEST_PRESENCE_STATUS, TEST_PRESENCE_AVAILABILITY, TEST_LAST_ACTIVITY,
+ TEST_CHAT_STATE, TEST_PRIORITY, TEST_BT_UID);
+
+ assertThat(contactElement.compareTo(contactElementSameLastActivity)).isEqualTo(0);
+ }
+} \ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoListingElementTest.java b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoListingElementTest.java
new file mode 100644
index 0000000000..84b0d7fac2
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/map/BluetoothMapConvoListingElementTest.java
@@ -0,0 +1,192 @@
+/*
+ * 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.map;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.SignedLongLong;
+import com.android.bluetooth.map.BluetoothMapUtils.TYPE;
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class BluetoothMapConvoListingElementTest {
+ private static final long TEST_ID = 1111;
+ private static final String TEST_NAME = "test_name";
+ private static final long TEST_LAST_ACTIVITY = 0;
+ private static final boolean TEST_READ = true;
+ private static final boolean TEST_REPORT_READ = true;
+ private static final long TEST_VERSION_COUNTER = 0;
+ private static final int TEST_CURSOR_INDEX = 1;
+ private static final TYPE TEST_TYPE = TYPE.EMAIL;
+ private static final String TEST_SUMMARY = "test_summary";
+ private static final String TEST_SMS_MMS_CONTACTS = "test_sms_mms_contacts";
+
+ private final BluetoothMapConvoContactElement TEST_CONTACT_ELEMENT_ONE =
+ new BluetoothMapConvoContactElement("test_uci_one", "test_name_one",
+ "test_display_name_one", "test_presence_status_one", 2, TEST_LAST_ACTIVITY, 2,
+ 1, "1111");
+
+ private final BluetoothMapConvoContactElement TEST_CONTACT_ELEMENT_TWO =
+ new BluetoothMapConvoContactElement("test_uci_two", "test_name_two",
+ "test_display_name_two", "test_presence_status_two", 1, TEST_LAST_ACTIVITY, 1,
+ 2, "1112");
+
+ private final List<BluetoothMapConvoContactElement> TEST_CONTACTS = new ArrayList<>(
+ Arrays.asList(TEST_CONTACT_ELEMENT_ONE, TEST_CONTACT_ELEMENT_TWO));
+
+ private final SignedLongLong signedLongLong = new SignedLongLong(TEST_ID, 0);
+
+ private BluetoothMapConvoListingElement mListingElement;
+
+ @Before
+ public void setUp() throws Exception {
+ mListingElement = new BluetoothMapConvoListingElement();
+
+ mListingElement.setCursorIndex(TEST_CURSOR_INDEX);
+ mListingElement.setVersionCounter(TEST_VERSION_COUNTER);
+ mListingElement.setName(TEST_NAME);
+ mListingElement.setType(TEST_TYPE);
+ mListingElement.setContacts(TEST_CONTACTS);
+ mListingElement.setLastActivity(TEST_LAST_ACTIVITY);
+ mListingElement.setRead(TEST_READ, TEST_REPORT_READ);
+ mListingElement.setConvoId(0, TEST_ID);
+ mListingElement.setSummary(TEST_SUMMARY);
+ mListingElement.setSmsMmsContacts(TEST_SMS_MMS_CONTACTS);
+ }
+
+ @Test
+ public void getters() throws Exception {
+ assertThat(mListingElement.getCursorIndex()).isEqualTo(TEST_CURSOR_INDEX);
+ assertThat(mListingElement.getVersionCounter()).isEqualTo(TEST_VERSION_COUNTER);
+ assertThat(mListingElement.getName()).isEqualTo(TEST_NAME);
+ assertThat(mListingElement.getType()).isEqualTo(TEST_TYPE);
+ assertThat(mListingElement.getContacts()).isEqualTo(TEST_CONTACTS);
+ assertThat(mListingElement.getLastActivity()).isEqualTo(TEST_LAST_ACTIVITY);
+ assertThat(mListingElement.getRead()).isEqualTo("READ");
+ assertThat(mListingElement.getReadBool()).isEqualTo(TEST_READ);
+ assertThat(mListingElement.getConvoId()).isEqualTo(signedLongLong.toHexString());
+ assertThat(mListingElement.getCpConvoId()).isEqualTo(
+ signedLongLong.getLeastSignificantBits());
+ assertThat(mListingElement.getFullSummary()).isEqualTo(TEST_SUMMARY);
+ assertThat(mListingElement.getSmsMmsContacts()).isEqualTo(TEST_SMS_MMS_CONTACTS);
+ }
+
+ @Test
+ public void incrementVersionCounter() {
+ mListingElement.incrementVersionCounter();
+ assertThat(mListingElement.getVersionCounter()).isEqualTo(TEST_VERSION_COUNTER + 1);
+ }
+
+ @Test
+ public void removeContactWithObject() {
+ mListingElement.removeContact(TEST_CONTACT_ELEMENT_TWO);
+ assertThat(mListingElement.getContacts().size()).isEqualTo(1);
+ }
+
+ @Test
+ public void removeContactWithIndex() {
+ mListingElement.removeContact(1);
+ assertThat(mListingElement.getContacts().size()).isEqualTo(1);
+ }
+
+ @Test
+ public void encodeToXml_thenDecodeToInstance_returnsCorrectly() throws Exception {
+ final XmlSerializer serializer = new FastXmlSerializer();
+ final StringWriter writer = new StringWriter();
+
+ serializer.setOutput(writer);
+ serializer.startDocument("UTF-8", true);
+ mListingElement.encode(serializer);
+ serializer.endDocument();
+
+ final XmlPullParserFactory parserFactory = XmlPullParserFactory.newInstance();
+ parserFactory.setNamespaceAware(true);
+ final XmlPullParser parser;
+ parser = parserFactory.newPullParser();
+
+ parser.setInput(new StringReader(writer.toString()));
+ parser.next();
+
+ BluetoothMapConvoListingElement listingElementFromXml =
+ BluetoothMapConvoListingElement.createFromXml(parser);
+
+ assertThat(listingElementFromXml.getVersionCounter()).isEqualTo(0);
+ assertThat(listingElementFromXml.getName()).isEqualTo(TEST_NAME);
+ assertThat(listingElementFromXml.getContacts()).isEqualTo(TEST_CONTACTS);
+ assertThat(listingElementFromXml.getLastActivity()).isEqualTo(TEST_LAST_ACTIVITY);
+ assertThat(listingElementFromXml.getRead()).isEqualTo("UNREAD");
+ assertThat(listingElementFromXml.getConvoId()).isEqualTo(signedLongLong.toHexString());
+ assertThat(listingElementFromXml.getFullSummary().trim()).isEqualTo(TEST_SUMMARY);
+ }
+
+ @Test
+ public void equalsWithSameValues_returnsTrue() {
+ BluetoothMapConvoListingElement listingElement = new BluetoothMapConvoListingElement();
+ listingElement.setName(TEST_NAME);
+ listingElement.setContacts(TEST_CONTACTS);
+ listingElement.setLastActivity(TEST_LAST_ACTIVITY);
+ listingElement.setRead(TEST_READ, TEST_REPORT_READ);
+
+ BluetoothMapConvoListingElement listingElementEqual = new BluetoothMapConvoListingElement();
+ listingElementEqual.setName(TEST_NAME);
+ listingElementEqual.setContacts(TEST_CONTACTS);
+ listingElementEqual.setLastActivity(TEST_LAST_ACTIVITY);
+ listingElementEqual.setRead(TEST_READ, TEST_REPORT_READ);
+
+ assertThat(listingElement).isEqualTo(listingElementEqual);
+ }
+
+ @Test
+ public void equalsWithDifferentRead_returnsFalse() {
+ BluetoothMapConvoListingElement
+ listingElement = new BluetoothMapConvoListingElement();
+
+ BluetoothMapConvoListingElement listingElementWithDifferentRead =
+ new BluetoothMapConvoListingElement();
+ listingElementWithDifferentRead.setRead(TEST_READ, TEST_REPORT_READ);
+
+ assertThat(listingElement).isNotEqualTo(listingElementWithDifferentRead);
+ }
+
+ @Test
+ public void compareToWithSameValues_returnsZero() {
+ BluetoothMapConvoListingElement
+ listingElement = new BluetoothMapConvoListingElement();
+ listingElement.setLastActivity(TEST_LAST_ACTIVITY);
+
+ BluetoothMapConvoListingElement listingElementSameLastActivity =
+ new BluetoothMapConvoListingElement();
+ listingElementSameLastActivity.setLastActivity(TEST_LAST_ACTIVITY);
+
+ assertThat(listingElement.compareTo(listingElementSameLastActivity)).isEqualTo(0);
+ }
+} \ No newline at end of file
diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposerTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposerTest.java
index d8e2b62647..33089f4cc0 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapCallLogComposerTest.java
@@ -38,6 +38,8 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.bluetooth.BluetoothMethodProxy;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -61,7 +63,7 @@ public class BluetoothPbapCallLogComposerTest {
private BluetoothPbapCallLogComposer mComposer;
@Spy
- BluetoothPbapMethodProxy mPbapCallProxy = BluetoothPbapMethodProxy.getInstance();
+ BluetoothMethodProxy mPbapCallProxy = BluetoothMethodProxy.getInstance();
@Mock
Cursor mMockCursor;
@@ -69,7 +71,7 @@ public class BluetoothPbapCallLogComposerTest {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- BluetoothPbapMethodProxy.setInstanceForTesting(mPbapCallProxy);
+ BluetoothMethodProxy.setInstanceForTesting(mPbapCallProxy);
doReturn(mMockCursor).when(mPbapCallProxy)
.contentResolverQuery(any(), any(), any(), any(), any(), any());
@@ -82,7 +84,7 @@ public class BluetoothPbapCallLogComposerTest {
@After
public void tearDown() throws Exception {
- BluetoothPbapMethodProxy.setInstanceForTesting(null);
+ BluetoothMethodProxy.setInstanceForTesting(null);
}
@Test
diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapObexServerTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapObexServerTest.java
index b888930020..1ad09189af 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapObexServerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapObexServerTest.java
@@ -27,15 +27,14 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.Context;
import android.os.Handler;
import android.os.UserManager;
-import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.bluetooth.BluetoothMethodProxy;
import com.android.obex.HeaderSet;
import com.android.obex.Operation;
import com.android.obex.ResponseCodes;
@@ -63,7 +62,7 @@ public class BluetoothPbapObexServerTest {
@Mock PbapStateMachine mMockStateMachine;
@Spy
- BluetoothPbapMethodProxy mPbapMethodProxy = BluetoothPbapMethodProxy.getInstance();
+ BluetoothMethodProxy mPbapMethodProxy = BluetoothMethodProxy.getInstance();
BluetoothPbapObexServer mServer;
@@ -85,14 +84,14 @@ public class BluetoothPbapObexServerTest {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- BluetoothPbapMethodProxy.setInstanceForTesting(mPbapMethodProxy);
+ BluetoothMethodProxy.setInstanceForTesting(mPbapMethodProxy);
mServer = new BluetoothPbapObexServer(
mMockHandler, InstrumentationRegistry.getTargetContext(), mMockStateMachine);
}
@After
public void tearDown() throws Exception {
- BluetoothPbapMethodProxy.setInstanceForTesting(null);
+ BluetoothMethodProxy.setInstanceForTesting(null);
}
@Test
diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapSimVcardManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapSimVcardManagerTest.java
index 5492e1cc98..023027542c 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapSimVcardManagerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapSimVcardManagerTest.java
@@ -35,6 +35,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.bluetooth.BluetoothMethodProxy;
import com.android.obex.Operation;
import com.android.obex.ResponseCodes;
@@ -60,7 +61,7 @@ public class BluetoothPbapSimVcardManagerTest {
private static final String TAG = BluetoothPbapSimVcardManagerTest.class.getSimpleName();
@Spy
- BluetoothPbapMethodProxy mPbapMethodProxy = BluetoothPbapMethodProxy.getInstance();
+ BluetoothMethodProxy mPbapMethodProxy = BluetoothMethodProxy.getInstance();
Context mContext;
BluetoothPbapSimVcardManager mManager;
@@ -70,14 +71,14 @@ public class BluetoothPbapSimVcardManagerTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- BluetoothPbapMethodProxy.setInstanceForTesting(mPbapMethodProxy);
+ BluetoothMethodProxy.setInstanceForTesting(mPbapMethodProxy);
mContext = InstrumentationRegistry.getTargetContext();
mManager = new BluetoothPbapSimVcardManager(mContext);
}
@After
public void tearDown() {
- BluetoothPbapMethodProxy.setInstanceForTesting(null);
+ BluetoothMethodProxy.setInstanceForTesting(null);
}
@Test
diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapVcardManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapVcardManagerTest.java
index b6cceb5709..fa8bb5a51e 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapVcardManagerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/pbap/BluetoothPbapVcardManagerTest.java
@@ -27,7 +27,6 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.database.Cursor;
-import android.net.Uri;
import android.provider.CallLog;
import android.provider.ContactsContract;
@@ -35,6 +34,7 @@ import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.bluetooth.BluetoothMethodProxy;
import com.android.bluetooth.R;
import org.junit.After;
@@ -56,7 +56,7 @@ public class BluetoothPbapVcardManagerTest {
private static final String TAG = BluetoothPbapVcardManagerTest.class.getSimpleName();
@Spy
- BluetoothPbapMethodProxy mPbapMethodProxy = BluetoothPbapMethodProxy.getInstance();
+ BluetoothMethodProxy mPbapMethodProxy = BluetoothMethodProxy.getInstance();
Context mContext;
BluetoothPbapVcardManager mManager;
@@ -64,14 +64,14 @@ public class BluetoothPbapVcardManagerTest {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- BluetoothPbapMethodProxy.setInstanceForTesting(mPbapMethodProxy);
+ BluetoothMethodProxy.setInstanceForTesting(mPbapMethodProxy);
mContext = InstrumentationRegistry.getTargetContext();
mManager = new BluetoothPbapVcardManager(mContext);
}
@After
public void tearDown() {
- BluetoothPbapMethodProxy.setInstanceForTesting(null);
+ BluetoothMethodProxy.setInstanceForTesting(null);
}
@Test
diff --git a/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandlerTest.java b/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandlerTest.java
new file mode 100644
index 0000000000..dc83c978a4
--- /dev/null
+++ b/android/app/tests/unit/src/com/android/bluetooth/pbapclient/PbapClientConnectionHandlerTest.java
@@ -0,0 +1,252 @@
+/*
+ * 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.pbapclient;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.accounts.Account;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.SdpPseRecord;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ServiceTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.bluetooth.TestUtils;
+import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashMap;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PbapClientConnectionHandlerTest {
+
+ private static final String TAG = "ConnHandlerTest";
+ private static final String REMOTE_DEVICE_ADDRESS = "00:00:00:00:00:00";
+
+ private HandlerThread mThread;
+ private Looper mLooper;
+ private Context mTargetContext;
+ private BluetoothDevice mRemoteDevice;
+
+ @Rule
+ public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+ @Mock
+ private AdapterService mAdapterService;
+
+ @Mock
+ private DatabaseManager mDatabaseManager;
+
+ private BluetoothAdapter mAdapter;
+
+ private PbapClientService mService;
+
+ private PbapClientStateMachine mStateMachine;
+
+ private PbapClientConnectionHandler mHandler;
+
+ @Before
+ public void setUp() throws Exception {
+ mTargetContext = spy(new ContextWrapper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
+ Assume.assumeTrue("Ignore test when PbapClientService is not enabled",
+ PbapClientService.isEnabled());
+ MockitoAnnotations.initMocks(this);
+ TestUtils.setAdapterService(mAdapterService);
+ doReturn(mDatabaseManager).when(mAdapterService).getDatabase();
+ doReturn(true, false).when(mAdapterService)
+ .isStartedProfile(anyString());
+ TestUtils.startService(mServiceRule, PbapClientService.class);
+ mService = PbapClientService.getPbapClientService();
+ assertThat(mService).isNotNull();
+
+ mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+ mThread = new HandlerThread("test_handler_thread");
+ mThread.start();
+ mLooper = mThread.getLooper();
+ mRemoteDevice = mAdapter.getRemoteDevice(REMOTE_DEVICE_ADDRESS);
+
+ mStateMachine = new PbapClientStateMachine(mService, mRemoteDevice);
+ mHandler = new PbapClientConnectionHandler.Builder()
+ .setLooper(mLooper)
+ .setClientSM(mStateMachine)
+ .setContext(mTargetContext)
+ .setRemoteDevice(mRemoteDevice)
+ .build();
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (!PbapClientService.isEnabled()) {
+ return;
+ }
+ TestUtils.stopService(mServiceRule, PbapClientService.class);
+ mService = PbapClientService.getPbapClientService();
+ assertThat(mService).isNull();
+ TestUtils.clearAdapterService(mAdapterService);
+ mLooper.quit();
+ }
+
+ @Test
+ public void connectSocket_whenBluetoothIsNotEnabled_returnsFalse() {
+ assertThat(mHandler.connectSocket()).isFalse();
+ }
+
+ @Test
+ public void connectSocket_whenBluetoothIsNotEnabled_returnsFalse_withInvalidL2capPsm() {
+ SdpPseRecord record = mock(SdpPseRecord.class);
+ mHandler.setPseRecord(record);
+
+ when(record.getL2capPsm()).thenReturn(PbapClientConnectionHandler.L2CAP_INVALID_PSM);
+ assertThat(mHandler.connectSocket()).isFalse();
+ }
+
+ @Test
+ public void connectSocket_whenBluetoothIsNotEnabled_returnsFalse_withValidL2capPsm() {
+ SdpPseRecord record = mock(SdpPseRecord.class);
+ mHandler.setPseRecord(record);
+
+ when(record.getL2capPsm()).thenReturn(1); // Valid PSM ranges 1 to 30;
+ assertThat(mHandler.connectSocket()).isFalse();
+ }
+
+ // TODO: Add connectObexSession_returnsTrue
+
+ @Test
+ public void connectObexSession_returnsFalse_withoutConnectingSocket() {
+ assertThat(mHandler.connectObexSession()).isFalse();
+ }
+
+ @Test
+ public void abort() {
+ SdpPseRecord record = mock(SdpPseRecord.class);
+ when(record.getL2capPsm()).thenReturn(1); // Valid PSM ranges 1 to 30;
+ mHandler.setPseRecord(record);
+ mHandler.connectSocket(); // Workaround for setting mSocket as non-null value
+ assertThat(mHandler.getSocket()).isNotNull();
+
+ mHandler.abort();
+
+ assertThat(mThread.isInterrupted()).isTrue();
+ assertThat(mHandler.getSocket()).isNull();
+ }
+
+ @Test
+ public void downloadContacts() {
+ final String path = PbapClientConnectionHandler.PB_PATH;
+
+ try {
+ mHandler.downloadContacts(path);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception happened.", e);
+ assertWithMessage("Exception should not be thrown!").fail();
+ }
+ }
+
+ @Test
+ public void downloadCallLog() {
+ final String path = PbapClientConnectionHandler.ICH_PATH;
+ final HashMap<String, Integer> callCounter = new HashMap<>();
+
+ try {
+ mHandler.downloadCallLog(path, callCounter);
+ } catch (Exception e) {
+ Log.e(TAG, "Exception happened.", e);
+ assertWithMessage("Exception should not be thrown!").fail();
+ }
+ }
+
+ @Test
+ public void addAccount() {
+ try {
+ mHandler.addAccount(mock(Account.class));
+ } catch (Exception e) {
+ Log.e(TAG, "Exception happened.", e);
+ assertWithMessage("Exception should not be thrown!").fail();
+ }
+ }
+
+ @Test
+ public void removeAccount() {
+ try {
+ mHandler.removeAccount();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception happened.", e);
+ assertWithMessage("Exception should not be thrown!").fail();
+ }
+ }
+
+ @Test
+ public void removeCallLog() {
+ try {
+ ContentResolver res = mock(ContentResolver.class);
+ when(mTargetContext.getContentResolver()).thenReturn(res);
+ mHandler.removeCallLog();
+
+ when(mTargetContext.getContentResolver()).thenReturn(null);
+ mHandler.removeCallLog();
+ } catch (Exception e) {
+ Log.e(TAG, "Exception happened.", e);
+ assertWithMessage("Exception should not be thrown!").fail();
+ }
+ }
+
+ @Test
+ public void isRepositorySupported_withoutSettingPseRecord_returnsFalse() {
+ mHandler.setPseRecord(null);
+ final int mask = 0x11;
+
+ assertThat(mHandler.isRepositorySupported(mask)).isFalse();
+ }
+
+ @Test
+ public void isRepositorySupported_withSettingPseRecord() {
+ SdpPseRecord record = mock(SdpPseRecord.class);
+ when(record.getSupportedRepositories()).thenReturn(1);
+ mHandler.setPseRecord(record);
+ final int mask = 0x11;
+
+ assertThat(mHandler.isRepositorySupported(mask)).isTrue();
+ }
+}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java
index 7ca7623792..bdd81699bb 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java
@@ -41,7 +41,9 @@ import androidx.test.rule.ServiceTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.bluetooth.btservice.AdapterService;
+import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
+import com.android.bluetooth.csip.CsipSetCoordinatorService;
import com.android.bluetooth.R;
import com.android.bluetooth.TestUtils;
import com.android.bluetooth.x.com.android.modules.utils.SynchronousResultReceiver;
@@ -73,6 +75,7 @@ public class VolumeControlServiceTest {
private VolumeControlService mService;
private VolumeControlService.BluetoothVolumeControlBinder mServiceBinder;
private BluetoothDevice mDevice;
+ private BluetoothDevice mDeviceTwo;
private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mDeviceQueueMap;
private static final int TIMEOUT_MS = 1000;
private static final int BT_LE_AUDIO_MAX_VOL = 255;
@@ -87,6 +90,8 @@ public class VolumeControlServiceTest {
@Mock private DatabaseManager mDatabaseManager;
@Mock private VolumeControlNativeInterface mNativeInterface;
@Mock private AudioManager mAudioManager;
+ @Mock private ServiceFactory mServiceFactory;
+ @Mock private CsipSetCoordinatorService mCsipService;
@Rule public final ServiceTestRule mServiceRule = new ServiceTestRule();
@@ -119,9 +124,12 @@ public class VolumeControlServiceTest {
startService();
mService.mVolumeControlNativeInterface = mNativeInterface;
mService.mAudioManager = mAudioManager;
+ mService.mFactory = mServiceFactory;
mServiceBinder = (VolumeControlService.BluetoothVolumeControlBinder) mService.initBinder();
mServiceBinder.mIsTesting = true;
+ doReturn(mCsipService).when(mServiceFactory).getCsipSetCoordinatorService();
+
// Override the timeout value to speed up the test
VolumeControlStateMachine.sConnectTimeoutMs = TIMEOUT_MS; // 1s
@@ -134,8 +142,10 @@ public class VolumeControlServiceTest {
// Get a device for testing
mDevice = TestUtils.getTestDevice(mAdapter, 0);
+ mDeviceTwo = TestUtils.getTestDevice(mAdapter, 1);
mDeviceQueueMap = new HashMap<>();
mDeviceQueueMap.put(mDevice, new LinkedBlockingQueue<>());
+ mDeviceQueueMap.put(mDeviceTwo, new LinkedBlockingQueue<>());
doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService)
.getBondState(any(BluetoothDevice.class));
doReturn(new ParcelUuid[]{BluetoothUuid.VOLUME_CONTROL}).when(mAdapterService)
@@ -631,6 +641,92 @@ public class VolumeControlServiceTest {
Assert.assertEquals(volume, mService.getGroupVolume(groupId));
}
+ /**
+ * Test setting volume for a group member who connects after the volume level
+ * for a group was already changed and cached.
+ */
+ @Test
+ public void testLateConnectingDevice() throws Exception {
+ int groupId = 1;
+ int groupVolume = 56;
+
+ // Both devices are in the same group
+ when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId);
+ when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId);
+
+ // Update the device policy so okToConnect() returns true
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(any(BluetoothDevice.class),
+ eq(BluetoothProfile.VOLUME_CONTROL)))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
+ doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));
+
+ generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+ mService.getConnectionState(mDevice));
+ Assert.assertTrue(mService.getDevices().contains(mDevice));
+
+ mService.setGroupVolume(groupId, groupVolume);
+ verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(groupVolume));
+ verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(groupVolume));
+
+ // Verify that second device gets the proper group volume level when connected
+ generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+ mService.getConnectionState(mDeviceTwo));
+ Assert.assertTrue(mService.getDevices().contains(mDeviceTwo));
+ verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(groupVolume));
+ }
+
+ /**
+ * Test setting volume for a new group member who is discovered after the volume level
+ * for a group was already changed and cached.
+ */
+ @Test
+ public void testLateDiscoveredGroupMember() throws Exception {
+ int groupId = 1;
+ int groupVolume = 56;
+
+ // For now only one device is in the group
+ when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId);
+ when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(-1);
+
+ // Update the device policy so okToConnect() returns true
+ when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager);
+ when(mDatabaseManager
+ .getProfileConnectionPolicy(any(BluetoothDevice.class),
+ eq(BluetoothProfile.VOLUME_CONTROL)))
+ .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
+ doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));
+
+ generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+ mService.getConnectionState(mDevice));
+ Assert.assertTrue(mService.getDevices().contains(mDevice));
+
+ // Set the group volume
+ mService.setGroupVolume(groupId, groupVolume);
+
+ // Verify that second device will not get the group volume level if it is not a group member
+ generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED,
+ BluetoothProfile.STATE_DISCONNECTED);
+ Assert.assertEquals(BluetoothProfile.STATE_CONNECTED,
+ mService.getConnectionState(mDeviceTwo));
+ Assert.assertTrue(mService.getDevices().contains(mDeviceTwo));
+ verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(groupVolume));
+
+ // But gets the volume when it becomes the group member
+ when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId);
+ mService.handleGroupNodeAdded(groupId, mDeviceTwo);
+ verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(groupVolume));
+ }
+
@Test
public void testServiceBinderGetDevicesMatchingConnectionStates() throws Exception {
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
diff --git a/android/pandora/.gitignore b/android/pandora/.gitignore
new file mode 100644
index 0000000000..cdb0870bb5
--- /dev/null
+++ b/android/pandora/.gitignore
@@ -0,0 +1,3 @@
+trace*
+log*
+out*
diff --git a/android/pandora/gen_cov.py b/android/pandora/gen_cov.py
new file mode 100755
index 0000000000..3943341b06
--- /dev/null
+++ b/android/pandora/gen_cov.py
@@ -0,0 +1,322 @@
+#!/usr/bin/env python
+
+import argparse
+import os
+from pathlib import Path
+import shutil
+import subprocess
+import sys
+import xml.etree.ElementTree as ET
+
+
+def run_pts_bot():
+ run_pts_bot_cmd = [
+ # atest command with verbose mode.
+ 'atest',
+ '-d',
+ '-v',
+ 'pts-bot',
+ # Coverage tool chains and specify that coverage should be flush to the
+ # disk between each tests.
+ '--',
+ '--coverage',
+ '--coverage-toolchain JACOCO',
+ '--coverage-toolchain CLANG',
+ '--coverage-flush',
+ ]
+ subprocess.run(run_pts_bot_cmd).returncode
+
+
+def run_unit_tests():
+
+ # Output logs directory
+ logs_out = Path('logs_bt_tests')
+ logs_out.mkdir(exist_ok=True)
+
+ mts_tests = []
+ android_build_top = os.getenv('ANDROID_BUILD_TOP')
+ mts_xml = ET.parse(
+ f'{android_build_top}/test/mts/tools/mts-tradefed/res/config/mts-bluetooth-tests-list.xml'
+ )
+
+ for child in mts_xml.getroot():
+ value = child.attrib['value']
+ if 'enable:true' in value:
+ test = value.replace(':enable:true', '')
+ mts_tests.append(test)
+
+ for test in mts_tests:
+ print(f'Test started: {test}')
+
+ # Env variables necessary for native unit tests.
+ env = os.environ.copy()
+ env['CLANG_COVERAGE_CONTINUOUS_MODE'] = 'true'
+ env['CLANG_COVERAGE'] = 'true'
+ env['NATIVE_COVERAGE_PATHS'] = 'packages/modules/Bluetooth'
+ run_test_cmd = [
+ # atest command with verbose mode.
+ 'atest',
+ '-d',
+ '-v',
+ test,
+ # Coverage tool chains and specify that coverage should be flush to the
+ # disk between each tests.
+ '--',
+ '--coverage',
+ '--coverage-toolchain JACOCO',
+ '--coverage-toolchain CLANG',
+ '--coverage-flush',
+ # Allows tests to use hidden APIs.
+ '--test-arg ',
+ 'com.android.compatibility.testtype.LibcoreTest:hidden-api-checks:false',
+ '--test-arg ',
+ 'com.android.tradefed.testtype.AndroidJUnitTest:hidden-api-checks:false',
+ '--test-arg ',
+ 'com.android.tradefed.testtype.InstrumentationTest:hidden-api-checks:false',
+ '--skip-system-status-check ',
+ 'com.android.tradefed.suite.checker.ShellStatusChecker',
+ ]
+ with open(f'{logs_out}/{test}.txt', 'w') as f:
+ returncode = subprocess.run(
+ run_test_cmd, env=env, stdout=f, stderr=subprocess.STDOUT).returncode
+ print(
+ f'Test ended [{"Success" if returncode == 0 else "Failed"}]: {test}')
+
+
+def generate_java_coverage(bt_apex_name, trace_path, coverage_out):
+
+ out = os.getenv('OUT')
+ android_host_out = os.getenv('ANDROID_HOST_OUT')
+
+ java_coverage_out = Path(f'{coverage_out}/java')
+ temp_path = Path(f'{coverage_out}/temp')
+ if temp_path.exists():
+ shutil.rmtree(temp_path, ignore_errors=True)
+ temp_path.mkdir()
+
+ framework_jar_path = Path(
+ f'{out}/obj/PACKAGING/jacoco_intermediates/JAVA_LIBRARIES/framework-bluetooth.{bt_apex_name}_intermediates'
+ )
+ service_jar_path = Path(
+ f'{out}/obj/PACKAGING/jacoco_intermediates/JAVA_LIBRARIES/service-bluetooth.{bt_apex_name}_intermediates'
+ )
+ app_jar_path = Path(
+ f'{out}/obj/PACKAGING/jacoco_intermediates/ETC/Bluetooth{"Google" if "com.google" in bt_apex_name else ""}.{bt_apex_name}_intermediates'
+ )
+
+ # From google3/configs/wireless/android/testing/atp/prod/mainline-engprod/templates/modules/bluetooth.gcl.
+ framework_exclude_classes = [
+ '**/com/android/bluetooth/x/**/*.class',
+ '**/*Test$*.class',
+ '**/android/bluetooth/I*$Default.class',
+ '**/android/bluetooth/**/I*$Default.class',
+ '**/android/bluetooth/I*$Stub.class',
+ '**/android/bluetooth/**/I*$Stub.class',
+ '**/android/bluetooth/I*$Stub$Proxy.class',
+ '**/android/bluetooth/**/I*$Stub$Proxy.class',
+ '**/com/android/internal/util/**/*.class',
+ '**/android/net/**/*.class',
+ ]
+ service_exclude_classes = [
+ '**/com/android/bluetooth/x/**/*.class',
+ '**/androidx/**/*.class',
+ '**/android/net/**/*.class',
+ '**/android/support/**/*.class',
+ '**/kotlin/**/*.class',
+ '**/*Test$*.class',
+ '**/com/android/internal/annotations/**/*.class',
+ '**/android/annotation/**/*.class',
+ '**/android/net/**/*.class',
+ ]
+ app_exclude_classes = [
+ '**/*Test$*.class',
+ '**/com/android/bluetooth/x/**/*.class',
+ '**/com/android/internal/annotations/**/*.class',
+ '**/com/android/internal/util/**/*.class',
+ '**/android/annotation/**/*.class',
+ '**/android/net/**/*.class',
+ '**/android/support/v4/**/*.class',
+ '**/androidx/**/*.class',
+ '**/kotlin/**/*.class',
+ '**/com/google/**/*.class',
+ '**/javax/**/*.class',
+ '**/android/hardware/**/*.class', # Added
+ '**/android/hidl/**/*.class', # Added
+ '**/com/android/bluetooth/**/BluetoothMetrics*.class', # Added
+ ]
+
+ # Merged ec files.
+ merged_ec_path = Path(f'{temp_path}/merged.ec')
+ subprocess.run((
+ f'java -jar {android_host_out}/framework/jacoco-cli.jar merge {trace_path.absolute()}/*.ec '
+ f'--destfile {merged_ec_path.absolute()}'),
+ shell=True)
+
+ # Copy and extract jar files.
+ framework_temp_path = Path(f'{temp_path}/{framework_jar_path.name}')
+ service_temp_path = Path(f'{temp_path}/{service_jar_path.name}')
+ app_temp_path = Path(f'{temp_path}/{app_jar_path.name}')
+
+ shutil.copytree(framework_jar_path, framework_temp_path)
+ shutil.copytree(service_jar_path, service_temp_path)
+ shutil.copytree(app_jar_path, app_temp_path)
+
+ current_dir_path = Path.cwd()
+ for p in [framework_temp_path, service_temp_path, app_temp_path]:
+ os.chdir(p.absolute())
+ os.system('jar xf jacoco-report-classes.jar')
+ os.chdir(current_dir_path)
+
+ os.remove(f'{framework_temp_path}/jacoco-report-classes.jar')
+ os.remove(f'{service_temp_path}/jacoco-report-classes.jar')
+ os.remove(f'{app_temp_path}/jacoco-report-classes.jar')
+
+ # Generate coverage report.
+ exclude_classes = []
+ for glob in framework_exclude_classes:
+ exclude_classes.extend(list(framework_temp_path.glob(glob)))
+ for glob in service_exclude_classes:
+ exclude_classes.extend(list(service_temp_path.glob(glob)))
+ for glob in app_exclude_classes:
+ exclude_classes.extend(list(app_temp_path.glob(glob)))
+
+ for c in exclude_classes:
+ if c.exists():
+ os.remove(c.absolute())
+
+ gen_java_cov_report_cmd = [
+ f'java',
+ f'-jar',
+ f'{android_host_out}/framework/jacoco-cli.jar',
+ f'report',
+ f'{merged_ec_path.absolute()}',
+ f'--classfiles',
+ f'{temp_path.absolute()}',
+ f'--html',
+ f'{java_coverage_out.absolute()}',
+ f'--name',
+ f'{java_coverage_out.absolute()}.html',
+ ]
+ subprocess.run(gen_java_cov_report_cmd)
+
+ # Cleanup.
+ shutil.rmtree(temp_path, ignore_errors=True)
+
+
+def generate_native_coverage(bt_apex_name, trace_path, coverage_out):
+
+ out = os.getenv('OUT')
+ android_build_top = os.getenv('ANDROID_BUILD_TOP')
+
+ native_coverage_out = Path(f'{coverage_out}/native')
+ temp_path = Path(f'{coverage_out}/temp')
+ if temp_path.exists():
+ shutil.rmtree(temp_path, ignore_errors=True)
+ temp_path.mkdir()
+
+ # From google3/configs/wireless/android/testing/atp/prod/mainline-engprod/templates/modules/bluetooth.gcl.
+ exclude_files = {
+ 'system/.*_aidl.*',
+ 'system/.*_test.*',
+ 'system/.*_mock.*',
+ 'system/.*_unittest.*',
+ 'system/binder/',
+ 'system/blueberry/',
+ 'system/build/',
+ 'system/conf/',
+ 'system/doc/',
+ 'system/test/',
+ 'system/gd/l2cap/',
+ 'system/gd/security/',
+ 'system/gd/neighbor/',
+ # 'android/', # Should not be excluded
+ }
+
+ # Merge profdata files.
+ profdata_path = Path(f'{temp_path}/coverage.profdata')
+ subprocess.run(
+ f'llvm-profdata merge --sparse -o {profdata_path.absolute()} {trace_path.absolute()}/*.profraw',
+ shell=True)
+
+ gen_native_cov_report_cmd = [
+ f'llvm-cov',
+ f'show',
+ f'-format=html',
+ f'-output-dir={native_coverage_out.absolute()}',
+ f'-instr-profile={profdata_path.absolute()}',
+ f'{out}/symbols/apex/{bt_apex_name}/lib64/libbluetooth_jni.so',
+ f'-path-equivalence=/proc/self/cwd,{android_build_top}',
+ f'/proc/self/cwd/packages/modules/Bluetooth',
+ ]
+ for f in exclude_files:
+ gen_native_cov_report_cmd.append(f'-ignore-filename-regex={f}')
+ subprocess.run(gen_native_cov_report_cmd, cwd=android_build_top)
+
+ # Cleanup.
+ shutil.rmtree(temp_path, ignore_errors=True)
+
+
+if __name__ == '__main__':
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ '--apex-name',
+ default='com.android.btservices',
+ help='bluetooth apex name. Default: com.android.btservices')
+ parser.add_argument(
+ '--java', action='store_true', help='generate Java coverage')
+ parser.add_argument(
+ '--native', action='store_true', help='generate native coverage')
+ parser.add_argument(
+ '--out',
+ type=str,
+ default='out_coverage',
+ help='out directory for coverage reports. Default: ./out_coverage')
+ parser.add_argument(
+ '--trace',
+ type=str,
+ default='trace',
+ help='trace directory with .ec and .profraw files. Default: ./trace')
+ parser.add_argument(
+ '--full-report',
+ action='store_true',
+ help='run all tests and compute coverage report')
+ args = parser.parse_args()
+
+ coverage_out = Path(args.out)
+ shutil.rmtree(coverage_out, ignore_errors=True)
+ coverage_out.mkdir()
+
+ if not args.full_report:
+ trace_path = Path(args.trace)
+ if (not trace_path.exists() or not trace_path.is_dir()):
+ sys.exit('Trace directory does not exist')
+
+ if (args.java):
+ generate_java_coverage(args.apex_name, trace_path, coverage_out)
+ if (args.native):
+ generate_native_coverage(args.apex_name, trace_path, coverage_out)
+
+ else:
+ # Compute Pandora coverage.
+ run_pts_bot()
+ coverage_out_pandora = Path(f'{coverage_out}/pandora')
+ coverage_out_pandora.mkdir()
+ trace_pandora = Path('trace_pandora')
+ shutil.rmtree(trace_pandora, ignore_errors=True)
+ subprocess.run(['adb', 'pull', '/data/misc/trace', trace_pandora])
+ generate_java_coverage(args.apex_name, trace_pandora,
+ coverage_out_pandora)
+ generate_native_coverage(args.apex_name, trace_pandora,
+ coverage_out_pandora)
+
+ # # Compute all coverage.
+ run_unit_tests()
+ coverage_out_mainline = Path(f'{coverage_out}/mainline')
+ coverage_out_mainline.mkdir()
+ trace_all = Path('trace_all')
+ shutil.rmtree(trace_all, ignore_errors=True)
+ subprocess.run(['adb', 'pull', '/data/misc/trace', trace_all])
+ generate_java_coverage(args.apex_name, trace_all, coverage_out_mainline)
+ generate_native_coverage(args.apex_name, trace_all,
+ coverage_out_mainline)
diff --git a/android/pandora/mmi2grpc/_build/protoc-gen-custom_grpc b/android/pandora/mmi2grpc/_build/protoc-gen-custom_grpc
index b560c5ba76..bf09ea5a80 100755
--- a/android/pandora/mmi2grpc/_build/protoc-gen-custom_grpc
+++ b/android/pandora/mmi2grpc/_build/protoc-gen-custom_grpc
@@ -101,21 +101,92 @@ def generate_service(imports, file, service):
f' {methods}\n'
).split('\n')
+def generate_servicer_method(method):
+ input_mode = 'stream' if method.client_streaming else 'unary'
+
+ if input_mode == 'stream':
+ return (
+ f'def {method.name}(self, request_iterator, context):\n'
+ f' context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n'
+ f' context.set_details("Method not implemented!")\n'
+ f' raise NotImplementedError("Method not implemented!")'
+ ).split('\n')
+ else:
+ return (
+ f'def {method.name}(self, request, context):\n'
+ f' context.set_code(grpc.StatusCode.UNIMPLEMENTED)\n'
+ f' context.set_details("Method not implemented!")\n'
+ f' raise NotImplementedError("Method not implemented!")'
+ ).split('\n')
+
+
+def generate_servicer(service):
+ methods = '\n\n '.join([
+ '\n '.join(
+ generate_servicer_method(method)
+ ) for method in service.method
+ ])
+ if len(methods) == 0:
+ methods = 'pass'
+ return (
+ f'class {service.name}Servicer:\n'
+ f'\n'
+ f' {methods}\n'
+ ).split('\n')
+
+def generate_rpc_method_handler(imports, method):
+ input_mode = 'stream' if method.client_streaming else 'unary'
+ output_mode = 'stream' if method.server_streaming else 'unary'
+
+ input_type = import_type(imports, method.input_type)
+ output_type = import_type(imports, method.output_type)
+
+ return (
+ f"'{method.name}': grpc.{input_mode}_{output_mode}_rpc_method_handler(\n"
+ f' servicer.{method.name},\n'
+ f' request_deserializer={input_type}.FromString,\n'
+ f' response_serializer={output_type}.SerializeToString,\n'
+ f' ),\n'
+ ).split('\n')
+
+def generate_add_servicer_to_server_method(imports, file, service):
+ method_handlers = ' '.join([
+ '\n '.join(
+ generate_rpc_method_handler(imports, method)
+ ) for method in service.method
+ ])
+ return (
+ f'def add_{service.name}Servicer_to_server(servicer, server):\n'
+ f' rpc_method_handlers = {{\n'
+ f' {method_handlers}\n'
+ f' }}\n'
+ f' generic_handler = grpc.method_handlers_generic_handler(\n'
+ f" '{file.package}.{service.name}', rpc_method_handlers)\n"
+ f' server.add_generic_rpc_handlers((generic_handler,))'
+ ).split('\n')
files = []
for file_name in request.file_to_generate:
file = next(filter(lambda x: x.name == file_name, request.proto_file))
- imports = set([])
+ imports = set(['import grpc'])
services = '\n'.join(sum([
generate_service(imports, file, service) for service in file.service
], []))
+ servicers = '\n'.join(sum([
+ generate_servicer(service) for service in file.service
+ ], []))
+
+ add_servicer_methods = '\n'.join(sum([
+ generate_add_servicer_to_server_method(imports, file, service) for service in file.service
+ ], []))
+
files.append(CodeGeneratorResponse.File(
name=file_name.replace('.proto', '_grpc.py'),
- content='\n'.join(imports) + '\n\n' + services
+ content='\n'.join(imports) + '\n\n' + services + '\n\n' + servicers + '\n\n' + add_servicer_methods + '\n'
))
reponse = CodeGeneratorResponse(file=files)
diff --git a/android/pandora/mmi2grpc/mmi2grpc/__init__.py b/android/pandora/mmi2grpc/mmi2grpc/__init__.py
index a4895909c8..fe6f4844dd 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/__init__.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/__init__.py
@@ -36,7 +36,8 @@ from mmi2grpc._rootcanal import RootCanal
from pandora_experimental.host_grpc import Host
-GRPC_PORT = 8999
+PANDORA_SERVER_PORT = 8999
+ROOTCANAL_CONTROL_PORT = 6212
MAX_RETRIES = 10
GRPC_SERVER_INIT_TIMEOUT = 10 # seconds
@@ -48,15 +49,15 @@ class IUT:
proxy which translates MMI calls to gRPC calls to the IUT.
"""
- def __init__(self, test: str, args: List[str], port: int = GRPC_PORT, **kwargs):
+ def __init__(self, test: str, args: List[str], **kwargs):
"""Init IUT class for a given test.
Args:
test: PTS test id.
args: test arguments.
- port: gRPC port exposed by the IUT test server.
"""
- self.port = port
+ self.pandora_server_port = int(args[0]) if len(args) > 0 else PANDORA_SERVER_PORT
+ self.rootcanal_control_port = int(args[1]) if len(args) > 1 else ROOTCANAL_CONTROL_PORT
self.test = test
self.rootcanal = None
@@ -73,12 +74,12 @@ class IUT:
def __enter__(self):
"""Resets the IUT when starting a PTS test."""
- self.rootcanal = RootCanal()
+ self.rootcanal = RootCanal(port=self.rootcanal_control_port)
self.rootcanal.reconnect_phone()
# Note: we don't keep a single gRPC channel instance in the IUT class
# because reset is allowed to close the gRPC server.
- with grpc.insecure_channel(f'localhost:{self.port}') as channel:
+ with grpc.insecure_channel(f'localhost:{self.pandora_server_port}') as channel:
self._retry(Host(channel).HardReset)(wait_for_ready=True)
def __exit__(self, exc_type, exc_value, exc_traceback):
@@ -121,7 +122,7 @@ class IUT:
mut_address = None
def read_local_address():
- with grpc.insecure_channel(f'localhost:{self.port}') as channel:
+ with grpc.insecure_channel(f'localhost:{self.pandora_server_port}') as channel:
nonlocal mut_address
mut_address = self._retry(Host(channel).ReadLocalAddress)(wait_for_ready=True).address
@@ -151,47 +152,47 @@ class IUT:
# Handles A2DP and AVDTP MMIs.
if profile in ('A2DP', 'AVDTP'):
if not self._a2dp:
- self._a2dp = A2DPProxy(grpc.insecure_channel(f'localhost:{self.port}'))
+ self._a2dp = A2DPProxy(grpc.insecure_channel(f'localhost:{self.pandora_server_port}'))
return self._a2dp.interact(test, interaction, description, pts_address)
# Handles AVRCP and AVCTP MMIs.
if profile in ('AVRCP', 'AVCTP'):
if not self._avrcp:
- self._avrcp = AVRCPProxy(grpc.insecure_channel(f'localhost:{self.port}'))
+ self._avrcp = AVRCPProxy(grpc.insecure_channel(f'localhost:{self.pandora_server_port}'))
return self._avrcp.interact(test, interaction, description, pts_address)
# Handles GATT MMIs.
if profile in ('GATT'):
if not self._gatt:
- self._gatt = GATTProxy(grpc.insecure_channel(f'localhost:{self.port}'))
+ self._gatt = GATTProxy(grpc.insecure_channel(f'localhost:{self.pandora_server_port}'))
return self._gatt.interact(test, interaction, description, pts_address)
# Handles HFP MMIs.
if profile in ('HFP'):
if not self._hfp:
- self._hfp = HFPProxy(grpc.insecure_channel(f'localhost:{self.port}'))
+ self._hfp = HFPProxy(grpc.insecure_channel(f'localhost:{self.pandora_server_port}'))
return self._hfp.interact(test, interaction, description, pts_address)
# Handles HID MMIs.
if profile in ('HID'):
if not self._hid:
- self._hid = HIDProxy(grpc.insecure_channel(f'localhost:{self.port}'), self.rootcanal)
+ self._hid = HIDProxy(grpc.insecure_channel(f'localhost:{self.pandora_server_port}'), self.rootcanal)
return self._hid.interact(test, interaction, description, pts_address)
# Handles HOGP MMIs.
if profile in ('HOGP'):
if not self._hogp:
- self._hogp = HOGPProxy(grpc.insecure_channel(f'localhost:{self.port}'))
+ self._hogp = HOGPProxy(grpc.insecure_channel(f'localhost:{self.pandora_server_port}'))
return self._hogp.interact(test, interaction, description, pts_address)
# Instantiates L2CAP proxy and reroutes corresponding MMIs to it.
if profile in ('L2CAP'):
if not self._l2cap:
- self._l2cap = L2CAPProxy(grpc.insecure_channel(f'localhost:{self.port}'))
+ self._l2cap = L2CAPProxy(grpc.insecure_channel(f'localhost:{self.pandora_server_port}'))
return self._l2cap.interact(test, interaction, description, pts_address)
# Handles SDP MMIs.
if profile in ('SDP'):
if not self._sdp:
- self._sdp = SDPProxy(grpc.insecure_channel(f'localhost:{self.port}'))
+ self._sdp = SDPProxy(grpc.insecure_channel(f'localhost:{self.pandora_server_port}'))
return self._sdp.interact(test, interaction, description, pts_address)
# Handles SM MMIs.
if profile in ('SM'):
if not self._sm:
- self._sm = SMProxy(grpc.insecure_channel(f'localhost:{self.port}'))
+ self._sm = SMProxy(grpc.insecure_channel(f'localhost:{self.pandora_server_port}'))
return self._sm.interact(test, interaction, description, pts_address)
# Handles unsupported profiles.
diff --git a/android/pandora/mmi2grpc/mmi2grpc/_rootcanal.py b/android/pandora/mmi2grpc/mmi2grpc/_rootcanal.py
index 60401d5283..c7e6b9ab2c 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/_rootcanal.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/_rootcanal.py
@@ -76,9 +76,8 @@ class TestChannel:
class RootCanal:
- def __init__(self):
- # port is CONTROL_ROOTCANAL_PORT defined in tradefed
- self.channel = TestChannel(port=6212)
+ def __init__(self, port):
+ self.channel = TestChannel(port)
self.disconnected_dev_phys = None
# discard initialization messages
@@ -128,6 +127,8 @@ class RootCanal:
target_phys = [le_phy]
elif "hci_device" in name:
target_phys = [classic_phy, le_phy]
+ else:
+ target_phys = []
for phy in target_phys:
if dev_i not in self._parse_phy(devices["Phys"][phy])[1]:
diff --git a/android/pandora/mmi2grpc/mmi2grpc/a2dp.py b/android/pandora/mmi2grpc/mmi2grpc/a2dp.py
index 8614c20612..a169d8a188 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/a2dp.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/a2dp.py
@@ -11,7 +11,6 @@
# 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.
-
"""A2DP proxy module."""
import time
@@ -49,15 +48,12 @@ class A2DPProxy(ProfileProxy):
def convert_frame(data):
return PlaybackAudioRequest(data=data, source=self.source)
- self.audio = AudioSignal(
- lambda frames: self.a2dp.PlaybackAudio(map(convert_frame, frames)),
- AUDIO_SIGNAL_AMPLITUDE,
- AUDIO_SIGNAL_SAMPLING_RATE
- )
+
+ self.audio = AudioSignal(lambda frames: self.a2dp.PlaybackAudio(map(convert_frame, frames)),
+ AUDIO_SIGNAL_AMPLITUDE, AUDIO_SIGNAL_SAMPLING_RATE)
@assert_description
- def TSC_AVDTP_mmi_iut_accept_connect(
- self, test: str, pts_addr: bytes, **kwargs):
+ def TSC_AVDTP_mmi_iut_accept_connect(self, test: str, pts_addr: bytes, **kwargs):
"""
If necessary, take action to accept the AVDTP Signaling Channel
Connection initiated by the tester.
@@ -71,28 +67,35 @@ class A2DPProxy(ProfileProxy):
"""
if "SRC" in test:
- self.connection = self.host.WaitConnection(
- address=pts_addr).connection
+ self.connection = self.host.WaitConnection(address=pts_addr).connection
try:
if "INT" in test:
- self.source = self.a2dp.OpenSource(
- connection=self.connection).source
+ self.source = self.a2dp.OpenSource(connection=self.connection).source
else:
- self.source = self.a2dp.WaitSource(
- connection=self.connection).source
+ self.source = self.a2dp.WaitSource(connection=self.connection).source
except RpcError:
pass
else:
- self.connection = self.host.WaitConnection(
- address=pts_addr).connection
+ self.connection = self.host.WaitConnection(address=pts_addr).connection
try:
- self.sink = self.a2dp.WaitSink(
- connection=self.connection).sink
+ self.sink = self.a2dp.WaitSink(connection=self.connection).sink
except RpcError:
pass
return "OK"
@assert_description
+ def TSC_AVDTP_mmi_iut_accept_disconnect(self, **kwargs):
+ """
+ If necessary, take action to accept the AVDTP Signaling Channnel
+ Disconnection initiated by the tester.
+
+ Note: If an AVCTP signaling
+ channel was established it will also be disconnected.
+ """
+
+ return "OK"
+
+ @assert_description
def TSC_AVDTP_mmi_iut_initiate_discover(self, **kwargs):
"""
Send a discover command to PTS.
@@ -152,8 +155,7 @@ class A2DPProxy(ProfileProxy):
return "OK"
@assert_description
- def TSC_AVDTP_mmi_iut_initiate_out_of_range(
- self, pts_addr: bytes, **kwargs):
+ def TSC_AVDTP_mmi_iut_initiate_out_of_range(self, pts_addr: bytes, **kwargs):
"""
Move the IUT out of range to create a link loss scenario.
@@ -162,8 +164,7 @@ class A2DPProxy(ProfileProxy):
"""
if self.connection is None:
- self.connection = self.host.GetConnection(
- address=pts_addr).connection
+ self.connection = self.host.GetConnection(address=pts_addr).connection
self.host.Disconnect(connection=self.connection)
self.connection = None
self.sink = None
@@ -181,11 +182,8 @@ class A2DPProxy(ProfileProxy):
if test == "AVDTP/SRC/ACP/SIG/SMG/BI-29-C":
time.sleep(2) # TODO: Remove, AVRCP SegFault
- if test in ("A2DP/SRC/CC/BV-09-I",
- "A2DP/SRC/SET/BV-04-I",
- "AVDTP/SRC/ACP/SIG/SMG/BV-18-C",
- "AVDTP/SRC/ACP/SIG/SMG/BV-20-C",
- "AVDTP/SRC/ACP/SIG/SMG/BV-22-C"):
+ if test in ("A2DP/SRC/CC/BV-09-I", "A2DP/SRC/SET/BV-04-I", "AVDTP/SRC/ACP/SIG/SMG/BV-18-C",
+ "AVDTP/SRC/ACP/SIG/SMG/BV-20-C", "AVDTP/SRC/ACP/SIG/SMG/BV-22-C"):
time.sleep(1) # TODO: Remove, AVRCP SegFault
if test == "A2DP/SRC/SUS/BV-01-I":
# Stream is not suspended when we receive the interaction
diff --git a/android/pandora/mmi2grpc/mmi2grpc/avrcp.py b/android/pandora/mmi2grpc/mmi2grpc/avrcp.py
index 2b89e0905b..b6317b66eb 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/avrcp.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/avrcp.py
@@ -59,7 +59,7 @@ class AVRCPProxy(ProfileProxy):
the IUT connects to PTS to establish pairing.
"""
- if "CT" in test:
+ if ("TG" in test and "TG/VLH" not in test) or "CT/VLH" in test:
self.connection = self.host.WaitConnection(address=pts_addr).connection
try:
@@ -149,11 +149,7 @@ class AVRCPProxy(ProfileProxy):
Take action to disconnect all A2DP and/or AVRCP connections.
"""
- if self.connection is None:
- self.connection = self.host.GetConnection(address=pts_addr).connection
- self.host.Disconnect(connection=self.connection)
- self.connection = None
- self.sink = None
+ self.a2dp.Close(source=self.source)
self.source = None
return "OK"
@@ -202,6 +198,7 @@ class AVRCPProxy(ProfileProxy):
The IUT should
then initiate an L2CAP_ConnectRsp and L2CAP_ConfigRsp.
"""
+
return "OK"
@assert_description
@@ -241,6 +238,7 @@ class AVRCPProxy(ProfileProxy):
Press 'OK' to
continue once the IUT has responded.
"""
+
return "OK"
@assert_description
@@ -258,6 +256,7 @@ class AVRCPProxy(ProfileProxy):
Press 'OK' to
continue once the IUT has responded.
"""
+
return "OK"
@assert_description
@@ -275,6 +274,7 @@ class AVRCPProxy(ProfileProxy):
the following parameter values:
* BD_ADDR = BD_ADDRLower_Tester
"""
+
return "OK"
@assert_description
@@ -298,6 +298,7 @@ class AVRCPProxy(ProfileProxy):
DATA[]Lower_Tester
* Length = LengthOf(DATA[]Lower_Tester)
"""
+
return "OK"
@assert_description
@@ -327,6 +328,7 @@ class AVRCPProxy(ProfileProxy):
* Length =
LengthOf(DATA[]Lower_Tester)
"""
+
return "OK"
@assert_description
@@ -356,4 +358,187 @@ class AVRCPProxy(ProfileProxy):
continue once the IUT has responded.
"""
#TODO: Remove trailing space post "values:" from docstring description
+
+ return "OK"
+
+ @assert_description
+ def TSC_AVDTP_mmi_iut_initiate_connect(self, pts_addr: bytes, **kwargs):
+ """
+ Create an AVDTP signaling channel.
+
+ Action: Create an audio or video
+ connection with PTS.
+ """
+ self.connection = self.host.Connect(address=pts_addr).connection
+ self.source = self.a2dp.OpenSource(connection=self.connection).source
+ return "OK"
+
+ @assert_description
+ def _mmi_690(self, **kwargs):
+ """
+ Press 'YES' if the IUT indicated receiving the [PLAY] command. Press
+ 'NO' otherwise.
+
+ Description: Verify that the Implementation Under Test
+ (IUT) successfully indicated that the current operation was pressed. Not
+ all commands (fast forward and rewind for example) have a noticeable
+ effect when pressed for a short period of time. For commands like that
+ it is acceptable to assume the effect took place and press 'YES'.
+ """
+
+ return "Yes"
+
+ @assert_description
+ def _mmi_691(self, **kwargs):
+ """
+ Press 'YES' if the IUT indicated receiving the [STOP] command. Press
+ 'NO' otherwise.
+
+ Description: Verify that the Implementation Under Test
+ (IUT) successfully indicated that the current operation was pressed. Not
+ all commands (fast forward and rewind for example) have a noticeable
+ effect when pressed for a short period of time. For commands like that
+ it is acceptable to assume the effect took place and press 'YES'.
+ """
+
+ return "Yes"
+
+ @assert_description
+ def _mmi_540(self, **kwargs):
+ """
+ Press 'YES' if the IUT supports press and hold functionality for the
+ [PLAY] command. Press 'NO' otherwise.
+
+ Description: Verify press and
+ hold functionality of passthrough operations that support press and
+ hold. Not all operations support press and hold, pressing 'NO' will not
+ fail the test case.
+ """
+
+ return "Yes"
+
+ @assert_description
+ def _mmi_615(self, **kwargs):
+ """
+ Press 'YES' if the IUT indicated press and hold functionality for the
+ [PLAY] command. Press 'NO' otherwise.
+
+ Description: Verify that the
+ Implementation Under Test (IUT) successfully indicated that the current
+ operation was held.
+ """
+
+ return "Yes"
+
+ @assert_description
+ def _mmi_541(self, **kwargs):
+ """
+ Press 'YES' if the IUT supports press and hold functionality for the
+ [STOP] command. Press 'NO' otherwise.
+
+ Description: Verify press and
+ hold functionality of passthrough operations that support press and
+ hold. Not all operations support press and hold, pressing 'NO' will not
+ fail the test case.
+ """
+
+ return "Yes"
+
+ @assert_description
+ def _mmi_616(self, **kwargs):
+ """
+ Press 'YES' if the IUT indicated press and hold functionality for the
+ [STOP] command. Press 'NO' otherwise.
+
+ Description: Verify that the
+ Implementation Under Test (IUT) successfully indicated that the current
+ operation was held.
+ """
+
+ return "Yes"
+
+ @assert_description
+ def TSC_AVRCP_mmi_user_confirm_media_is_streaming(self, **kwargs):
+ """
+ Press 'OK' when the IUT is in a state where media is playing.
+ Description: PTS is preparing the streaming state for the next
+ passthrough command, if the current streaming state is not relevant to
+ this IUT, please press 'OK to continue.
+ """
+ if not self.a2dp.IsSuspended(source=self.source).is_suspended:
+ return "Yes"
+ else:
+ return "No"
+
+ @assert_description
+ def TSC_AVRCP_mmi_iut_reject_invalid_get_capabilities(self, **kwargs):
+ """
+ The IUT should reject the invalid Get Capabilities command sent by PTS.
+ Description: Verify that the IUT can properly reject a Get Capabilities
+ command that contains an invalid capability.
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_AVRCP_mmi_iut_accept_get_capabilities(self, **kwargs):
+ """
+ Take action to send a valid response to the [Get Capabilities] command
+ sent by the PTS.
+ """
+ # This will be done as part as the a2dp.OpenSource or a2dp.WaitSource
+
+ return "OK"
+
+ @assert_description
+ def TSC_AVRCP_mmi_iut_accept_get_element_attributes(self, **kwargs):
+ """
+ Take action to send a valid response to the [Get Element Attributes]
+ command sent by the PTS.
+ """
+ # This will be done as part as the a2dp.OpenSource or a2dp.WaitSource
+
+ return "OK"
+
+ @assert_description
+ def TSC_AVRCP_mmi_iut_reject_invalid_command_control_channel(self, **kwargs):
+ """
+ PTS has sent an invalid command over the control channel. The IUT must
+ respond with a general reject on the control channel.
+
+ Description:
+ Verify that the IUT can properly reject an invalid command sent over the
+ control channel.
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_AVRCP_mmi_iut_reject_invalid_command_browsing_channel(self, **kwargs):
+ """
+ PTS has sent an invalid command over the browsing channel. The IUT must
+ respond with a general reject on the browsing channel.
+
+ Description:
+ Verify that the IUT can properly reject an invalid command sent over the
+ browsing channel.
+ """
+
+ return "OK"
+
+ @assert_description
+ def TSC_AVCTP_mmi_register_ConnectCfm_CB(self, **kwargs):
+ """
+ Using the Upper Tester send an AVCT_EventRegistration command from the
+ AVCTP Upper Interface to the IUT with the following input parameter
+ values:
+ * Event = AVCT_Connect_Cfm
+ * Callback =
+ ConnectCfm_CBTest_System
+ * PID = PIDTest_System
+
+ Press 'OK' to
+ continue once the IUT has responded.
+ """
+
return "OK" \ No newline at end of file
diff --git a/android/pandora/mmi2grpc/mmi2grpc/l2cap.py b/android/pandora/mmi2grpc/mmi2grpc/l2cap.py
index c3f54dce58..730f560f4c 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/l2cap.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/l2cap.py
@@ -1,13 +1,15 @@
import time
+import sys
from mmi2grpc._helpers import assert_description
from mmi2grpc._helpers import match_description
from mmi2grpc._proxy import ProfileProxy
+
from pandora_experimental.host_grpc import Host
-from pandora_experimental.host_pb2 import Connection
+from pandora_experimental.host_pb2 import Connection, ConnectabilityMode, AddressType
from pandora_experimental.l2cap_grpc import L2CAP
+
from typing import Optional
-import sys
class L2CAPProxy(ProfileProxy):
@@ -88,7 +90,10 @@ class L2CAPProxy(ProfileProxy):
"""
Place the IUT into LE connectable mode.
"""
- self.host.SetLEConnectable()
+ self.host.StartAdvertising(
+ connectability_mode=ConnectabilityMode.CONNECTABILITY_CONNECTABLE,
+ own_address_type=AddressType.PUBLIC,
+ )
# not strictly necessary, but can save time on waiting connection
tests_to_open_bluetooth_server_socket = [
"L2CAP/LE/CFC/BV-03-C",
@@ -97,6 +102,7 @@ class L2CAPProxy(ProfileProxy):
"L2CAP/LE/CFC/BV-09-C",
"L2CAP/LE/CFC/BV-13-C",
"L2CAP/LE/CFC/BV-20-C",
+ "L2CAP/LE/CFC/BI-01-C",
]
tests_require_secure_connection = [
"L2CAP/LE/CFC/BV-13-C",
@@ -335,3 +341,21 @@ class L2CAPProxy(ProfileProxy):
print('error in MMI_UPPER_TESTER_CONFIRM_RECEIVE_REJECT_RESOURCES', file=sys.stderr)
raise Exception("Unexpected RECEIVE_COMMAND")
return "OK"
+
+ def MMI_IUT_ENABLE_LE_CONNECTION(self, pts_addr: bytes, **kwargs):
+ """
+ Initiate or create LE ACL connection to the PTS.
+ """
+ self.connection = self.host.ConnectLE(address=pts_addr).connection
+ return "OK"
+
+ @assert_description
+ def MMI_IUT_SEND_ACL_DISCONNECTION(self, **kwargs):
+ """
+ Initiate an ACL disconnection from the IUT to the PTS.
+ Description :
+ The Implementation Under Test(IUT) should disconnect ACL channel by
+ sending a disconnect request to PTS.
+ """
+ self.host.DisconnectLE(connection=self.connection)
+ return "OK"
diff --git a/android/pandora/mmi2grpc/mmi2grpc/sm.py b/android/pandora/mmi2grpc/mmi2grpc/sm.py
index 697d2d36a4..42174cbde5 100644
--- a/android/pandora/mmi2grpc/mmi2grpc/sm.py
+++ b/android/pandora/mmi2grpc/mmi2grpc/sm.py
@@ -12,45 +12,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""SMP proxy module."""
+from queue import Empty, Queue
+from threading import Thread
import sys
+import time
-from mmi2grpc._helpers import assert_description
+from mmi2grpc._helpers import assert_description, match_description
from mmi2grpc._proxy import ProfileProxy
from mmi2grpc._streaming import StreamWrapper
from pandora_experimental.security_grpc import Security
from pandora_experimental.host_grpc import Host
-
-# The tests needs the MMI to accept pairing confirmation request.
-NEEDS_PAIRING_CONFIRMATION = {
- "SM/CEN/EKS/BV-01-C",
- "SM/CEN/JW/BI-04-C",
- "SM/CEN/JW/BI-01-C",
- "SM/CEN/KDU/BV-04-C",
- "SM/CEN/KDU/BV-05-C",
- "SM/CEN/KDU/BV-06-C",
- "SM/CEN/KDU/BV-10-C",
- "SM/CEN/KDU/BV-11-C",
-}
-
-ACCEPTS_REMOTE_PAIRING_CONFIRMATION = {
- "SM/CEN/KDU/BI-01-C",
- "SM/CEN/KDU/BI-02-C",
- "SM/CEN/KDU/BI-03-C",
-}
+from pandora_experimental.host_pb2 import ConnectabilityMode, AddressType
def debug(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
-def get_event(pairing_stream: StreamWrapper, addr: str):
- for event in pairing_stream:
- if event.address == addr:
- return event
- return None
-
-
class SMProxy(ProfileProxy):
def __init__(self, channel):
@@ -59,33 +38,25 @@ class SMProxy(ProfileProxy):
self.host = Host(channel)
self.connection = None
self.pairing_stream = None
+ self.passkey_queue = Queue()
+ self._handle_pairing_requests()
@assert_description
- def MMI_IUT_ENABLE_CONNECTION_SM(self, test, pts_addr: bytes, **kwargs):
+ def MMI_IUT_ENABLE_CONNECTION_SM(self, pts_addr: bytes, **kwargs):
"""
Initiate an connection from the IUT to the PTS.
"""
self.connection = self.host.ConnectLE(address=pts_addr).connection
- self.pairing_stream = self.security.OnPairing()
-
- if self.connection and test in ACCEPTS_REMOTE_PAIRING_CONFIRMATION:
- event = get_event(pairing_stream=self.pairing_stream, addr=pts_addr)
- self.pairing_stream.send(event=event, confirm=True)
- self.pairing_stream.close()
return "OK"
@assert_description
- def MMI_ASK_IUT_PERFORM_PAIRING_PROCESS(self, test, pts_addr: bytes, **kwargs):
+ def MMI_ASK_IUT_PERFORM_PAIRING_PROCESS(self, **kwargs):
"""
Please start pairing process.
"""
if self.connection:
self.security.Pair(connection=self.connection)
- if test in NEEDS_PAIRING_CONFIRMATION:
- event = get_event(pairing_stream=self.pairing_stream, addr=pts_addr)
- self.pairing_stream.send(event=event, confirm=True)
- self.pairing_stream.close()
- return "OK"
+ return "OK"
@assert_description
def MMI_IUT_SEND_DISCONNECTION_REQUEST(self, **kwargs):
@@ -104,7 +75,6 @@ class SMProxy(ProfileProxy):
"""
Please confirm the following number matches IUT: 385874.
"""
-
return "OK"
@assert_description
@@ -114,3 +84,97 @@ class SMProxy(ProfileProxy):
"""
self.host.SoftReset()
return "OK"
+
+ @assert_description
+ def MMI_TESTER_ENABLE_CONNECTION_SM(self, **kwargs):
+ """
+ Action: Place the IUT in connectable mode
+ """
+ self.host.StartAdvertising(
+ connectability_mode=ConnectabilityMode.CONNECTABILITY_CONNECTABLE,
+ own_address_type=AddressType.PUBLIC,
+ )
+ return "OK"
+
+ @assert_description
+ def MMI_IUT_SMP_TIMEOUT_30_SECONDS(self, **kwargs):
+ """
+ Wait for the 30 seconds. Lower tester will not send corresponding or
+ next SMP message.
+ """
+ return "OK"
+
+ @assert_description
+ def MMI_IUT_SMP_TIMEOUT_ADDITIONAL_10_SECONDS(self, **kwargs):
+ """
+ Wait for an additional 10 seconds. Lower test will send corresponding or
+ next SMP message.
+ """
+ return "OK"
+
+ @match_description
+ def MMI_DISPLAY_PASSKEY_CODE(self, passkey: str, **kwargs):
+ """
+ Please enter (?P<passkey>[0-9]*) in the IUT.
+ """
+ self.passkey_queue.put(passkey)
+ return "OK"
+
+ @assert_description
+ def MMI_ENTER_PASSKEY_CODE(self, **kwargs):
+ """
+ Please enter 6 digit passkey code.
+ """
+
+ return "OK"
+
+ @assert_description
+ def MMI_ENTER_WRONG_DYNAMIC_PASSKEY_CODE(self, **kwargs):
+ """
+ Please enter invalid 6 digit pin code.
+ """
+
+ return "OK"
+
+ @assert_description
+ def MMI_IUT_ABORT_PAIRING_PROCESS_DISCONNECT(self, **kwargs):
+ """
+ Lower tester expects IUT aborts pairing process, and disconnect.
+ """
+
+ return "OK"
+
+ @assert_description
+ def MMI_IUT_ACCEPT_CONNECTION_BR_EDR(self, **kwargs):
+ """
+ Please prepare IUT into a connectable mode in BR/EDR.
+
+ Description:
+ Verify that the Implementation Under Test (IUT) can accept a connect
+ request from PTS.
+ """
+
+ return "OK"
+
+ @assert_description
+ def _mmi_2001(self, **kwargs):
+ """
+ Please verify the passKey is correct: 000000
+ """
+ return "OK"
+
+ def _handle_pairing_requests(self):
+
+ def task():
+ pairing_events = self.security.OnPairing()
+ for event in pairing_events:
+ if event.just_works or event.numeric_comparison:
+ pairing_events.send(event=event, confirm=True)
+ if event.passkey_entry_request:
+ try:
+ passkey = self.passkey_queue.get(timeout=15)
+ pairing_events.send(event=event, passkey=int(passkey))
+ except Empty:
+ debug("No passkey provided within 15 seconds")
+
+ Thread(target=task).start()
diff --git a/android/pandora/server/Android.bp b/android/pandora/server/Android.bp
index 362fa453db..69a2b21b66 100644
--- a/android/pandora/server/Android.bp
+++ b/android/pandora/server/Android.bp
@@ -44,6 +44,7 @@ android_test_helper_app {
},
test_suites: [
+ "general-tests",
"device-tests",
"mts-bluetooth",
],
diff --git a/android/pandora/server/configs/PtsBotTest.xml b/android/pandora/server/configs/PtsBotTest.xml
index 43f1c9e7a5..42f3ec965a 100644
--- a/android/pandora/server/configs/PtsBotTest.xml
+++ b/android/pandora/server/configs/PtsBotTest.xml
@@ -20,10 +20,14 @@
</target_preparer>
<test class="com.android.tradefed.testtype.pandora.PtsBotTest" >
+ <!-- Creates a randomized temp dir for pts-bot binaries and avoid
+ conflicts when running multiple pts-bot on the same machine -->
+ <option name="create-bin-temp-dir" value="true"/>
<!-- mmi2grpc is contained inside pts-bot folder -->
<option name="mmi2grpc" value="pts-bot" />
<option name="tests-config-file" value="pts_bot_tests_config.json" />
- <option name="inconclusive-max-retries" value="3" />
+ <option name="max-flaky-tests" value="2" />
+ <option name="max-retries-per-test" value="3" />
<option name="physical" value="false" />
<option name="profile" value="A2DP/SRC" />
<option name="profile" value="A2DP/SNK" />
@@ -42,10 +46,23 @@
<option name="profile" value="HFP/AG/TCA" />
<option name="profile" value="HID/HOS" />
<option name="profile" value="HOGP/RH" />
- <option name="profile" value="L2CAP/LE/CFC" />
+ <option name="profile" value="L2CAP/LE" />
<option name="profile" value="SDP/SR" />
<option name="profile" value="SM/CEN/EKS" />
<option name="profile" value="SM/CEN/JW" />
<option name="profile" value="SM/CEN/KDU" />
+ <option name="profile" value="SM/CEN/PROT" />
+ <option name="profile" value="SM/CEN/PKE" />
+ <option name="profile" value="SM/CEN/SCJW" />
+ <option name="profile" value="SM/CEN/SCPK" />
+ <option name="profile" value="SM/PER/PROT" />
+ <option name="profile" value="SM/PER/JW" />
+ <option name="profile" value="SM/PER/PKE" />
+ <option name="profile" value="SM/PER/EKS" />
+ <option name="profile" value="SM/PER/KDU" />
+ <option name="profile" value="SM/PER/SCJW" />
+ <option name="profile" value="SM/PER/SCPK" />
+ <option name="profile" value="SM/PER/SCCT" />
+ <option name="profile" value="HOGP/RH" />
</test>
</configuration>
diff --git a/android/pandora/server/configs/PtsBotTestMts.xml b/android/pandora/server/configs/PtsBotTestMts.xml
index f7212158dd..05a9f06ac5 100644
--- a/android/pandora/server/configs/PtsBotTestMts.xml
+++ b/android/pandora/server/configs/PtsBotTestMts.xml
@@ -20,9 +20,14 @@
</target_preparer>
<test class="com.android.tradefed.testtype.pandora.PtsBotTest" >
+ <!-- Creates a randomized temp dir for pts-bot binaries and avoid
+ conflicts when running multiple pts-bot on the same machine -->
+ <option name="create-bin-temp-dir" value="true"/>
<!-- mmi2grpc is contained inside testcases folder -->
<option name="mmi2grpc" value="testcases" />
<option name="tests-config-file" value="pts_bot_tests_config.json" />
+ <option name="max-flaky-tests" value="3" />
+ <option name="max-retries-per-test" value="3" />
<option name="physical" value="false" />
<option name="profile" value="A2DP/SRC" />
<option name="profile" value="A2DP/SNK" />
@@ -39,7 +44,7 @@
<option name="profile" value="HFP/AG/PSI" />
<option name="profile" value="HFP/AG/SLC" />
<option name="profile" value="HFP/AG/TCA" />
- <option name="profile" value="L2CAP/LE/CFC" />
+ <option name="profile" value="L2CAP/LE" />
<option name="profile" value="SDP/SR" />
<option name="profile" value="SM/CEN/EKS" />
<option name="profile" value="SM/CEN/JW" />
diff --git a/android/pandora/server/configs/pts_bot_tests_config.json b/android/pandora/server/configs/pts_bot_tests_config.json
index d422be86c5..8f4b8655b1 100644
--- a/android/pandora/server/configs/pts_bot_tests_config.json
+++ b/android/pandora/server/configs/pts_bot_tests_config.json
@@ -8,6 +8,7 @@
"A2DP/SRC/SET/BV-02-I",
"A2DP/SRC/SET/BV-04-I",
"A2DP/SNK/AS/BV-01-I",
+ "A2DP/SNK/AS/BV-02-I",
"A2DP/SNK/CC/BV-01-I",
"A2DP/SNK/CC/BV-02-I",
"A2DP/SNK/CC/BV-05-I",
@@ -15,6 +16,7 @@
"A2DP/SNK/CC/BV-07-I",
"A2DP/SNK/CC/BV-08-I",
"A2DP/SNK/REL/BV-01-I",
+ "A2DP/SNK/REL/BV-02-I",
"A2DP/SNK/SET/BV-01-I",
"A2DP/SNK/SET/BV-02-I",
"A2DP/SNK/SET/BV-03-I",
@@ -91,13 +93,25 @@
"AVDTP/SNK/INT/SIG/SMG/BI-3",
"AVRCP/TG/CEC/BV-01-I",
"AVRCP/TG/CRC/BV-01-I",
+ "AVRCP/TG/CRC/BV-02-I",
"AVRCP/TG/ICC/BV-01-I",
"AVRCP/TG/ICC/BV-02-I",
+ "AVRCP/TG/INV/BI-01-C",
+ "AVRCP/TG/INV/BI-02-C",
+ "AVRCP/TG/MDI/BV-02-C",
+ "AVRCP/TG/MDI/BV-04-C",
+ "AVRCP/TG/MDI/BV-05-C",
+ "AVRCP/TG/MPS/BV-02-C",
"AVRCP/TG/MPS/BV-01-I",
+ "AVRCP/TG/MPS/BV-02-I",
+ "AVRCP/TG/MPS/BV-03-I",
+ "AVRCP/TG/PTT/BV-01-I",
+ "AVRCP/TG/PTT/BV-05-I",
"AVRCP/CT/CEC/BV-02-I",
"AVRCP/CT/CRC/BV-02-I",
- "AVRCP/TG/MDI/BV-02-C",
- "AVRCP/TG/MPS/BV-02-C",
+ "AVRCP/TG/CON/BV-04-C",
+ "AVRCP/TG/CFG/BV-02-C",
+ "AVRCP/TG/CFG/BI-01-C",
"GATT/CL/GAC/BV-01-C",
"GATT/CL/GAD/BV-01-C",
"GATT/CL/GAD/BV-02-C",
@@ -135,7 +149,6 @@
"HID/HOS/HCE/BV-01-I",
"HID/HOS/HCE/BV-03-I",
"HID/HOS/HCE/BV-04-I",
- "HID/HOS/HCR/BV-01-I",
"HID/HOS/HCR/BV-02-I",
"HID/HOS/HDT/BV-01-I",
"HID/HOS/HDT/BV-02-I",
@@ -165,6 +178,7 @@
"HOGP/RH/HGRF/BV-05-I",
"HOGP/RH/HGRF/BV-10-I",
"HOGP/RH/HGRF/BV-12-I",
+ "L2CAP/LE/CFC/BI-01-C",
"L2CAP/LE/CFC/BV-01-C",
"L2CAP/LE/CFC/BV-02-C",
"L2CAP/LE/CFC/BV-03-C",
@@ -180,6 +194,10 @@
"L2CAP/LE/CFC/BV-19-C",
"L2CAP/LE/CFC/BV-20-C",
"L2CAP/LE/CFC/BV-21-C",
+ "L2CAP/LE/CPU/BV-02-C",
+ "L2CAP/LE/CPU/BI-01-C",
+ "L2CAP/LE/CPU/BI-02-C",
+ "L2CAP/LE/REJ/BI-01-C",
"SDP/SR/BRW/BV-02-C",
"SDP/SR/SA/BI-01-C",
"SDP/SR/SA/BI-02-C",
@@ -225,7 +243,34 @@
"SM/CEN/KDU/BV-06-C",
"SM/CEN/KDU/BV-10-C",
"SM/CEN/KDU/BI-03-C",
- "SM/CEN/KDU/BV-11-C"
+ "SM/CEN/KDU/BV-11-C",
+ "SM/CEN/PROT/BV-01-C",
+ "SM/CEN/PKE/BV-04-C",
+ "SM/CEN/PKE/BI-01-C",
+ "SM/CEN/PKE/BI-02-C",
+ "SM/CEN/SCJW/BV-04-C",
+ "SM/CEN/SCJW/BI-01-C",
+ "SM/CEN/SCPK/BI-01-C",
+ "SM/PER/PROT/BV-02-C",
+ "SM/PER/JW/BV-02-C",
+ "SM/PER/JW/BI-03-C",
+ "SM/PER/JW/BI-02-C",
+ "SM/PER/PKE/BV-02-C",
+ "SM/PER/PKE/BV-05-C",
+ "SM/PER/PKE/BI-03-C",
+ "SM/PER/EKS/BV-02-C",
+ "SM/PER/EKS/BI-02-C",
+ "SM/PER/KDU/BV-01-C",
+ "SM/PER/KDU/BV-02-C",
+ "SM/PER/KDU/BV-03-C",
+ "SM/PER/KDU/BV-07-C",
+ "SM/PER/KDU/BV-08-C",
+ "SM/PER/KDU/BV-09-C",
+ "SM/PER/KDU/BI-01-C",
+ "SM/PER/SCJW/BV-03-C",
+ "SM/PER/SCJW/BI-02-C",
+ "SM/PER/SCPK/BV-02-C",
+ "SM/PER/SCPK/BI-03-C"
],
"skip": [
"A2DP/SRC/AS/BV-01-I",
@@ -238,8 +283,6 @@
"A2DP/SRC/SUS/BV-01-I",
"A2DP/SRC/SUS/BV-02-I",
"A2DP/SRC/SYN/BV-02-I",
- "A2DP/SNK/AS/BV-02-I",
- "A2DP/SNK/REL/BV-02-I",
"A2DP/SNK/SDP/BV-02-I",
"A2DP/SNK/SET/BV-04-I",
"A2DP/SNK/SET/BV-05-I",
@@ -279,10 +322,6 @@
"AVDTP/SNK/INT/SIG/SMG/ESR05/BV-13-C",
"AVDTP/SNK/INT/SIG/SYN/BV-02-C",
"AVDTP/SNK/INT/SIG/SYN/BV-04-C",
- "AVRCP/TG/PTT/BV-01-I",
- "AVRCP/TG/PTT/BV-05-I",
- "AVRCP/TG/MPS/BV-02-I",
- "AVRCP/TG/MPS/BV-03-I",
"AVRCP/TG/MCN/CB/BV-01-I",
"AVRCP/TG/MCN/CB/BV-02-I",
"AVRCP/TG/MCN/CB/BV-03-I",
@@ -292,18 +331,11 @@
"AVRCP/TG/MCN/NP/BV-04-I",
"AVRCP/TG/MCN/NP/BV-05-I",
"AVRCP/TG/MCN/NP/BV-06-I",
- "AVRCP/TG/CFG/BV-02-C",
- "AVRCP/TG/CFG/BI-01-C",
- "AVRCP/TG/MDI/BV-04-C",
- "AVRCP/TG/MDI/BV-05-C",
- "AVRCP/TG/CON/BV-04-C",
"AVRCP/TG/NFY/BV-02-C",
"AVRCP/TG/NFY/BV-04-C",
"AVRCP/TG/NFY/BV-06-C",
"AVRCP/TG/NFY/BV-07-C",
"AVRCP/TG/NFY/BI-01-C",
- "AVRCP/TG/INV/BI-01-C",
- "AVRCP/TG/INV/BI-02-C",
"AVRCP/TG/MPS/BI-01-C",
"AVRCP/TG/MPS/BV-04-C",
"AVRCP/TG/MPS/BI-02-C",
@@ -329,7 +361,6 @@
"AVRCP/CT/PTT/BV-01-I",
"AVRCP/CT/PTH/BV-01-C",
"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",
@@ -366,11 +397,27 @@
"HFP/AG/TCA/BV-03-I",
"HFP/AG/TCA/BV-04-I",
"HFP/AG/TCA/BV-05-I",
- "L2CAP/LE/CFC/BI-01-C",
+ "HID/HOS/HCR/BV-01-I",
"L2CAP/LE/CFC/BV-07-C",
"L2CAP/LE/CFC/BV-11-C",
"L2CAP/LE/CFC/BV-13-C",
- "L2CAP/LE/CFC/BV-15-C"
+ "L2CAP/LE/CFC/BV-15-C",
+ "L2CAP/LE/CID/BV-01-C",
+ "L2CAP/LE/CID/BV-02-C",
+ "L2CAP/LE/CPU/BV-01-C",
+ "L2CAP/LE/REJ/BI-02-C",
+ "SM/CEN/PKE/BV-01-C",
+ "SM/CEN/SCJW/BV-01-C",
+ "SM/CEN/SCPK/BI-02-C",
+ "SM/CEN/SCPK/BV-01-C",
+ "SM/CEN/SCPK/BV-04-C",
+ "SM/PER/SCJW/BV-02-C",
+ "SM/PER/SCPK/BI-04-C",
+ "SM/PER/SCPK/BV-03-C",
+ "SM/PER/SCCT/BV-04-C",
+ "SM/PER/SCCT/BV-06-C",
+ "SM/PER/SCCT/BV-08-C",
+ "SM/PER/SCCT/BV-10-C"
],
"ics": {
"TSPC_4.0HCI_1a_2": true,
@@ -1399,4 +1446,4 @@
"SPP": {},
"SUM ICS": {}
}
-} \ No newline at end of file
+}
diff --git a/android/pandora/server/proto/pandora_experimental/gatt.proto b/android/pandora/server/proto/pandora_experimental/gatt.proto
index 455005273e..4004513414 100644
--- a/android/pandora/server/proto/pandora_experimental/gatt.proto
+++ b/android/pandora/server/proto/pandora_experimental/gatt.proto
@@ -34,6 +34,9 @@ service GATT {
// Reads characteristic with given descriptor handle.
rpc ReadCharacteristicDescriptorFromHandle(ReadCharacteristicDescriptorRequest) returns (ReadCharacteristicDescriptorResponse);
+
+ // Register a GATT service
+ rpc RegisterService(RegisterServiceRequest) returns (RegisterServiceResponse);
}
enum AttStatusCode {
@@ -172,3 +175,22 @@ message ReadCharacteristicDescriptorResponse {
AttValue value = 1;
AttStatusCode status = 2;
}
+
+message GattServiceParams {
+ string uuid = 1;
+ repeated GattCharacteristicParams characteristics = 2;
+}
+
+message GattCharacteristicParams {
+ uint32 properties = 1;
+ uint32 permissions = 2;
+ string uuid = 3;
+}
+
+message RegisterServiceRequest {
+ GattServiceParams service = 1;
+}
+
+message RegisterServiceResponse {
+ GattService service = 1;
+}
diff --git a/android/pandora/server/proto/pandora_experimental/host.proto b/android/pandora/server/proto/pandora_experimental/host.proto
index 346b56293c..44b46106e6 100644
--- a/android/pandora/server/proto/pandora_experimental/host.proto
+++ b/android/pandora/server/proto/pandora_experimental/host.proto
@@ -41,8 +41,23 @@ service Host {
rpc GetLEConnection(GetLEConnectionRequest) returns (GetLEConnectionResponse);
// Disconnect ongoing LE connection.
rpc DisconnectLE(DisconnectLERequest) returns (google.protobuf.Empty);
- // Start LE advertisement
- rpc SetLEConnectable(google.protobuf.Empty) returns (google.protobuf.Empty);
+ // Create and enable an advertising set using legacy or extended advertising,
+ // except periodic advertising.
+ rpc StartAdvertising(StartAdvertisingRequest) returns (StartAdvertisingResponse);
+ // Create and enable a periodic advertising set.
+ rpc StartPeriodicAdvertising(StartPeriodicAdvertisingRequest) returns (StartPeriodicAdvertisingResponse);
+ // Remove an advertising set.
+ rpc StopAdvertising(StopAdvertisingRequest) returns (StopAdvertisingResponse);
+ // Run BR/EDR inquiry and returns each device found
+ rpc RunInquiry(RunInquiryRequest) returns (stream RunInquiryResponse);
+ // Run LE discovery (scanning) and return each device found
+ rpc RunDiscovery(RunDiscoveryRequest) returns (stream RunDiscoveryResponse);
+ // Set BREDR connectability mode
+ rpc SetConnectabilityMode(SetConnectabilityModeRequest) returns (SetConnectabilityModeResponse);
+ // Set BREDR discoverable mode
+ rpc SetDiscoverabilityMode(SetDiscoverabilityModeRequest) returns (SetDiscoverabilityModeResponse);
+ // Get device name from connection
+ rpc GetDeviceName(GetDeviceNameRequest) returns (GetDeviceNameResponse);
}
// Response of the `ReadLocalAddress` method.
@@ -54,15 +69,33 @@ message ReadLocalAddressResponse {
// A Token representing an ACL connection.
// It's acquired via a Connect on the Host service.
message Connection {
- // Opaque value filled by the gRPC server, must not
- // be modified nor crafted.
+ // Opaque value filled by the gRPC server, must not be modified nor crafted
+ // Android specific: it's secretly an encoded InternelConnectionRef created using newConnection
bytes cookie = 1;
}
+// Internal representation of a Connection - not exposed to clients, included here
+// just for code-generation convenience
+message InternalConnectionRef {
+ bytes address = 1;
+ Transport transport = 2;
+}
+
+// WARNING: Leaving this enum empty will default to BREDR, so make sure that this is a
+// valid default whenever used, and that we always populate this value.
+enum Transport {
+ TRANSPORT_BREDR = 0;
+ TRANSPORT_LE = 1;
+}
+
// Request of the `Connect` method.
message ConnectRequest {
// Peer Bluetooth Device Address as array of 6 bytes.
bytes address = 1;
+ // Whether we want to initiate pairing as part of the connection
+ bool skip_pairing = 2;
+ // Whether confirmation prompts should be auto-accepted or handled manually
+ bool manually_confirm = 3;
}
// Response of the `Connect` method.
@@ -141,3 +174,126 @@ message GetLEConnectionResponse {
message DisconnectLERequest {
Connection connection = 1;
}
+
+message AdvertisingHandle {
+ bytes cookie = 1;
+}
+
+enum AddressType {
+ PUBLIC = 0x00;
+ RANDOM = 0x01;
+}
+
+// Advertising Data including one or multiple AD types.
+// Since the Flags AD type is mandatory, it must be automatically set by the
+// IUT.
+// include_<AD type> fields are used for AD type which are generally not
+// exposed and that must be set by the IUT when specified.
+// See Core Supplement, Part A, Data Types for details
+message AdvertisingData {
+ repeated string service_uuids = 1;
+ bool include_local_name = 2;
+ bytes manufacturer_specific_data = 3;
+ bool include_tx_power_level = 4;
+ bool include_peripheral_connection_interval_range = 5;
+ repeated string service_solicitation = 6;
+ map<string, bytes> service_data = 7;
+ // Must be on 16 bits.
+ uint32 appearance = 8;
+ repeated bytes public_target_addresses = 9;
+ repeated bytes random_target_addresses = 10;
+ bool include_advertising_interval = 11;
+ bool include_le_address = 12;
+ bool include_le_role = 13;
+ string uri = 14;
+}
+
+message StartAdvertisingRequest {
+ bool legacy = 1;
+ DiscoverabilityMode discovery_mode = 2;
+ ConnectabilityMode connectability_mode = 3;
+ AddressType own_address_type = 4;
+ // If none, undirected.
+ bytes peer_address = 5;
+ AdvertisingData advertising_data = 6;
+ // If none, not scannable.
+ AdvertisingData scan_response_data = 7;
+}
+
+message StartAdvertisingResponse {
+ AdvertisingHandle handle = 1;
+}
+
+message StartPeriodicAdvertisingRequest {
+ AddressType own_address_type = 1;
+ // If none, undirected.
+ bytes peer_address = 2;
+ uint32 interval_min = 3;
+ uint32 interval_max = 4;
+ AdvertisingData advertising_data = 5;
+}
+
+message StartPeriodicAdvertisingResponse {
+ AdvertisingHandle handle = 1;
+}
+
+message StopAdvertisingRequest {
+ AdvertisingHandle handle = 1;
+}
+
+message StopAdvertisingResponse {}
+
+message RunInquiryRequest {
+}
+
+message RunInquiryResponse {
+ repeated Device device = 1;
+}
+
+message RunDiscoveryRequest {
+}
+
+message RunDiscoveryResponse {
+ Device device = 1;
+ uint32 flags = 2;
+}
+
+message Device {
+ string name = 1;
+ bytes address = 2;
+}
+
+// 5.3 Vol 3C 4.1 Discoverability Modes
+enum DiscoverabilityMode {
+ DISCOVERABILITY_UNSPECIFIED = 0;
+ DISCOVERABILITY_NONE = 1;
+ DISCOVERABILITY_LIMITED = 2;
+ DISCOVERABILITY_GENERAL = 3;
+}
+
+// 5.3 Vol 3C 4.2 Connectability Modes
+enum ConnectabilityMode {
+ CONNECTABILITY_UNSPECIFIED = 0;
+ CONNECTABILITY_NOT_CONNECTABLE = 1;
+ CONNECTABILITY_CONNECTABLE = 2;
+}
+
+message SetConnectabilityModeRequest {
+ ConnectabilityMode connectability = 1;
+}
+
+message SetConnectabilityModeResponse {}
+
+message SetDiscoverabilityModeRequest {
+ DiscoverabilityMode discoverability = 1;
+}
+
+message SetDiscoverabilityModeResponse {}
+
+message GetDeviceNameRequest {
+ Connection connection = 1;
+}
+
+message GetDeviceNameResponse {
+ string name = 1;
+}
diff --git a/android/pandora/server/src/com/android/pandora/A2dpSink.kt b/android/pandora/server/src/com/android/pandora/A2dpSink.kt
new file mode 100644
index 0000000000..d1a0be36f8
--- /dev/null
+++ b/android/pandora/server/src/com/android/pandora/A2dpSink.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.pandora
+
+import android.bluetooth.BluetoothA2dpSink
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothProfile
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.media.*
+import android.util.Log
+import io.grpc.Status
+import io.grpc.stub.StreamObserver
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.shareIn
+import pandora.A2DPGrpc.A2DPImplBase
+import pandora.A2dpProto.*
+
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class A2dpSink(val context: Context) : A2DPImplBase() {
+ private val TAG = "PandoraA2dpSink"
+
+ private val scope: CoroutineScope
+ private val flow: Flow<Intent>
+
+ private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
+ private val bluetoothAdapter = bluetoothManager.adapter
+ private val bluetoothA2dpSink =
+ getProfileProxy<BluetoothA2dpSink>(context, BluetoothProfile.A2DP_SINK)
+
+ init {
+ scope = CoroutineScope(Dispatchers.Default)
+ val intentFilter = IntentFilter()
+ intentFilter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED)
+
+ flow = intentFlow(context, intentFilter).shareIn(scope, SharingStarted.Eagerly)
+ }
+
+ fun deinit() {
+ bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, bluetoothA2dpSink)
+ scope.cancel()
+ }
+
+ override fun waitSink(
+ request: WaitSinkRequest,
+ responseObserver: StreamObserver<WaitSinkResponse>
+ ) {
+ grpcUnary<WaitSinkResponse>(scope, responseObserver) {
+ val device = request.connection.toBluetoothDevice(bluetoothAdapter)
+ Log.i(TAG, "waitSink: device=$device")
+
+ if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
+ Log.e(TAG, "Device is not bonded, cannot wait for stream")
+ throw Status.UNKNOWN.asException()
+ }
+
+ if (bluetoothA2dpSink.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
+ val state =
+ flow
+ .filter { it.getAction() == BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED }
+ .filter { it.getBluetoothDeviceExtra() == device }
+ .map { it.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR) }
+ .filter {
+ it == BluetoothProfile.STATE_CONNECTED || it == BluetoothProfile.STATE_DISCONNECTED
+ }
+ .first()
+
+ if (state == BluetoothProfile.STATE_DISCONNECTED) {
+ Log.e(TAG, "waitStream failed, A2DP has been disconnected")
+ throw Status.UNKNOWN.asException()
+ }
+ }
+
+ val sink = Sink.newBuilder().setConnection(request.connection).build()
+ WaitSinkResponse.newBuilder().setSink(sink).build()
+ }
+ }
+
+ override fun close(request: CloseRequest, responseObserver: StreamObserver<CloseResponse>) {
+ grpcUnary<CloseResponse>(scope, responseObserver) {
+ val device =
+ if (request.hasSink()) {
+ request.sink.connection.toBluetoothDevice(bluetoothAdapter)
+ } else {
+ Log.e(TAG, "Sink device required")
+ throw Status.UNKNOWN.asException()
+ }
+ Log.i(TAG, "close: device=$device")
+ if (bluetoothA2dpSink.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) {
+ Log.e(TAG, "Device is not connected, cannot close")
+ throw Status.UNKNOWN.asException()
+ }
+
+ val a2dpConnectionStateChangedFlow =
+ flow
+ .filter { it.getAction() == BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED }
+ .filter { it.getBluetoothDeviceExtra() == device }
+ .map { it.getIntExtra(BluetoothProfile.EXTRA_STATE, BluetoothAdapter.ERROR) }
+ bluetoothA2dpSink.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN)
+ a2dpConnectionStateChangedFlow.filter { it == BluetoothProfile.STATE_DISCONNECTED }.first()
+ CloseResponse.getDefaultInstance()
+ }
+ }
+}
diff --git a/android/pandora/server/src/com/android/pandora/Gatt.kt b/android/pandora/server/src/com/android/pandora/Gatt.kt
index 11ab70de19..122c634563 100644
--- a/android/pandora/server/src/com/android/pandora/Gatt.kt
+++ b/android/pandora/server/src/com/android/pandora/Gatt.kt
@@ -20,18 +20,20 @@ import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothGattCharacteristic
import android.bluetooth.BluetoothGattDescriptor
import android.bluetooth.BluetoothGattService
+import android.bluetooth.BluetoothGattService.SERVICE_TYPE_PRIMARY
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
-import com.google.protobuf.Empty
import io.grpc.Status
import io.grpc.stub.StreamObserver
import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -45,15 +47,15 @@ import pandora.GattProto.*
class Gatt(private val context: Context) : GATTImplBase() {
private val TAG = "PandoraGatt"
- private val mScope: CoroutineScope
- private val flow: Flow<Intent>
+ private val mScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
private val mBluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
private val mBluetoothAdapter = mBluetoothManager.adapter
- init {
- mScope = CoroutineScope(Dispatchers.Default)
+ private val serverManager by lazy { GattServerManager(mBluetoothManager, context, mScope) }
+ private val flow: Flow<Intent>
+ init {
val intentFilter = IntentFilter()
intentFilter.addAction(BluetoothDevice.ACTION_UUID)
@@ -61,14 +63,18 @@ class Gatt(private val context: Context) : GATTImplBase() {
}
fun deinit() {
+ serverManager.server.close()
mScope.cancel()
}
- override fun exchangeMTU(request: ExchangeMTURequest, responseObserver: StreamObserver<ExchangeMTUResponse>) {
+ override fun exchangeMTU(
+ request: ExchangeMTURequest,
+ responseObserver: StreamObserver<ExchangeMTUResponse>
+ ) {
grpcUnary<ExchangeMTUResponse>(mScope, responseObserver) {
val mtu = request.mtu
Log.i(TAG, "exchangeMTU MTU=$mtu")
- if (!GattInstance.get(request.connection.cookie).mGatt.requestMtu(mtu)) {
+ if (!GattInstance.get(request.connection.address).mGatt.requestMtu(mtu)) {
Log.e(TAG, "Error on requesting MTU $mtu")
throw Status.UNKNOWN.asException()
}
@@ -77,45 +83,37 @@ class Gatt(private val context: Context) : GATTImplBase() {
}
override fun writeAttFromHandle(
- request: WriteRequest,
- responseObserver: StreamObserver<WriteResponse>
+ request: WriteRequest,
+ responseObserver: StreamObserver<WriteResponse>
) {
grpcUnary<WriteResponse>(mScope, responseObserver) {
Log.i(TAG, "writeAttFromHandle handle=${request.handle}")
- val gattInstance = GattInstance.get(request.connection.cookie)
+ val gattInstance = GattInstance.get(request.connection.address)
var characteristic: BluetoothGattCharacteristic? =
- getCharacteristicWithHandle(request.handle, gattInstance)
+ getCharacteristicWithHandle(request.handle, gattInstance)
if (characteristic == null) {
val descriptor: BluetoothGattDescriptor? =
- getDescriptorWithHandle(request.handle, gattInstance)
- checkNotNull(descriptor) { "Found no characteristic or descriptor with handle ${request.handle}" }
- val valueWrote = gattInstance.writeDescriptorBlocking(
- descriptor,
- request.value.toByteArray()
- )
- WriteResponse.newBuilder()
- .setHandle(valueWrote.handle)
- .setStatus(valueWrote.status)
- .build()
+ getDescriptorWithHandle(request.handle, gattInstance)
+ checkNotNull(descriptor) {
+ "Found no characteristic or descriptor with handle ${request.handle}"
+ }
+ val valueWrote =
+ gattInstance.writeDescriptorBlocking(descriptor, request.value.toByteArray())
+ WriteResponse.newBuilder().setHandle(valueWrote.handle).setStatus(valueWrote.status).build()
} else {
- val valueWrote = gattInstance.writeCharacteristicBlocking(
- characteristic,
- request.value.toByteArray()
- )
- WriteResponse.newBuilder()
- .setHandle(valueWrote.handle)
- .setStatus(valueWrote.status)
- .build()
+ val valueWrote =
+ gattInstance.writeCharacteristicBlocking(characteristic, request.value.toByteArray())
+ WriteResponse.newBuilder().setHandle(valueWrote.handle).setStatus(valueWrote.status).build()
}
}
}
override fun discoverServiceByUuid(
- request: DiscoverServiceByUuidRequest,
- responseObserver: StreamObserver<DiscoverServicesResponse>
+ request: DiscoverServiceByUuidRequest,
+ responseObserver: StreamObserver<DiscoverServicesResponse>
) {
grpcUnary<DiscoverServicesResponse>(mScope, responseObserver) {
- val gattInstance = GattInstance.get(request.connection.cookie)
+ val gattInstance = GattInstance.get(request.connection.address)
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.
@@ -130,32 +128,32 @@ class Gatt(private val context: Context) : GATTImplBase() {
}
override fun discoverServices(
- request: DiscoverServicesRequest,
- responseObserver: StreamObserver<DiscoverServicesResponse>
+ request: DiscoverServicesRequest,
+ responseObserver: StreamObserver<DiscoverServicesResponse>
) {
grpcUnary<DiscoverServicesResponse>(mScope, responseObserver) {
Log.i(TAG, "discoverServices")
- val gattInstance = GattInstance.get(request.connection.cookie)
+ val gattInstance = GattInstance.get(request.connection.address)
check(gattInstance.mGatt.discoverServices())
gattInstance.waitForDiscoveryEnd()
DiscoverServicesResponse.newBuilder()
- .addAllServices(generateServicesList(gattInstance.mGatt.services, 1))
- .build()
+ .addAllServices(generateServicesList(gattInstance.mGatt.services, 1))
+ .build()
}
}
override fun discoverServicesSdp(
- request: DiscoverServicesSdpRequest,
- responseObserver: StreamObserver<DiscoverServicesSdpResponse>
+ request: DiscoverServicesSdpRequest,
+ responseObserver: StreamObserver<DiscoverServicesSdpResponse>
) {
grpcUnary<DiscoverServicesSdpResponse>(mScope, responseObserver) {
Log.i(TAG, "discoverServicesSdp")
val bluetoothDevice = request.address.toBluetoothDevice(mBluetoothAdapter)
check(bluetoothDevice.fetchUuidsWithSdp())
flow
- .filter { it.getAction() == BluetoothDevice.ACTION_UUID }
- .filter { it.getBluetoothDeviceExtra() == bluetoothDevice }
- .first()
+ .filter { it.getAction() == BluetoothDevice.ACTION_UUID }
+ .filter { it.getBluetoothDeviceExtra() == bluetoothDevice }
+ .first()
val uuidsList = arrayListOf<String>()
for (parcelUuid in bluetoothDevice.getUuids()) {
uuidsList.add(parcelUuid.toString())
@@ -164,76 +162,102 @@ class Gatt(private val context: Context) : GATTImplBase() {
}
}
- override fun clearCache(request: ClearCacheRequest, responseObserver: StreamObserver<ClearCacheResponse>) {
+ override fun clearCache(
+ request: ClearCacheRequest,
+ responseObserver: StreamObserver<ClearCacheResponse>
+ ) {
grpcUnary<ClearCacheResponse>(mScope, responseObserver) {
Log.i(TAG, "clearCache")
- val gattInstance = GattInstance.get(request.connection.cookie)
+ val gattInstance = GattInstance.get(request.connection.address)
check(gattInstance.mGatt.refresh())
ClearCacheResponse.newBuilder().build()
}
}
override fun readCharacteristicFromHandle(
- request: ReadCharacteristicRequest,
- responseObserver: StreamObserver<ReadCharacteristicResponse>
+ request: ReadCharacteristicRequest,
+ responseObserver: StreamObserver<ReadCharacteristicResponse>
) {
grpcUnary<ReadCharacteristicResponse>(mScope, responseObserver) {
Log.i(TAG, "readCharacteristicFromHandle handle=${request.handle}")
- val gattInstance = GattInstance.get(request.connection.cookie)
+ val gattInstance = GattInstance.get(request.connection.address)
val characteristic: BluetoothGattCharacteristic? =
- getCharacteristicWithHandle(request.handle, gattInstance)
+ getCharacteristicWithHandle(request.handle, gattInstance)
checkNotNull(characteristic) { "Characteristic handle ${request.handle} not found." }
val readValue = gattInstance.readCharacteristicBlocking(characteristic)
ReadCharacteristicResponse.newBuilder()
- .setValue(
- AttValue.newBuilder()
- .setHandle(readValue.handle)
- .setValue(readValue.value)
- )
- .setStatus(readValue.status)
- .build()
+ .setValue(AttValue.newBuilder().setHandle(readValue.handle).setValue(readValue.value))
+ .setStatus(readValue.status)
+ .build()
}
}
override fun readCharacteristicsFromUuid(
- request: ReadCharacteristicsFromUuidRequest,
- responseObserver: StreamObserver<ReadCharacteristicsFromUuidResponse>
+ request: ReadCharacteristicsFromUuidRequest,
+ responseObserver: StreamObserver<ReadCharacteristicsFromUuidResponse>
) {
grpcUnary<ReadCharacteristicsFromUuidResponse>(mScope, responseObserver) {
Log.i(TAG, "readCharacteristicsFromUuid uuid=${request.uuid}")
- val gattInstance = GattInstance.get(request.connection.cookie)
+ val gattInstance = GattInstance.get(request.connection.address)
tryDiscoverServices(gattInstance)
val readValues =
- gattInstance.readCharacteristicUuidBlocking(
- UUID.fromString(request.uuid),
- request.startHandle,
- request.endHandle
- )
+ gattInstance.readCharacteristicUuidBlocking(
+ UUID.fromString(request.uuid), request.startHandle, request.endHandle)
ReadCharacteristicsFromUuidResponse.newBuilder()
- .addAllCharacteristicsRead(generateReadValuesList(readValues))
- .build()
+ .addAllCharacteristicsRead(generateReadValuesList(readValues))
+ .build()
}
}
override fun readCharacteristicDescriptorFromHandle(
- request: ReadCharacteristicDescriptorRequest,
- responseObserver: StreamObserver<ReadCharacteristicDescriptorResponse>
+ request: ReadCharacteristicDescriptorRequest,
+ responseObserver: StreamObserver<ReadCharacteristicDescriptorResponse>
) {
grpcUnary<ReadCharacteristicDescriptorResponse>(mScope, responseObserver) {
Log.i(TAG, "readCharacteristicDescriptorFromHandle handle=${request.handle}")
- val gattInstance = GattInstance.get(request.connection.cookie)
+ val gattInstance = GattInstance.get(request.connection.address)
val descriptor: BluetoothGattDescriptor? =
- getDescriptorWithHandle(request.handle, gattInstance)
+ getDescriptorWithHandle(request.handle, gattInstance)
checkNotNull(descriptor) { "Descriptor handle ${request.handle} not found." }
val readValue = gattInstance.readDescriptorBlocking(descriptor)
ReadCharacteristicDescriptorResponse.newBuilder()
- .setValue(
- AttValue.newBuilder()
- .setHandle(readValue.handle)
- .setValue(readValue.value)
- )
- .setStatus(readValue.status)
- .build()
+ .setValue(AttValue.newBuilder().setHandle(readValue.handle).setValue(readValue.value))
+ .setStatus(readValue.status)
+ .build()
+ }
+ }
+
+ override fun registerService(
+ request: RegisterServiceRequest,
+ responseObserver: StreamObserver<RegisterServiceResponse>
+ ) {
+ grpcUnary(mScope, responseObserver) {
+ val service =
+ BluetoothGattService(UUID.fromString(request.service.uuid), SERVICE_TYPE_PRIMARY)
+ for (characteristic in request.service.characteristicsList) {
+ service.addCharacteristic(
+ BluetoothGattCharacteristic(
+ UUID.fromString(characteristic.uuid),
+ characteristic.properties,
+ characteristic.permissions))
+ }
+
+ val fullService = coroutineScope {
+ val firstService = mScope.async { serverManager.newServiceFlow.first() }
+ serverManager.server.addService(service)
+ firstService.await()
+ }
+
+ RegisterServiceResponse.newBuilder()
+ .setService(
+ GattService.newBuilder()
+ .setHandle(fullService.instanceId)
+ .setType(fullService.type)
+ .setUuid(fullService.uuid.toString())
+ .addAllIncludedServices(generateServicesList(service.includedServices, 1))
+ .addAllCharacteristics(generateCharacteristicsList(service.characteristics))
+ .build())
+ .build()
}
}
@@ -242,8 +266,8 @@ class Gatt(private val context: Context) : GATTImplBase() {
* package-private so we have to redefine it here.
*/
private suspend fun getCharacteristicWithHandle(
- handle: Int,
- gattInstance: GattInstance
+ handle: Int,
+ gattInstance: GattInstance
): BluetoothGattCharacteristic? {
tryDiscoverServices(gattInstance)
for (service: BluetoothGattService in gattInstance.mGatt.services.orEmpty()) {
@@ -261,8 +285,8 @@ class Gatt(private val context: Context) : GATTImplBase() {
* package-private so we have to redefine it here.
*/
private suspend fun getDescriptorWithHandle(
- handle: Int,
- gattInstance: GattInstance
+ handle: Int,
+ gattInstance: GattInstance
): BluetoothGattDescriptor? {
tryDiscoverServices(gattInstance)
for (service: BluetoothGattService in gattInstance.mGatt.services.orEmpty()) {
@@ -279,18 +303,18 @@ class Gatt(private val context: Context) : GATTImplBase() {
/** Generates a list of GattService from a list of BluetoothGattService. */
private fun generateServicesList(
- servicesList: List<BluetoothGattService>,
- dpth: Int
+ servicesList: List<BluetoothGattService>,
+ dpth: Int
): ArrayList<GattService> {
val newServicesList = arrayListOf<GattService>()
for (service in servicesList) {
val serviceBuilder =
- GattService.newBuilder()
- .setHandle(service.getInstanceId())
- .setType(service.getType())
- .setUuid(service.getUuid().toString())
- .addAllIncludedServices(generateServicesList(service.getIncludedServices(), dpth + 1))
- .addAllCharacteristics(generateCharacteristicsList(service.characteristics))
+ GattService.newBuilder()
+ .setHandle(service.getInstanceId())
+ .setType(service.getType())
+ .setUuid(service.getUuid().toString())
+ .addAllIncludedServices(generateServicesList(service.getIncludedServices(), dpth + 1))
+ .addAllCharacteristics(generateCharacteristicsList(service.characteristics))
newServicesList.add(serviceBuilder.build())
}
return newServicesList
@@ -298,17 +322,17 @@ class Gatt(private val context: Context) : GATTImplBase() {
/** Generates a list of GattCharacteristic from a list of BluetoothGattCharacteristic. */
private fun generateCharacteristicsList(
- characteristicsList: List<BluetoothGattCharacteristic>
+ characteristicsList: List<BluetoothGattCharacteristic>
): ArrayList<GattCharacteristic> {
val newCharacteristicsList = arrayListOf<GattCharacteristic>()
for (characteristic in characteristicsList) {
val characteristicBuilder =
- GattCharacteristic.newBuilder()
- .setProperties(characteristic.getProperties())
- .setPermissions(characteristic.getPermissions())
- .setUuid(characteristic.getUuid().toString())
- .addAllDescriptors(generateDescriptorsList(characteristic.getDescriptors()))
- .setHandle(characteristic.getInstanceId())
+ GattCharacteristic.newBuilder()
+ .setProperties(characteristic.getProperties())
+ .setPermissions(characteristic.getPermissions())
+ .setUuid(characteristic.getUuid().toString())
+ .addAllDescriptors(generateDescriptorsList(characteristic.getDescriptors()))
+ .setHandle(characteristic.getInstanceId())
newCharacteristicsList.add(characteristicBuilder.build())
}
return newCharacteristicsList
@@ -316,15 +340,15 @@ class Gatt(private val context: Context) : GATTImplBase() {
/** Generates a list of GattCharacteristicDescriptor from a list of BluetoothGattDescriptor. */
private fun generateDescriptorsList(
- descriptorsList: List<BluetoothGattDescriptor>
+ descriptorsList: List<BluetoothGattDescriptor>
): ArrayList<GattCharacteristicDescriptor> {
val newDescriptorsList = arrayListOf<GattCharacteristicDescriptor>()
for (descriptor in descriptorsList) {
val descriptorBuilder =
- GattCharacteristicDescriptor.newBuilder()
- .setHandle(descriptor.getInstanceId())
- .setPermissions(descriptor.getPermissions())
- .setUuid(descriptor.getUuid().toString())
+ GattCharacteristicDescriptor.newBuilder()
+ .setHandle(descriptor.getInstanceId())
+ .setPermissions(descriptor.getPermissions())
+ .setUuid(descriptor.getUuid().toString())
newDescriptorsList.add(descriptorBuilder.build())
}
return newDescriptorsList
@@ -332,18 +356,14 @@ class Gatt(private val context: Context) : GATTImplBase() {
/** Generates a list of ReadCharacteristicResponse from a list of GattInstanceValueRead. */
private fun generateReadValuesList(
- readValuesList: ArrayList<GattInstance.GattInstanceValueRead>
+ readValuesList: ArrayList<GattInstance.GattInstanceValueRead>
): ArrayList<ReadCharacteristicResponse> {
val newReadValuesList = arrayListOf<ReadCharacteristicResponse>()
for (readValue in readValuesList) {
val readValueBuilder =
- ReadCharacteristicResponse.newBuilder()
- .setValue(
- AttValue.newBuilder()
- .setHandle(readValue.handle)
- .setValue(readValue.value)
- )
- .setStatus(readValue.status)
+ ReadCharacteristicResponse.newBuilder()
+ .setValue(AttValue.newBuilder().setHandle(readValue.handle).setValue(readValue.value))
+ .setStatus(readValue.status)
newReadValuesList.add(readValueBuilder.build())
}
return newReadValuesList
diff --git a/android/pandora/server/src/com/android/pandora/GattServerManager.kt b/android/pandora/server/src/com/android/pandora/GattServerManager.kt
new file mode 100644
index 0000000000..d753a84876
--- /dev/null
+++ b/android/pandora/server/src/com/android/pandora/GattServerManager.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.pandora
+
+import android.bluetooth.BluetoothGatt
+import android.bluetooth.BluetoothGattServer
+import android.bluetooth.BluetoothGattServerCallback
+import android.bluetooth.BluetoothGattService
+import android.bluetooth.BluetoothManager
+import android.content.Context
+import java.util.UUID
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+
+class GattServerManager(bluetoothManager: BluetoothManager, context: Context, globalScope: CoroutineScope) {
+ val services = mutableMapOf<UUID, BluetoothGattService>()
+ val server: BluetoothGattServer
+
+ val newServiceFlow = MutableSharedFlow<BluetoothGattService>(extraBufferCapacity = 8)
+
+ init {
+ newServiceFlow.map {
+ services[it.uuid] = it
+ }.launchIn(globalScope)
+ }
+
+ init {
+ val callback =
+ object : BluetoothGattServerCallback() {
+ override fun onServiceAdded(status: Int, service: BluetoothGattService) {
+ check(status == BluetoothGatt.GATT_SUCCESS)
+ check(newServiceFlow.tryEmit(service))
+ }
+ }
+ server = bluetoothManager.openGattServer(context, callback)
+ }
+}
diff --git a/android/pandora/server/src/com/android/pandora/Host.kt b/android/pandora/server/src/com/android/pandora/Host.kt
index 6862d75fd9..f0811488d4 100644
--- a/android/pandora/server/src/com/android/pandora/Host.kt
+++ b/android/pandora/server/src/com/android/pandora/Host.kt
@@ -17,33 +17,40 @@
package com.android.pandora
import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothAssignedNumbers
import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothDevice.ADDRESS_TYPE_PUBLIC
+import android.bluetooth.BluetoothDevice.ADDRESS_TYPE_RANDOM
import android.bluetooth.BluetoothDevice.BOND_BONDED
+import android.bluetooth.BluetoothDevice.TRANSPORT_AUTO
+import android.bluetooth.BluetoothDevice.TRANSPORT_BREDR
import android.bluetooth.BluetoothDevice.TRANSPORT_LE
import android.bluetooth.BluetoothManager
import android.bluetooth.BluetoothProfile
import android.bluetooth.le.AdvertiseCallback
import android.bluetooth.le.AdvertiseData
import android.bluetooth.le.AdvertiseSettings
-import android.bluetooth.le.AdvertisingSetParameters
import android.bluetooth.le.ScanCallback
import android.bluetooth.le.ScanResult
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.MacAddress
+import android.os.ParcelUuid
import android.util.Log
import com.google.protobuf.ByteString
import com.google.protobuf.Empty
import io.grpc.Status
import io.grpc.stub.StreamObserver
-import kotlin.Result.Companion.failure
-import kotlin.Result.Companion.success
-import kotlin.coroutines.suspendCoroutine
+import java.io.IOException
+import java.time.Duration
+import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.sendBlocking
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
@@ -55,19 +62,25 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
import pandora.HostGrpc.HostImplBase
import pandora.HostProto.*
@kotlinx.coroutines.ExperimentalCoroutinesApi
class Host(private val context: Context, private val server: Server) : HostImplBase() {
private val TAG = "PandoraHost"
- private val ADVERTISEMENT_DURATION_MILLIS: Int = 10000
+
private val scope: CoroutineScope
private val flow: Flow<Intent>
private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
private val bluetoothAdapter = bluetoothManager.adapter
+ private var connectability = ConnectabilityMode.CONNECTABILITY_UNSPECIFIED
+ private var discoverability = DiscoverabilityMode.DISCOVERABILITY_UNSPECIFIED
+
+ private val advertisers = mutableMapOf<UUID, AdvertiseCallback>()
+
init {
scope = CoroutineScope(Dispatchers.Default)
@@ -77,6 +90,9 @@ class Host(private val context: Context, private val server: Server) : HostImplB
intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)
+ intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
+ intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
+ intentFilter.addAction(BluetoothDevice.ACTION_FOUND)
// Creates a shared flow of intents that can be used in all methods in the coroutine scope.
// This flow is started eagerly to make sure that the broadcast receiver is registered before
@@ -216,81 +232,59 @@ class Host(private val context: Context, private val server: Server) : HostImplB
acceptPairingAndAwaitBonded(bluetoothDevice)
WaitConnectionResponse.newBuilder()
- .setConnection(
- Connection.newBuilder()
- .setCookie(ByteString.copyFromUtf8(bluetoothDevice.address))
- .build()
- )
+ .setConnection(newConnection(bluetoothDevice, Transport.TRANSPORT_BREDR))
.build()
}
}
- /**
- * Set the device in advertisement mode for #ADVERTISEMENT_DURATION_MILLIS milliseconds.
- * @param request Request sent by the client.
- * @param responseObserver Response to build and set back to the client.
- */
- override fun setLEConnectable(
- request: Empty,
- responseObserver: StreamObserver<Empty>,
- ) {
- // Creates a gRPC coroutine in a given coroutine scope which executes a given suspended function
- // returning a gRPC response and sends it on a given gRPC stream observer.
- grpcUnary<Empty>(scope, responseObserver) {
- Log.i(TAG, "setLEConnectable")
- val advertiser = bluetoothAdapter.getBluetoothLeAdvertiser()
- val advertiseSettings =
- AdvertiseSettings.Builder()
- .setConnectable(true)
- .setOwnAddressType(AdvertisingSetParameters.ADDRESS_TYPE_PUBLIC)
- .setTimeout(ADVERTISEMENT_DURATION_MILLIS)
- .build()
- val advertiseData = AdvertiseData.Builder().build()
- suspendCoroutine<Boolean> { continuation ->
- val advertiseCallback =
- object : AdvertiseCallback() {
- override fun onStartFailure(errorCode: Int) {
- Log.i(TAG, "Advertising failed: $errorCode")
- continuation.resumeWith(failure(Exception("Advertising failed: $errorCode")))
- }
-
- override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
- Log.i(TAG, "Advertising success")
- continuation.resumeWith(success(true))
- }
- }
- advertiser.startAdvertising(advertiseSettings, advertiseData, advertiseCallback)
- }
-
- // Response sent to client
- Empty.getDefaultInstance()
- }
- }
-
override fun connect(request: ConnectRequest, responseObserver: StreamObserver<ConnectResponse>) {
grpcUnary(scope, responseObserver) {
val bluetoothDevice = request.address.toBluetoothDevice(bluetoothAdapter)
Log.i(TAG, "connect: address=$bluetoothDevice")
+ bluetoothAdapter.cancelDiscovery()
+
if (!bluetoothDevice.isConnected()) {
- if (bluetoothDevice.bondState == BOND_BONDED) {
- // already bonded, just reconnect
- bluetoothDevice.connect()
- waitConnectionIntent(bluetoothDevice)
+ if (request.skipPairing) {
+ // do an SDP request to trigger a temporary BREDR connection
+ try {
+ withTimeout(1500) { bluetoothDevice.createRfcommSocket(3).connect() }
+ } catch (e: IOException) {
+ // ignore
+ }
} else {
- // need to bond
- bluetoothDevice.createBond()
- acceptPairingAndAwaitBonded(bluetoothDevice)
+ if (bluetoothDevice.bondState == BOND_BONDED) {
+ // already bonded, just reconnect
+ bluetoothDevice.connect()
+ waitConnectionIntent(bluetoothDevice)
+ } else {
+ // need to bond
+ bluetoothDevice.createBond()
+ if (!request.manuallyConfirm) {
+ acceptPairingAndAwaitBonded(bluetoothDevice)
+ }
+ }
}
}
ConnectResponse.newBuilder()
- .setConnection(
- Connection.newBuilder()
- .setCookie(ByteString.copyFromUtf8(bluetoothDevice.address))
- .build()
- )
+ .setConnection(newConnection(bluetoothDevice, Transport.TRANSPORT_BREDR))
+ .build()
+ }
+ }
+
+ override fun getConnection(
+ request: GetConnectionRequest,
+ responseObserver: StreamObserver<GetConnectionResponse>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ val device = bluetoothAdapter.getRemoteDevice(request.address.toByteArray())
+ check(
+ device.isConnected() && device.type != BluetoothDevice.DEVICE_TYPE_LE
+ ) // either classic or dual
+ GetConnectionResponse.newBuilder()
+ .setConnection(newConnection(device, Transport.TRANSPORT_BREDR))
.build()
}
}
@@ -316,6 +310,7 @@ class Host(private val context: Context, private val server: Server) : HostImplB
bluetoothDevice.disconnect()
connectionStateChangedFlow.filter { it == BluetoothAdapter.STATE_DISCONNECTED }.first()
+
DisconnectResponse.getDefaultInstance()
}
}
@@ -326,13 +321,17 @@ class Host(private val context: Context, private val server: Server) : HostImplB
) {
grpcUnary<ConnectLEResponse>(scope, responseObserver) {
val address = request.address.decodeAsMacAddressToString()
- Log.i(TAG, "connectLE: $address")
- val device = scanLeDevice(address)
- GattInstance(device!!, TRANSPORT_LE, context).waitForState(BluetoothProfile.STATE_CONNECTED)
+ Log.i(TAG, "connect LE: $address")
+ val device = scanLeDevice(address)!!
+ GattInstance(device, TRANSPORT_LE, context)
+
+ flow
+ .filter { it.action == BluetoothDevice.ACTION_ACL_CONNECTED }
+ .filter { it.getBluetoothDeviceExtra() == device }
+ .first()
+
ConnectLEResponse.newBuilder()
- .setConnection(
- Connection.newBuilder().setCookie(ByteString.copyFromUtf8(device.address)).build()
- )
+ .setConnection(newConnection(device, Transport.TRANSPORT_LE))
.build()
}
}
@@ -344,13 +343,10 @@ class Host(private val context: Context, private val server: Server) : HostImplB
grpcUnary<GetLEConnectionResponse>(scope, responseObserver) {
val address = request.address.decodeAsMacAddressToString()
Log.i(TAG, "getLEConnection: $address")
- val device =
- bluetoothAdapter.getRemoteLeDevice(address, BluetoothDevice.ADDRESS_TYPE_PUBLIC)
+ val device = bluetoothAdapter.getRemoteLeDevice(address, BluetoothDevice.ADDRESS_TYPE_PUBLIC)
if (device.isConnected) {
GetLEConnectionResponse.newBuilder()
- .setConnection(
- Connection.newBuilder().setCookie(ByteString.copyFromUtf8(device.address)).build()
- )
+ .setConnection(newConnection(device, Transport.TRANSPORT_LE))
.build()
} else {
Log.e(TAG, "Device: $device is not connected")
@@ -361,7 +357,7 @@ class Host(private val context: Context, private val server: Server) : HostImplB
override fun disconnectLE(request: DisconnectLERequest, responseObserver: StreamObserver<Empty>) {
grpcUnary<Empty>(scope, responseObserver) {
- val address = request.connection.cookie.toByteArray().decodeToString()
+ val address = request.connection.address
Log.i(TAG, "disconnectLE: $address")
val gattInstance = GattInstance.get(address)
@@ -405,4 +401,217 @@ class Host(private val context: Context, private val server: Server) : HostImplB
}
return bluetoothDevice
}
+
+ override fun startAdvertising(
+ request: StartAdvertisingRequest,
+ responseObserver: StreamObserver<StartAdvertisingResponse>
+ ) {
+ Log.d(TAG, "startAdvertising")
+ grpcUnary(scope, responseObserver) {
+ val handle = UUID.randomUUID()
+
+ callbackFlow {
+ val callback =
+ object : AdvertiseCallback() {
+ override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
+ sendBlocking(
+ StartAdvertisingResponse.newBuilder()
+ .setHandle(
+ AdvertisingHandle.newBuilder()
+ .setCookie(ByteString.copyFromUtf8(handle.toString()))
+ )
+ .build()
+ )
+ }
+ override fun onStartFailure(errorCode: Int) {
+ error("failed to start advertising")
+ }
+ }
+
+ advertisers[handle] = callback
+
+ val advertisingDataBuilder = AdvertiseData.Builder()
+
+ for (service_uuid in request.advertisingData.serviceUuidsList) {
+ advertisingDataBuilder.addServiceUuid(ParcelUuid.fromString(service_uuid))
+ }
+
+ advertisingDataBuilder
+ .setIncludeDeviceName(request.advertisingData.includeLocalName)
+ .setIncludeTxPowerLevel(request.advertisingData.includeTxPowerLevel)
+ .addManufacturerData(
+ BluetoothAssignedNumbers.GOOGLE,
+ request.advertisingData.manufacturerSpecificData.toByteArray()
+ )
+
+ bluetoothAdapter.bluetoothLeAdvertiser.startAdvertising(
+ AdvertiseSettings.Builder()
+ .setConnectable(
+ request.connectabilityMode == ConnectabilityMode.CONNECTABILITY_CONNECTABLE
+ )
+ .setOwnAddressType(
+ when (request.ownAddressType!!) {
+ AddressType.PUBLIC -> ADDRESS_TYPE_PUBLIC
+ AddressType.RANDOM -> ADDRESS_TYPE_RANDOM
+ AddressType.UNRECOGNIZED ->
+ error("unrecognized address type ${request.ownAddressType}")
+ }
+ )
+ .build(),
+ advertisingDataBuilder.build(),
+ callback,
+ )
+
+ awaitClose { /* no-op */}
+ }
+ .first()
+ }
+ }
+
+ override fun runInquiry(
+ request: RunInquiryRequest,
+ responseObserver: StreamObserver<RunInquiryResponse>
+ ) {
+ Log.d(TAG, "runInquiry")
+ grpcServerStream(scope, responseObserver) {
+ launch {
+ try {
+ bluetoothAdapter.startDiscovery()
+ awaitCancellation()
+ } finally {
+ bluetoothAdapter.cancelDiscovery()
+ }
+ }
+ flow
+ .filter { it.action == BluetoothDevice.ACTION_FOUND }
+ .map {
+ val device = it.getBluetoothDeviceExtra()
+ Log.i(TAG, "Device found: $device")
+ RunInquiryResponse.newBuilder()
+ .addDevice(
+ Device.newBuilder()
+ .setName(device.name)
+ .setAddress(device.toByteString())
+ )
+ .build()
+ }
+ }
+ }
+
+ override fun setConnectabilityMode(
+ request: SetConnectabilityModeRequest,
+ responseObserver: StreamObserver<SetConnectabilityModeResponse>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ Log.d(TAG, "setConnectabilityMode")
+ connectability = request.connectability!!
+
+ val scanMode =
+ when (connectability) {
+ ConnectabilityMode.CONNECTABILITY_UNSPECIFIED,
+ ConnectabilityMode.UNRECOGNIZED -> null
+ ConnectabilityMode.CONNECTABILITY_NOT_CONNECTABLE -> {
+ BluetoothAdapter.SCAN_MODE_NONE
+ }
+ ConnectabilityMode.CONNECTABILITY_CONNECTABLE -> {
+ if (
+ discoverability == DiscoverabilityMode.DISCOVERABILITY_LIMITED ||
+ discoverability == DiscoverabilityMode.DISCOVERABILITY_GENERAL
+ ) {
+ BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
+ } else {
+ BluetoothAdapter.SCAN_MODE_CONNECTABLE
+ }
+ }
+ }
+
+ if (scanMode != null) {
+ bluetoothAdapter.setScanMode(scanMode)
+ }
+ SetConnectabilityModeResponse.getDefaultInstance()
+ }
+ }
+
+ override fun setDiscoverabilityMode(
+ request: SetDiscoverabilityModeRequest,
+ responseObserver: StreamObserver<SetDiscoverabilityModeResponse>
+ ) {
+ Log.d(TAG, "setDiscoverabilityMode")
+ grpcUnary(scope, responseObserver) {
+ discoverability = request.discoverability!!
+
+ val scanMode =
+ when (discoverability) {
+ DiscoverabilityMode.DISCOVERABILITY_UNSPECIFIED,
+ DiscoverabilityMode.UNRECOGNIZED -> null
+ DiscoverabilityMode.DISCOVERABILITY_NONE ->
+ if (connectability == ConnectabilityMode.CONNECTABILITY_CONNECTABLE) {
+ BluetoothAdapter.SCAN_MODE_CONNECTABLE
+ } else {
+ BluetoothAdapter.SCAN_MODE_NONE
+ }
+ DiscoverabilityMode.DISCOVERABILITY_LIMITED,
+ DiscoverabilityMode.DISCOVERABILITY_GENERAL ->
+ BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE
+ }
+
+ if (scanMode != null) {
+ bluetoothAdapter.setScanMode(scanMode)
+ }
+
+ if (request.discoverability == DiscoverabilityMode.DISCOVERABILITY_LIMITED) {
+ bluetoothAdapter.setDiscoverableTimeout(
+ Duration.ofSeconds(120)
+ ) // limited discoverability needs a timeout, 120s is Android default
+ }
+ SetDiscoverabilityModeResponse.getDefaultInstance()
+ }
+ }
+
+ override fun runDiscovery(
+ request: RunDiscoveryRequest,
+ responseObserver: StreamObserver<RunDiscoveryResponse>
+ ) {
+ Log.d(TAG, "runDiscovery")
+ grpcServerStream(scope, responseObserver) {
+ callbackFlow {
+ val callback =
+ object : ScanCallback() {
+ override fun onScanResult(callbackType: Int, result: ScanResult) {
+ sendBlocking(
+ RunDiscoveryResponse.newBuilder()
+ .setDevice(
+ Device.newBuilder()
+ .setAddress(
+ ByteString.copyFrom(
+ MacAddress.fromString(result.device.address).toByteArray()
+ )
+ )
+ .setName(result.device.name ?: "")
+ )
+ .setFlags(result.scanRecord?.advertiseFlags ?: 0)
+ .build()
+ )
+ }
+
+ override fun onScanFailed(errorCode: Int) {
+ error("scan failed")
+ }
+ }
+ bluetoothAdapter.bluetoothLeScanner.startScan(callback)
+
+ awaitClose { bluetoothAdapter.bluetoothLeScanner.stopScan(callback) }
+ }
+ }
+ }
+
+ override fun getDeviceName(
+ request: GetDeviceNameRequest,
+ responseObserver: StreamObserver<GetDeviceNameResponse>
+ ) {
+ grpcUnary(scope, responseObserver) {
+ val device = request.connection.toBluetoothDevice(bluetoothAdapter)
+ GetDeviceNameResponse.newBuilder().setName(device.name).build()
+ }
+ }
}
diff --git a/android/pandora/server/src/com/android/pandora/Security.kt b/android/pandora/server/src/com/android/pandora/Security.kt
index e7ded2990a..01247f03bd 100644
--- a/android/pandora/server/src/com/android/pandora/Security.kt
+++ b/android/pandora/server/src/com/android/pandora/Security.kt
@@ -18,9 +18,15 @@ package com.android.pandora
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
+import android.bluetooth.BluetoothDevice.ACTION_PAIRING_REQUEST
+import android.bluetooth.BluetoothDevice.BOND_BONDED
import android.bluetooth.BluetoothDevice.DEVICE_TYPE_CLASSIC
+import android.bluetooth.BluetoothDevice.DEVICE_TYPE_DUAL
import android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE
import android.bluetooth.BluetoothDevice.EXTRA_PAIRING_VARIANT
+import android.bluetooth.BluetoothDevice.TRANSPORT_AUTO
+import android.bluetooth.BluetoothDevice.TRANSPORT_BREDR
+import android.bluetooth.BluetoothDevice.TRANSPORT_LE
import android.bluetooth.BluetoothManager
import android.content.Context
import android.content.Intent
@@ -31,6 +37,7 @@ import com.google.protobuf.Empty
import io.grpc.stub.StreamObserver
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
@@ -57,6 +64,7 @@ class Security(private val context: Context) : SecurityImplBase() {
init {
val intentFilter = IntentFilter()
intentFilter.addAction(BluetoothDevice.ACTION_PAIRING_REQUEST)
+ intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
flow = intentFlow(context, intentFilter).shareIn(globalScope, SharingStarted.Eagerly)
}
@@ -68,8 +76,17 @@ class Security(private val context: Context) : SecurityImplBase() {
override fun pair(request: PairRequest, responseObserver: StreamObserver<Empty>) {
grpcUnary(globalScope, responseObserver) {
val bluetoothDevice = request.connection.toBluetoothDevice(bluetoothAdapter)
- Log.i(TAG, "pair: ${bluetoothDevice.address}")
- bluetoothDevice.createBond()
+ Log.i(
+ TAG,
+ "pair: ${bluetoothDevice.address} (current bond state: ${bluetoothDevice.bondState})"
+ )
+ bluetoothDevice.createBond(
+ when (request.connection.transport!!) {
+ Transport.TRANSPORT_LE -> TRANSPORT_LE
+ Transport.TRANSPORT_BREDR -> TRANSPORT_BREDR
+ Transport.UNRECOGNIZED -> TRANSPORT_AUTO
+ }
+ )
Empty.getDefaultInstance()
}
}
@@ -78,26 +95,28 @@ class Security(private val context: Context) : SecurityImplBase() {
request: DeletePairingRequest,
responseObserver: StreamObserver<DeletePairingResponse>
) {
- grpcUnary<DeletePairingResponse>(globalScope, responseObserver) {
+ grpcUnary(globalScope, responseObserver) {
val bluetoothDevice = request.address.toBluetoothDevice(bluetoothAdapter)
Log.i(TAG, "DeletePairing: device=$bluetoothDevice")
+ val unbonded =
+ globalScope.async {
+ flow
+ .filter { it.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED }
+ .filter { it.getBluetoothDeviceExtra() == bluetoothDevice }
+ .filter {
+ it.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothAdapter.ERROR) ==
+ BluetoothDevice.BOND_NONE
+ }
+ .first()
+ }
+
if (bluetoothDevice.removeBond()) {
Log.i(TAG, "DeletePairing: device=$bluetoothDevice - wait BOND_NONE intent")
- flow
- .filter { it.getAction() == BluetoothDevice.ACTION_BOND_STATE_CHANGED }
- .filter { it.getBluetoothDeviceExtra() == bluetoothDevice }
- .filter {
- it.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothAdapter.ERROR) ==
- BluetoothDevice.BOND_NONE
- }
- .filter {
- it.getIntExtra(BluetoothDevice.EXTRA_REASON, BluetoothAdapter.ERROR) ==
- BluetoothDevice.BOND_SUCCESS
- }
- .first()
+ unbonded.await()
} else {
Log.i(TAG, "DeletePairing: device=$bluetoothDevice - Already unpaired")
+ unbonded.cancel()
}
DeletePairingResponse.getDefaultInstance()
}
@@ -125,63 +144,62 @@ class Security(private val context: Context) : SecurityImplBase() {
}
.launchIn(this)
- flow.map { intent ->
- val device = intent.getBluetoothDeviceExtra()
- val variant = intent.getIntExtra(EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR)
- Log.i(
- TAG,
- "OnPairing: Handling PairingEvent ${variant} for device ${device.address}"
- )
- val eventBuilder =
- PairingEvent.newBuilder().setAddress(ByteString.copyFrom(device.toByteArray()))
- when (variant) {
- // SSP / LE Just Works
- BluetoothDevice.PAIRING_VARIANT_CONSENT ->
- eventBuilder.justWorks = Empty.getDefaultInstance()
-
- // SSP / LE Numeric Comparison
- BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION ->
- eventBuilder.numericComparison =
- intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR)
- BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY -> {
- val passkey =
- intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR)
- eventBuilder.passkeyEntryNotification = passkey
- }
+ flow
+ .filter { intent -> intent.action == ACTION_PAIRING_REQUEST }
+ .map { intent ->
+ val device = intent.getBluetoothDeviceExtra()
+ val variant = intent.getIntExtra(EXTRA_PAIRING_VARIANT, BluetoothDevice.ERROR)
+ Log.i(TAG, "OnPairing: Handling PairingEvent ${variant} for device ${device.address}")
+ val eventBuilder =
+ PairingEvent.newBuilder().setAddress(device.toByteString())
+ when (variant) {
+ // SSP / LE Just Works
+ BluetoothDevice.PAIRING_VARIANT_CONSENT ->
+ eventBuilder.justWorks = Empty.getDefaultInstance()
+
+ // SSP / LE Numeric Comparison
+ BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION ->
+ eventBuilder.numericComparison =
+ intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR)
+ BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY -> {
+ val passkey =
+ intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR)
+ eventBuilder.passkeyEntryNotification = passkey
+ }
- // Out-Of-Band not currently supported
- BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT ->
- error("Received OOB pairing confirmation (UNSUPPORTED)")
-
- // Legacy PIN entry, or LE legacy passkey entry, depending on transport
- BluetoothDevice.PAIRING_VARIANT_PIN ->
- when (device.type) {
- DEVICE_TYPE_CLASSIC -> eventBuilder.pinCodeRequest = Empty.getDefaultInstance()
- DEVICE_TYPE_LE ->
- eventBuilder.passkeyEntryRequest = Empty.getDefaultInstance()
- else -> error("cannot determine pairing variant, since transport is unknown")
+ // Out-Of-Band not currently supported
+ BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT ->
+ error("Received OOB pairing confirmation (UNSUPPORTED)")
+
+ // Legacy PIN entry, or LE legacy passkey entry, depending on transport
+ BluetoothDevice.PAIRING_VARIANT_PIN ->
+ when (device.type) {
+ DEVICE_TYPE_CLASSIC -> eventBuilder.pinCodeRequest = Empty.getDefaultInstance()
+ DEVICE_TYPE_LE -> eventBuilder.passkeyEntryRequest = Empty.getDefaultInstance()
+ else -> error("cannot determine pairing variant, since transport is unknown")
+ }
+ BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS ->
+ eventBuilder.pinCodeRequest = Empty.getDefaultInstance()
+
+ // Legacy PIN entry or LE legacy passkey entry, except we just generate the PIN in
+ // the
+ // stack and display it to the user for convenience
+ BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN -> {
+ val passkey =
+ intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR)
+ when (device.type) {
+ DEVICE_TYPE_CLASSIC ->
+ eventBuilder.pinCodeNotification =
+ ByteString.copyFrom(passkey.toString().toByteArray())
+ DEVICE_TYPE_LE -> eventBuilder.passkeyEntryNotification = passkey
+ else -> error("cannot determine pairing variant, since transport is unknown")
+ }
}
- BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS ->
- eventBuilder.pinCodeRequest = Empty.getDefaultInstance()
-
- // Legacy PIN entry or LE legacy passkey entry, except we just generate the PIN in the
- // stack and display it to the user for convenience
- BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN -> {
- val passkey =
- intent.getIntExtra(BluetoothDevice.EXTRA_PAIRING_KEY, BluetoothDevice.ERROR)
- when (device.type) {
- DEVICE_TYPE_CLASSIC ->
- eventBuilder.pinCodeNotification =
- ByteString.copyFrom(passkey.toString().toByteArray())
- DEVICE_TYPE_LE -> eventBuilder.passkeyEntryNotification = passkey
- else -> error("cannot determine pairing variant, since transport is unknown")
+ else -> {
+ error("Received unknown pairing variant $variant")
}
}
- else -> {
- error("Received unknown pairing variant $variant")
- }
+ eventBuilder.build()
}
- eventBuilder.build()
- }
}
}
diff --git a/android/pandora/server/src/com/android/pandora/Server.kt b/android/pandora/server/src/com/android/pandora/Server.kt
index 4c55be6696..566c29d450 100644
--- a/android/pandora/server/src/com/android/pandora/Server.kt
+++ b/android/pandora/server/src/com/android/pandora/Server.kt
@@ -16,6 +16,8 @@
package com.android.pandora
+import android.bluetooth.BluetoothManager
+import android.bluetooth.BluetoothProfile
import android.content.Context
import android.util.Log
import io.grpc.Server as GrpcServer
@@ -28,7 +30,8 @@ class Server(context: Context) {
private val GRPC_PORT = 8999
private var host: Host
- private var a2dp: A2dp
+ private var a2dp: A2dp? = null
+ private var a2dpSink: A2dpSink? = null
private var avrcp: Avrcp
private var gatt: Gatt
private var hfp: Hfp
@@ -39,24 +42,34 @@ class Server(context: Context) {
init {
host = Host(context, this)
- a2dp = A2dp(context)
avrcp = Avrcp(context)
gatt = Gatt(context)
hfp = Hfp(context)
hid = Hid(context)
l2cap = L2cap(context)
security = Security(context)
- grpcServer =
+
+ val grpcServerBuilder =
NettyServerBuilder.forPort(GRPC_PORT)
.addService(host)
- .addService(a2dp)
.addService(avrcp)
.addService(gatt)
.addService(hfp)
.addService(hid)
.addService(l2cap)
.addService(security)
- .build()
+
+ val bluetoothAdapter = context.getSystemService(BluetoothManager::class.java)!!.adapter
+ val is_a2dp_source = bluetoothAdapter.getSupportedProfiles().contains(BluetoothProfile.A2DP)
+ if (is_a2dp_source) {
+ a2dp = A2dp(context)
+ grpcServerBuilder.addService(a2dp!!)
+ } else {
+ a2dpSink = A2dpSink(context)
+ grpcServerBuilder.addService(a2dpSink!!)
+ }
+
+ grpcServer = grpcServerBuilder.build()
Log.d(TAG, "Starting Pandora Server")
grpcServer.start()
@@ -69,7 +82,8 @@ class Server(context: Context) {
fun deinit() {
host.deinit()
- a2dp.deinit()
+ a2dp?.deinit()
+ a2dpSink?.deinit()
avrcp.deinit()
gatt.deinit()
hfp.deinit()
diff --git a/android/pandora/server/src/com/android/pandora/Utils.kt b/android/pandora/server/src/com/android/pandora/Utils.kt
index a50b823cbb..a59deb0859 100644
--- a/android/pandora/server/src/com/android/pandora/Utils.kt
+++ b/android/pandora/server/src/com/android/pandora/Utils.kt
@@ -29,6 +29,7 @@ import android.os.ParcelFileDescriptor
import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import com.google.protobuf.ByteString
+import io.grpc.stub.ServerCallStreamObserver
import io.grpc.stub.StreamObserver
import java.io.BufferedReader
import java.io.InputStreamReader
@@ -52,6 +53,8 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import pandora.HostProto.Connection
+import pandora.HostProto.InternalConnectionRef
+import pandora.HostProto.Transport
fun shell(cmd: String): String {
val fd = InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(cmd)
@@ -137,7 +140,6 @@ fun <T> grpcUnary(
* Example usage:
* ```
* override fun grpcMethod(
- * request: TypeOfRequest,
* responseObserver: StreamObserver<TypeOfResponse> {
* grpcBidirectionalStream(scope, responseObserver) {
* block
@@ -196,6 +198,56 @@ fun <T, U> grpcBidirectionalStream(
}
/**
+ * Creates a gRPC coroutine in a given coroutine scope which executes a given suspended function
+ * taking in a Flow of gRPC requests and returning a Flow of gRPC responses and sends it on a given
+ * gRPC stream observer.
+ *
+ * @param T the type of gRPC response.
+ * @param scope coroutine scope used to run the coroutine.
+ * @param responseObserver the gRPC stream observer on which to send the response.
+ * @param block the suspended function producing the response Flow.
+ * @return a StreamObserver for the incoming requests.
+ *
+ * Example usage:
+ * ```
+ * override fun grpcMethod(
+ * request: TypeOfRequest,
+ * responseObserver: StreamObserver<TypeOfResponse> {
+ * grpcServerStream(scope, responseObserver) {
+ * block
+ * }
+ * }
+ * }
+ * ```
+ */
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+fun <T> grpcServerStream(
+ scope: CoroutineScope,
+ responseObserver: StreamObserver<T>,
+ block: CoroutineScope.() -> Flow<T>
+) {
+ val serverCallStreamObserver = responseObserver as ServerCallStreamObserver<T>
+
+ val job =
+ scope.launch {
+ block()
+ .onEach { responseObserver.onNext(it) }
+ .onCompletion { error ->
+ if (error == null) {
+ responseObserver.onCompleted()
+ }
+ }
+ .catch {
+ it.printStackTrace()
+ responseObserver.onError(it)
+ }
+ .launchIn(this)
+ }
+
+ serverCallStreamObserver.setOnCancelHandler { job.cancel() }
+}
+
+/**
* Synchronous method to get a Bluetooth profile proxy.
*
* @param T the type of profile proxy (e.g. BluetoothA2dp)
@@ -230,11 +282,11 @@ fun <T> getProfileProxy(context: Context, profile: Int): T {
if (proxy == null) {
Log.w(TAG, "profile proxy $profile is null")
}
- return proxy as T
+ return proxy!! as T
}
fun Intent.getBluetoothDeviceExtra(): BluetoothDevice =
- this.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
+ this.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)!!
fun ByteString.decodeAsMacAddressToString(): String =
MacAddress.fromBytes(this.toByteArray()).toString().uppercase()
@@ -243,6 +295,24 @@ fun ByteString.toBluetoothDevice(adapter: BluetoothAdapter): BluetoothDevice =
adapter.getRemoteDevice(this.decodeAsMacAddressToString())
fun Connection.toBluetoothDevice(adapter: BluetoothAdapter): BluetoothDevice =
- adapter.getRemoteDevice(this.cookie.toByteArray().decodeToString())
+ adapter.getRemoteDevice(address)
+
+val Connection.address: String
+ get() = InternalConnectionRef.parseFrom(this.cookie).address.decodeAsMacAddressToString()
+
+val Connection.transport: Transport
+ get() = InternalConnectionRef.parseFrom(this.cookie).transport
+
+fun newConnection(device: BluetoothDevice, transport: Transport) =
+ Connection.newBuilder()
+ .setCookie(
+ InternalConnectionRef.newBuilder()
+ .setAddress(device.toByteString())
+ .setTransport(transport)
+ .build()
+ .toByteString()
+ )
+ .build()!!
-fun BluetoothDevice.toByteArray(): ByteArray = MacAddress.fromString(this.address).toByteArray()
+fun BluetoothDevice.toByteString() =
+ ByteString.copyFrom(MacAddress.fromString(this.address).toByteArray())!!
diff --git a/build.py b/build.py
index 9de30f9523..049189aba4 100755
--- a/build.py
+++ b/build.py
@@ -537,10 +537,16 @@ class HostBuild():
shutil.rmtree(self.output_dir)
# Remove Cargo.lock that may have become generated
- try:
- os.remove(os.path.join(self.platform_dir, 'bt', 'Cargo.lock'))
- except FileNotFoundError:
- pass
+ cargo_lock_files = [
+ os.path.join(self.platform_dir, 'bt', 'Cargo.lock'),
+ os.path.join(self.platform_dir, 'bt', 'tools', 'rootcanal', 'Cargo.lock'),
+ ]
+ for lock_file in cargo_lock_files:
+ try:
+ os.remove(lock_file)
+ print('Removed {}'.format(lock_file))
+ except FileNotFoundError:
+ pass
def _target_all(self):
""" Build all common targets (skipping doc, test, and clean).
diff --git a/system/audio_bluetooth_hw/Android.bp b/system/audio_bluetooth_hw/Android.bp
index 9dc7f53234..cc89bdf7c8 100644
--- a/system/audio_bluetooth_hw/Android.bp
+++ b/system/audio_bluetooth_hw/Android.bp
@@ -9,8 +9,18 @@ package {
default_applicable_licenses: ["system_bt_license"],
}
+cc_defaults {
+ name: "audio_bluetooth_hw_defaults",
+ defaults: ["fluoride_common_options"],
+ cflags: [
+ // suppress the warning in stream_apis.cc
+ "-Wno-sign-compare",
+ ],
+}
+
cc_library_shared {
name: "audio.bluetooth.default",
+ defaults: ["audio_bluetooth_hw_defaults"],
relative_install_path: "hw",
proprietary: true,
srcs: [
@@ -37,15 +47,11 @@ cc_library_shared {
"libbluetooth_audio_session",
"libhidlbase",
],
- cflags: [
- "-Wall",
- "-Werror",
- "-Wno-unused-parameter",
- ],
}
cc_test {
name: "audio_bluetooth_hw_test",
+ defaults: ["audio_bluetooth_hw_defaults"],
srcs: [
"utils.cc",
"utils_unittest.cc",
@@ -56,9 +62,4 @@ cc_test {
"liblog",
"libutils",
],
- cflags: [
- "-Wall",
- "-Werror",
- "-Wno-unused-parameter",
- ],
}
diff --git a/system/binder/android/bluetooth/IBluetoothLeAudio.aidl b/system/binder/android/bluetooth/IBluetoothLeAudio.aidl
index 1859833d56..6fc9c8b20d 100644
--- a/system/binder/android/bluetooth/IBluetoothLeAudio.aidl
+++ b/system/binder/android/bluetooth/IBluetoothLeAudio.aidl
@@ -67,12 +67,15 @@ oneway interface IBluetoothLeAudio {
void setCcidInformation(in ParcelUuid userUuid, in int ccid, in int contextType, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
void setInCall(in boolean inCall, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
+ void setInactiveForHfpHandover(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
/* Same value as bluetooth::groups::kGroupUnknown */
const int LE_AUDIO_GROUP_ID_INVALID = -1;
const int GROUP_STATUS_INACTIVE = 0;
const int GROUP_STATUS_ACTIVE = 1;
+ const int GROUP_STATUS_TURNED_IDLE_DURING_CALL = 2;
const int GROUP_NODE_ADDED = 1;
const int GROUP_NODE_REMOVED = 2;
diff --git a/system/blueberry/facade/topshim/facade.proto b/system/blueberry/facade/topshim/facade.proto
index 4d3f8b8625..3e3ac7e4b4 100644
--- a/system/blueberry/facade/topshim/facade.proto
+++ b/system/blueberry/facade/topshim/facade.proto
@@ -15,12 +15,15 @@ service AdapterService {
rpc LeRand(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc SetEventFilterConnectionSetupAllDevices(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc AllowWakeByHid(google.protobuf.Empty) returns (google.protobuf.Empty) {}
- rpc RemoveBond(RemoveBondRequest) returns (google.protobuf.Empty) {}
rpc RestoreFilterAcceptList(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc SetDefaultEventMask(google.protobuf.Empty) returns (google.protobuf.Empty) {}
rpc SetEventFilterInquiryResultAllDevices(google.protobuf.Empty) returns (google.protobuf.Empty) {}
}
+service SecurityService {
+ rpc RemoveBond(RemoveBondRequest) returns (google.protobuf.Empty) {}
+}
+
service GattService {
// Advertiser
rpc RegisterAdvertiser(google.protobuf.Empty) returns (google.protobuf.Empty) {}
diff --git a/system/blueberry/tests/topshim/adapter/adapter_test.py b/system/blueberry/tests/topshim/adapter/adapter_test.py
index acc44c9dcd..811718e4be 100644
--- a/system/blueberry/tests/topshim/adapter/adapter_test.py
+++ b/system/blueberry/tests/topshim/adapter/adapter_test.py
@@ -14,8 +14,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import asyncio
-
from blueberry.tests.gd.cert.truth import assertThat
from blueberry.tests.topshim.lib.topshim_base_test import TopshimBaseTest
from blueberry.tests.topshim.lib.adapter_client import AdapterClient
@@ -25,15 +23,11 @@ from mobly import test_runner
class AdapterTest(TopshimBaseTest):
- async def __verify_enable_page_scan(self):
- await self.dut_adapter.set_enable_page_scan()
- return await self.dut_adapter.le_rand()
-
def test_verify_adapter_started(self):
print("Adapter is verified when test starts")
def test_enable_page_scan(self):
- self.post(self.__verify_enable_page_scan())
+ self.dut().enable_page_scan()
if __name__ == "__main__":
diff --git a/system/blueberry/tests/topshim/lib/adapter_client.py b/system/blueberry/tests/topshim/lib/adapter_client.py
index 303fbbc856..a85e72e746 100644
--- a/system/blueberry/tests/topshim/lib/adapter_client.py
+++ b/system/blueberry/tests/topshim/lib/adapter_client.py
@@ -19,11 +19,12 @@ import grpc
from blueberry.facade.topshim import facade_pb2
from blueberry.facade.topshim import facade_pb2_grpc
+from blueberry.tests.topshim.lib.async_closable import AsyncClosable
from google.protobuf import empty_pb2 as empty_proto
-class AdapterClient():
+class AdapterClient(AsyncClosable):
"""
Wrapper gRPC interface to the Topshim/BTIF layer
"""
@@ -39,7 +40,7 @@ class AdapterClient():
self.__adapter_stub = facade_pb2_grpc.AdapterServiceStub(self.__channel)
self.__adapter_event_stream = self.__adapter_stub.FetchEvents(facade_pb2.FetchEventsRequest())
- async def terminate(self):
+ async def close(self):
for task in self.__task_list:
task.cancel()
task = None
@@ -75,9 +76,15 @@ class AdapterClient():
await self.__adapter_stub.ToggleStack(facade_pb2.ToggleStackRequest(start_stack=is_start))
return await self._verify_adapter_started()
- async def set_enable_page_scan(self):
+ async def enable_page_scan(self):
"""Enable page scan (might be used for A2dp sink to be discoverable)"""
await self.__adapter_stub.SetDiscoveryMode(facade_pb2.SetDiscoveryModeRequest(enable_page_scan=True))
+ return await self.le_rand()
+
+ async def disable_page_scan(self):
+ """Enable page scan (might be used for A2dp sink to be discoverable)"""
+ await self.__adapter_stub.SetDiscoveryMode(facade_pb2.SetDiscoveryModeRequest(enable_page_scan=False))
+ return await self.le_rand()
async def clear_event_filter(self):
await self.__adapter_stub.ClearEventFilter(empty_proto.Empty())
@@ -111,9 +118,6 @@ class AdapterClient():
async def allow_wake_by_hid(self):
await self.__adapter_stub.AllowWakeByHid(empty_proto.Empty())
- async def remove_bond(self, address):
- await self.__adapter_stub.RemoveBond(facade_pb2.RemoveBondRequest(address=address))
-
class A2dpAutomationHelper():
"""Invoke gRPC on topshim for A2DP testing"""
diff --git a/system/blueberry/tests/topshim/lib/async_closable.py b/system/blueberry/tests/topshim/lib/async_closable.py
new file mode 100644
index 0000000000..5eea802d68
--- /dev/null
+++ b/system/blueberry/tests/topshim/lib/async_closable.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import asyncio
+import time
+from abc import ABC, abstractmethod
+import logging
+
+
+class AsyncClosable(ABC):
+
+ async def __async_exit(self, type=None, value=None, traceback=None):
+ try:
+ return await self.close()
+ except Exception:
+ logging.warning("Failed to close or already closed")
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ asyncio.run_until_complete(self.__async_exit(type, value, traceback))
+ return traceback is None
+
+ def __del__(self):
+ asyncio.get_event_loop().run_until_complete(self.__async_exit())
+
+ @abstractmethod
+ async def close(self):
+ pass
+
+
+async def asyncSafeClose(closable):
+ if closable is None:
+ logging.warn("Tried to close an object that is None")
+ return
+ await closable.close()
diff --git a/system/blueberry/tests/topshim/lib/gatt_client.py b/system/blueberry/tests/topshim/lib/gatt_client.py
index 3e397ef69b..dcd0b8b7a9 100644
--- a/system/blueberry/tests/topshim/lib/gatt_client.py
+++ b/system/blueberry/tests/topshim/lib/gatt_client.py
@@ -19,11 +19,13 @@ import grpc
from blueberry.facade.topshim import facade_pb2
from blueberry.facade.topshim import facade_pb2_grpc
+from blueberry.tests.topshim.lib.async_closable import AsyncClosable
+from blueberry.tests.topshim.lib.async_closable import asyncSafeClose
from google.protobuf import empty_pb2 as empty_proto
-class GattClient():
+class GattClient(AsyncClosable):
"""
Wrapper gRPC interface to the GATT Service
"""
@@ -39,7 +41,7 @@ class GattClient():
self.__gatt_stub = facade_pb2_grpc.GattServiceStub(self.__channel)
#self.__gatt_event_stream = self.__gatt_stub.FetchEvents(facade_pb2.FetchEventsRequest())
- async def terminate(self):
+ async def close(self):
"""
Terminate the current tasks
"""
diff --git a/system/blueberry/tests/topshim/lib/security_client.py b/system/blueberry/tests/topshim/lib/security_client.py
new file mode 100644
index 0000000000..9e4ed57700
--- /dev/null
+++ b/system/blueberry/tests/topshim/lib/security_client.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python3
+#
+# 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.
+
+import grpc
+
+from blueberry.facade.topshim import facade_pb2
+from blueberry.facade.topshim import facade_pb2_grpc
+from blueberry.tests.topshim.lib.async_closable import AsyncClosable
+from blueberry.tests.topshim.lib.async_closable import asyncSafeClose
+
+from google.protobuf import empty_pb2 as empty_proto
+
+
+class SecurityClient(AsyncClosable):
+ """
+ Wrapper gRPC interface to the GATT Service
+ """
+ # Timeout for async wait
+ DEFAULT_TIMEOUT = 2
+ __task_list = []
+ __channel = None
+ __security_stub = None
+ __adapter_event_stream = None
+ __adapter_client = None
+
+ def __init__(self, adapter_client, port=8999):
+ self.__channel = grpc.aio.insecure_channel("localhost:%d" % port)
+ self.__security_stub = facade_pb2_grpc.SecurityServiceStub(self.__channel)
+ self.__adapter_client = adapter_client
+ #self.__gatt_event_stream = self.__security_stub.FetchEvents(facade_pb2.FetchEventsRequest())
+
+ async def close(self):
+ """
+ Terminate the current tasks
+ """
+ for task in self.__task_list:
+ task.cancel()
+ task = None
+ self.__task_list.clear()
+ await self.__channel.close()
+
+ async def remove_bond(self, address):
+ """
+ Removes a bonding entry for a given address
+ """
+ await self.__security_stub.RemoveBond(facade_pb2.RemoveBondRequest(address=address))
+ return await self.__adapter_client.le_rand()
+
+ async def bond_using_numeric_comparison(self, address):
+ """
+ Bond to a given address using numeric comparison method
+ """
+ # Set IO Capabilities
+ # Enable Page scan
+ # Become discoverable
+ # Discover device
+ # Initiate bond
+ return await self.__adapter_client.le_rand()
diff --git a/system/blueberry/tests/topshim/lib/topshim_base_test.py b/system/blueberry/tests/topshim/lib/topshim_base_test.py
index 84a348012b..9fe907121a 100644
--- a/system/blueberry/tests/topshim/lib/topshim_base_test.py
+++ b/system/blueberry/tests/topshim/lib/topshim_base_test.py
@@ -32,7 +32,10 @@ from blueberry.tests.gd.cert.os_utils import TerminalColor
from blueberry.tests.gd.cert.tracelogger import TraceLogger
from blueberry.tests.gd.cert.truth import assertThat
from blueberry.tests.topshim.lib.adapter_client import AdapterClient
+from blueberry.tests.topshim.lib.async_closable import asyncSafeClose
from blueberry.tests.topshim.lib.gatt_client import GattClient
+from blueberry.tests.topshim.lib.security_client import SecurityClient
+from blueberry.tests.topshim.lib.topshim_device import TopshimDevice
from mobly import asserts
from mobly import base_test
@@ -157,32 +160,35 @@ def dump_crashes_core(dut, cert, rootcanal_running, rootcanal_process, rootcanal
class TopshimBaseTest(base_test.BaseTestClass):
- # TODO(optedoblivion): Make TopshimTestStack class
- dut_adapter = None
- dut_gatt = None
+ __dut = None
+ __cert = None
- cert_adapter = None
- cert_gatt = None
-
- async def _setup_adapter(self):
- self.dut_adapter = AdapterClient(port=self.dut_port)
- self.cert_adapter = AdapterClient(port=self.cert_port)
- started = await self.dut_adapter._verify_adapter_started()
+ async def __setup_adapter(self):
+ dut_adapter = AdapterClient(port=self.dut_port)
+ cert_adapter = AdapterClient(port=self.cert_port)
+ started = await dut_adapter._verify_adapter_started()
assertThat(started).isTrue()
- started = await self.cert_adapter._verify_adapter_started()
+ started = started and await cert_adapter._verify_adapter_started()
assertThat(started).isTrue()
- self.dut_gatt = GattClient(port=self.dut_port)
- self.cert_gatt = GattClient(port=self.cert_port)
+ self.__dut = TopshimDevice(dut_adapter, self.dut_port)
+ self.__cert = TopshimDevice(cert_adapter, self.cert_port)
return started
- async def _teardown_adapter(self):
- await self.dut_adapter.terminate()
- await self.dut_gatt.terminate()
- await self.cert_adapter.terminate()
- await self.cert_gatt.terminate()
+ async def __teardown_adapter(self):
+ await asyncSafeClose(self.__dut)
+ await asyncSafeClose(self.__cert)
+
+ def dut(self):
+ """
+ Get a handle on the DUT device
+ """
+ return self.__dut
- def post(self, async_fn):
- asyncio.get_event_loop().run_until_complete(async_fn)
+ def cert(self):
+ """
+ Get a handle on the CERT device
+ """
+ return self.__cert
def setup_class(self):
"""
@@ -217,7 +223,7 @@ class TopshimBaseTest(base_test.BaseTestClass):
self.cert_port = controllers[0].grpc_port
self.dut_port = controllers[1].grpc_port
asyncio.set_event_loop(asyncio.new_event_loop())
- asyncio.get_event_loop().run_until_complete(self._setup_adapter())
+ asyncio.get_event_loop().run_until_complete(self.__setup_adapter())
def teardown_class(self):
_teardown_class_core(
@@ -225,4 +231,4 @@ class TopshimBaseTest(base_test.BaseTestClass):
rootcanal_process=self.rootcanal_process,
rootcanal_logger=self.rootcanal_logger,
subprocess_wait_timeout_seconds=1)
- asyncio.get_event_loop().run_until_complete(self._teardown_adapter())
+ asyncio.get_event_loop().run_until_complete(self.__teardown_adapter())
diff --git a/system/blueberry/tests/topshim/lib/topshim_device.py b/system/blueberry/tests/topshim/lib/topshim_device.py
index 1e853d118e..2ff8043850 100644
--- a/system/blueberry/tests/topshim/lib/topshim_device.py
+++ b/system/blueberry/tests/topshim/lib/topshim_device.py
@@ -1,5 +1,4 @@
#!/usr/bin/env python3
-#
# Copyright 2019 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,11 +13,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import asyncio
import logging
from blueberry.tests.gd.cert.gd_device import GdHostOnlyDevice
from blueberry.tests.gd.cert.gd_device import MOBLY_CONTROLLER_CONFIG_NAME
from blueberry.tests.gd.cert.os_utils import get_gd_root
+from blueberry.tests.topshim.lib.async_closable import AsyncClosable
+from blueberry.tests.topshim.lib.async_closable import asyncSafeClose
+from blueberry.tests.topshim.lib.gatt_client import GattClient
+from blueberry.tests.topshim.lib.security_client import SecurityClient
def create(configs):
@@ -62,3 +66,89 @@ def get_instances_with_configs(configs):
device.setup()
devices.append(device)
return devices
+
+
+class TopshimDevice(AsyncClosable):
+ __adapter = None
+ __gatt = None
+ __security = None
+
+ async def __le_rand_wrapper(self, async_fn):
+ await async_fn
+
+
+# await self.__adapter.le_rand()
+
+ def __post(self, async_fn):
+ asyncio.get_event_loop().run_until_complete(self.__le_rand_wrapper(async_fn))
+
+ def __init__(self, adapter, grpc_port):
+ self.__adapter = adapter
+ self.__gatt = GattClient(port=grpc_port)
+ self.__security = SecurityClient(adapter, port=grpc_port)
+
+ async def close(self):
+ """
+ Implement abstract method to close out any streams or jobs.
+ """
+ await asyncSafeClose(self.__adapter)
+ await asyncSafeClose(self.__gatt)
+ await asyncSafeClose(self.__security)
+
+ def enable_page_scan(self):
+ self.__post(self.__adapter.enable_page_scan())
+
+ def disable_page_scan(self):
+ self.__post(self.__adapter.disable_page_scan())
+
+ def start_advertising(self):
+ """
+ Starts BLE Advertiser for the stack.
+ Assumes stack defaults. Which in our case would be RRPA
+ """
+ self.__post(self.__gatt.advertising_enable())
+
+ def stop_advertising(self):
+ """
+ Stop BLE Advertiser.
+ """
+ self.__post(self.__gatt.advertising_disable())
+
+ def start_scanning(self):
+ pass
+
+ def stop_scanning(self):
+ pass
+
+ def clear_event_mask(self):
+ self.__post(self.__adapter.clear_event_mask())
+
+ def clear_event_filter(self):
+ self.__post(self.__adapter.clear_event_filter())
+
+ def clear_filter_accept_list(self):
+ self.__post(self.__adapter.clear_filter_accept_list())
+
+ def disconnect_all_acls(self):
+ self.__post(self.__adapter.disconnect_all_acls())
+
+ def allow_wake_by_hid(self):
+ self.__post(self.__adapter.allow_wake_by_hid())
+
+ def set_default_event_mask(self):
+ self.__post(self.__adapter.set_default_event_mask())
+
+ def set_event_filter_inquiry_result_all_devices(self):
+ self.__post(self.__adapter.set_event_filter_inquiry_result_all_devices())
+
+ def set_event_filter_connection_setup_all_devices(self):
+ self.__post(self.__adapter.set_event_filter_connection_setup_all_devices())
+
+ def le_rand(self):
+ self.__post(self.__adapter.le_rand())
+
+ def remove_bonded_device(self, address):
+ """
+ Removes a bonding entry for a given address.
+ """
+ self.__post(self.__security.remove_bond(address))
diff --git a/system/blueberry/tests/topshim/power/suspend_test.py b/system/blueberry/tests/topshim/power/suspend_test.py
index 51b8b7c0f6..79f09004c9 100644
--- a/system/blueberry/tests/topshim/power/suspend_test.py
+++ b/system/blueberry/tests/topshim/power/suspend_test.py
@@ -23,79 +23,79 @@ from mobly import test_runner
class SuspendTest(TopshimBaseTest):
- async def __verify_no_wake_suspend(self):
+ def __verify_no_wake_suspend(self):
# Start suspend work
- await self.dut_adapter.clear_event_mask()
- await self.dut_adapter.clear_event_filter()
- await self.dut_adapter.clear_filter_accept_list()
- await self.dut_gatt.advertising_disable()
- await self.dut_gatt.stop_scan()
- await self.dut_adapter.disconnect_all_acls()
- return await self.dut_adapter.le_rand()
-
- async def __verify_no_wake_resume(self):
+ self.dut().clear_event_mask()
+ self.dut().clear_event_filter()
+ self.dut().clear_filter_accept_list()
+ self.dut().stop_advertising()
+ self.dut().stop_scanning()
+ self.dut().disconnect_all_acls()
+ self.dut().le_rand()
+
+ def __verify_no_wake_resume(self):
# Start resume work
- await self.dut_adapter.set_default_event_mask()
- await self.dut_adapter.set_event_filter_inquiry_result_all_devices()
- await self.dut_adapter.set_event_filter_connection_setup_all_devices()
- return await self.dut_adapter.le_rand()
-
- async def __verify_wakeful_suspend(self, is_a2dp_connected):
- await self.dut_adapter.clear_event_mask()
- await self.dut_adapter.clear_event_filter()
- await self.dut_adapter.clear_filter_accept_list()
- await self.dut_gatt.advertising_disable()
- await self.dut_gatt.stop_scan()
+ self.dut().set_default_event_mask()
+ self.dut().set_event_filter_inquiry_result_all_devices()
+ self.dut().set_event_filter_connection_setup_all_devices()
+ self.dut().le_rand()
+
+ def __verify_wakeful_suspend(self, is_a2dp_connected):
+ self.dut().clear_event_mask()
+ self.dut().clear_event_filter()
+ self.dut().clear_filter_accept_list()
+ self.dut().stop_advertising()
+ self.dut().stop_scanning()
if is_a2dp_connected:
- # await self.media_server.disconnect_a2dp()
+ # self.media_server.disconnect_a2dp()
pass
- await self.dut_adapter.disconnect_all_acls()
- await self.dut_adapter.allow_wake_by_hid()
- return await self.dut_adapter.le_rand()
+ self.dut().disconnect_all_acls()
+ self.dut().allow_wake_by_hid()
+ self.dut().le_rand()
- async def __verify_wakeful_resume(self, was_a2dp_connected):
+ def __verify_wakeful_resume(self, was_a2dp_connected):
# Start resume work
- await self.dut_adapter.set_default_event_mask()
- await self.dut_adapter.set_event_filter_inquiry_result_all_devices()
- await self.dut_adapter.set_event_filter_connection_setup_all_devices()
+ self.dut().set_default_event_mask()
+ self.dut().set_event_filter_inquiry_result_all_devices()
+ self.dut().set_event_filter_connection_setup_all_devices()
if was_a2dp_connected:
# restore filter accept list?
- await self.dut_adapter.restore_filter_accept_list()
+ self.dut().restore_filter_accept_list()
# reconnect a2dp
- # await self.media_server.reconnect_last_a2dp()
- # await self.gatt.restart_all_previous_advertising()
- await self.dut_gatt.advertising_enable()
- return await self.dut_adapter.le_rand()
+ # self.media_server.reconnect_last_a2dp()
+ # self.gatt.restart_all_previous_advertising()
+ self.dut().start_advertising()
+ self.dut().le_rand()
def test_no_wake_suspend(self):
- self.post(self.__verify_no_wake_suspend())
+ self.__verify_no_wake_suspend()
def test_no_wake_resume(self):
- self.post(self.__verify_no_wake_resume())
+ self.__verify_no_wake_resume()
def test_no_wake_suspend_then_resume(self):
- self.post(self.__verify_no_wake_suspend())
- self.post(self.__verify_no_wake_resume())
+ self.__verify_no_wake_suspend()
+ self.__verify_no_wake_resume()
def test_no_wake_suspend_then_resume_then_suspend(self):
- self.post(self.__verify_no_wake_suspend())
- self.post(self.__verify_no_wake_resume())
- self.post(self.__verify_no_wake_suspend())
+ self.__verify_no_wake_suspend()
+ self.__verify_no_wake_resume()
+ self.__verify_no_wake_suspend()
def test_wakeful_suspend_no_a2dp(self):
- self.post(self.__verify_wakeful_suspend(False))
+ self.__verify_wakeful_suspend(False)
def test_wakeful_resume_no_a2dp(self):
- self.post(self.__verify_wakeful_resume(False))
+ self.__verify_wakeful_resume(False)
def test_wakeful_suspend_then_resume_no_a2dp(self):
- self.post(self.__verify_wakeful_suspend(False))
- self.post(self.__verify_wakeful_resume(False))
+ self.__verify_wakeful_suspend(False)
+ self.__verify_wakeful_resume(False)
def test_wakeful_suspend_then_resume_then_suspend_no_a2dp(self):
- self.post(self.__verify_wakeful_suspend(False))
- self.post(self.__verify_wakeful_resume(False))
- self.post(self.__verify_wakeful_suspend(False))
+ self.__verify_wakeful_suspend(False)
+ self.__verify_wakeful_resume(False)
+ self.__verify_wakeful_suspend(False)
if __name__ == "__main__":
diff --git a/system/blueberry/tests/topshim/security/security_test.py b/system/blueberry/tests/topshim/security/classic_security_test.py
index 95ce05d17c..a34bd918a0 100644
--- a/system/blueberry/tests/topshim/security/security_test.py
+++ b/system/blueberry/tests/topshim/security/classic_security_test.py
@@ -21,16 +21,13 @@ from blueberry.tests.topshim.lib.adapter_client import AdapterClient
from mobly import test_runner
-class SecurityTest(TopshimBaseTest):
+class ClassicSecurityTest(TopshimBaseTest):
DEFAULT_ADDRESS = "01:02:03:04:05:06"
- async def __test_remove_bond(self, address):
- await self.dut_adapter.remove_bond(address)
- return await self.dut_adapter.le_rand()
-
def test_remove_bond_with_no_bonded_devices(self):
- self.post(self.__test_remove_bond(self.DEFAULT_ADDRESS))
+ self.dut().remove_bonded_device(self.DEFAULT_ADDRESS)
+ self.dut().le_rand()
if __name__ == "__main__":
diff --git a/system/blueberry/tests/topshim/topshim_test_runner.py b/system/blueberry/tests/topshim/topshim_test_runner.py
index 7b914f9878..64152c684f 100644
--- a/system/blueberry/tests/topshim/topshim_test_runner.py
+++ b/system/blueberry/tests/topshim/topshim_test_runner.py
@@ -16,12 +16,12 @@
from blueberry.tests.topshim.adapter.adapter_test import AdapterTest
from blueberry.tests.topshim.power.suspend_test import SuspendTest
-from blueberry.tests.topshim.security.security_test import SecurityTest
+from blueberry.tests.topshim.security.classic_security_test import ClassicSecurityTest
from mobly import suite_runner
import argparse
-ALL_TESTS = [AdapterTest, SecurityTest, SuspendTest]
+ALL_TESTS = [AdapterTest, ClassicSecurityTest, SuspendTest]
def main():
diff --git a/system/bta/dm/bta_dm_act.cc b/system/bta/dm/bta_dm_act.cc
index f91b593dde..65389ad1ff 100644
--- a/system/bta/dm/bta_dm_act.cc
+++ b/system/bta/dm/bta_dm_act.cc
@@ -670,13 +670,6 @@ void bta_dm_remove_device(const RawAddress& bd_addr) {
if (!other_address_connected && !other_address.IsEmpty()) {
bta_dm_process_remove_device(other_address);
}
-
- /* Check the length of the paired devices, and if 0 then reset IRK */
- auto paired_devices = btif_config_get_paired_devices();
- if (paired_devices.empty()) {
- LOG_INFO("Last paired device removed, resetting IRK");
- btm_ble_reset_id();
- }
}
/*******************************************************************************
@@ -4221,6 +4214,20 @@ void bta_dm_set_event_filter_inquiry_result_all_devices() {
/*******************************************************************************
*
+ * Function bta_dm_ble_reset_id
+ *
+ * Description Reset the local adapter BLE keys.
+ *
+ * Parameters:
+ *
+ ******************************************************************************/
+void bta_dm_ble_reset_id(void) {
+ VLOG(1) << "bta_dm_ble_reset_id in bta_dm_act";
+ bluetooth::shim::BTM_BleResetId();
+}
+
+/*******************************************************************************
+ *
* Function bta_dm_gattc_callback
*
* Description This is GATT client callback function used in DM.
diff --git a/system/bta/dm/bta_dm_api.cc b/system/bta/dm/bta_dm_api.cc
index 347abcff3a..01b497c367 100644
--- a/system/bta/dm/bta_dm_api.cc
+++ b/system/bta/dm/bta_dm_api.cc
@@ -719,3 +719,17 @@ void BTA_DmSetEventFilterInquiryResultAllDevices() {
FROM_HERE,
base::Bind(bta_dm_set_event_filter_inquiry_result_all_devices));
}
+
+/*******************************************************************************
+ *
+ * Function BTA_DmBleResetId
+ *
+ * Description This function resets the ble keys such as IRK
+ *
+ * Returns void
+ *
+ ******************************************************************************/
+void BTA_DmBleResetId(void) {
+ APPL_TRACE_API("BTA_DmBleResetId");
+ do_in_main_thread(FROM_HERE, base::Bind(bta_dm_ble_reset_id));
+}
diff --git a/system/bta/dm/bta_dm_int.h b/system/bta/dm/bta_dm_int.h
index f02441c886..2491af1d57 100644
--- a/system/bta/dm/bta_dm_int.h
+++ b/system/bta/dm/bta_dm_int.h
@@ -552,6 +552,8 @@ extern void bta_dm_restore_filter_accept_list();
extern void bta_dm_set_default_event_mask();
extern void bta_dm_set_event_filter_inquiry_result_all_devices();
+extern void bta_dm_ble_reset_id(void);
+
uint8_t bta_dm_search_get_state();
void bta_dm_search_set_state(uint8_t state);
diff --git a/system/bta/gatt/bta_gattc_act.cc b/system/bta/gatt/bta_gattc_act.cc
index 9de15c9422..ecb9a87259 100644
--- a/system/bta/gatt/bta_gattc_act.cc
+++ b/system/bta/gatt/bta_gattc_act.cc
@@ -768,6 +768,26 @@ void bta_gattc_start_discover(tBTA_GATTC_CLCB* p_clcb,
p_clcb->p_srcb->update_count = 0;
p_clcb->p_srcb->state = BTA_GATTC_SERV_DISC_ACT;
+ /* This is workaround for the embedded devices being already on the market
+ * and having a serious problem with handle Read By Type with
+ * GATT_UUID_DATABASE_HASH. With this workaround, Android will assume that
+ * embedded device having LMP version lower than 5.1 (0x0a), it does not
+ * support GATT Caching.
+ */
+ uint8_t lmp_version = 0;
+ if (!BTM_ReadRemoteVersion(p_clcb->bda, &lmp_version, nullptr, nullptr)) {
+ LOG_WARN("Could not read remote version for %s",
+ p_clcb->bda.ToString().c_str());
+ }
+
+ if (lmp_version < 0x0a) {
+ LOG_WARN(
+ " Device LMP version 0x%02x < Bluetooth 5.1. Ignore database cache "
+ "read.",
+ lmp_version);
+ p_clcb->p_srcb->srvc_hdl_db_hash = false;
+ }
+
/* read db hash if db hash characteristic exists */
if (bta_gattc_is_robust_caching_enabled() &&
p_clcb->p_srcb->srvc_hdl_db_hash &&
diff --git a/system/bta/gatt/bta_gattc_utils.cc b/system/bta/gatt/bta_gattc_utils.cc
index f49ae36e3c..1412702180 100644
--- a/system/bta/gatt/bta_gattc_utils.cc
+++ b/system/bta/gatt/bta_gattc_utils.cc
@@ -677,5 +677,5 @@ tBTA_GATTC_CLCB* bta_gattc_find_int_disconn_clcb(tBTA_GATTC_DATA* p_msg) {
*
******************************************************************************/
bool bta_gattc_is_robust_caching_enabled() {
- return bluetooth::common::init_flags::gatt_robust_caching_is_enabled();
+ return bluetooth::common::init_flags::gatt_robust_caching_client_is_enabled();
}
diff --git a/system/bta/hf_client/bta_hf_client_rfc.cc b/system/bta/hf_client/bta_hf_client_rfc.cc
index d964003b57..2999eb9ed1 100644
--- a/system/bta/hf_client/bta_hf_client_rfc.cc
+++ b/system/bta/hf_client/bta_hf_client_rfc.cc
@@ -116,6 +116,7 @@ static void bta_hf_client_mgmt_cback(uint32_t code, uint16_t port_handle) {
if (client_cb == NULL) {
APPL_TRACE_ERROR("%s: error allocating a new handle", __func__);
p_buf->hdr.event = BTA_HF_CLIENT_RFC_CLOSE_EVT;
+ RFCOMM_RemoveConnection(port_handle);
} else {
// Set the connection fields for this new CB
client_cb->conn_handle = port_handle;
diff --git a/system/bta/include/bta_api.h b/system/bta/include/bta_api.h
index c140247b74..f7cad22c53 100644
--- a/system/bta/include/bta_api.h
+++ b/system/bta/include/bta_api.h
@@ -1289,4 +1289,15 @@ extern void BTA_DmSetDefaultEventMask();
*******************************************************************************/
extern void BTA_DmSetEventFilterInquiryResultAllDevices();
+/*******************************************************************************
+ *
+ * Function BTA_DmBleResetId
+ *
+ * Description This function resets the ble keys such as IRK
+ *
+ * Returns void
+ *
+ ******************************************************************************/
+extern void BTA_DmBleResetId(void);
+
#endif /* BTA_API_H */
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index ccc68bc3e6..7467ca9ec2 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -240,7 +240,8 @@ class LeAudioClientImpl : public LeAudioClient {
lc3_decoder_right(nullptr),
audio_source_instance_(nullptr),
audio_sink_instance_(nullptr),
- suspend_timeout_(alarm_new("LeAudioSuspendTimeout")) {
+ suspend_timeout_(alarm_new("LeAudioSuspendTimeout")),
+ disable_timer_(alarm_new("LeAudioDisableTimer")) {
LeAudioGroupStateMachine::Initialize(state_machine_callbacks_);
groupStateMachine_ = LeAudioGroupStateMachine::Get();
@@ -634,11 +635,72 @@ class LeAudioClientImpl : public LeAudioClient {
group_remove_node(group, address, true);
}
+ AudioContexts adjustMetadataContexts(AudioContexts metadata_context_type) {
+ /* This function takes already filtered contexts which we are plannig to use
+ * in the Enable or UpdateMetadata command.
+ * Note we are not changing stream configuration here, but just the list of
+ * the contexts in the Metadata which will be provide to remote side.
+ * Ideally, we should send all the bits we have, but not all headsets like
+ * it.
+ */
+ if (osi_property_get_bool(kAllowMultipleContextsInMetadata, false)) {
+ return metadata_context_type;
+ }
+
+ LOG_DEBUG("Converting to single context type: %lu",
+ metadata_context_type.to_ulong());
+
+ if (metadata_context_type.to_ulong() &
+ static_cast<uint16_t>(LeAudioContextType::CONVERSATIONAL)) {
+ return static_cast<uint16_t>(LeAudioContextType::CONVERSATIONAL);
+ }
+ if (metadata_context_type.to_ulong() &
+ static_cast<uint16_t>(LeAudioContextType::GAME)) {
+ return static_cast<uint16_t>(LeAudioContextType::GAME);
+ }
+ if (metadata_context_type.to_ulong() &
+ static_cast<uint16_t>(LeAudioContextType::EMERGENCYALARM)) {
+ return static_cast<uint16_t>(LeAudioContextType::EMERGENCYALARM);
+ }
+ if (metadata_context_type.to_ulong() &
+ static_cast<uint16_t>(LeAudioContextType::ALERTS)) {
+ return static_cast<uint16_t>(LeAudioContextType::ALERTS);
+ }
+ if (metadata_context_type.to_ulong() &
+ static_cast<uint16_t>(LeAudioContextType::RINGTONE)) {
+ return static_cast<uint16_t>(LeAudioContextType::RINGTONE);
+ }
+ if (metadata_context_type.to_ulong() &
+ static_cast<uint16_t>(LeAudioContextType::VOICEASSISTANTS)) {
+ return static_cast<uint16_t>(LeAudioContextType::VOICEASSISTANTS);
+ }
+ if (metadata_context_type.to_ulong() &
+ static_cast<uint16_t>(LeAudioContextType::INSTRUCTIONAL)) {
+ return static_cast<uint16_t>(LeAudioContextType::INSTRUCTIONAL);
+ }
+ if (metadata_context_type.to_ulong() &
+ static_cast<uint16_t>(LeAudioContextType::NOTIFICATIONS)) {
+ return static_cast<uint16_t>(LeAudioContextType::NOTIFICATIONS);
+ }
+ if (metadata_context_type.to_ulong() &
+ static_cast<uint16_t>(LeAudioContextType::LIVE)) {
+ return static_cast<uint16_t>(LeAudioContextType::LIVE);
+ }
+ if (metadata_context_type.to_ulong() &
+ static_cast<uint16_t>(LeAudioContextType::MEDIA)) {
+ return static_cast<uint16_t>(LeAudioContextType::MEDIA);
+ }
+
+ return static_cast<uint16_t>(LeAudioContextType::UNSPECIFIED);
+ }
+
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;
+ auto adjusted_metadata_context_type =
+ adjustMetadataContexts(metadata_context_type);
DLOG(INFO) << __func__;
if (context_type >= static_cast<uint16_t>(LeAudioContextType::RFU)) {
LOG(ERROR) << __func__ << ", stream context type is not supported: "
@@ -679,7 +741,8 @@ class LeAudioClientImpl : public LeAudioClient {
bool result = groupStateMachine_->StartStream(
group, static_cast<LeAudioContextType>(final_context_type),
- metadata_context_type, GetAllCcids(metadata_context_type));
+ adjusted_metadata_context_type,
+ GetAllCcids(adjusted_metadata_context_type));
if (result)
stream_setup_start_timestamp_ =
bluetooth::common::time_get_os_boottime_us();
@@ -833,15 +896,16 @@ class LeAudioClientImpl : public LeAudioClient {
return;
}
+ auto group_id_to_close = active_group_id_;
+ active_group_id_ = bluetooth::groups::kGroupUnknown;
+
if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_);
StopAudio();
ClientAudioIntefraceRelease();
- GroupStop(active_group_id_);
- callbacks_->OnGroupStatus(active_group_id_, GroupStatus::INACTIVE);
- active_group_id_ = group_id;
-
+ GroupStop(group_id_to_close);
+ callbacks_->OnGroupStatus(group_id_to_close, GroupStatus::INACTIVE);
return;
}
@@ -1144,6 +1208,7 @@ class LeAudioClientImpl : public LeAudioClient {
BtaGattQueue::Clean(leAudioDevice->conn_id_);
BTA_GATTC_Close(leAudioDevice->conn_id_);
leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
+ leAudioDevice->mtu_ = 0;
}
void DeregisterNotifications(LeAudioDevice* leAudioDevice) {
@@ -1464,11 +1529,7 @@ class LeAudioClientImpl : public LeAudioClient {
leAudioDevice->connecting_actively_ = false;
leAudioDevice->conn_id_ = conn_id;
-
- if (mtu == GATT_DEF_BLE_MTU_SIZE) {
- LOG(INFO) << __func__ << ", Configure MTU";
- BtaGattQueue::ConfigureMtu(leAudioDevice->conn_id_, 240);
- }
+ leAudioDevice->mtu_ = mtu;
if (BTM_SecIsSecurityPending(address)) {
/* if security collision happened, wait for encryption done
@@ -1506,6 +1567,14 @@ class LeAudioClientImpl : public LeAudioClient {
void RegisterKnownNotifications(LeAudioDevice* leAudioDevice) {
LOG(INFO) << __func__ << " device: " << leAudioDevice->address_;
+ if (leAudioDevice->ctp_hdls_.val_hdl == 0) {
+ LOG_ERROR(
+ "Control point characteristic is mandatory - disconnecting device %s",
+ leAudioDevice->address_.ToString().c_str());
+ DisconnectDevice(leAudioDevice);
+ return;
+ }
+
/* GATTC will ommit not registered previously handles */
for (auto pac_tuple : leAudioDevice->snk_pacs_) {
subscribe_for_notification(leAudioDevice->conn_id_,
@@ -1537,14 +1606,19 @@ class LeAudioClientImpl : public LeAudioClient {
leAudioDevice->address_,
leAudioDevice->audio_supp_cont_hdls_);
- if (leAudioDevice->ctp_hdls_.val_hdl != 0)
- subscribe_for_notification(leAudioDevice->conn_id_,
- leAudioDevice->address_,
- leAudioDevice->ctp_hdls_);
-
for (struct ase& ase : leAudioDevice->ases_)
subscribe_for_notification(leAudioDevice->conn_id_,
leAudioDevice->address_, ase.hdls);
+
+ subscribe_for_notification(leAudioDevice->conn_id_, leAudioDevice->address_,
+ leAudioDevice->ctp_hdls_);
+ }
+
+ void changeMtuIfPossible(LeAudioDevice* leAudioDevice) {
+ if (leAudioDevice->mtu_ == GATT_DEF_BLE_MTU_SIZE) {
+ LOG(INFO) << __func__ << ", Configure MTU";
+ BtaGattQueue::ConfigureMtu(leAudioDevice->conn_id_, GATT_MAX_MTU_SIZE);
+ }
}
void OnEncryptionComplete(const RawAddress& address, uint8_t status) {
@@ -1569,6 +1643,8 @@ class LeAudioClientImpl : public LeAudioClient {
return;
}
+ changeMtuIfPossible(leAudioDevice);
+
/* If we know services, register for notifications */
if (leAudioDevice->known_service_handles_)
RegisterKnownNotifications(leAudioDevice);
@@ -1584,7 +1660,7 @@ class LeAudioClientImpl : public LeAudioClient {
* just notify connected */
if (leAudioDevice->known_service_handles_ &&
!leAudioDevice->notify_connected_after_read_) {
- connectionReady(leAudioDevice);
+ LOG_INFO("Wait for CCC registration and MTU change request");
return;
}
@@ -1610,6 +1686,7 @@ class LeAudioClientImpl : public LeAudioClient {
callbacks_->OnConnectionState(ConnectionState::DISCONNECTED, address);
leAudioDevice->conn_id_ = GATT_INVALID_CONN_ID;
+ leAudioDevice->mtu_ = 0;
leAudioDevice->closing_stream_for_disconnection_ = false;
leAudioDevice->encrypted_ = false;
@@ -1687,6 +1764,16 @@ class LeAudioClientImpl : public LeAudioClient {
DeregisterNotifications(leAudioDevice);
}
+ void OnMtuChanged(uint16_t conn_id, uint16_t mtu) {
+ LeAudioDevice* leAudioDevice = leAudioDevices_.FindByConnId(conn_id);
+ if (!leAudioDevice) {
+ LOG_DEBUG("Unknown connectect id %d", conn_id);
+ return;
+ }
+
+ leAudioDevice->mtu_ = mtu;
+ }
+
void OnGattServiceDiscoveryDone(const RawAddress& address) {
LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address);
if (!leAudioDevice) {
@@ -2075,6 +2162,15 @@ class LeAudioClientImpl : public LeAudioClient {
if (status == GATT_SUCCESS) {
LOG(INFO) << __func__
<< ", successfully registered on ccc: " << loghex(hdl);
+
+ if (leAudioDevice->ctp_hdls_.ccc_hdl == hdl &&
+ leAudioDevice->known_service_handles_ &&
+ !leAudioDevice->notify_connected_after_read_) {
+ /* Reconnection case. Control point is the last CCC LeAudio is
+ * registering for on reconnection */
+ connectionReady(leAudioDevice);
+ }
+
return;
}
@@ -2898,13 +2994,31 @@ class LeAudioClientImpl : public LeAudioClient {
return;
}
+ if (stack_config_get_interface()
+ ->get_pts_le_audio_disable_ases_before_stopping()) {
+ LOG_INFO("Stream disable_timer_ started");
+ if (alarm_is_scheduled(disable_timer_)) alarm_cancel(disable_timer_);
+
+ alarm_set_on_mloop(
+ disable_timer_, kAudioDisableTimeoutMs,
+ [](void* data) {
+ if (instance) instance->GroupSuspend(PTR_TO_INT(data));
+ },
+ INT_TO_PTR(active_group_id_));
+ }
+
/* Group should tie in time to get requested status */
uint64_t timeoutMs = kAudioSuspentKeepIsoAliveTimeoutMs;
timeoutMs = osi_property_get_int32(kAudioSuspentKeepIsoAliveTimeoutMsProp,
timeoutMs);
- DLOG(INFO) << __func__
- << " Stream suspend_timeout_ started: " << suspend_timeout_;
+ if (stack_config_get_interface()
+ ->get_pts_le_audio_disable_ases_before_stopping()) {
+ timeoutMs += kAudioDisableTimeoutMs;
+ }
+
+ LOG_DEBUG("Stream suspend_timeout_ started: %d ms",
+ static_cast<int>(timeoutMs));
if (alarm_is_scheduled(suspend_timeout_)) alarm_cancel(suspend_timeout_);
alarm_set_on_mloop(
@@ -2916,9 +3030,9 @@ class LeAudioClientImpl : public LeAudioClient {
}
void OnAudioSinkSuspend() {
- DLOG(INFO) << __func__
- << " IN: audio_receiver_state_: " << audio_receiver_state_
- << " audio_sender_state_: " << audio_sender_state_;
+ LOG_DEBUG(" IN: audio_receiver_state_: %s, audio_sender_state_: %s",
+ ToString(audio_receiver_state_).c_str(),
+ ToString(audio_sender_state_).c_str());
/* Note: This callback is from audio hal driver.
* Bluetooth peer is a Sink for Audio Framework.
@@ -3060,9 +3174,9 @@ class LeAudioClientImpl : public LeAudioClient {
}
void OnAudioSourceSuspend() {
- DLOG(INFO) << __func__
- << " IN: audio_receiver_state_: " << audio_receiver_state_
- << " audio_sender_state_: " << audio_sender_state_;
+ LOG_DEBUG(" IN: audio_receiver_state_: %s, audio_sender_state_: %s",
+ ToString(audio_receiver_state_).c_str(),
+ ToString(audio_sender_state_).c_str());
/* Note: This callback is from audio hal driver.
* Bluetooth peer is a Source for Audio Framework.
@@ -3223,20 +3337,22 @@ class LeAudioClientImpl : public LeAudioClient {
return LeAudioContextType::UNSPECIFIED;
}
+ auto adjusted_contexts = adjustMetadataContexts(available_contexts);
+
using T = std::underlying_type<LeAudioContextType>::type;
/* Mini policy. Voice is prio 1, game prio 2, media is prio 3 */
- if ((available_contexts &
+ if ((adjusted_contexts &
AudioContexts(static_cast<T>(LeAudioContextType::CONVERSATIONAL)))
.any())
return LeAudioContextType::CONVERSATIONAL;
- if ((available_contexts &
+ if ((adjusted_contexts &
AudioContexts(static_cast<T>(LeAudioContextType::GAME)))
.any())
return LeAudioContextType::GAME;
- if ((available_contexts &
+ if ((adjusted_contexts &
AudioContexts(static_cast<T>(LeAudioContextType::RINGTONE)))
.any()) {
if (!in_call_) {
@@ -3245,7 +3361,7 @@ class LeAudioClientImpl : public LeAudioClient {
return LeAudioContextType::RINGTONE;
}
- if ((available_contexts &
+ if ((adjusted_contexts &
AudioContexts(static_cast<T>(LeAudioContextType::MEDIA)))
.any())
return LeAudioContextType::MEDIA;
@@ -3253,8 +3369,8 @@ class LeAudioClientImpl : public LeAudioClient {
/*TODO do something smarter here */
/* Get context for the first non-zero bit */
uint16_t context_type = 0b1;
- while (available_contexts != 0b1) {
- available_contexts = available_contexts >> 1;
+ while (adjusted_contexts != 0b1) {
+ adjusted_contexts = adjusted_contexts >> 1;
context_type = context_type << 1;
}
@@ -3311,7 +3427,14 @@ class LeAudioClientImpl : public LeAudioClient {
bool is_group_streaming =
(group->GetTargetState() == AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
- metadata_context_types_ = GetAllowedAudioContextsFromSourceMetadata(
+ if (audio_receiver_state_ == AudioState::STARTED) {
+ /* If the receiver is starte. Take into account current context type */
+ metadata_context_types_ = adjustMetadataContexts(metadata_context_types_);
+ } else {
+ metadata_context_types_ = 0;
+ }
+
+ metadata_context_types_ |= GetAllowedAudioContextsFromSourceMetadata(
source_metadata, group->GetActiveContexts());
if (stack_config_get_interface()
@@ -3675,6 +3798,20 @@ class LeAudioClientImpl : public LeAudioClient {
}
}
+ void NotifyUpperLayerGroupTurnedIdleDuringCall(int group_id) {
+ if (!osi_property_get_bool(kNotifyUpperLayerAboutGroupBeingInIdleDuringCall,
+ false)) {
+ return;
+ }
+ /* If group is inactive, phone is in call and Group is not having CIS
+ * connected, notify upper layer about it, so it can decide to create SCO if
+ * it is in the handover case
+ */
+ if (in_call_ && active_group_id_ == bluetooth::groups::kGroupUnknown) {
+ callbacks_->OnGroupStatus(group_id, GroupStatus::TURNED_IDLE_DURING_CALL);
+ }
+ }
+
void StatusReportCb(int group_id, GroupStreamStatus status) {
LOG_INFO("status: %d , audio_sender_state %s, audio_receiver_state %s",
static_cast<int>(status),
@@ -3736,15 +3873,19 @@ class LeAudioClientImpl : public LeAudioClient {
stream_setup_start_timestamp_ = 0;
if (group && group->IsPendingConfiguration()) {
SuspendedForReconfiguration();
+ auto adjusted_metedata_context_type =
+ adjustMetadataContexts(metadata_context_types_);
if (groupStateMachine_->ConfigureStream(
- group, configuration_context_type_, metadata_context_types_,
- GetAllCcids(metadata_context_types_))) {
+ group, configuration_context_type_,
+ adjusted_metedata_context_type,
+ GetAllCcids(adjusted_metedata_context_type))) {
/* If configuration succeed wait for new status. */
return;
}
}
CancelStreamingRequest();
if (group) {
+ NotifyUpperLayerGroupTurnedIdleDuringCall(group->group_id_);
HandlePendingAvailableContexts(group);
HandlePendingDeviceDisconnection(group);
}
@@ -3772,6 +3913,8 @@ class LeAudioClientImpl : public LeAudioClient {
LeAudioGroupStateMachine* groupStateMachine_;
int active_group_id_;
LeAudioContextType configuration_context_type_;
+ static constexpr char kAllowMultipleContextsInMetadata[] =
+ "persist.bluetooth.leaudio.allow.multiple.contexts";
AudioContexts metadata_context_types_;
uint64_t stream_setup_start_timestamp_;
uint64_t stream_setup_end_timestamp_;
@@ -3782,6 +3925,8 @@ class LeAudioClientImpl : public LeAudioClient {
AudioState audio_sender_state_;
/* Keep in call state. */
bool in_call_;
+ static constexpr char kNotifyUpperLayerAboutGroupBeingInIdleDuringCall[] =
+ "persist.bluetooth.leaudio.notify.idle.during.call";
/* Current stream configuration */
LeAudioCodecConfiguration current_source_codec_config;
@@ -3820,9 +3965,11 @@ class LeAudioClientImpl : public LeAudioClient {
const void* audio_source_instance_;
const void* audio_sink_instance_;
static constexpr uint64_t kAudioSuspentKeepIsoAliveTimeoutMs = 5000;
+ static constexpr uint64_t kAudioDisableTimeoutMs = 3000;
static constexpr char kAudioSuspentKeepIsoAliveTimeoutMsProp[] =
"persist.bluetooth.leaudio.audio.suspend.timeoutms";
alarm_t* suspend_timeout_;
+ alarm_t* disable_timer_;
static constexpr uint64_t kDeviceAttachDelayMs = 500;
std::vector<int16_t> cached_channel_data_;
@@ -3903,6 +4050,7 @@ void le_audio_gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
instance->OnServiceChangeEvent(p_data->remote_bda);
break;
case BTA_GATTC_CFG_MTU_EVT:
+ instance->OnMtuChanged(p_data->cfg_mtu.conn_id, p_data->cfg_mtu.mtu);
break;
default:
diff --git a/system/bta/le_audio/client_audio.h b/system/bta/le_audio/client_audio.h
index 4da151b710..28c913e475 100644
--- a/system/bta/le_audio/client_audio.h
+++ b/system/bta/le_audio/client_audio.h
@@ -134,7 +134,7 @@ class LeAudioClientAudioSource {
const void* Acquire(bool is_broadcasting_session_type);
bool InitAudioSinkThread(const std::string name);
- bluetooth::common::MessageLoopThread* worker_thread_;
+ bluetooth::common::MessageLoopThread* worker_thread_ = nullptr;
private:
bool SinkOnResumeReq(bool start_media_task);
@@ -147,9 +147,9 @@ class LeAudioClientAudioSource {
bluetooth::common::RepeatingTimer audio_timer_;
LeAudioCodecConfiguration source_codec_config_;
- LeAudioClientAudioSinkReceiver* audioSinkReceiver_;
+ LeAudioClientAudioSinkReceiver* audioSinkReceiver_ = nullptr;
bluetooth::audio::le_audio::LeAudioClientInterface::Sink*
- sinkClientInterface_;
+ sinkClientInterface_ = nullptr;
/* Guard audio sink receiver mutual access from stack with internal mutex */
std::mutex sinkInterfaceMutex_;
@@ -180,9 +180,9 @@ class LeAudioUnicastClientAudioSink {
bool SourceOnSuspendReq();
bool SourceOnMetadataUpdateReq(const sink_metadata_t& sink_metadata);
- LeAudioClientAudioSourceReceiver* audioSourceReceiver_;
+ LeAudioClientAudioSourceReceiver* audioSourceReceiver_ = nullptr;
bluetooth::audio::le_audio::LeAudioClientInterface::Source*
- sourceClientInterface_;
+ sourceClientInterface_ = nullptr;
};
class LeAudioUnicastClientAudioSource : public LeAudioClientAudioSource {
diff --git a/system/bta/le_audio/devices.cc b/system/bta/le_audio/devices.cc
index 1541ca7068..4e4283d8dd 100644
--- a/system/bta/le_audio/devices.cc
+++ b/system/bta/le_audio/devices.cc
@@ -200,18 +200,22 @@ void LeAudioDeviceGroup::SetCigState(le_audio::types::CigState state) {
}
bool LeAudioDeviceGroup::Activate(LeAudioContextType context_type) {
+ bool is_activate = false;
for (auto leAudioDevice : leAudioDevices_) {
if (leAudioDevice.expired()) continue;
bool activated = leAudioDevice.lock()->ActivateConfiguredAses(context_type);
- LOG_INFO("Device %s is activated now: ",
- leAudioDevice.lock().get()->address_.ToString().c_str());
+ LOG_INFO("Device %s is %s",
+ leAudioDevice.lock().get()->address_.ToString().c_str(),
+ activated ? "activated" : " not activated");
if (activated) {
- if (!CigAssignCisIds(leAudioDevice.lock().get())) return false;
+ if (!CigAssignCisIds(leAudioDevice.lock().get())) {
+ return false;
+ }
+ is_activate = true;
}
}
-
- return true;
+ return is_activate;
}
LeAudioDevice* LeAudioDeviceGroup::GetFirstDevice(void) {
@@ -406,17 +410,6 @@ LeAudioDevice* LeAudioDeviceGroup::GetNextActiveDeviceByDataPathState(
return iter->lock().get();
}
-bool LeAudioDeviceGroup::SetContextType(LeAudioContextType context_type) {
- /* XXX: group context policy ? / may it disallow to change type ?) */
- context_type_ = context_type;
-
- return true;
-}
-
-LeAudioContextType LeAudioDeviceGroup::GetContextType(void) {
- return context_type_;
-}
-
uint32_t LeAudioDeviceGroup::GetSduInterval(uint8_t direction) {
for (LeAudioDevice* leAudioDevice = GetFirstActiveDevice();
leAudioDevice != nullptr;
@@ -1575,9 +1568,6 @@ const set_configurations::AudioSetConfiguration*
LeAudioDeviceGroup::GetActiveConfiguration(void) {
return active_context_to_configuration_map[active_context_type_];
}
-AudioContexts LeAudioDeviceGroup::GetActiveContexts(void) {
- return active_contexts_mask_;
-}
std::optional<LeAudioCodecConfiguration>
LeAudioDeviceGroup::GetCodecConfigurationByDirection(
@@ -1773,10 +1763,6 @@ void LeAudioDeviceGroup::CreateStreamVectorForOffloader(uint8_t direction) {
}
}
-types::LeAudioContextType LeAudioDeviceGroup::GetCurrentContextType(void) {
- return active_context_type_;
-}
-
bool LeAudioDeviceGroup::IsPendingConfiguration(void) {
return stream_conf.pending_configuration;
}
@@ -2431,8 +2417,7 @@ bool LeAudioDevice::ActivateConfiguredAses(LeAudioContextType context_type) {
LOG_INFO(" Configuring device %s", address_.ToString().c_str());
for (auto& ase : ases_) {
- if (!ase.active &&
- ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED &&
+ if (ase.state == AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED &&
ase.configured_for_context_type == context_type) {
LOG_INFO(
" conn_id: %d, ase id %d, cis id %d, cis_handle 0x%04x is activated.",
@@ -2446,14 +2431,13 @@ bool LeAudioDevice::ActivateConfiguredAses(LeAudioContextType context_type) {
}
void LeAudioDevice::DeactivateAllAses(void) {
- /* Just clear states and keep previous configuration for use
- * in case device will get reconnected
- */
for (auto& ase : ases_) {
if (ase.active) {
ase.state = AseState::BTA_LE_AUDIO_ASE_STATE_IDLE;
ase.data_path_state = AudioStreamDataPathState::IDLE;
ase.active = false;
+ ase.cis_id = le_audio::kInvalidCisId;
+ ase.cis_conn_hdl = 0;
}
}
}
@@ -2636,7 +2620,11 @@ void LeAudioDevices::Dump(int fd, int group_id) {
void LeAudioDevices::Cleanup(void) {
for (auto const& device : leAudioDevices_) {
- device->DisconnectAcl();
+ if (device->conn_id_ != GATT_INVALID_CONN_ID) {
+ BtaGattQueue::Clean(device->conn_id_);
+ BTA_GATTC_Close(device->conn_id_);
+ device->DisconnectAcl();
+ }
}
leAudioDevices_.clear();
}
diff --git a/system/bta/le_audio/devices.h b/system/bta/le_audio/devices.h
index 29cb9725bc..cd5a0fe8a3 100644
--- a/system/bta/le_audio/devices.h
+++ b/system/bta/le_audio/devices.h
@@ -60,6 +60,7 @@ class LeAudioDevice {
bool connecting_actively_;
bool closing_stream_for_disconnection_;
uint16_t conn_id_;
+ uint16_t mtu_;
bool encrypted_;
int group_id_;
bool csis_member_;
@@ -93,6 +94,7 @@ class LeAudioDevice {
connecting_actively_(first_connection),
closing_stream_for_disconnection_(false),
conn_id_(GATT_INVALID_CONN_ID),
+ mtu_(0),
encrypted_(false),
group_id_(group_id),
csis_member_(false),
@@ -212,10 +214,10 @@ class LeAudioDeviceGroup {
transport_latency_stom_us_(0),
active_context_type_(types::LeAudioContextType::UNINITIALIZED),
metadata_context_type_(0),
+ active_contexts_mask_(0),
pending_update_available_contexts_(std::nullopt),
target_state_(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE),
- current_state_(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE),
- context_type_(types::LeAudioContextType::UNINITIALIZED) {}
+ current_state_(types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {}
~LeAudioDeviceGroup(void);
void AddNode(const std::shared_ptr<LeAudioDevice>& leAudioDevice);
@@ -263,8 +265,6 @@ class LeAudioDeviceGroup {
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);
uint8_t GetSCA(void);
uint8_t GetPacking(void);
@@ -284,14 +284,12 @@ class LeAudioDeviceGroup {
bool ReloadAudioLocations(void);
bool ReloadAudioDirections(void);
const set_configurations::AudioSetConfiguration* GetActiveConfiguration(void);
- types::LeAudioContextType GetCurrentContextType(void);
bool IsPendingConfiguration(void);
void SetPendingConfiguration(void);
void ClearPendingConfiguration(void);
bool IsConfigurationSupported(
LeAudioDevice* leAudioDevice,
const set_configurations::AudioSetConfiguration* audio_set_conf);
- types::AudioContexts GetActiveContexts(void);
std::optional<LeAudioCodecConfiguration> GetCodecConfigurationByDirection(
types::LeAudioContextType group_context_type, uint8_t direction);
bool IsContextSupported(types::LeAudioContextType group_context_type);
@@ -323,10 +321,18 @@ class LeAudioDeviceGroup {
pending_update_available_contexts_ = audio_contexts;
}
+ inline types::LeAudioContextType GetCurrentContextType(void) const {
+ return active_context_type_;
+ }
+
inline types::AudioContexts GetMetadataContextType(void) const {
return metadata_context_type_;
}
+ inline types::AudioContexts GetActiveContexts(void) {
+ return active_contexts_mask_;
+ }
+
bool IsInTransition(void);
bool IsReleasing(void);
void Dump(int fd);
@@ -351,6 +357,7 @@ class LeAudioDeviceGroup {
types::LeAudioContextType active_context_type_;
types::AudioContexts metadata_context_type_;
types::AudioContexts active_contexts_mask_;
+
std::optional<types::AudioContexts> pending_update_available_contexts_;
std::map<types::LeAudioContextType,
const set_configurations::AudioSetConfiguration*>
@@ -358,7 +365,6 @@ class LeAudioDeviceGroup {
types::AseState target_state_;
types::AseState current_state_;
- types::LeAudioContextType context_type_;
std::vector<std::weak_ptr<LeAudioDevice>> leAudioDevices_;
};
diff --git a/system/bta/le_audio/devices_test.cc b/system/bta/le_audio/devices_test.cc
index 78b5df187b..2077e37c18 100644
--- a/system/bta/le_audio/devices_test.cc
+++ b/system/bta/le_audio/devices_test.cc
@@ -651,6 +651,8 @@ class LeAudioAseConfigurationTest : public Test {
void TestAsesInactivated(const LeAudioDevice* device) {
for (const auto& ase : device->ases_) {
ASSERT_FALSE(ase.active);
+ ASSERT_TRUE(ase.cis_id == ::le_audio::kInvalidCisId);
+ ASSERT_TRUE(ase.cis_conn_hdl == 0);
}
}
@@ -948,10 +950,20 @@ TEST_F(LeAudioAseConfigurationTest, test_reconnection_media) {
TestSingleAseConfiguration(LeAudioContextType::MEDIA, data, 2, configuration);
- SetCisInformationToActiveAse();
+ /* Generate CISes, symulate CIG creation and assign cis handles to ASEs.*/
+ group_->CigGenerateCisIds(LeAudioContextType::MEDIA);
+ std::vector<uint16_t> handles = {0x0012, 0x0013};
+ group_->CigAssignCisConnHandles(handles);
+ group_->CigAssignCisIds(left);
+ group_->CigAssignCisIds(right);
+ TestActiveAses();
/* Left got disconnected */
left->DeactivateAllAses();
+
+ /* Unassign from the group*/
+ group_->CigUnassignCis(left);
+
TestAsesInactivated(left);
/* Prepare reconfiguration */
@@ -979,6 +991,12 @@ TEST_F(LeAudioAseConfigurationTest, test_reconnection_media) {
TestGroupAseConfigurationVerdict(data[i]);
}
+ /* Before device is rejoining, and group already exist, cis handles are
+ * assigned before sending codec config
+ */
+ group_->CigAssignCisIds(left);
+ group_->CigAssignCisConnHandlesToAses(left);
+
TestActiveAses();
}
} // namespace
diff --git a/system/bta/le_audio/le_audio_client_test.cc b/system/bta/le_audio/le_audio_client_test.cc
index 071598be72..0507e68215 100644
--- a/system/bta/le_audio/le_audio_client_test.cc
+++ b/system/bta/le_audio/le_audio_client_test.cc
@@ -68,6 +68,11 @@ extern struct fake_osi_alarm_set_on_mloop fake_osi_alarm_set_on_mloop_;
std::map<std::string, int> mock_function_count_map;
constexpr int max_num_of_ases = 5;
+static constexpr char kNotifyUpperLayerAboutGroupBeingInIdleDuringCall[] =
+ "persist.bluetooth.leaudio.notify.idle.during.call";
+
+void osi_property_set_bool(const char* key, bool value);
+
// Disables most likely false-positives from base::SplitString()
extern "C" const char* __asan_default_options() {
return "detect_container_overflow=0";
@@ -143,6 +148,7 @@ bool get_pts_connect_eatt_before_encryption(void) { return false; }
bool get_pts_unencrypt_broadcast(void) { return false; }
bool get_pts_eatt_peripheral_collision_support(void) { return false; }
bool get_pts_force_le_audio_multiple_contexts_metadata(void) { return false; }
+bool get_pts_le_audio_disable_ases_before_stopping(void) { return false; }
config_t* get_all(void) { return nullptr; }
stack_config_t mock_stack_config{
@@ -164,6 +170,8 @@ stack_config_t mock_stack_config{
get_pts_eatt_peripheral_collision_support,
.get_pts_force_le_audio_multiple_contexts_metadata =
get_pts_force_le_audio_multiple_contexts_metadata,
+ .get_pts_le_audio_disable_ases_before_stopping =
+ get_pts_le_audio_disable_ases_before_stopping,
.get_all = get_all,
};
const stack_config_t* stack_config_get_interface(void) {
@@ -672,8 +680,9 @@ class UnicastTestNoInit : public Test {
return false;
}
- group->Configure(group->GetContextType(),
- static_cast<uint16_t>(group->GetContextType()), {});
+ group->Configure(
+ group->GetCurrentContextType(),
+ static_cast<uint16_t>(group->GetCurrentContextType()), {});
if (!group->CigAssignCisIds(leAudioDevice)) return false;
group->CigAssignCisConnHandlesToAses(leAudioDevice);
@@ -756,15 +765,6 @@ class UnicastTestNoInit : public Test {
types::LeAudioContextType context_type,
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) {
- /* TODO: Switch context of group */
- group->SetContextType(context_type);
- }
- return true;
- }
-
/* Do what ReleaseCisIds(group) does: start */
LeAudioDevice* leAudioDevice = group->GetFirstDevice();
while (leAudioDevice != nullptr) {
@@ -785,7 +785,6 @@ class UnicastTestNoInit : public Test {
if (group->GetState() ==
types::AseState::BTA_LE_AUDIO_ASE_STATE_IDLE) {
group->CigGenerateCisIds(context_type);
- group->SetContextType(context_type);
std::vector<uint16_t> conn_handles;
for (uint8_t i = 0; i < (uint8_t)(group->cises_.size()); i++) {
@@ -3778,5 +3777,127 @@ TEST_F(UnicastTest, StartNotSupportedContextType) {
* is needed to restart the stream */
SinkAudioResume();
}
+
+TEST_F(UnicastTest, NotifyAboutGroupTunrnedIdleEnabled) {
+ const RawAddress test_address0 = GetTestAddress(0);
+ int group_id = bluetooth::groups::kGroupUnknown;
+
+ osi_property_set_bool(kNotifyUpperLayerAboutGroupBeingInIdleDuringCall, true);
+
+ SetSampleDatabaseEarbudsValid(
+ 1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
+ codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt,
+ default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/,
+ true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/,
+ 0 /*rank*/);
+ EXPECT_CALL(mock_client_callbacks_,
+ OnConnectionState(ConnectionState::CONNECTED, test_address0))
+ .Times(1);
+ EXPECT_CALL(mock_client_callbacks_,
+ OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED))
+ .WillOnce(DoAll(SaveArg<1>(&group_id)));
+
+ ConnectLeAudio(test_address0);
+ ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown);
+
+ // Start streaming
+ uint8_t cis_count_out = 1;
+ uint8_t cis_count_in = 0;
+
+ LeAudioClient::Get()->SetInCall(true);
+
+ // Audio sessions are started only when device gets active
+ EXPECT_CALL(*mock_unicast_audio_source_, Start(_, _)).Times(1);
+ EXPECT_CALL(*mock_audio_sink_, Start(_, _)).Times(1);
+ LeAudioClient::Get()->GroupSetActive(group_id);
+
+ StartStreaming(AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE,
+ AUDIO_CONTENT_TYPE_UNKNOWN, group_id);
+
+ Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+ Mock::VerifyAndClearExpectations(mock_unicast_audio_source_);
+ SyncOnMainLoop();
+
+ // Verify Data transfer on one audio source cis
+ TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+
+ // Release
+
+ /* To be called twice
+ * 1. GroupStatus::INACTIVE
+ * 2. GroupStatus::TURNED_IDLE_DURING_CALL
+ */
+ EXPECT_CALL(mock_client_callbacks_, OnGroupStatus(group_id, _)).Times(2);
+
+ EXPECT_CALL(*mock_unicast_audio_source_, Stop()).Times(1);
+ EXPECT_CALL(*mock_unicast_audio_source_, Release(_)).Times(1);
+ EXPECT_CALL(*mock_audio_sink_, Release(_)).Times(1);
+ LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown);
+
+ Mock::VerifyAndClearExpectations(mock_unicast_audio_source_);
+
+ LeAudioClient::Get()->SetInCall(false);
+ osi_property_set_bool(kNotifyUpperLayerAboutGroupBeingInIdleDuringCall,
+ false);
+}
+
+TEST_F(UnicastTest, NotifyAboutGroupTunrnedIdleDisabled) {
+ const RawAddress test_address0 = GetTestAddress(0);
+ int group_id = bluetooth::groups::kGroupUnknown;
+
+ SetSampleDatabaseEarbudsValid(
+ 1, test_address0, codec_spec_conf::kLeAudioLocationStereo,
+ codec_spec_conf::kLeAudioLocationStereo, default_channel_cnt,
+ default_channel_cnt, 0x0004, false /*add_csis*/, true /*add_cas*/,
+ true /*add_pacs*/, default_ase_cnt /*add_ascs_cnt*/, 1 /*set_size*/,
+ 0 /*rank*/);
+ EXPECT_CALL(mock_client_callbacks_,
+ OnConnectionState(ConnectionState::CONNECTED, test_address0))
+ .Times(1);
+ EXPECT_CALL(mock_client_callbacks_,
+ OnGroupNodeStatus(test_address0, _, GroupNodeStatus::ADDED))
+ .WillOnce(DoAll(SaveArg<1>(&group_id)));
+
+ ConnectLeAudio(test_address0);
+ ASSERT_NE(group_id, bluetooth::groups::kGroupUnknown);
+
+ // Start streaming
+ uint8_t cis_count_out = 1;
+ uint8_t cis_count_in = 0;
+
+ LeAudioClient::Get()->SetInCall(true);
+
+ // Audio sessions are started only when device gets active
+ EXPECT_CALL(*mock_unicast_audio_source_, Start(_, _)).Times(1);
+ EXPECT_CALL(*mock_audio_sink_, Start(_, _)).Times(1);
+ LeAudioClient::Get()->GroupSetActive(group_id);
+
+ StartStreaming(AUDIO_USAGE_NOTIFICATION_TELEPHONY_RINGTONE,
+ AUDIO_CONTENT_TYPE_UNKNOWN, group_id);
+
+ Mock::VerifyAndClearExpectations(&mock_client_callbacks_);
+ Mock::VerifyAndClearExpectations(mock_unicast_audio_source_);
+ SyncOnMainLoop();
+
+ // Verify Data transfer on one audio source cis
+ TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920);
+
+ // Release
+
+ /* To be called once only
+ * 1. GroupStatus::INACTIVE
+ */
+ EXPECT_CALL(mock_client_callbacks_, OnGroupStatus(group_id, _)).Times(1);
+
+ EXPECT_CALL(*mock_unicast_audio_source_, Stop()).Times(1);
+ EXPECT_CALL(*mock_unicast_audio_source_, Release(_)).Times(1);
+ EXPECT_CALL(*mock_audio_sink_, Release(_)).Times(1);
+ LeAudioClient::Get()->GroupSetActive(bluetooth::groups::kGroupUnknown);
+
+ Mock::VerifyAndClearExpectations(mock_unicast_audio_source_);
+
+ LeAudioClient::Get()->SetInCall(false);
+}
+
} // namespace
} // namespace le_audio
diff --git a/system/bta/le_audio/le_audio_types.cc b/system/bta/le_audio/le_audio_types.cc
index b6e63e5e85..f6d03e1a62 100644
--- a/system/bta/le_audio/le_audio_types.cc
+++ b/system/bta/le_audio/le_audio_types.cc
@@ -579,8 +579,8 @@ uint32_t AdjustAllocationForOffloader(uint32_t allocation) {
namespace types {
std::ostream& operator<<(std::ostream& os, const types::CigState& state) {
- static const char* char_value_[4] = {"NONE", "CREATING", "CREATED",
- "REMOVING"};
+ static const char* char_value_[5] = {"NONE", "CREATING", "CREATED",
+ "REMOVING", "RECOVERING"};
os << char_value_[static_cast<uint8_t>(state)] << " ("
<< "0x" << std::setfill('0') << std::setw(2) << static_cast<int>(state)
diff --git a/system/bta/le_audio/le_audio_types.h b/system/bta/le_audio/le_audio_types.h
index f37c1109f2..776ef52551 100644
--- a/system/bta/le_audio/le_audio_types.h
+++ b/system/bta/le_audio/le_audio_types.h
@@ -311,7 +311,7 @@ constexpr uint16_t kMaxTransportLatencyMin = 0x0005;
constexpr uint16_t kMaxTransportLatencyMax = 0x0FA0;
/* Enums */
-enum class CigState : uint8_t { NONE, CREATING, CREATED, REMOVING };
+enum class CigState : uint8_t { NONE, CREATING, CREATED, REMOVING, RECOVERING };
/* ASE states according to BAP defined state machine states */
enum class AseState : uint8_t {
diff --git a/system/bta/le_audio/mock_iso_manager.cc b/system/bta/le_audio/mock_iso_manager.cc
index fe521eb3bf..a276be6620 100644
--- a/system/bta/le_audio/mock_iso_manager.cc
+++ b/system/bta/le_audio/mock_iso_manager.cc
@@ -58,7 +58,9 @@ void IsoManager::ReconfigureCig(
pimpl_->ReconfigureCig(cig_id, std::move(cig_params));
}
-void IsoManager::RemoveCig(uint8_t cig_id) { pimpl_->RemoveCig(cig_id); }
+void IsoManager::RemoveCig(uint8_t cig_id, bool force) {
+ pimpl_->RemoveCig(cig_id, force);
+}
void IsoManager::EstablishCis(
struct iso_manager::cis_establish_params conn_params) {
diff --git a/system/bta/le_audio/mock_iso_manager.h b/system/bta/le_audio/mock_iso_manager.h
index 8db3ade2a5..e5a3247dd6 100644
--- a/system/bta/le_audio/mock_iso_manager.h
+++ b/system/bta/le_audio/mock_iso_manager.h
@@ -43,7 +43,7 @@ struct MockIsoManager {
(void), ReconfigureCig,
(uint8_t cig_id,
struct bluetooth::hci::iso_manager::cig_create_params cig_params));
- MOCK_METHOD((void), RemoveCig, (uint8_t cig_id));
+ MOCK_METHOD((void), RemoveCig, (uint8_t cig_id, bool force));
MOCK_METHOD(
(void), EstablishCis,
(struct bluetooth::hci::iso_manager::cis_establish_params conn_params));
diff --git a/system/bta/le_audio/state_machine.cc b/system/bta/le_audio/state_machine.cc
index ff1381a76c..20ae741a04 100644
--- a/system/bta/le_audio/state_machine.cc
+++ b/system/bta/le_audio/state_machine.cc
@@ -172,16 +172,16 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
switch (group->GetState()) {
case AseState::BTA_LE_AUDIO_ASE_STATE_CODEC_CONFIGURED:
if (group->GetCurrentContextType() == context_type) {
- group->Activate(context_type);
- if (!group->Activate(context_type)) {
- LOG(ERROR) << __func__ << ", failed to activate ASEs";
- return false;
+ if (group->Activate(context_type)) {
+ SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
+ if (CigCreate(group)) {
+ return true;
+ }
}
- SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
- CigCreate(group);
- return true;
+ LOG_INFO("Could not activate device, try to configure it again");
}
+ /* We are going to reconfigure whole group. Clear Cises.*/
ReleaseCisIds(group);
/* If configuration is needed */
@@ -193,7 +193,6 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
}
group->CigGenerateCisIds(context_type);
- group->SetContextType(context_type);
/* All ASEs should aim to achieve target state */
SetTargetState(group, AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING);
PrepareAndSendCodecConfigure(group, group->GetFirstActiveDevice());
@@ -362,6 +361,18 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
}
if (status != HCI_SUCCESS) {
+ if (status == HCI_ERR_COMMAND_DISALLOWED) {
+ /*
+ * We are here, because stack has no chance to remove CIG when it was
+ * shut during streaming. In the same time, controller probably was not
+ * Reseted, which creates the issue. Lets remove CIG and try to create
+ * it again.
+ */
+ group->SetCigState(CigState::RECOVERING);
+ IsoManager::GetInstance()->RemoveCig(group->group_id_, true);
+ return;
+ }
+
group->SetCigState(CigState::NONE);
LOG_ERROR(", failed to create CIG, reason: 0x%02x, new cig state: %s",
+status, ToString(group->cig_state_).c_str());
@@ -405,8 +416,33 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
leAudioDevice->link_quality_timer = nullptr;
}
+ void ProcessHciNotifyOnCigRemoveRecovering(uint8_t status,
+ LeAudioDeviceGroup* group) {
+ group->SetCigState(CigState::NONE);
+
+ if (status != HCI_SUCCESS) {
+ LOG_ERROR(
+ "Could not recover from the COMMAND DISALLOAD on CigCreate. Status "
+ "on CIG remove is 0x%02x",
+ status);
+ StopStream(group);
+ return;
+ }
+ LOG_INFO("Succeed on CIG Recover - back to creating CIG");
+ if (!CigCreate(group)) {
+ LOG_ERROR("Could not create CIG. Stop the stream for group %d",
+ group->group_id_);
+ StopStream(group);
+ }
+ }
+
void ProcessHciNotifOnCigRemove(uint8_t status,
LeAudioDeviceGroup* group) override {
+ if (group->GetCigState() == CigState::RECOVERING) {
+ ProcessHciNotifyOnCigRemoveRecovering(status, group);
+ return;
+ }
+
if (status != HCI_SUCCESS) {
group->SetCigState(CigState::CREATED);
LOG_ERROR(
@@ -1135,7 +1171,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
group->CigUnassignCis(leAudioDevice);
}
- void CigCreate(LeAudioDeviceGroup* group) {
+ bool CigCreate(LeAudioDeviceGroup* group) {
uint32_t sdu_interval_mtos, sdu_interval_stom;
uint16_t max_trans_lat_mtos, max_trans_lat_stom;
uint8_t packing, framing, sca;
@@ -1147,7 +1183,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
if (group->GetCigState() != CigState::NONE) {
LOG_WARN(" Group %p, id: %d has invalid cig state: %s ", group,
group->group_id_, ToString(group->cig_state_).c_str());
- return;
+ return false;
}
sdu_interval_mtos =
@@ -1238,6 +1274,7 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
IsoManager::GetInstance()->CreateCig(group->group_id_, std::move(param));
LOG_DEBUG("Group: %p, id: %d cig state: %s", group, group->group_id_,
ToString(group->cig_state_).c_str());
+ return true;
}
static void CisCreateForDevice(LeAudioDevice* leAudioDevice) {
@@ -1577,7 +1614,11 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
if (group->GetTargetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
- CigCreate(group);
+ if (!CigCreate(group)) {
+ LOG_ERROR("Could not create CIG. Stop the stream for group %d",
+ group->group_id_);
+ StopStream(group);
+ }
return;
}
@@ -1662,7 +1703,11 @@ class LeAudioGroupStateMachineImpl : public LeAudioGroupStateMachine {
if (group->GetTargetState() ==
AseState::BTA_LE_AUDIO_ASE_STATE_STREAMING) {
- CigCreate(group);
+ if (!CigCreate(group)) {
+ LOG_ERROR("Could not create CIG. Stop the stream for group %d",
+ group->group_id_);
+ StopStream(group);
+ }
return;
}
diff --git a/system/bta/le_audio/state_machine_test.cc b/system/bta/le_audio/state_machine_test.cc
index b8a5fb2d14..56cb8cc4cd 100644
--- a/system/bta/le_audio/state_machine_test.cc
+++ b/system/bta/le_audio/state_machine_test.cc
@@ -249,13 +249,19 @@ class StateMachineTest : public Test {
for (auto i = 0u; i < p.cis_cfgs.size(); ++i) {
conn_handles.push_back(UNIQUE_CIS_CONN_HANDLE(cig_id, i));
}
+ auto status = HCI_SUCCESS;
+ if (group_create_command_disallowed_) {
+ group_create_command_disallowed_ = false;
+ status = HCI_ERR_COMMAND_DISALLOWED;
+ }
+
LeAudioGroupStateMachine::Get()->ProcessHciNotifOnCigCreate(
- group.get(), 0, cig_id, conn_handles);
+ group.get(), status, cig_id, conn_handles);
}
});
ON_CALL(*mock_iso_manager_, RemoveCig)
- .WillByDefault([this](uint8_t cig_id) {
+ .WillByDefault([this](uint8_t cig_id, bool force) {
DLOG(INFO) << "CreateRemove";
auto& group = le_audio_device_groups_[cig_id];
@@ -1148,6 +1154,7 @@ class StateMachineTest : public Test {
std::vector<std::shared_ptr<LeAudioDevice>> le_audio_devices_;
std::map<uint8_t, std::unique_ptr<LeAudioDeviceGroup>>
le_audio_device_groups_;
+ bool group_create_command_disallowed_ = false;
};
TEST_F(StateMachineTest, testInit) {
@@ -1263,7 +1270,48 @@ TEST_F(StateMachineTest, testConfigureQosSingle) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
+
+ InjectInitialIdleNotification(group);
+
+ ASSERT_TRUE(LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type));
+
+ // Check if group has transitioned to a proper state
+ ASSERT_EQ(group->GetState(),
+ types::AseState::BTA_LE_AUDIO_ASE_STATE_QOS_CONFIGURED);
+}
+
+TEST_F(StateMachineTest, testConfigureQosSingleRecoverCig) {
+ const auto context_type = kContextTypeRingtone;
+ const int leaudio_group_id = 3;
+
+ /* Assume that on previous BT OFF CIG was not removed */
+ group_create_command_disallowed_ = true;
+
+ // Prepare fake connected device group
+ auto* group = PrepareSingleTestDeviceGroup(leaudio_group_id, context_type);
+
+ /* Since we prepared device with Ringtone context in mind, only one ASE
+ * should have been configured.
+ */
+ auto* leAudioDevice = group->GetFirstDevice();
+ PrepareConfigureCodecHandler(group, 1);
+ PrepareConfigureQosHandler(group, 1);
+
+ // Start the configuration and stream Media content
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(1, leAudioDevice->ctp_hdls_.val_hdl, _,
+ GATT_WRITE_NO_RSP, _, _))
+ .Times(3);
+
+ EXPECT_CALL(*mock_iso_manager_, CreateCig(_, _)).Times(2);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, EstablishCis(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
InjectInitialIdleNotification(group);
@@ -1307,7 +1355,7 @@ TEST_F(StateMachineTest, testConfigureQosMultiple) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
InjectInitialIdleNotification(group);
@@ -1346,7 +1394,7 @@ TEST_F(StateMachineTest, testStreamSingle) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
InjectInitialIdleNotification(group);
@@ -1391,7 +1439,7 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSink) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
InjectInitialIdleNotification(group);
@@ -1437,7 +1485,7 @@ TEST_F(StateMachineTest, testStreamSkipEnablingSinkSource) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
InjectInitialIdleNotification(group);
@@ -1477,7 +1525,7 @@ TEST_F(StateMachineTest, testStreamMultipleConversational) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
InjectInitialIdleNotification(group);
@@ -1529,7 +1577,7 @@ TEST_F(StateMachineTest, testStreamMultiple) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
InjectInitialIdleNotification(group);
@@ -1592,7 +1640,7 @@ TEST_F(StateMachineTest, testDisableSingle) {
_, bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput))
.Times(1);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
InjectInitialIdleNotification(group);
@@ -1660,7 +1708,7 @@ TEST_F(StateMachineTest, testDisableMultiple) {
_, bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionInput))
.Times(2);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
InjectInitialIdleNotification(group);
@@ -1725,7 +1773,7 @@ TEST_F(StateMachineTest, testDisableBidirectional) {
bluetooth::hci::iso_manager::kRemoveIsoDataPathDirectionOutput))
.Times(1);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
@@ -1771,7 +1819,7 @@ TEST_F(StateMachineTest, testReleaseSingle) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
InjectInitialIdleNotification(group);
@@ -1827,7 +1875,7 @@ TEST_F(StateMachineTest, testReleaseCachingSingle) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
InjectInitialIdleNotification(group);
@@ -1900,7 +1948,7 @@ TEST_F(StateMachineTest, testStreamCachingSingle) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(4);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
InjectInitialIdleNotification(group);
@@ -1987,7 +2035,7 @@ TEST_F(StateMachineTest, testActivateStreamCachingSingle) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(5);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
InjectInitialIdleNotification(group);
@@ -2069,7 +2117,7 @@ TEST_F(StateMachineTest, testReleaseMultiple) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(2);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
InjectInitialIdleNotification(group);
@@ -2126,7 +2174,7 @@ TEST_F(StateMachineTest, testReleaseBidirectional) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
InjectInitialIdleNotification(group);
@@ -2175,7 +2223,7 @@ TEST_F(StateMachineTest, testDisableAndReleaseBidirectional) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(2);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(1);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(1);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(1);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(1);
// Start the configuration and stream Media content
LeAudioGroupStateMachine::Get()->StartStream(
@@ -2209,7 +2257,7 @@ TEST_F(StateMachineTest, testAseIdAssignmentIdle) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
for (auto* device = group->GetFirstDevice(); device != nullptr;
device = group->GetNextDevice(device)) {
@@ -2239,7 +2287,7 @@ TEST_F(StateMachineTest, testAseIdAssignmentCodecConfigured) {
EXPECT_CALL(*mock_iso_manager_, SetupIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, RemoveIsoDataPath(_, _)).Times(0);
EXPECT_CALL(*mock_iso_manager_, DisconnectCis(_, _)).Times(0);
- EXPECT_CALL(*mock_iso_manager_, RemoveCig(_)).Times(0);
+ EXPECT_CALL(*mock_iso_manager_, RemoveCig(_, _)).Times(0);
for (auto* device = group->GetFirstDevice(); device != nullptr;
device = group->GetNextDevice(device)) {
@@ -2727,5 +2775,154 @@ TEST_F(StateMachineTest, testAttachDeviceToTheStream) {
ASSERT_NE(std::find(ccids->begin(), ccids->end(), media_ccid), ccids->end());
}
+TEST_F(StateMachineTest, StartStreamAfterConfigure) {
+ const auto context_type = kContextTypeMedia;
+ const auto leaudio_group_id = 6;
+ const auto num_devices = 2;
+
+ ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+
+ // Prepare multiple fake connected devices in a group
+ auto* group =
+ PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+ ASSERT_EQ(group->Size(), num_devices);
+
+ PrepareConfigureCodecHandler(group, 0, true);
+ PrepareConfigureQosHandler(group);
+ PrepareEnableHandler(group);
+ PrepareDisableHandler(group);
+ PrepareReleaseHandler(group);
+
+ InjectInitialIdleNotification(group);
+
+ auto* leAudioDevice = group->GetFirstDevice();
+ auto expected_devices_written = 0;
+ while (leAudioDevice) {
+ /* Three Writes:
+ * 1. Codec configure
+ * 2: Codec QoS
+ * 3: Enabling
+ */
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl, _,
+ GATT_WRITE_NO_RSP, _, _))
+ .Times(3);
+ expected_devices_written++;
+ leAudioDevice = group->GetNextDevice(leAudioDevice);
+ }
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(mock_callbacks_,
+ StatusReportCb(
+ leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::CONFIGURED_BY_USER));
+
+ // Start the configuration and stream Media content
+ group->SetPendingConfiguration();
+ LeAudioGroupStateMachine::Get()->ConfigureStream(
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+ group->ClearPendingConfiguration();
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+}
+
+TEST_F(StateMachineTest, StartStreamCachedConfig) {
+ const auto context_type = kContextTypeMedia;
+ const auto leaudio_group_id = 6;
+ const auto num_devices = 2;
+
+ ContentControlIdKeeper::GetInstance()->SetCcid(media_context, media_ccid);
+
+ // Prepare multiple fake connected devices in a group
+ auto* group =
+ PrepareSingleTestDeviceGroup(leaudio_group_id, context_type, num_devices);
+ ASSERT_EQ(group->Size(), num_devices);
+
+ PrepareConfigureCodecHandler(group, 0, true);
+ PrepareConfigureQosHandler(group);
+ PrepareEnableHandler(group);
+ PrepareDisableHandler(group);
+ PrepareReleaseHandler(group);
+
+ InjectInitialIdleNotification(group);
+
+ auto* leAudioDevice = group->GetFirstDevice();
+ auto expected_devices_written = 0;
+ while (leAudioDevice) {
+ /* Three Writes:
+ * 1: Codec config
+ * 2: Codec QoS (+1 after restart)
+ * 3: Enabling (+1 after restart)
+ * 4: Release (1)
+ */
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(leAudioDevice->conn_id_,
+ leAudioDevice->ctp_hdls_.val_hdl, _,
+ GATT_WRITE_NO_RSP, _, _))
+ .Times(6);
+ expected_devices_written++;
+ leAudioDevice = group->GetNextDevice(leAudioDevice);
+ }
+ ASSERT_EQ(expected_devices_written, num_devices);
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+ // Validate GroupStreamStatus
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::RELEASING));
+
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(
+ leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::CONFIGURED_AUTONOMOUS));
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StopStream(group);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+
+ // Restart stream
+ EXPECT_CALL(
+ mock_callbacks_,
+ StatusReportCb(leaudio_group_id,
+ bluetooth::le_audio::GroupStreamStatus::STREAMING));
+
+ // Start the configuration and stream Media content
+ LeAudioGroupStateMachine::Get()->StartStream(
+ group, static_cast<types::LeAudioContextType>(context_type),
+ context_type);
+
+ testing::Mock::VerifyAndClearExpectations(&mock_callbacks_);
+}
+
} // namespace internal
} // namespace le_audio
diff --git a/system/bta/vc/vc.cc b/system/bta/vc/vc.cc
index 403b93d936..1d406a973a 100644
--- a/system/bta/vc/vc.cc
+++ b/system/bta/vc/vc.cc
@@ -743,13 +743,23 @@ class VolumeControlImpl : public VolumeControl {
int group_id, bool is_autonomous,
uint8_t opcode,
std::vector<uint8_t>& arguments) {
- DLOG(INFO) << __func__ << " num of devices: " << devices.size()
- << " group_id: " << group_id
- << " is_autonomous: " << is_autonomous << " opcode: " << +opcode
- << " arg size: " << arguments.size();
-
- ongoing_operations_.emplace_back(latest_operation_id_++, group_id,
- is_autonomous, opcode, arguments, devices);
+ LOG_DEBUG(
+ "num of devices: %zu, group_id: %d, is_autonomous: %s opcode: %d, arg "
+ "size: %zu",
+ devices.size(), group_id, is_autonomous ? "true" : "false", +opcode,
+ arguments.size());
+
+ if (std::find_if(ongoing_operations_.begin(), ongoing_operations_.end(),
+ [opcode, &arguments](const VolumeOperation& op) {
+ return (op.opcode_ == opcode) &&
+ std::equal(op.arguments_.begin(),
+ op.arguments_.end(),
+ arguments.begin());
+ }) == ongoing_operations_.end()) {
+ ongoing_operations_.emplace_back(latest_operation_id_++, group_id,
+ is_autonomous, opcode, arguments,
+ devices);
+ }
}
void MuteUnmute(std::variant<RawAddress, int> addr_or_group_id, bool mute) {
@@ -760,11 +770,13 @@ class VolumeControlImpl : public VolumeControl {
if (std::holds_alternative<RawAddress>(addr_or_group_id)) {
LOG_DEBUG("Address: %s: ",
(std::get<RawAddress>(addr_or_group_id)).ToString().c_str());
- std::vector<RawAddress> devices = {
- std::get<RawAddress>(addr_or_group_id)};
-
- PrepareVolumeControlOperation(devices, bluetooth::groups::kGroupUnknown,
- false, opcode, arg);
+ VolumeControlDevice* dev = volume_control_devices_.FindByAddress(
+ std::get<RawAddress>(addr_or_group_id));
+ if (dev && dev->IsConnected()) {
+ std::vector<RawAddress> devices = {dev->address};
+ PrepareVolumeControlOperation(devices, bluetooth::groups::kGroupUnknown,
+ false, opcode, arg);
+ }
} else {
/* Handle group change */
auto group_id = std::get<int>(addr_or_group_id);
@@ -815,14 +827,17 @@ class VolumeControlImpl : public VolumeControl {
uint8_t opcode = kControlPointOpcodeSetAbsoluteVolume;
if (std::holds_alternative<RawAddress>(addr_or_group_id)) {
- DLOG(INFO) << __func__ << " " << std::get<RawAddress>(addr_or_group_id);
- std::vector<RawAddress> devices = {
- std::get<RawAddress>(addr_or_group_id)};
-
- RemovePendingVolumeControlOperations(devices,
- bluetooth::groups::kGroupUnknown);
- PrepareVolumeControlOperation(devices, bluetooth::groups::kGroupUnknown,
- false, opcode, arg);
+ LOG_DEBUG("Address: %s: ",
+ std::get<RawAddress>(addr_or_group_id).ToString().c_str());
+ VolumeControlDevice* dev = volume_control_devices_.FindByAddress(
+ std::get<RawAddress>(addr_or_group_id));
+ if (dev && dev->IsConnected() && (dev->volume != volume)) {
+ std::vector<RawAddress> devices = {dev->address};
+ RemovePendingVolumeControlOperations(devices,
+ bluetooth::groups::kGroupUnknown);
+ PrepareVolumeControlOperation(devices, bluetooth::groups::kGroupUnknown,
+ false, opcode, arg);
+ }
} else {
/* Handle group change */
auto group_id = std::get<int>(addr_or_group_id);
diff --git a/system/bta/vc/vc_test.cc b/system/bta/vc/vc_test.cc
index bdc93e1f40..71fca365c8 100644
--- a/system/bta/vc/vc_test.cc
+++ b/system/bta/vc/vc_test.cc
@@ -915,10 +915,37 @@ class VolumeControlValueSetTest : public VolumeControlTest {
};
TEST_F(VolumeControlValueSetTest, test_set_volume) {
- std::vector<uint8_t> expected_data({0x04, 0x00, 0x10});
- EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, expected_data,
- GATT_WRITE, _, _));
+ ON_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0024, _, GATT_WRITE, _, _))
+ .WillByDefault([this](uint16_t conn_id, uint16_t handle,
+ std::vector<uint8_t> value,
+ tGATT_WRITE_TYPE write_type, GATT_WRITE_OP_CB cb,
+ void* cb_data) {
+ std::vector<uint8_t> ntf_value({
+ value[2], // volume level
+ 0, // muted
+ static_cast<uint8_t>(value[1] + 1), // change counter
+ });
+ GetNotificationEvent(0x0021, ntf_value);
+ });
+
+ const std::vector<uint8_t> vol_x10({0x04, 0x00, 0x10});
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(conn_id, 0x0024, vol_x10, GATT_WRITE, _, _))
+ .Times(1);
+ VolumeControl::Get()->SetVolume(test_address, 0x10);
+
+ // Same volume level should not be applied twice
+ const std::vector<uint8_t> vol_x10_2({0x04, 0x01, 0x10});
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(conn_id, 0x0024, vol_x10_2, GATT_WRITE, _, _))
+ .Times(0);
VolumeControl::Get()->SetVolume(test_address, 0x10);
+
+ const std::vector<uint8_t> vol_x20({0x04, 0x01, 0x20});
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(conn_id, 0x0024, vol_x20, GATT_WRITE, _, _))
+ .Times(1);
+ VolumeControl::Get()->SetVolume(test_address, 0x20);
}
TEST_F(VolumeControlValueSetTest, test_mute) {
diff --git a/system/btif/Android.bp b/system/btif/Android.bp
index 732d585b80..f522cf469d 100644
--- a/system/btif/Android.bp
+++ b/system/btif/Android.bp
@@ -35,14 +35,11 @@ btifCommonIncludes = [
cc_library {
name: "libstatslog_bt",
+ defaults: ["fluoride_common_options"],
host_supported: true,
generated_sources: ["statslog_bt.cpp"],
generated_headers: ["statslog_bt.h"],
export_generated_headers: ["statslog_bt.h"],
- cflags: [
- "-Wall",
- "-Werror",
- ],
shared_libs: [
"libcutils",
],
diff --git a/system/btif/src/btif_a2dp_source.cc b/system/btif/src/btif_a2dp_source.cc
index 55f46dd31c..0439ce47cd 100644
--- a/system/btif/src/btif_a2dp_source.cc
+++ b/system/btif/src/btif_a2dp_source.cc
@@ -1015,12 +1015,22 @@ static bool btif_a2dp_source_enqueue_callback(BT_HDR* p_buf, size_t frames_n,
if (status != BTM_CMD_STARTED) {
LOG_WARN("%s: Cannot read RSSI: status %d", __func__, status);
}
+
+ // Intel controllers don't handle ReadFailedContactCounter very well, it
+ // sends back Hardware Error event which will crash the daemon. So
+ // temporarily disable this for Floss.
+ // TODO(b/249876976): Intel controllers to handle this command correctly.
+ // And if the need for disabling metrics-related HCI call grows, consider
+ // creating a framework to avoid ifdefs.
+#ifndef TARGET_FLOSS
status = BTM_ReadFailedContactCounter(peer_bda,
btm_read_failed_contact_counter_cb);
if (status != BTM_CMD_STARTED) {
LOG_WARN("%s: Cannot read Failed Contact Counter: status %d", __func__,
status);
}
+#endif
+
status =
BTM_ReadTxPower(peer_bda, BT_TRANSPORT_BR_EDR, btm_read_tx_power_cb);
if (status != BTM_CMD_STARTED) {
diff --git a/system/btif/src/btif_av.cc b/system/btif/src/btif_av.cc
index d0ded18386..c3cb8d72ff 100644
--- a/system/btif/src/btif_av.cc
+++ b/system/btif/src/btif_av.cc
@@ -696,7 +696,9 @@ static void btif_av_handle_event(uint8_t peer_sep,
tBTA_AV_HNDL bta_handle,
const BtifAvEvent& btif_av_event);
static void btif_report_connection_state(const RawAddress& peer_address,
- btav_connection_state_t state);
+ btav_connection_state_t state,
+ const bt_status_t status,
+ uint8_t error_code);
static void btif_report_audio_state(const RawAddress& peer_address,
btav_audio_state_t state);
static void btif_av_report_sink_audio_config_state(
@@ -1548,8 +1550,6 @@ bool BtifAvStateMachine::StateIdle::ProcessEvent(uint32_t event, void* p_data) {
case BTA_AV_OPEN_EVT: {
tBTA_AV* p_bta_data = (tBTA_AV*)p_data;
- btav_connection_state_t state;
- int av_state;
tBTA_AV_STATUS status = p_bta_data->open.status;
bool can_connect = true;
@@ -1560,44 +1560,55 @@ bool BtifAvStateMachine::StateIdle::ProcessEvent(uint32_t event, void* p_data) {
(status == BTA_AV_SUCCESS) ? "SUCCESS" : "FAILED",
p_bta_data->open.edr);
+ btif_report_connection_state(
+ peer_.PeerAddress(), BTAV_CONNECTION_STATE_CONNECTING,
+ bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);
+
if (p_bta_data->open.status == BTA_AV_SUCCESS) {
- state = BTAV_CONNECTION_STATE_CONNECTED;
- av_state = BtifAvStateMachine::kStateOpened;
peer_.SetEdr(p_bta_data->open.edr);
CHECK(peer_.PeerSep() == p_bta_data->open.sep);
- // Check whether connection is allowed
- if (peer_.IsSink()) {
- can_connect = btif_av_source.AllowedToConnect(peer_.PeerAddress());
- if (!can_connect) src_disconnect_sink(peer_.PeerAddress());
- } else if (peer_.IsSource()) {
- can_connect = btif_av_sink.AllowedToConnect(peer_.PeerAddress());
- if (!can_connect) sink_disconnect_src(peer_.PeerAddress());
- }
- } else {
- state = BTAV_CONNECTION_STATE_DISCONNECTED;
- av_state = BtifAvStateMachine::kStateIdle;
- }
- if (!can_connect) {
- BTIF_TRACE_ERROR(
- "%s: Cannot connect to peer %s: too many connected "
- "peers",
- __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str());
- } else {
- // Report the connection state to the application
- btif_report_connection_state(peer_.PeerAddress(), state);
- // Change state to Open/Idle based on the status
- peer_.StateMachine().TransitionTo(av_state);
- if (peer_.IsSink()) {
- // If queued PLAY command, send it now
- btif_rc_check_handle_pending_play(
- p_bta_data->open.bd_addr,
- (p_bta_data->open.status == BTA_AV_SUCCESS));
- } else if (peer_.IsSource() &&
- (p_bta_data->open.status == BTA_AV_SUCCESS)) {
- // Bring up AVRCP connection as well
- BTA_AvOpenRc(peer_.BtaHandle());
+ can_connect = peer_.IsSink()
+ ? btif_av_source.AllowedToConnect(peer_.PeerAddress())
+ : btif_av_sink.AllowedToConnect(peer_.PeerAddress());
+
+ if (!can_connect) {
+ BTIF_TRACE_ERROR(
+ "%s: Cannot connect to peer %s: too many connected "
+ "peers",
+ __PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str());
+
+ if (peer_.IsSink()) {
+ src_disconnect_sink(peer_.PeerAddress());
+ } else if (peer_.IsSource()) {
+ sink_disconnect_src(peer_.PeerAddress());
+ }
+
+ btif_report_connection_state(
+ peer_.PeerAddress(), BTAV_CONNECTION_STATE_DISCONNECTED,
+ bt_status_t::BT_STATUS_NOMEM, BTA_AV_FAIL_RESOURCES);
+ peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateIdle);
+ } else {
+ if (peer_.IsSink()) {
+ // If queued PLAY command, send it now
+ btif_rc_check_handle_pending_play(
+ p_bta_data->open.bd_addr,
+ (p_bta_data->open.status == BTA_AV_SUCCESS));
+ } else if (peer_.IsSource() &&
+ (p_bta_data->open.status == BTA_AV_SUCCESS)) {
+ // Bring up AVRCP connection as well
+ BTA_AvOpenRc(peer_.BtaHandle());
+ }
+ btif_report_connection_state(
+ peer_.PeerAddress(), BTAV_CONNECTION_STATE_CONNECTED,
+ bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);
+ peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateOpened);
}
+ } else {
+ btif_report_connection_state(peer_.PeerAddress(),
+ BTAV_CONNECTION_STATE_DISCONNECTED,
+ bt_status_t::BT_STATUS_FAIL, status);
+ peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateIdle);
}
btif_queue_advance();
} break;
@@ -1649,7 +1660,8 @@ void BtifAvStateMachine::StateOpening::OnEnter() {
// Inform the application that we are entering connecting state
btif_report_connection_state(peer_.PeerAddress(),
- BTAV_CONNECTION_STATE_CONNECTING);
+ BTAV_CONNECTION_STATE_CONNECTING,
+ bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);
}
void BtifAvStateMachine::StateOpening::OnExit() {
@@ -1675,14 +1687,16 @@ bool BtifAvStateMachine::StateOpening::ProcessEvent(uint32_t event,
// it is in an intermediate state. In other states we can handle
// incoming/outgoing connect/disconnect requests.
BTIF_TRACE_WARNING(
- "%s: Peer %s : event=%s: transitioning to Idle due to ACL Disconnect",
+ "%s: Peer %s : event=%s: transitioning to Idle due to ACL "
+ "Disconnect",
__PRETTY_FUNCTION__, peer_.PeerAddress().ToString().c_str(),
BtifAvEvent::EventName(event).c_str());
log_counter_metrics_btif(android::bluetooth::CodePathCounterKeyEnum::
A2DP_CONNECTION_ACL_DISCONNECTED,
1);
btif_report_connection_state(peer_.PeerAddress(),
- BTAV_CONNECTION_STATE_DISCONNECTED);
+ BTAV_CONNECTION_STATE_DISCONNECTED,
+ bt_status_t::BT_STATUS_FAIL, BTA_AV_FAIL);
peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateIdle);
if (peer_.SelfInitiatedConnection()) {
btif_queue_advance();
@@ -1696,8 +1710,9 @@ bool BtifAvStateMachine::StateOpening::ProcessEvent(uint32_t event,
log_counter_metrics_btif(android::bluetooth::CodePathCounterKeyEnum::
A2DP_CONNECTION_REJECT_EVT,
1);
- btif_report_connection_state(peer_.PeerAddress(),
- BTAV_CONNECTION_STATE_DISCONNECTED);
+ btif_report_connection_state(
+ peer_.PeerAddress(), BTAV_CONNECTION_STATE_DISCONNECTED,
+ bt_status_t::BT_STATUS_AUTH_REJECTED, BTA_AV_FAIL);
peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateIdle);
if (peer_.SelfInitiatedConnection()) {
btif_queue_advance();
@@ -1706,7 +1721,6 @@ bool BtifAvStateMachine::StateOpening::ProcessEvent(uint32_t event,
case BTA_AV_OPEN_EVT: {
tBTA_AV* p_bta_data = (tBTA_AV*)p_data;
- btav_connection_state_t state;
int av_state;
tBTA_AV_STATUS status = p_bta_data->open.status;
@@ -1718,10 +1732,13 @@ bool BtifAvStateMachine::StateOpening::ProcessEvent(uint32_t event,
p_bta_data->open.edr);
if (p_bta_data->open.status == BTA_AV_SUCCESS) {
- state = BTAV_CONNECTION_STATE_CONNECTED;
av_state = BtifAvStateMachine::kStateOpened;
peer_.SetEdr(p_bta_data->open.edr);
CHECK(peer_.PeerSep() == p_bta_data->open.sep);
+ // Report the connection state to the application
+ btif_report_connection_state(
+ peer_.PeerAddress(), BTAV_CONNECTION_STATE_CONNECTED,
+ bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);
log_counter_metrics_btif(
android::bluetooth::CodePathCounterKeyEnum::A2DP_CONNECTION_SUCCESS,
1);
@@ -1738,15 +1755,16 @@ bool BtifAvStateMachine::StateOpening::ProcessEvent(uint32_t event,
BTA_AvCloseRc(peer_handle);
}
}
- state = BTAV_CONNECTION_STATE_DISCONNECTED;
av_state = BtifAvStateMachine::kStateIdle;
+ // Report the connection state to the application
+ btif_report_connection_state(peer_.PeerAddress(),
+ BTAV_CONNECTION_STATE_DISCONNECTED,
+ bt_status_t::BT_STATUS_FAIL, status);
log_counter_metrics_btif(
android::bluetooth::CodePathCounterKeyEnum::A2DP_CONNECTION_FAILURE,
1);
}
- // Report the connection state to the application
- btif_report_connection_state(peer_.PeerAddress(), state);
// Change state to Open/Idle based on the status
peer_.StateMachine().TransitionTo(av_state);
if (peer_.IsSink()) {
@@ -1815,7 +1833,8 @@ bool BtifAvStateMachine::StateOpening::ProcessEvent(uint32_t event,
case BTA_AV_CLOSE_EVT:
btif_a2dp_on_stopped(nullptr);
btif_report_connection_state(peer_.PeerAddress(),
- BTAV_CONNECTION_STATE_DISCONNECTED);
+ BTAV_CONNECTION_STATE_DISCONNECTED,
+ bt_status_t::BT_STATUS_FAIL, BTA_AV_FAIL);
peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateIdle);
log_counter_metrics_btif(
android::bluetooth::CodePathCounterKeyEnum::A2DP_CONNECTION_CLOSE, 1);
@@ -1827,7 +1846,8 @@ bool BtifAvStateMachine::StateOpening::ProcessEvent(uint32_t event,
case BTIF_AV_DISCONNECT_REQ_EVT:
BTA_AvClose(peer_.BtaHandle());
btif_report_connection_state(peer_.PeerAddress(),
- BTAV_CONNECTION_STATE_DISCONNECTED);
+ BTAV_CONNECTION_STATE_DISCONNECTED,
+ bt_status_t::BT_STATUS_FAIL, BTA_AV_FAIL);
peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateIdle);
log_counter_metrics_btif(android::bluetooth::CodePathCounterKeyEnum::
A2DP_CONNECTION_DISCONNECTED,
@@ -1929,7 +1949,8 @@ bool BtifAvStateMachine::StateOpened::ProcessEvent(uint32_t event,
return true;
// If remote tries to start A2DP when DUT is A2DP Source, then Suspend.
- // If A2DP is Sink and call is active, then disconnect the AVDTP channel.
+ // If A2DP is Sink and call is active, then disconnect the AVDTP
+ // channel.
bool should_suspend = false;
if (peer_.IsSink()) {
if (!peer_.CheckFlags(BtifAvPeer::kFlagPendingStart |
@@ -1945,8 +1966,8 @@ bool BtifAvStateMachine::StateOpened::ProcessEvent(uint32_t event,
should_suspend = true;
}
- // If peer is A2DP Source, do ACK commands to audio HAL and start media
- // task
+ // If peer is A2DP Source, do ACK commands to audio HAL and start
+ // media task
if (btif_a2dp_on_started(peer_.PeerAddress(), &p_av->start)) {
// Only clear pending flag after acknowledgement
peer_.ClearFlags(BtifAvPeer::kFlagPendingStart);
@@ -1977,8 +1998,9 @@ bool BtifAvStateMachine::StateOpened::ProcessEvent(uint32_t event,
}
// Inform the application that we are disconnecting
- btif_report_connection_state(peer_.PeerAddress(),
- BTAV_CONNECTION_STATE_DISCONNECTING);
+ btif_report_connection_state(
+ peer_.PeerAddress(), BTAV_CONNECTION_STATE_DISCONNECTING,
+ bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);
// Wait in closing state until fully closed
peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateClosing);
@@ -1986,6 +2008,10 @@ bool BtifAvStateMachine::StateOpened::ProcessEvent(uint32_t event,
case BTA_AV_CLOSE_EVT:
// AVDTP link is closed
+ // Inform the application that we are disconnecting
+ btif_report_connection_state(
+ peer_.PeerAddress(), BTAV_CONNECTION_STATE_DISCONNECTING,
+ bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);
// Change state to Idle, send acknowledgement if start is pending
if (peer_.CheckFlags(BtifAvPeer::kFlagPendingStart)) {
BTIF_TRACE_WARNING("%s: Peer %s : failed pending start request",
@@ -2003,8 +2029,9 @@ bool BtifAvStateMachine::StateOpened::ProcessEvent(uint32_t event,
}
// Inform the application that we are disconnected
- btif_report_connection_state(peer_.PeerAddress(),
- BTAV_CONNECTION_STATE_DISCONNECTED);
+ btif_report_connection_state(
+ peer_.PeerAddress(), BTAV_CONNECTION_STATE_DISCONNECTED,
+ bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);
peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateIdle);
break;
@@ -2164,8 +2191,9 @@ bool BtifAvStateMachine::StateStarted::ProcessEvent(uint32_t event,
}
// Inform the application that we are disconnecting
- btif_report_connection_state(peer_.PeerAddress(),
- BTAV_CONNECTION_STATE_DISCONNECTING);
+ btif_report_connection_state(
+ peer_.PeerAddress(), BTAV_CONNECTION_STATE_DISCONNECTING,
+ bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);
// Wait in closing state until fully closed
peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateClosing);
@@ -2220,8 +2248,8 @@ bool BtifAvStateMachine::StateStarted::ProcessEvent(uint32_t event,
peer_.SetFlags(BtifAvPeer::kFlagPendingStop);
peer_.ClearFlags(BtifAvPeer::kFlagLocalSuspendPending);
- // Don't change the encoder and audio provider state by a non-active peer
- // since they are shared between peers
+ // Don't change the encoder and audio provider state by a non-active
+ // peer since they are shared between peers
if (peer_.IsActivePeer() || !btif_av_stream_started_ready()) {
btif_a2dp_on_stopped(&p_av->suspend);
}
@@ -2239,6 +2267,10 @@ bool BtifAvStateMachine::StateStarted::ProcessEvent(uint32_t event,
peer_.PeerAddress().ToString().c_str(),
BtifAvEvent::EventName(event).c_str(),
peer_.FlagsToString().c_str());
+ // Inform the application that we are disconnecting
+ btif_report_connection_state(
+ peer_.PeerAddress(), BTAV_CONNECTION_STATE_DISCONNECTING,
+ bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);
peer_.SetFlags(BtifAvPeer::kFlagPendingStop);
@@ -2248,8 +2280,9 @@ bool BtifAvStateMachine::StateStarted::ProcessEvent(uint32_t event,
}
// Inform the application that we are disconnected
- btif_report_connection_state(peer_.PeerAddress(),
- BTAV_CONNECTION_STATE_DISCONNECTED);
+ btif_report_connection_state(
+ peer_.PeerAddress(), BTAV_CONNECTION_STATE_DISCONNECTED,
+ bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);
peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateIdle);
break;
@@ -2327,8 +2360,9 @@ bool BtifAvStateMachine::StateClosing::ProcessEvent(uint32_t event,
case BTA_AV_CLOSE_EVT:
// Inform the application that we are disconnecting
- btif_report_connection_state(peer_.PeerAddress(),
- BTAV_CONNECTION_STATE_DISCONNECTED);
+ btif_report_connection_state(
+ peer_.PeerAddress(), BTAV_CONNECTION_STATE_DISCONNECTED,
+ bt_status_t::BT_STATUS_SUCCESS, BTA_AV_SUCCESS);
peer_.StateMachine().TransitionTo(BtifAvStateMachine::kStateIdle);
break;
@@ -2371,9 +2405,9 @@ bool BtifAvStateMachine::StateClosing::ProcessEvent(uint32_t event,
}
/**
- * Timer to trigger AV Open on the Source if the remote Sink device establishes
- * AVRCP connection without AV connection. The timer is needed to interoperate
- * with headsets that do establish AV after AVRCP connection.
+ * Timer to trigger AV Open on the Source if the remote Sink device
+ * establishes AVRCP connection without AV connection. The timer is needed to
+ * interoperate with headsets that do establish AV after AVRCP connection.
*/
static void btif_av_source_initiate_av_open_timer_timeout(void* data) {
BtifAvPeer* peer = (BtifAvPeer*)data;
@@ -2399,8 +2433,8 @@ static void btif_av_source_initiate_av_open_timer_timeout(void* data) {
}
/**
- * Timer to trigger AV Open on the Sink if the remote Source device establishes
- * AVRCP connection without AV connection.
+ * Timer to trigger AV Open on the Sink if the remote Source device
+ * establishes AVRCP connection without AV connection.
*/
static void btif_av_sink_initiate_av_open_timer_timeout(void* data) {
BtifAvPeer* peer = (BtifAvPeer*)data;
@@ -2432,18 +2466,24 @@ static void btif_av_sink_initiate_av_open_timer_timeout(void* data) {
* @param state the connection state
*/
static void btif_report_connection_state(const RawAddress& peer_address,
- btav_connection_state_t state) {
+ btav_connection_state_t state,
+ bt_status_t status,
+ uint8_t error_code) {
LOG_INFO("%s: peer_address=%s state=%d", __func__,
peer_address.ToString().c_str(), state);
if (btif_av_source.Enabled()) {
- do_in_jni_thread(FROM_HERE,
- base::Bind(btif_av_source.Callbacks()->connection_state_cb,
- peer_address, state, btav_error_t{}));
+ do_in_jni_thread(
+ FROM_HERE,
+ base::Bind(btif_av_source.Callbacks()->connection_state_cb,
+ peer_address, state,
+ btav_error_t{.status = status, .error_code = error_code}));
} else if (btif_av_sink.Enabled()) {
- do_in_jni_thread(FROM_HERE,
- base::Bind(btif_av_sink.Callbacks()->connection_state_cb,
- peer_address, state, btav_error_t{}));
+ do_in_jni_thread(
+ FROM_HERE,
+ base::Bind(btif_av_sink.Callbacks()->connection_state_cb, peer_address,
+ state,
+ btav_error_t{.status = status, .error_code = error_code}));
}
}
@@ -2528,8 +2568,8 @@ static void btif_av_report_sink_audio_config_state(
}
/**
- * Call out to JNI / JAVA layers to retrieve whether the mandatory codec is more
- * preferred than others.
+ * Call out to JNI / JAVA layers to retrieve whether the mandatory codec is
+ * more preferred than others.
*
* @param peer_address the peer address
*/
@@ -3252,8 +3292,8 @@ bt_status_t btif_av_source_execute_service(bool enable) {
// Added BTA_AV_FEAT_NO_SCO_SSPD - this ensures that the BTA does not
// auto-suspend av streaming on AG events(SCO or Call). The suspend shall
// be initiated by the app/audioflinger layers.
- // Support for browsing for SDP record should work only if we enable BROWSE
- // while registering.
+ // Support for browsing for SDP record should work only if we enable
+ // BROWSE while registering.
tBTA_AV_FEAT features = BTA_AV_FEAT_RCTG | BTA_AV_FEAT_METADATA |
BTA_AV_FEAT_VENDOR | BTA_AV_FEAT_NO_SCO_SSPD;
diff --git a/system/btif/src/btif_hf.cc b/system/btif/src/btif_hf.cc
index 83197554e9..48eccff256 100644
--- a/system/btif/src/btif_hf.cc
+++ b/system/btif/src/btif_hf.cc
@@ -320,10 +320,17 @@ static void btif_hf_upstreams_evt(uint16_t event, char* p_param) {
break;
// RFCOMM connected or failed to connect
case BTA_AG_OPEN_EVT:
- // Check if an outoging connection is pending
+ bt_hf_callbacks->ConnectionStateCallback(BTHF_CONNECTION_STATE_CONNECTING,
+ &(p_data->open.bd_addr));
+ // Check if an outgoing connection is pending
if (btif_hf_cb[idx].is_initiator) {
+ // There is an outgoing connection.
+ // Check the incoming open event status and the outgoing connection
+ // state.
if ((p_data->open.status != BTA_AG_SUCCESS) &&
btif_hf_cb[idx].state != BTHF_CONNECTION_STATE_CONNECTING) {
+ // Check if the incoming open event and the outgoing connection are
+ // for the same device.
if (p_data->open.bd_addr == btif_hf_cb[idx].connected_bda) {
LOG(WARNING) << __func__ << ": btif_hf_cb state["
<< p_data->open.status
@@ -350,10 +357,14 @@ static void btif_hf_upstreams_evt(uint16_t event, char* p_param) {
break;
}
+ // There is an outgoing connection.
+ // Check the outgoing connection state and address.
CHECK_EQ(btif_hf_cb[idx].state, BTHF_CONNECTION_STATE_CONNECTING)
<< "Control block must be in connecting state when initiating";
CHECK(!btif_hf_cb[idx].connected_bda.IsEmpty())
<< "Remote device address must not be empty when initiating";
+ // Check if the incoming open event and the outgoing connection are
+ // for the same device.
if (btif_hf_cb[idx].connected_bda != p_data->open.bd_addr) {
LOG(WARNING) << __func__
<< ": possible connection collision, ignore the "
@@ -372,6 +383,8 @@ static void btif_hf_upstreams_evt(uint16_t event, char* p_param) {
btif_queue_advance();
}
}
+
+ // There is no pending outgoing connection.
if (p_data->open.status == BTA_AG_SUCCESS) {
// In case this is an incoming connection
btif_hf_cb[idx].connected_bda = p_data->open.bd_addr;
@@ -408,12 +421,15 @@ static void btif_hf_upstreams_evt(uint16_t event, char* p_param) {
"SLC and RFCOMM both disconnected event:%s idx:%d"
" btif_hf_cb.handle:%d",
dump_hf_event(event), idx, btif_hf_cb[idx].handle);
+ RawAddress connected_bda = btif_hf_cb[idx].connected_bda;
+ bt_hf_callbacks->ConnectionStateCallback(
+ BTHF_CONNECTION_STATE_DISCONNECTING, &connected_bda);
// If AG_OPEN was received but SLC was not connected in time, then
// AG_CLOSE may be received. We need to advance the queue here.
bool failed_to_setup_slc =
(btif_hf_cb[idx].state != BTHF_CONNECTION_STATE_SLC_CONNECTED) &&
btif_hf_cb[idx].is_initiator;
- RawAddress connected_bda = btif_hf_cb[idx].connected_bda;
+
reset_control_block(&btif_hf_cb[idx]);
bt_hf_callbacks->ConnectionStateCallback(btif_hf_cb[idx].state,
&connected_bda);
diff --git a/system/btif/src/btif_storage.cc b/system/btif/src/btif_storage.cc
index d73934a3c3..470f04904a 100644
--- a/system/btif/src/btif_storage.cc
+++ b/system/btif/src/btif_storage.cc
@@ -969,6 +969,13 @@ bt_status_t btif_storage_remove_bonded_device(
/* write bonded info immediately */
btif_config_flush();
+
+ /* Check the length of the paired devices, and if 0 then reset IRK */
+ auto paired_devices = btif_config_get_paired_devices();
+ if (paired_devices.empty()) {
+ LOG_INFO("Last paired device removed, resetting IRK");
+ BTA_DmBleResetId();
+ }
return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL;
}
@@ -1378,7 +1385,10 @@ bt_status_t btif_storage_add_ble_local_key(const Octet16& key,
return BT_STATUS_FAIL;
}
int ret = btif_config_set_bin("Adapter", name, key.data(), key.size());
- btif_config_save();
+ // Had to change this to flush to get it to work on test.
+ // Seems to work in the real world on a phone... but not sure why there's a
+ // race in test. Investigate b/239828132
+ btif_config_flush();
return ret ? BT_STATUS_SUCCESS : BT_STATUS_FAIL;
}
diff --git a/system/build/Android.bp b/system/build/Android.bp
index 32f79717e9..37dae66c30 100644
--- a/system/build/Android.bp
+++ b/system/build/Android.bp
@@ -23,8 +23,20 @@ bootstrap_go_package {
pluginFor: ["soong_build"],
}
+cc_defaults {
+ name: "fluoride_common_options",
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ // there are too many unused parameters in all the code.
+ "-Wno-unused-parameter",
+ ],
+}
+
fluoride_defaults {
name: "libchrome_support_defaults",
+ defaults: ["fluoride_common_options"],
static_libs: [
"libchrome",
"libmodpb64",
@@ -33,11 +45,6 @@ fluoride_defaults {
shared_libs: [
"libbase",
],
- cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
- ],
target: {
darwin: {
enabled: false,
@@ -54,12 +61,8 @@ fluoride_defaults {
// default to be used only on platform libs that can rely on shared libchrome
fluoride_defaults {
name: "libchrome_shared_support_defaults",
+ defaults: ["fluoride_common_options"],
shared_libs: ["libchrome"],
- cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
- ],
target: {
darwin: {
enabled: false,
@@ -72,13 +75,12 @@ fluoride_defaults {
// requires no shared libraries, and no explicit sanitization.
fluoride_defaults {
name: "fluoride_types_defaults_fuzzable",
+ defaults: ["fluoride_common_options"],
cflags: [
"-DEXPORT_SYMBOL=__attribute__((visibility(\"default\")))",
"-fvisibility=hidden",
// struct BT_HDR is defined as a variable-size header in a struct.
"-Wno-gnu-variable-sized-type-not-at-end",
- // there are too many unused parameters in all the code.
- "-Wno-unused-parameter",
"-DLOG_NDEBUG=1",
],
conlyflags: [
diff --git a/system/conf/bt_stack.conf b/system/conf/bt_stack.conf
index 84e1a99b6f..f82351bb63 100644
--- a/system/conf/bt_stack.conf
+++ b/system/conf/bt_stack.conf
@@ -97,6 +97,9 @@ TRC_HID_DEV=2
# Use EATT for all services
#PTS_UseEattForAllServices=true
+# Suspend stream after some timeout in LE Audio client module
+#PTS_LeAudioSuspendStreaming=true
+
# Force to update metadata with multiple CCIDs
#PTS_ForceLeAudioMultipleContextsMetadata=true
diff --git a/system/embdrv/lc3/Android.bp b/system/embdrv/lc3/Android.bp
index a16f30ed48..6f92aee786 100644
--- a/system/embdrv/lc3/Android.bp
+++ b/system/embdrv/lc3/Android.bp
@@ -21,9 +21,7 @@ cc_library_static {
cflags: [
"-O3",
"-ffast-math",
- "-Werror",
"-Wmissing-braces",
- "-Wno-unused-parameter",
"-Wno-#warnings",
"-Wuninitialized",
"-Wno-self-assign",
diff --git a/system/gd/Android.bp b/system/gd/Android.bp
index 4d971bf410..2c474757cd 100644
--- a/system/gd/Android.bp
+++ b/system/gd/Android.bp
@@ -10,6 +10,9 @@ package {
cc_defaults {
name: "gd_defaults",
+ defaults: [
+ "fluoride_common_options",
+ ],
tidy_checks: [
"-performance-unnecessary-value-param",
],
@@ -42,14 +45,12 @@ cc_defaults {
"-fvisibility=hidden",
"-DLOG_NDEBUG=1",
"-DGOOGLE_PROTOBUF_NO_RTTI",
- "-Wno-unused-parameter",
"-Wno-unused-result",
],
conlyflags: [
"-std=c99",
],
header_libs: ["jni_headers"],
-
}
// Enables code coverage for a set of source files. Must be combined with
diff --git a/system/gd/common/byte_array_test.cc b/system/gd/common/byte_array_test.cc
index 114a79c344..7a3e3edb65 100644
--- a/system/gd/common/byte_array_test.cc
+++ b/system/gd/common/byte_array_test.cc
@@ -22,32 +22,128 @@
using bluetooth::common::ByteArray;
-static const char* test_bytes = "4c68384139f574d836bcf34e9dfb01bf\0";
-static uint8_t test_data[16] = {
+namespace {
+const char* byte_string16 = "4c68384139f574d836bcf34e9dfb01bf\0";
+const uint8_t byte_data16[16] = {
0x4c, 0x68, 0x38, 0x41, 0x39, 0xf5, 0x74, 0xd8, 0x36, 0xbc, 0xf3, 0x4e, 0x9d, 0xfb, 0x01, 0xbf};
-static uint8_t data[16] = {
- 0x4c, 0x87, 0x49, 0xe1, 0x2e, 0x55, 0x0f, 0x7f, 0x60, 0x8b, 0x4f, 0x96, 0xd7, 0xc5, 0xbc, 0x2a};
+const char* byte_string21 = "4c68384139f574d836bcf34e9dfb01bf0011223344\0";
+const uint8_t byte_data21[21] = {0x4c, 0x68, 0x38, 0x41, 0x39, 0xf5, 0x74, 0xd8, 0x36, 0xbc, 0xf3,
+ 0x4e, 0x9d, 0xfb, 0x01, 0xbf, 0x00, 0x11, 0x22, 0x33, 0x44};
+const char* byte_string23 = "4c68384139f574d836bcf34e9dfb01bf00112233445566\0";
+const uint8_t byte_data23[23] = {0x4c, 0x68, 0x38, 0x41, 0x39, 0xf5, 0x74, 0xd8, 0x36, 0xbc, 0xf3, 0x4e,
+ 0x9d, 0xfb, 0x01, 0xbf, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66};
+const char* byte_string28 = "4c68384139f574d836bcf34e9dfb01bf00112233445566778899aabb\0";
+const uint8_t byte_data28[28] = {0x4c, 0x68, 0x38, 0x41, 0x39, 0xf5, 0x74, 0xd8, 0x36, 0xbc, 0xf3, 0x4e, 0x9d, 0xfb,
+ 0x01, 0xbf, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb};
-TEST(ByteArrayTest, test_constructor_array) {
- ByteArray<16> byte_array(data);
-
- for (size_t i = 0; i < ByteArray<16>::kLength; i++) {
+template <typename T, size_t N>
+void simple_constructor_test(const T (&data)[N]) {
+ ByteArray<N> byte_array(data);
+ for (size_t i = 0; i < ByteArray<N>::kLength; i++) {
ASSERT_EQ(data[i], byte_array.bytes[i]);
}
}
-TEST(ByteArrayTest, test_from_str) {
- auto byte_array = ByteArray<16>::FromString(test_bytes);
+template <typename T, size_t N>
+void simple_const_constructor_test(const T (&data)[N]) {
+ const ByteArray<N> byte_array(data);
+ for (size_t i = 0; i < ByteArray<N>::kLength; i++) {
+ ASSERT_EQ(data[i], byte_array.data()[i]);
+ }
+}
+
+template <typename T, size_t N>
+void simple_array_constructor_test(const T (&data)[N]) {
+ std::array<uint8_t, N> array_of_bytes;
+ std::copy(data, data + N, std::begin(array_of_bytes));
+
+ ByteArray<N> byte_array(array_of_bytes);
+ for (size_t i = 0; i < ByteArray<N>::kLength; i++) {
+ ASSERT_EQ(data[i], byte_array.data()[i]);
+ }
+}
+
+template <typename T, size_t N>
+void simple_from_string_test(const char* byte_string, const T (&data)[N]) {
+ auto byte_array = ByteArray<N>::FromString(byte_string);
ASSERT_TRUE(byte_array);
- for (size_t i = 0; i < ByteArray<16>::kLength; i++) {
- ASSERT_EQ(test_data[i], byte_array->bytes[i]);
+ for (size_t i = 0; i < ByteArray<N>::kLength; i++) {
+ ASSERT_EQ(data[i], byte_array->bytes[i]);
}
}
-TEST(ByteArrayTest, test_to_str) {
- ByteArray<16> byte_array = {
- {0x4C, 0x68, 0x38, 0x41, 0x39, 0xf5, 0x74, 0xd8, 0x36, 0xbc, 0xf3, 0x4e, 0x9d, 0xfb, 0x01, 0xbf}};
+template <typename T, size_t N>
+void simple_to_string_test(const char* byte_string, const T (&data)[N]) {
+ const ByteArray<N> byte_array(data);
std::string str = byte_array.ToString();
- ASSERT_STREQ(str.c_str(), test_bytes);
-} \ No newline at end of file
+ ASSERT_STREQ(str.c_str(), byte_string);
+}
+
+template <typename T, size_t N>
+void simple_from_legacy_string_test(const char* byte_string, const T (&data)[N]) {
+ auto byte_array = ByteArray<N>::FromLegacyConfigString(byte_string);
+ ASSERT_TRUE(byte_array);
+
+ for (size_t i = 0; i < ByteArray<N>::kLength; i++) {
+ ASSERT_EQ(data[i], byte_array->bytes[i]);
+ }
+}
+
+template <typename T, size_t N>
+void simple_to_legacy_string_test(const char* byte_string, const T (&data)[N]) {
+ const ByteArray<N> byte_array(data);
+ std::string str = byte_array.ToLegacyConfigString();
+ ASSERT_STREQ(str.c_str(), byte_string);
+}
+
+} // namespace
+
+TEST(ByteArrayTest, test_simple_constructor) {
+ simple_constructor_test<const uint8_t, 16>(byte_data16);
+ simple_constructor_test<const uint8_t, 21>(byte_data21);
+ simple_constructor_test<const uint8_t, 23>(byte_data23);
+ simple_constructor_test<const uint8_t, 28>(byte_data28);
+}
+
+TEST(ByteArrayTest, test_simple_const_constructor) {
+ simple_const_constructor_test<const uint8_t, 16>(byte_data16);
+ simple_const_constructor_test<const uint8_t, 21>(byte_data21);
+ simple_const_constructor_test<const uint8_t, 23>(byte_data23);
+ simple_const_constructor_test<const uint8_t, 28>(byte_data28);
+}
+
+TEST(ByteArrayTest, test_simple_array_constructor) {
+ simple_array_constructor_test<const uint8_t, 16>(byte_data16);
+ simple_array_constructor_test<const uint8_t, 21>(byte_data21);
+ simple_array_constructor_test<const uint8_t, 23>(byte_data23);
+ simple_array_constructor_test<const uint8_t, 28>(byte_data28);
+}
+
+TEST(ByteArrayTest, test_from_str) {
+ simple_from_string_test<const uint8_t, 16>(byte_string16, byte_data16);
+ simple_from_string_test<const uint8_t, 21>(byte_string21, byte_data21);
+ simple_from_string_test<const uint8_t, 23>(byte_string23, byte_data23);
+ simple_from_string_test<const uint8_t, 28>(byte_string28, byte_data28);
+}
+
+TEST(ByteArrayTest, test_from_legacy_str) {
+ simple_from_legacy_string_test<const uint8_t, 16>(byte_string16, byte_data16);
+ simple_from_legacy_string_test<const uint8_t, 21>(byte_string21, byte_data21);
+ simple_from_legacy_string_test<const uint8_t, 23>(byte_string23, byte_data23);
+ simple_from_legacy_string_test<const uint8_t, 28>(byte_string28, byte_data28);
+}
+
+TEST(ByteArrayTest, test_to_str) {
+ simple_to_string_test<const uint8_t, 16>(byte_string16, byte_data16);
+ simple_to_string_test<const uint8_t, 21>(byte_string21, byte_data21);
+ simple_to_string_test<const uint8_t, 23>(byte_string23, byte_data23);
+ simple_to_string_test<const uint8_t, 28>(byte_string28, byte_data28);
+}
+
+TEST(ByteArrayTest, test_to_legacy_str) {
+ simple_to_legacy_string_test<const uint8_t, 16>(byte_string16, byte_data16);
+ simple_to_legacy_string_test<const uint8_t, 21>(byte_string21, byte_data21);
+ simple_to_legacy_string_test<const uint8_t, 23>(byte_string23, byte_data23);
+ simple_to_legacy_string_test<const uint8_t, 28>(byte_string28, byte_data28);
+}
diff --git a/system/gd/common/list_map_test.cc b/system/gd/common/list_map_test.cc
index 580616f1a5..b587e01672 100644
--- a/system/gd/common/list_map_test.cc
+++ b/system/gd/common/list_map_test.cc
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-#include <chrono>
#include <memory>
#include <gmock/gmock.h>
@@ -329,7 +328,6 @@ TEST(ListMapTest, for_loop_test) {
}
TEST(ListMapTest, pressure_test) {
- auto started = std::chrono::high_resolution_clock::now();
int num_entries = 0xFFFF; // 2^16 = 65535
ListMap<int, int> list_map;
@@ -351,13 +349,6 @@ TEST(ListMapTest, pressure_test) {
EXPECT_TRUE(list_map.extract(key));
}
EXPECT_EQ(list_map.size(), 0ul);
-
- // test execution time
- auto done = std::chrono::high_resolution_clock::now();
- int execution_time = std::chrono::duration_cast<std::chrono::microseconds>(done - started).count();
- // Shouldn't be more than 1000ms
- int execution_time_per_cycle_us = 10;
- EXPECT_LT(execution_time, execution_time_per_cycle_us * num_entries);
}
} // namespace testing
diff --git a/system/gd/common/lru_cache_test.cc b/system/gd/common/lru_cache_test.cc
index d4aebf3f6e..8018d2a62b 100644
--- a/system/gd/common/lru_cache_test.cc
+++ b/system/gd/common/lru_cache_test.cc
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-#include <chrono>
#include <limits>
#include <gmock/gmock.h>
@@ -419,7 +418,6 @@ TEST(LruCacheTest, for_loop_test) {
}
TEST(LruCacheTest, pressure_test) {
- auto started = std::chrono::high_resolution_clock::now();
int capacity = 0xFFFF; // 2^16 = 65535
LruCache<int, int> cache(static_cast<size_t>(capacity));
@@ -449,13 +447,6 @@ TEST(LruCacheTest, pressure_test) {
EXPECT_TRUE(cache.extract(key));
}
EXPECT_EQ(cache.size(), 0ul);
-
- // test execution time
- auto done = std::chrono::high_resolution_clock::now();
- int execution_time = std::chrono::duration_cast<std::chrono::microseconds>(done - started).count();
- // Shouldn't be more than 1120ms
- int execution_time_per_cycle_us = 17;
- EXPECT_LT(execution_time, execution_time_per_cycle_us * capacity);
}
} // namespace testing
diff --git a/system/gd/dumpsys/Android.bp b/system/gd/dumpsys/Android.bp
index 22456518e0..a7fdaa2dcd 100644
--- a/system/gd/dumpsys/Android.bp
+++ b/system/gd/dumpsys/Android.bp
@@ -177,7 +177,7 @@ cc_library {
cc_test {
name: "bluetooth_flatbuffer_tests",
test_suites: ["device-tests"],
- defaults: ["mts_defaults"],
+ defaults: ["fluoride_common_options", "mts_defaults"],
host_supported: true,
test_options: {
unit_test: true,
@@ -192,9 +192,4 @@ cc_test {
generated_headers: [
"BluetoothFlatbufferTestData_h",
],
- cflags: [
- "-Werror",
- "-Wall",
- "-Wextra",
- ],
}
diff --git a/system/gd/dumpsys/bundler/Android.bp b/system/gd/dumpsys/bundler/Android.bp
index ce2c4e289c..3754611b23 100644
--- a/system/gd/dumpsys/bundler/Android.bp
+++ b/system/gd/dumpsys/bundler/Android.bp
@@ -46,13 +46,8 @@ genrule {
cc_defaults {
name: "bluetooth_flatbuffer_bundler_defaults",
+ defaults: ["fluoride_common_options"],
cpp_std: "c++17",
- cflags: [
- "-Wall",
- "-Werror",
- "-Wno-unused-parameter",
- "-Wno-unused-variable",
- ],
generated_headers: [
"BluetoothGeneratedBundlerSchema_h_bfbs",
],
diff --git a/system/gd/dumpsys/bundler/bundler.cc b/system/gd/dumpsys/bundler/bundler.cc
index 08a2cca6f1..f2e6559a37 100644
--- a/system/gd/dumpsys/bundler/bundler.cc
+++ b/system/gd/dumpsys/bundler/bundler.cc
@@ -153,7 +153,7 @@ void WriteHeaderFile(FILE* fp, const uint8_t* data, size_t data_len) {
fprintf(fp, "extern const std::string& GetBundledSchemaData();\n");
fprintf(fp, "const unsigned char %sdata_[%zu] = {\n", namespace_prefix.c_str(), data_len);
- for (auto i = 0; i < data_len; i++) {
+ for (size_t i = 0; i < data_len; i++) {
fprintf(fp, " 0x%02x", data[i]);
if (i != data_len - 1) {
fprintf(fp, ",");
diff --git a/system/gd/dumpsys/bundler/test.cc b/system/gd/dumpsys/bundler/test.cc
index 929b6fff33..24d2f913f2 100644
--- a/system/gd/dumpsys/bundler/test.cc
+++ b/system/gd/dumpsys/bundler/test.cc
@@ -66,7 +66,7 @@ TEST_F(BundlerTest, CreateBinarySchemaBundle) {
std::vector<flatbuffers::Offset<bluetooth::dumpsys::BundledSchemaMap>> vector_map;
std::list<std::string> bundled_names;
ASSERT_TRUE(CreateBinarySchemaBundle(&builder, filenames, &vector_map, &bundled_names));
- ASSERT_EQ(0, vector_map.size());
+ ASSERT_EQ((unsigned int)0, vector_map.size());
}
TEST_F(BundlerTest, WriteHeaderFile) {
diff --git a/system/gd/hal/hci_hal_host.cc b/system/gd/hal/hci_hal_host.cc
index a68434b127..d026113081 100644
--- a/system/gd/hal/hci_hal_host.cc
+++ b/system/gd/hal/hci_hal_host.cc
@@ -362,6 +362,8 @@ class HciHalHost : public HciHal {
ASSERT_LOG(received_size != -1, "Can't receive from socket: %s", strerror(errno));
if (received_size == 0) {
LOG_WARN("Can't read H4 header. EOF received");
+ // First close sock fd before raising sigint
+ close(sock_fd_);
raise(SIGINT);
return;
}
diff --git a/system/gd/hci/Android.bp b/system/gd/hci/Android.bp
index a105c42a72..cc1439f083 100644
--- a/system/gd/hci/Android.bp
+++ b/system/gd/hci/Android.bp
@@ -34,6 +34,7 @@ filegroup {
name: "BluetoothHciUnitTestSources",
srcs: [
"acl_manager/le_impl_test.cc",
+ "acl_manager/classic_acl_connection_test.cc",
"acl_builder_test.cc",
"acl_manager_unittest.cc",
"address_unittest.cc",
diff --git a/system/gd/hci/acl_manager/classic_acl_connection_test.cc b/system/gd/hci/acl_manager/classic_acl_connection_test.cc
new file mode 100644
index 0000000000..ea5d89ef84
--- /dev/null
+++ b/system/gd/hci/acl_manager/classic_acl_connection_test.cc
@@ -0,0 +1,338 @@
+/*
+ * 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 "hci/acl_manager/classic_acl_connection.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <cstdint>
+#include <future>
+#include <list>
+#include <memory>
+#include <mutex>
+#include <queue>
+#include <vector>
+
+#include "hci/acl_connection_interface.h"
+#include "hci/acl_manager/connection_management_callbacks.h"
+#include "hci/address.h"
+#include "hci/hci_packets.h"
+#include "os/handler.h"
+#include "os/log.h"
+#include "os/thread.h"
+
+using namespace bluetooth;
+using namespace std::chrono_literals;
+
+namespace {
+constexpr char kAddress[] = "00:11:22:33:44:55";
+constexpr uint16_t kConnectionHandle = 123;
+constexpr size_t kQueueSize = 10;
+
+std::vector<hci::DisconnectReason> disconnect_reason_vector = {
+ hci::DisconnectReason::AUTHENTICATION_FAILURE,
+ hci::DisconnectReason::REMOTE_USER_TERMINATED_CONNECTION,
+ hci::DisconnectReason::REMOTE_DEVICE_TERMINATED_CONNECTION_LOW_RESOURCES,
+ hci::DisconnectReason::REMOTE_DEVICE_TERMINATED_CONNECTION_POWER_OFF,
+ hci::DisconnectReason::UNSUPPORTED_REMOTE_FEATURE,
+ hci::DisconnectReason::PAIRING_WITH_UNIT_KEY_NOT_SUPPORTED,
+ hci::DisconnectReason::UNACCEPTABLE_CONNECTION_PARAMETERS,
+};
+
+std::vector<hci::ErrorCode> error_code_vector = {
+ hci::ErrorCode::SUCCESS,
+ hci::ErrorCode::UNKNOWN_HCI_COMMAND,
+ hci::ErrorCode::UNKNOWN_CONNECTION,
+ hci::ErrorCode::HARDWARE_FAILURE,
+ hci::ErrorCode::PAGE_TIMEOUT,
+ hci::ErrorCode::AUTHENTICATION_FAILURE,
+ hci::ErrorCode::PIN_OR_KEY_MISSING,
+ hci::ErrorCode::MEMORY_CAPACITY_EXCEEDED,
+ hci::ErrorCode::CONNECTION_TIMEOUT,
+ hci::ErrorCode::CONNECTION_LIMIT_EXCEEDED,
+ hci::ErrorCode::SYNCHRONOUS_CONNECTION_LIMIT_EXCEEDED,
+ hci::ErrorCode::CONNECTION_ALREADY_EXISTS,
+ hci::ErrorCode::COMMAND_DISALLOWED,
+ hci::ErrorCode::CONNECTION_REJECTED_LIMITED_RESOURCES,
+ hci::ErrorCode::CONNECTION_REJECTED_SECURITY_REASONS,
+ hci::ErrorCode::CONNECTION_REJECTED_UNACCEPTABLE_BD_ADDR,
+ hci::ErrorCode::CONNECTION_ACCEPT_TIMEOUT,
+ hci::ErrorCode::UNSUPPORTED_FEATURE_OR_PARAMETER_VALUE,
+ hci::ErrorCode::INVALID_HCI_COMMAND_PARAMETERS,
+ hci::ErrorCode::REMOTE_USER_TERMINATED_CONNECTION,
+ hci::ErrorCode::REMOTE_DEVICE_TERMINATED_CONNECTION_LOW_RESOURCES,
+ hci::ErrorCode::REMOTE_DEVICE_TERMINATED_CONNECTION_POWER_OFF,
+ hci::ErrorCode::CONNECTION_TERMINATED_BY_LOCAL_HOST,
+ hci::ErrorCode::REPEATED_ATTEMPTS,
+ hci::ErrorCode::PAIRING_NOT_ALLOWED,
+ hci::ErrorCode::UNKNOWN_LMP_PDU,
+ hci::ErrorCode::UNSUPPORTED_REMOTE_OR_LMP_FEATURE,
+ hci::ErrorCode::SCO_OFFSET_REJECTED,
+ hci::ErrorCode::SCO_INTERVAL_REJECTED,
+ hci::ErrorCode::SCO_AIR_MODE_REJECTED,
+ hci::ErrorCode::INVALID_LMP_OR_LL_PARAMETERS,
+ hci::ErrorCode::UNSPECIFIED_ERROR,
+ hci::ErrorCode::UNSUPPORTED_LMP_OR_LL_PARAMETER,
+ hci::ErrorCode::ROLE_CHANGE_NOT_ALLOWED,
+ hci::ErrorCode::TRANSACTION_RESPONSE_TIMEOUT,
+ hci::ErrorCode::LINK_LAYER_COLLISION,
+ hci::ErrorCode::ENCRYPTION_MODE_NOT_ACCEPTABLE,
+ hci::ErrorCode::ROLE_SWITCH_FAILED,
+ hci::ErrorCode::CONTROLLER_BUSY,
+ hci::ErrorCode::ADVERTISING_TIMEOUT,
+ hci::ErrorCode::CONNECTION_FAILED_ESTABLISHMENT,
+ hci::ErrorCode::LIMIT_REACHED,
+ hci::ErrorCode::STATUS_UNKNOWN,
+};
+
+// Generic template for all commands
+template <typename T, typename U>
+T CreateCommand(U u) {
+ T command;
+ return command;
+}
+
+template <>
+hci::DisconnectView CreateCommand(std::shared_ptr<std::vector<uint8_t>> bytes) {
+ return hci::DisconnectView::Create(
+ hci::AclCommandView::Create(hci::CommandView::Create(hci::PacketView<hci::kLittleEndian>(bytes))));
+}
+
+} // namespace
+
+class TestAclConnectionInterface : public hci::AclConnectionInterface {
+ private:
+ void EnqueueCommand(
+ std::unique_ptr<hci::AclCommandBuilder> command,
+ common::ContextualOnceCallback<void(hci::CommandStatusView)> on_status) override {
+ const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+ command_queue_.push(std::move(command));
+ command_status_callbacks.push_back(std::move(on_status));
+ if (command_promise_ != nullptr) {
+ command_promise_->set_value();
+ command_promise_.reset();
+ }
+ }
+
+ void EnqueueCommand(
+ std::unique_ptr<hci::AclCommandBuilder> command,
+ common::ContextualOnceCallback<void(hci::CommandCompleteView)> on_complete) override {
+ const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+ command_queue_.push(std::move(command));
+ command_complete_callbacks.push_back(std::move(on_complete));
+ if (command_promise_ != nullptr) {
+ command_promise_->set_value();
+ command_promise_.reset();
+ }
+ }
+
+ public:
+ virtual ~TestAclConnectionInterface() = default;
+
+ std::unique_ptr<hci::CommandBuilder> DequeueCommand() {
+ const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+ auto packet = std::move(command_queue_.front());
+ command_queue_.pop();
+ return std::move(packet);
+ }
+
+ std::shared_ptr<std::vector<uint8_t>> DequeueCommandBytes() {
+ auto command = DequeueCommand();
+ auto bytes = std::make_shared<std::vector<uint8_t>>();
+ packet::BitInserter bi(*bytes);
+ command->Serialize(bi);
+ return bytes;
+ }
+
+ bool IsPacketQueueEmpty() const {
+ const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+ return command_queue_.empty();
+ }
+
+ size_t NumberOfQueuedCommands() const {
+ const std::lock_guard<std::mutex> lock(command_queue_mutex_);
+ return command_queue_.size();
+ }
+
+ private:
+ std::list<common::ContextualOnceCallback<void(hci::CommandCompleteView)>> command_complete_callbacks;
+ std::list<common::ContextualOnceCallback<void(hci::CommandStatusView)>> command_status_callbacks;
+ std::queue<std::unique_ptr<hci::CommandBuilder>> command_queue_;
+ mutable std::mutex command_queue_mutex_;
+ std::unique_ptr<std::promise<void>> command_promise_;
+ std::unique_ptr<std::future<void>> command_future_;
+};
+
+class TestConnectionManagementCallbacks : public hci::acl_manager::ConnectionManagementCallbacks {
+ public:
+ ~TestConnectionManagementCallbacks() = default;
+ void OnConnectionPacketTypeChanged(uint16_t packet_type) override {}
+ void OnAuthenticationComplete(hci::ErrorCode hci_status) override {}
+ void OnEncryptionChange(hci::EncryptionEnabled enabled) override {}
+ void OnChangeConnectionLinkKeyComplete() override {}
+ void OnReadClockOffsetComplete(uint16_t clock_offset) override {}
+ void OnModeChange(hci::ErrorCode status, hci::Mode current_mode, uint16_t interval) override {}
+ void OnSniffSubrating(
+ hci::ErrorCode hci_status,
+ uint16_t maximum_transmit_latency,
+ uint16_t maximum_receive_latency,
+ uint16_t minimum_remote_timeout,
+ uint16_t minimum_local_timeout) override {}
+ void OnQosSetupComplete(
+ hci::ServiceType service_type,
+ uint32_t token_rate,
+ uint32_t peak_bandwidth,
+ uint32_t latency,
+ uint32_t delay_variation) override {}
+ void OnFlowSpecificationComplete(
+ hci::FlowDirection flow_direction,
+ hci::ServiceType service_type,
+ uint32_t token_rate,
+ uint32_t token_bucket_size,
+ uint32_t peak_bandwidth,
+ uint32_t access_latency) override {}
+ void OnFlushOccurred() override {}
+ void OnRoleDiscoveryComplete(hci::Role current_role) override {}
+ void OnReadLinkPolicySettingsComplete(uint16_t link_policy_settings) override {}
+ void OnReadAutomaticFlushTimeoutComplete(uint16_t flush_timeout) override {}
+ void OnReadTransmitPowerLevelComplete(uint8_t transmit_power_level) override {}
+ void OnReadLinkSupervisionTimeoutComplete(uint16_t link_supervision_timeout) override {}
+ void OnReadFailedContactCounterComplete(uint16_t failed_contact_counter) override {}
+ void OnReadLinkQualityComplete(uint8_t link_quality) override {}
+ void OnReadAfhChannelMapComplete(hci::AfhMode afh_mode, std::array<uint8_t, 10> afh_channel_map) override {}
+ void OnReadRssiComplete(uint8_t rssi) override {}
+ void OnReadClockComplete(uint32_t clock, uint16_t accuracy) override {}
+ void OnCentralLinkKeyComplete(hci::KeyFlag key_flag) override {}
+ void OnRoleChange(hci::ErrorCode hci_status, hci::Role new_role) override {}
+ void OnDisconnection(hci::ErrorCode reason) override {
+ on_disconnection_error_code_queue_.push(reason);
+ }
+ void OnReadRemoteVersionInformationComplete(
+ hci::ErrorCode hci_status, uint8_t lmp_version, uint16_t manufacturer_name, uint16_t sub_version) override {}
+ void OnReadRemoteSupportedFeaturesComplete(uint64_t features) override {}
+ void OnReadRemoteExtendedFeaturesComplete(uint8_t page_number, uint8_t max_page_number, uint64_t features) override {}
+
+ std::queue<hci::ErrorCode> on_disconnection_error_code_queue_;
+};
+
+namespace bluetooth {
+namespace hci {
+namespace acl_manager {
+
+class ClassicAclConnectionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ ASSERT_TRUE(hci::Address::FromString(kAddress, address_));
+ thread_ = new os::Thread("thread", os::Thread::Priority::NORMAL);
+ handler_ = new os::Handler(thread_);
+ queue_ = std::make_shared<hci::acl_manager::AclConnection::Queue>(kQueueSize);
+ sync_handler();
+ }
+
+ void TearDown() override {
+ handler_->Clear();
+ delete handler_;
+ delete thread_;
+ }
+
+ void sync_handler() {
+ ASSERT(handler_ != nullptr);
+
+ auto promise = std::promise<void>();
+ auto future = promise.get_future();
+ handler_->BindOnceOn(&promise, &std::promise<void>::set_value).Invoke();
+ auto status = future.wait_for(2s);
+ ASSERT_EQ(status, std::future_status::ready);
+ }
+
+ Address address_;
+ os::Handler* handler_{nullptr};
+ os::Thread* thread_{nullptr};
+ std::shared_ptr<hci::acl_manager::AclConnection::Queue> queue_;
+
+ TestAclConnectionInterface acl_connection_interface_;
+ TestConnectionManagementCallbacks callbacks_;
+};
+
+TEST_F(ClassicAclConnectionTest, simple) {
+ AclConnectionInterface* acl_connection_interface = nullptr;
+ ClassicAclConnection* connection =
+ new ClassicAclConnection(queue_, acl_connection_interface, kConnectionHandle, address_);
+ connection->RegisterCallbacks(&callbacks_, handler_);
+
+ delete connection;
+}
+
+class ClassicAclConnectionWithCallbacksTest : public ClassicAclConnectionTest {
+ protected:
+ void SetUp() override {
+ ClassicAclConnectionTest::SetUp();
+ connection_ =
+ std::make_unique<ClassicAclConnection>(queue_, &acl_connection_interface_, kConnectionHandle, address_);
+ connection_->RegisterCallbacks(&callbacks_, handler_);
+ is_callbacks_registered_ = true;
+ connection_management_callbacks_ =
+ connection_->GetEventCallbacks([this](uint16_t hci_handle) { is_callbacks_invalidated_ = true; });
+ is_callbacks_invalidated_ = false;
+ }
+
+ void TearDown() override {
+ connection_.reset();
+ ASSERT_TRUE(is_callbacks_invalidated_);
+ ClassicAclConnectionTest::TearDown();
+ }
+
+ protected:
+ std::unique_ptr<ClassicAclConnection> connection_;
+ ConnectionManagementCallbacks* connection_management_callbacks_;
+ bool is_callbacks_registered_{false};
+ bool is_callbacks_invalidated_{false};
+};
+
+TEST_F(ClassicAclConnectionWithCallbacksTest, Disconnect) {
+ for (const auto& reason : disconnect_reason_vector) {
+ ASSERT_TRUE(connection_->Disconnect(reason));
+ }
+
+ for (const auto& reason : disconnect_reason_vector) {
+ ASSERT_FALSE(acl_connection_interface_.IsPacketQueueEmpty());
+ auto command = CreateCommand<DisconnectView>(acl_connection_interface_.DequeueCommandBytes());
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(reason, command.GetReason());
+ ASSERT_EQ(kConnectionHandle, command.GetConnectionHandle());
+ }
+ ASSERT_TRUE(acl_connection_interface_.IsPacketQueueEmpty());
+}
+
+TEST_F(ClassicAclConnectionWithCallbacksTest, OnDisconnection) {
+ for (const auto& error_code : error_code_vector) {
+ connection_management_callbacks_->OnDisconnection(error_code);
+ }
+
+ sync_handler();
+ ASSERT_TRUE(!callbacks_.on_disconnection_error_code_queue_.empty());
+
+ for (const auto& error_code : error_code_vector) {
+ ASSERT_EQ(error_code, callbacks_.on_disconnection_error_code_queue_.front());
+ callbacks_.on_disconnection_error_code_queue_.pop();
+ }
+}
+
+} // namespace acl_manager
+} // namespace hci
+} // namespace bluetooth
diff --git a/system/gd/hci/acl_manager/le_impl_test.cc b/system/gd/hci/acl_manager/le_impl_test.cc
index 02129e8dae..e1238d49d6 100644
--- a/system/gd/hci/acl_manager/le_impl_test.cc
+++ b/system/gd/hci/acl_manager/le_impl_test.cc
@@ -47,17 +47,31 @@ using ::bluetooth::packet::BitInserter;
using ::bluetooth::packet::RawBuilder;
using ::bluetooth::testing::LogCapture;
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::SaveArg;
+
namespace {
+constexpr bool kCrashOnUnknownHandle = true;
constexpr char kFixedAddress[] = "c0:aa:bb:cc:dd:ee";
+constexpr char kLocalRandomAddress[] = "04:c0:aa:bb:cc:dd:ee";
+constexpr char kRemoteRandomAddress[] = "04:11:22:33:44:55";
constexpr char kRemoteAddress[] = "00:11:22:33:44:55";
+constexpr uint16_t kHciHandle = 123;
[[maybe_unused]] constexpr bool kAddToFilterAcceptList = true;
[[maybe_unused]] constexpr bool kSkipFilterAcceptList = !kAddToFilterAcceptList;
[[maybe_unused]] constexpr bool kIsDirectConnection = true;
[[maybe_unused]] constexpr bool kIsBackgroundConnection = !kIsDirectConnection;
-[[maybe_unused]] constexpr ::bluetooth::crypto_toolbox::Octet16 kRotationIrk = {};
-[[maybe_unused]] constexpr std::chrono::milliseconds kMinimumRotationTime(14 * 1000);
-[[maybe_unused]] constexpr std::chrono::milliseconds kMaximumRotationTime(16 * 1000);
-[[maybe_unused]] constexpr std::array<uint8_t, 16> kPeerIdentityResolvingKey({
+constexpr crypto_toolbox::Octet16 kRotationIrk = {};
+constexpr std::chrono::milliseconds kMinimumRotationTime(14 * 1000);
+constexpr std::chrono::milliseconds kMaximumRotationTime(16 * 1000);
+constexpr uint16_t kIntervalMax = 0x40;
+constexpr uint16_t kIntervalMin = 0x20;
+constexpr uint16_t kLatency = 0x60;
+constexpr uint16_t kLength = 0x5678;
+constexpr uint16_t kTime = 0x1234;
+constexpr uint16_t kTimeout = 0x80;
+constexpr std::array<uint8_t, 16> kPeerIdentityResolvingKey({
0x00,
0x01,
0x02,
@@ -75,7 +89,7 @@ constexpr char kRemoteAddress[] = "00:11:22:33:44:55";
0x0e,
0x0f,
});
-[[maybe_unused]] constexpr std::array<uint8_t, 16> kLocalIdentityResolvingKey({
+constexpr std::array<uint8_t, 16> kLocalIdentityResolvingKey({
0x80,
0x81,
0x82,
@@ -94,47 +108,51 @@ constexpr char kRemoteAddress[] = "00:11:22:33:44:55";
0x8f,
});
-// Generic template for all commands
-template <typename T, typename U>
-T CreateCommand(U u) {
- T command;
- return command;
+template <typename B>
+std::shared_ptr<std::vector<uint8_t>> Serialize(std::unique_ptr<B> build) {
+ auto bytes = std::make_shared<std::vector<uint8_t>>();
+ BitInserter bi(*bytes);
+ build->Serialize(bi);
+ return bytes;
+}
+
+template <typename T>
+T CreateCommandView(std::shared_ptr<std::vector<uint8_t>> bytes) {
+ return T::Create(hci::CommandView::Create(hci::PacketView<hci::kLittleEndian>(bytes)));
}
-template <>
-[[maybe_unused]] hci::LeSetAddressResolutionEnableView CreateCommand(std::shared_ptr<std::vector<uint8_t>> bytes) {
- return hci::LeSetAddressResolutionEnableView::Create(
- hci::LeSecurityCommandView::Create(hci::CommandView::Create(hci::PacketView<hci::kLittleEndian>(bytes))));
+template <typename T>
+T CreateAclCommandView(std::shared_ptr<std::vector<uint8_t>> bytes) {
+ return T::Create(CreateCommandView<hci::AclCommandView>(bytes));
}
-template <>
-[[maybe_unused]] hci::LeAddDeviceToResolvingListView CreateCommand(std::shared_ptr<std::vector<uint8_t>> bytes) {
- return hci::LeAddDeviceToResolvingListView::Create(
- hci::LeSecurityCommandView::Create(hci::CommandView::Create(hci::PacketView<hci::kLittleEndian>(bytes))));
+template <typename T>
+T CreateLeConnectionManagementCommandView(std::shared_ptr<std::vector<uint8_t>> bytes) {
+ return T::Create(CreateAclCommandView<hci::LeConnectionManagementCommandView>(bytes));
}
-template <>
-[[maybe_unused]] hci::LeSetPrivacyModeView CreateCommand(std::shared_ptr<std::vector<uint8_t>> bytes) {
- return hci::LeSetPrivacyModeView::Create(
- hci::LeSecurityCommandView::Create(hci::CommandView::Create(hci::PacketView<hci::kLittleEndian>(bytes))));
+template <typename T>
+T CreateLeSecurityCommandView(std::shared_ptr<std::vector<uint8_t>> bytes) {
+ return T::Create(CreateCommandView<hci::LeSecurityCommandView>(bytes));
+}
+
+template <typename T>
+T CreateLeEventView(std::shared_ptr<std::vector<uint8_t>> bytes) {
+ return T::Create(hci::LeMetaEventView::Create(hci::EventView::Create(hci::PacketView<hci::kLittleEndian>(bytes))));
}
[[maybe_unused]] hci::CommandCompleteView ReturnCommandComplete(hci::OpCode op_code, hci::ErrorCode error_code) {
std::vector<uint8_t> success_vector{static_cast<uint8_t>(error_code)};
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter bi(*bytes);
auto builder = hci::CommandCompleteBuilder::Create(uint8_t{1}, op_code, std::make_unique<RawBuilder>(success_vector));
- builder->Serialize(bi);
+ auto bytes = Serialize<hci::CommandCompleteBuilder>(std::move(builder));
return hci::CommandCompleteView::Create(hci::EventView::Create(hci::PacketView<hci::kLittleEndian>(bytes)));
}
[[maybe_unused]] hci::CommandStatusView ReturnCommandStatus(hci::OpCode op_code, hci::ErrorCode error_code) {
std::vector<uint8_t> success_vector{static_cast<uint8_t>(error_code)};
- auto bytes = std::make_shared<std::vector<uint8_t>>();
- BitInserter bi(*bytes);
auto builder = hci::CommandStatusBuilder::Create(
hci::ErrorCode::SUCCESS, uint8_t{1}, op_code, std::make_unique<RawBuilder>(success_vector));
- builder->Serialize(bi);
+ auto bytes = Serialize<hci::CommandStatusBuilder>(std::move(builder));
return hci::CommandStatusView::Create(hci::EventView::Create(hci::PacketView<hci::kLittleEndian>(bytes)));
}
@@ -364,17 +382,46 @@ class TestHciLayer : public HciLayer {
CommandInterfaceImpl<AclCommandBuilder> le_acl_connection_manager_interface_{*this};
};
-class LeConnectionCallbacksTest : public LeConnectionCallbacks {
+class MockLeConnectionCallbacks : public LeConnectionCallbacks {
public:
- virtual ~LeConnectionCallbacksTest() = default;
- virtual void OnLeConnectSuccess(
- AddressWithType address_with_type, std::unique_ptr<LeAclConnection> connection) override {}
- virtual void OnLeConnectFail(AddressWithType address_with_type, ErrorCode reason) override {}
+ MOCK_METHOD(
+ void,
+ OnLeConnectSuccess,
+ (AddressWithType address_with_type, std::unique_ptr<LeAclConnection> connection),
+ (override));
+ MOCK_METHOD(void, OnLeConnectFail, (AddressWithType address_with_type, ErrorCode reason), (override));
+};
+
+class MockLeConnectionManagementCallbacks : public LeConnectionManagementCallbacks {
+ public:
+ MOCK_METHOD(
+ void,
+ OnConnectionUpdate,
+ (hci::ErrorCode hci_status,
+ uint16_t connection_interval,
+ uint16_t connection_latency,
+ uint16_t supervision_timeout),
+ (override));
+ MOCK_METHOD(
+ void,
+ OnDataLengthChange,
+ (uint16_t tx_octets, uint16_t tx_time, uint16_t rx_octets, uint16_t rx_time),
+ (override));
+ MOCK_METHOD(void, OnDisconnection, (ErrorCode reason), (override));
+ MOCK_METHOD(
+ void,
+ OnReadRemoteVersionInformationComplete,
+ (hci::ErrorCode hci_status, uint8_t lmp_version, uint16_t manufacturer_name, uint16_t sub_version),
+ (override));
+ MOCK_METHOD(void, OnLeReadRemoteFeaturesComplete, (hci::ErrorCode hci_status, uint64_t features), (override));
+ MOCK_METHOD(void, OnPhyUpdate, (hci::ErrorCode hci_status, uint8_t tx_phy, uint8_t rx_phy), (override));
+ MOCK_METHOD(void, OnLocalAddressUpdate, (AddressWithType address_with_type), (override));
};
class LeImplTest : public ::testing::Test {
protected:
void SetUp() override {
+ bluetooth::common::InitFlags::SetAllForTesting();
thread_ = new Thread("thread", Thread::Priority::NORMAL);
handler_ = new Handler(thread_);
controller_ = new TestController();
@@ -383,15 +430,18 @@ class LeImplTest : public ::testing::Test {
round_robin_scheduler_ = new RoundRobinScheduler(handler_, controller_, hci_queue_.GetUpEnd());
hci_queue_.GetDownEnd()->RegisterDequeue(
handler_, common::Bind(&LeImplTest::HciDownEndDequeue, common::Unretained(this)));
- le_impl_ = new le_impl(hci_layer_, controller_, handler_, round_robin_scheduler_, true);
+ le_impl_ = new le_impl(hci_layer_, controller_, handler_, round_robin_scheduler_, kCrashOnUnknownHandle);
le_impl_->handle_register_le_callbacks(&mock_le_connection_callbacks_, handler_);
Address address;
Address::FromString(kFixedAddress, address);
fixed_address_ = AddressWithType(address, AddressType::PUBLIC_DEVICE_ADDRESS);
- Address::FromString(kRemoteAddress, address);
- remote_public_address_ = AddressWithType(address, AddressType::PUBLIC_DEVICE_ADDRESS);
+ Address::FromString(kRemoteAddress, remote_address_);
+ remote_public_address_with_type_ = AddressWithType(remote_address_, AddressType::PUBLIC_DEVICE_ADDRESS);
+
+ Address::FromString(kLocalRandomAddress, local_rpa_);
+ Address::FromString(kRemoteRandomAddress, remote_rpa_);
}
void set_random_device_address_policy() {
@@ -464,16 +514,6 @@ class LeImplTest : public ::testing::Test {
}
}
- class MockLeConnectionCallbacks : public LeConnectionCallbacks {
- public:
- MOCK_METHOD(
- void,
- OnLeConnectSuccess,
- (AddressWithType address_with_type, std::unique_ptr<LeAclConnection> connection),
- (override));
- MOCK_METHOD(void, OnLeConnectFail, (AddressWithType, ErrorCode reason), (override));
- } mock_le_connection_callbacks_;
-
protected:
void set_privacy_policy_for_initiator_address(
const AddressWithType& address, const LeAddressManager::AddressPolicy& policy) {
@@ -481,8 +521,12 @@ class LeImplTest : public ::testing::Test {
policy, address, kRotationIrk, kMinimumRotationTime, kMaximumRotationTime);
}
+ Address remote_address_;
AddressWithType fixed_address_;
AddressWithType remote_public_address_;
+ Address local_rpa_;
+ Address remote_rpa_;
+ AddressWithType remote_public_address_with_type_;
uint16_t packet_count_;
std::unique_ptr<std::promise<void>> packet_promise_;
@@ -497,15 +541,22 @@ class LeImplTest : public ::testing::Test {
TestController* controller_;
RoundRobinScheduler* round_robin_scheduler_{nullptr};
- LeConnectionCallbacksTest connection_callbacks_;
+ MockLeConnectionCallbacks mock_le_connection_callbacks_;
+ MockLeConnectionManagementCallbacks connection_management_callbacks_;
+
struct le_impl* le_impl_;
};
-class LeImplWithCallbacksTest : public LeImplTest {
+class LeImplRegisteredWithAddressManagerTest : public LeImplTest {
protected:
void SetUp() override {
LeImplTest::SetUp();
- le_impl_->handle_register_le_callbacks(&connection_callbacks_, handler_);
+ set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_PUBLIC_ADDRESS);
+
+ le_impl_->register_with_address_manager();
+ sync_handler(); // Let |LeAddressManager::register_client| execute on handler
+ ASSERT_TRUE(le_impl_->address_manager_registered);
+ ASSERT_TRUE(le_impl_->pause_connection);
}
void TearDown() override {
@@ -513,7 +564,47 @@ class LeImplWithCallbacksTest : public LeImplTest {
}
};
-TEST_F(LeImplTest, nop) {}
+class LeImplWithConnectionTest : public LeImplTest {
+ protected:
+ void SetUp() override {
+ LeImplTest::SetUp();
+
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _))
+ .WillOnce([&](AddressWithType addr, std::unique_ptr<LeAclConnection> conn) {
+ remote_address_with_type_ = addr;
+ connection_ = std::move(conn);
+ connection_->RegisterCallbacks(&connection_management_callbacks_, handler_);
+ });
+
+ auto command = LeEnhancedConnectionCompleteBuilder::Create(
+ ErrorCode::SUCCESS,
+ kHciHandle,
+ Role::PERIPHERAL,
+ AddressType::PUBLIC_DEVICE_ADDRESS,
+ remote_address_,
+ local_rpa_,
+ remote_rpa_,
+ 0x0024,
+ 0x0000,
+ 0x0011,
+ ClockAccuracy::PPM_30);
+ auto bytes = Serialize<LeEnhancedConnectionCompleteBuilder>(std::move(command));
+ auto view = CreateLeEventView<hci::LeEnhancedConnectionCompleteView>(bytes);
+ ASSERT_TRUE(view.IsValid());
+ le_impl_->on_le_event(view);
+
+ sync_handler();
+ ASSERT_EQ(remote_public_address_with_type_, remote_address_with_type_);
+ }
+
+ void TearDown() override {
+ connection_.reset();
+ LeImplTest::TearDown();
+ }
+
+ AddressWithType remote_address_with_type_;
+ std::unique_ptr<LeAclConnection> connection_;
+};
TEST_F(LeImplTest, add_device_to_connect_list) {
le_impl_->add_device_to_connect_list({{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}, AddressType::PUBLIC_DEVICE_ADDRESS});
@@ -574,7 +665,7 @@ TEST_F(LeImplTest, connection_complete_with_periperal_role) {
hci::Address remote_address;
Address::FromString("D0:05:04:03:02:01", remote_address);
hci::AddressWithType address_with_type(remote_address, hci::AddressType::PUBLIC_DEVICE_ADDRESS);
- EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, ::testing::_));
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, _));
hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
0x0041,
@@ -613,7 +704,7 @@ TEST_F(LeImplTest, enhanced_connection_complete_with_periperal_role) {
hci::Address remote_address;
Address::FromString("D0:05:04:03:02:01", remote_address);
hci::AddressWithType address_with_type(remote_address, hci::AddressType::PUBLIC_DEVICE_ADDRESS);
- EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, ::testing::_));
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, _));
hci_layer_->IncomingLeMetaEvent(LeEnhancedConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
0x0041,
@@ -652,7 +743,7 @@ TEST_F(LeImplTest, connection_complete_with_central_role) {
ASSERT_EQ(ConnectabilityState::ARMED, le_impl_->connectability_state_);
// Receive connection complete of outgoing connection (Role::CENTRAL)
- EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, ::testing::_));
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, _));
hci_layer_->IncomingLeMetaEvent(LeConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
0x0041,
@@ -690,7 +781,7 @@ TEST_F(LeImplTest, enhanced_connection_complete_with_central_role) {
ASSERT_EQ(ConnectabilityState::ARMED, le_impl_->connectability_state_);
// Receive connection complete of outgoing connection (Role::CENTRAL)
- EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, ::testing::_));
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(address_with_type, _));
hci_layer_->IncomingLeMetaEvent(LeEnhancedConnectionCompleteBuilder::Create(
ErrorCode::SUCCESS,
0x0041,
@@ -710,7 +801,6 @@ TEST_F(LeImplTest, enhanced_connection_complete_with_central_role) {
}
TEST_F(LeImplTest, register_with_address_manager__AddressPolicyNotSet) {
- bluetooth::common::InitFlags::SetAllForTesting();
auto log_capture = std::make_unique<LogCapture>();
std::promise<void> promise;
@@ -763,7 +853,6 @@ TEST_F(LeImplTest, register_with_address_manager__AddressPolicyNotSet) {
}
TEST_F(LeImplTest, disarm_connectability_DISARMED) {
- bluetooth::common::InitFlags::SetAllForTesting();
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::DISARMED;
@@ -777,7 +866,6 @@ TEST_F(LeImplTest, disarm_connectability_DISARMED) {
}
TEST_F(LeImplTest, disarm_connectability_DISARMED_extended) {
- bluetooth::common::InitFlags::SetAllForTesting();
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::DISARMED;
@@ -792,7 +880,6 @@ TEST_F(LeImplTest, disarm_connectability_DISARMED_extended) {
}
TEST_F(LeImplTest, disarm_connectability_ARMING) {
- bluetooth::common::InitFlags::SetAllForTesting();
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::ARMING;
@@ -805,7 +892,6 @@ TEST_F(LeImplTest, disarm_connectability_ARMING) {
}
TEST_F(LeImplTest, disarm_connectability_ARMING_extended) {
- bluetooth::common::InitFlags::SetAllForTesting();
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::ARMING;
@@ -820,7 +906,6 @@ TEST_F(LeImplTest, disarm_connectability_ARMING_extended) {
}
TEST_F(LeImplTest, disarm_connectability_ARMED) {
- bluetooth::common::InitFlags::SetAllForTesting();
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::ARMED;
@@ -834,7 +919,6 @@ TEST_F(LeImplTest, disarm_connectability_ARMED) {
}
TEST_F(LeImplTest, disarm_connectability_ARMED_extended) {
- bluetooth::common::InitFlags::SetAllForTesting();
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::ARMED;
@@ -849,7 +933,6 @@ TEST_F(LeImplTest, disarm_connectability_ARMED_extended) {
}
TEST_F(LeImplTest, disarm_connectability_DISARMING) {
- bluetooth::common::InitFlags::SetAllForTesting();
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::DISARMING;
@@ -863,7 +946,6 @@ TEST_F(LeImplTest, disarm_connectability_DISARMING) {
}
TEST_F(LeImplTest, disarm_connectability_DISARMING_extended) {
- bluetooth::common::InitFlags::SetAllForTesting();
std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
le_impl_->connectability_state_ = ConnectabilityState::DISARMING;
@@ -877,6 +959,503 @@ TEST_F(LeImplTest, disarm_connectability_DISARMING_extended) {
ASSERT_TRUE(log_capture->Rewind()->Find("in unexpected state:ConnectabilityState::DISARMING"));
}
+TEST_F(LeImplTest, register_with_address_manager__AddressPolicyPublicAddress) {
+ std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
+
+ set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_PUBLIC_ADDRESS);
+
+ le_impl_->register_with_address_manager();
+ sync_handler(); // Let |eAddressManager::register_client| execute on handler
+ ASSERT_TRUE(le_impl_->address_manager_registered);
+ ASSERT_TRUE(le_impl_->pause_connection);
+
+ le_impl_->ready_to_unregister = true;
+
+ le_impl_->check_for_unregister();
+ sync_handler(); // Let |LeAddressManager::unregister_client| execute on handler
+ ASSERT_FALSE(le_impl_->address_manager_registered);
+ ASSERT_FALSE(le_impl_->pause_connection);
+
+ ASSERT_TRUE(log_capture->Rewind()->Find("SetPrivacyPolicyForInitiatorAddress with policy 1"));
+ ASSERT_TRUE(log_capture->Rewind()->Find("Client unregistered"));
+}
+
+TEST_F(LeImplTest, register_with_address_manager__AddressPolicyStaticAddress) {
+ std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
+
+ set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_STATIC_ADDRESS);
+
+ le_impl_->register_with_address_manager();
+ sync_handler(); // Let |LeAddressManager::register_client| execute on handler
+ ASSERT_TRUE(le_impl_->address_manager_registered);
+ ASSERT_TRUE(le_impl_->pause_connection);
+
+ le_impl_->ready_to_unregister = true;
+
+ le_impl_->check_for_unregister();
+ sync_handler(); // Let |LeAddressManager::unregister_client| execute on handler
+ ASSERT_FALSE(le_impl_->address_manager_registered);
+ ASSERT_FALSE(le_impl_->pause_connection);
+
+ ASSERT_TRUE(log_capture->Rewind()->Find("SetPrivacyPolicyForInitiatorAddress with policy 2"));
+ ASSERT_TRUE(log_capture->Rewind()->Find("Client unregistered"));
+}
+
+TEST_F(LeImplTest, register_with_address_manager__AddressPolicyNonResolvableAddress) {
+ std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
+
+ set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_NON_RESOLVABLE_ADDRESS);
+
+ le_impl_->register_with_address_manager();
+ sync_handler(); // Let |LeAddressManager::register_client| execute on handler
+ ASSERT_TRUE(le_impl_->address_manager_registered);
+ ASSERT_TRUE(le_impl_->pause_connection);
+
+ le_impl_->ready_to_unregister = true;
+
+ le_impl_->check_for_unregister();
+ sync_handler(); // Let |LeAddressManager::unregister_client| execute on handler
+ ASSERT_FALSE(le_impl_->address_manager_registered);
+ ASSERT_FALSE(le_impl_->pause_connection);
+
+ ASSERT_TRUE(log_capture->Rewind()->Find("SetPrivacyPolicyForInitiatorAddress with policy 3"));
+ ASSERT_TRUE(log_capture->Rewind()->Find("Client unregistered"));
+}
+
+TEST_F(LeImplTest, register_with_address_manager__AddressPolicyResolvableAddress) {
+ std::unique_ptr<LogCapture> log_capture = std::make_unique<LogCapture>();
+
+ set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_RESOLVABLE_ADDRESS);
+
+ le_impl_->register_with_address_manager();
+ sync_handler(); // Let |LeAddressManager::register_client| execute on handler
+ ASSERT_TRUE(le_impl_->address_manager_registered);
+ ASSERT_TRUE(le_impl_->pause_connection);
+
+ le_impl_->ready_to_unregister = true;
+
+ le_impl_->check_for_unregister();
+ sync_handler(); // Let |LeAddressManager::unregister_client| execute on handler
+ ASSERT_FALSE(le_impl_->address_manager_registered);
+ ASSERT_FALSE(le_impl_->pause_connection);
+
+ ASSERT_TRUE(log_capture->Rewind()->Find("SetPrivacyPolicyForInitiatorAddress with policy 4"));
+ ASSERT_TRUE(log_capture->Rewind()->Find("Client unregistered"));
+}
+
+TEST_F(LeImplTest, add_device_to_resolving_list) {
+ // Some kind of privacy policy must be set for LeAddressManager to operate properly
+ set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_PUBLIC_ADDRESS);
+ // Let LeAddressManager::resume_registered_clients execute
+ sync_handler();
+
+ ASSERT_EQ(0UL, hci_layer_->NumberOfQueuedCommands());
+
+ // le_impl should not be registered with address manager
+ ASSERT_FALSE(le_impl_->address_manager_registered);
+ ASSERT_FALSE(le_impl_->pause_connection);
+
+ ASSERT_EQ(0UL, le_impl_->le_address_manager_->NumberCachedCommands());
+ // Acknowledge that the le_impl has quiesced all relevant controller state
+ le_impl_->add_device_to_resolving_list(
+ remote_public_address_with_type_, kPeerIdentityResolvingKey, kLocalIdentityResolvingKey);
+ ASSERT_EQ(3UL, le_impl_->le_address_manager_->NumberCachedCommands());
+
+ sync_handler(); // Let |LeAddressManager::register_client| execute on handler
+ ASSERT_TRUE(le_impl_->address_manager_registered);
+ ASSERT_TRUE(le_impl_->pause_connection);
+
+ le_impl_->le_address_manager_->AckPause(le_impl_);
+ sync_handler(); // Allow |LeAddressManager::ack_pause| to complete
+
+ ASSERT_FALSE(hci_layer_->IsPacketQueueEmpty());
+ {
+ // Inform controller to disable address resolution
+ auto command = CreateLeSecurityCommandView<LeSetAddressResolutionEnableView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(Enable::DISABLED, command.GetAddressResolutionEnable());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE, ErrorCode::SUCCESS));
+ }
+ sync_handler(); // |LeAddressManager::check_cached_commands|
+
+ ASSERT_FALSE(hci_layer_->IsPacketQueueEmpty());
+ {
+ auto command = CreateLeSecurityCommandView<LeAddDeviceToResolvingListView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(PeerAddressType::PUBLIC_DEVICE_OR_IDENTITY_ADDRESS, command.GetPeerIdentityAddressType());
+ ASSERT_EQ(remote_public_address_with_type_.GetAddress(), command.GetPeerIdentityAddress());
+ ASSERT_EQ(kPeerIdentityResolvingKey, command.GetPeerIrk());
+ ASSERT_EQ(kLocalIdentityResolvingKey, command.GetLocalIrk());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_ADD_DEVICE_TO_RESOLVING_LIST, ErrorCode::SUCCESS));
+ }
+ sync_handler(); // |LeAddressManager::check_cached_commands|
+
+ ASSERT_FALSE(hci_layer_->IsPacketQueueEmpty());
+ {
+ auto command = CreateLeSecurityCommandView<LeSetAddressResolutionEnableView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(Enable::ENABLED, command.GetAddressResolutionEnable());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE, ErrorCode::SUCCESS));
+ }
+ sync_handler(); // |LeAddressManager::check_cached_commands|
+
+ ASSERT_TRUE(hci_layer_->IsPacketQueueEmpty());
+ ASSERT_TRUE(le_impl_->address_manager_registered);
+
+ le_impl_->ready_to_unregister = true;
+
+ le_impl_->check_for_unregister();
+ sync_handler();
+ ASSERT_FALSE(le_impl_->address_manager_registered);
+ ASSERT_FALSE(le_impl_->pause_connection);
+}
+
+TEST_F(LeImplTest, add_device_to_resolving_list__SupportsBlePrivacy) {
+ controller_->supports_ble_privacy_ = true;
+
+ // Some kind of privacy policy must be set for LeAddressManager to operate properly
+ set_privacy_policy_for_initiator_address(fixed_address_, LeAddressManager::AddressPolicy::USE_PUBLIC_ADDRESS);
+ // Let LeAddressManager::resume_registered_clients execute
+ sync_handler();
+
+ ASSERT_EQ(0UL, hci_layer_->NumberOfQueuedCommands());
+
+ // le_impl should not be registered with address manager
+ ASSERT_FALSE(le_impl_->address_manager_registered);
+ ASSERT_FALSE(le_impl_->pause_connection);
+
+ ASSERT_EQ(0UL, le_impl_->le_address_manager_->NumberCachedCommands());
+ // Acknowledge that the le_impl has quiesced all relevant controller state
+ le_impl_->add_device_to_resolving_list(
+ remote_public_address_with_type_, kPeerIdentityResolvingKey, kLocalIdentityResolvingKey);
+ ASSERT_EQ(4UL, le_impl_->le_address_manager_->NumberCachedCommands());
+
+ sync_handler(); // Let |LeAddressManager::register_client| execute on handler
+ ASSERT_TRUE(le_impl_->address_manager_registered);
+ ASSERT_TRUE(le_impl_->pause_connection);
+
+ le_impl_->le_address_manager_->AckPause(le_impl_);
+ sync_handler(); // Allow |LeAddressManager::ack_pause| to complete
+
+ ASSERT_FALSE(hci_layer_->IsPacketQueueEmpty());
+ {
+ // Inform controller to disable address resolution
+ auto command = CreateLeSecurityCommandView<LeSetAddressResolutionEnableView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(Enable::DISABLED, command.GetAddressResolutionEnable());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE, ErrorCode::SUCCESS));
+ }
+ sync_handler(); // |LeAddressManager::check_cached_commands|
+
+ ASSERT_FALSE(hci_layer_->IsPacketQueueEmpty());
+ {
+ auto command = CreateLeSecurityCommandView<LeAddDeviceToResolvingListView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(PeerAddressType::PUBLIC_DEVICE_OR_IDENTITY_ADDRESS, command.GetPeerIdentityAddressType());
+ ASSERT_EQ(remote_public_address_with_type_.GetAddress(), command.GetPeerIdentityAddress());
+ ASSERT_EQ(kPeerIdentityResolvingKey, command.GetPeerIrk());
+ ASSERT_EQ(kLocalIdentityResolvingKey, command.GetLocalIrk());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_ADD_DEVICE_TO_RESOLVING_LIST, ErrorCode::SUCCESS));
+ }
+ sync_handler(); // |LeAddressManager::check_cached_commands|
+
+ ASSERT_FALSE(hci_layer_->IsPacketQueueEmpty());
+ {
+ auto command = CreateLeSecurityCommandView<LeSetPrivacyModeView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(PrivacyMode::DEVICE, command.GetPrivacyMode());
+ ASSERT_EQ(remote_public_address_with_type_.GetAddress(), command.GetPeerIdentityAddress());
+ ASSERT_EQ(PeerAddressType::PUBLIC_DEVICE_OR_IDENTITY_ADDRESS, command.GetPeerIdentityAddressType());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_SET_PRIVACY_MODE, ErrorCode::SUCCESS));
+ }
+ sync_handler(); // |LeAddressManager::check_cached_commands|
+
+ ASSERT_FALSE(hci_layer_->IsPacketQueueEmpty());
+ {
+ auto command = CreateLeSecurityCommandView<LeSetAddressResolutionEnableView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(command.IsValid());
+ ASSERT_EQ(Enable::ENABLED, command.GetAddressResolutionEnable());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE, ErrorCode::SUCCESS));
+ }
+ sync_handler(); // |LeAddressManager::check_cached_commands|
+
+ ASSERT_TRUE(hci_layer_->IsPacketQueueEmpty());
+ ASSERT_TRUE(le_impl_->address_manager_registered);
+
+ le_impl_->ready_to_unregister = true;
+
+ le_impl_->check_for_unregister();
+ sync_handler();
+ ASSERT_FALSE(le_impl_->address_manager_registered);
+ ASSERT_FALSE(le_impl_->pause_connection);
+}
+
+TEST_F(LeImplTest, connectability_state_machine_text) {
+ ASSERT_STREQ(
+ "ConnectabilityState::DISARMED", connectability_state_machine_text(ConnectabilityState::DISARMED).c_str());
+ ASSERT_STREQ("ConnectabilityState::ARMING", connectability_state_machine_text(ConnectabilityState::ARMING).c_str());
+ ASSERT_STREQ("ConnectabilityState::ARMED", connectability_state_machine_text(ConnectabilityState::ARMED).c_str());
+ ASSERT_STREQ(
+ "ConnectabilityState::DISARMING", connectability_state_machine_text(ConnectabilityState::DISARMING).c_str());
+}
+
+TEST_F(LeImplTest, on_le_event__CONNECTION_COMPLETE_CENTRAL) {
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _)).Times(1);
+ set_random_device_address_policy();
+ auto command = LeConnectionCompleteBuilder::Create(
+ ErrorCode::SUCCESS,
+ kHciHandle,
+ Role::CENTRAL,
+ AddressType::PUBLIC_DEVICE_ADDRESS,
+ remote_address_,
+ 0x0024,
+ 0x0000,
+ 0x0011,
+ ClockAccuracy::PPM_30);
+ auto bytes = Serialize<LeConnectionCompleteBuilder>(std::move(command));
+ auto view = CreateLeEventView<hci::LeConnectionCompleteView>(bytes);
+ ASSERT_TRUE(view.IsValid());
+ le_impl_->on_le_event(view);
+}
+
+TEST_F(LeImplTest, on_le_event__CONNECTION_COMPLETE_PERIPHERAL) {
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _)).Times(1);
+ set_random_device_address_policy();
+ auto command = LeConnectionCompleteBuilder::Create(
+ ErrorCode::SUCCESS,
+ kHciHandle,
+ Role::PERIPHERAL,
+ AddressType::PUBLIC_DEVICE_ADDRESS,
+ remote_address_,
+ 0x0024,
+ 0x0000,
+ 0x0011,
+ ClockAccuracy::PPM_30);
+ auto bytes = Serialize<LeConnectionCompleteBuilder>(std::move(command));
+ auto view = CreateLeEventView<hci::LeConnectionCompleteView>(bytes);
+ ASSERT_TRUE(view.IsValid());
+ le_impl_->on_le_event(view);
+}
+
+TEST_F(LeImplTest, on_le_event__ENHANCED_CONNECTION_COMPLETE_CENTRAL) {
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _)).Times(1);
+ set_random_device_address_policy();
+ auto command = LeEnhancedConnectionCompleteBuilder::Create(
+ ErrorCode::SUCCESS,
+ kHciHandle,
+ Role::CENTRAL,
+ AddressType::PUBLIC_DEVICE_ADDRESS,
+ remote_address_,
+ local_rpa_,
+ remote_rpa_,
+ 0x0024,
+ 0x0000,
+ 0x0011,
+ ClockAccuracy::PPM_30);
+ auto bytes = Serialize<LeEnhancedConnectionCompleteBuilder>(std::move(command));
+ auto view = CreateLeEventView<hci::LeEnhancedConnectionCompleteView>(bytes);
+ ASSERT_TRUE(view.IsValid());
+ le_impl_->on_le_event(view);
+}
+
+TEST_F(LeImplTest, on_le_event__ENHANCED_CONNECTION_COMPLETE_PERIPHERAL) {
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectSuccess(_, _)).Times(1);
+ auto command = LeEnhancedConnectionCompleteBuilder::Create(
+ ErrorCode::SUCCESS,
+ kHciHandle,
+ Role::PERIPHERAL,
+ AddressType::PUBLIC_DEVICE_ADDRESS,
+ remote_address_,
+ local_rpa_,
+ remote_rpa_,
+ 0x0024,
+ 0x0000,
+ 0x0011,
+ ClockAccuracy::PPM_30);
+ auto bytes = Serialize<LeEnhancedConnectionCompleteBuilder>(std::move(command));
+ auto view = CreateLeEventView<hci::LeEnhancedConnectionCompleteView>(bytes);
+ ASSERT_TRUE(view.IsValid());
+ le_impl_->on_le_event(view);
+}
+
+TEST_F(LeImplWithConnectionTest, on_le_event__PHY_UPDATE_COMPLETE) {
+ hci::ErrorCode hci_status{ErrorCode::STATUS_UNKNOWN};
+ hci::PhyType tx_phy{0};
+ hci::PhyType rx_phy{0};
+
+ // Send a phy update
+ {
+ EXPECT_CALL(connection_management_callbacks_, OnPhyUpdate(_, _, _))
+ .WillOnce([&](hci::ErrorCode _hci_status, uint8_t _tx_phy, uint8_t _rx_phy) {
+ hci_status = _hci_status;
+ tx_phy = static_cast<PhyType>(_tx_phy);
+ rx_phy = static_cast<PhyType>(_rx_phy);
+ });
+ auto command = LePhyUpdateCompleteBuilder::Create(ErrorCode::SUCCESS, kHciHandle, 0x01, 0x02);
+ auto bytes = Serialize<LePhyUpdateCompleteBuilder>(std::move(command));
+ auto view = CreateLeEventView<hci::LePhyUpdateCompleteView>(bytes);
+ ASSERT_TRUE(view.IsValid());
+ le_impl_->on_le_event(view);
+ }
+
+ sync_handler();
+ ASSERT_EQ(ErrorCode::SUCCESS, hci_status);
+ ASSERT_EQ(PhyType::LE_1M, tx_phy);
+ ASSERT_EQ(PhyType::LE_2M, rx_phy);
+}
+
+TEST_F(LeImplWithConnectionTest, on_le_event__DATA_LENGTH_CHANGE) {
+ uint16_t tx_octets{0};
+ uint16_t tx_time{0};
+ uint16_t rx_octets{0};
+ uint16_t rx_time{0};
+
+ // Send a data length event
+ {
+ EXPECT_CALL(connection_management_callbacks_, OnDataLengthChange(_, _, _, _))
+ .WillOnce([&](uint16_t _tx_octets, uint16_t _tx_time, uint16_t _rx_octets, uint16_t _rx_time) {
+ tx_octets = _tx_octets;
+ tx_time = _tx_time;
+ rx_octets = _rx_octets;
+ rx_time = _rx_time;
+ });
+ auto command = LeDataLengthChangeBuilder::Create(kHciHandle, 0x1234, 0x5678, 0x9abc, 0xdef0);
+ auto bytes = Serialize<LeDataLengthChangeBuilder>(std::move(command));
+ auto view = CreateLeEventView<hci::LeDataLengthChangeView>(bytes);
+ ASSERT_TRUE(view.IsValid());
+ le_impl_->on_le_event(view);
+ }
+
+ sync_handler();
+ ASSERT_EQ(0x1234, tx_octets);
+ ASSERT_EQ(0x5678, tx_time);
+ ASSERT_EQ(0x9abc, rx_octets);
+ ASSERT_EQ(0xdef0, rx_time);
+}
+
+TEST_F(LeImplWithConnectionTest, on_le_event__REMOTE_CONNECTION_PARAMETER_REQUEST) {
+ // Send a remote connection parameter request
+ auto command = hci::LeRemoteConnectionParameterRequestBuilder::Create(
+ kHciHandle, kIntervalMin, kIntervalMax, kLatency, kTimeout);
+ auto bytes = Serialize<LeRemoteConnectionParameterRequestBuilder>(std::move(command));
+ {
+ auto view = CreateLeEventView<hci::LeRemoteConnectionParameterRequestView>(bytes);
+ ASSERT_TRUE(view.IsValid());
+ le_impl_->on_le_event(view);
+ }
+
+ sync_handler();
+
+ ASSERT_FALSE(hci_layer_->IsPacketQueueEmpty());
+
+ auto view = CreateLeConnectionManagementCommandView<LeRemoteConnectionParameterRequestReplyView>(
+ hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(view.IsValid());
+
+ ASSERT_EQ(kIntervalMin, view.GetIntervalMin());
+ ASSERT_EQ(kIntervalMax, view.GetIntervalMax());
+ ASSERT_EQ(kLatency, view.GetLatency());
+ ASSERT_EQ(kTimeout, view.GetTimeout());
+}
+
+TEST_F(LeImplRegisteredWithAddressManagerTest, clear_resolving_list) {
+ le_impl_->clear_resolving_list();
+ ASSERT_EQ(3UL, le_impl_->le_address_manager_->NumberCachedCommands());
+
+ sync_handler(); // Allow |LeAddressManager::pause_registered_clients| to complete
+ sync_handler(); // Allow |LeAddressManager::handle_next_command| to complete
+
+ ASSERT_EQ(1UL, hci_layer_->NumberOfQueuedCommands());
+ {
+ auto view = CreateLeSecurityCommandView<LeSetAddressResolutionEnableView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(view.IsValid());
+ ASSERT_EQ(Enable::DISABLED, view.GetAddressResolutionEnable());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE, ErrorCode::SUCCESS));
+ }
+
+ sync_handler(); // Allow |LeAddressManager::check_cached_commands| to complete
+ ASSERT_EQ(1UL, hci_layer_->NumberOfQueuedCommands());
+ {
+ auto view = CreateLeSecurityCommandView<LeClearResolvingListView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(view.IsValid());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_CLEAR_RESOLVING_LIST, ErrorCode::SUCCESS));
+ }
+
+ sync_handler(); // Allow |LeAddressManager::handle_next_command| to complete
+ ASSERT_EQ(1UL, hci_layer_->NumberOfQueuedCommands());
+ {
+ auto view = CreateLeSecurityCommandView<LeSetAddressResolutionEnableView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(view.IsValid());
+ ASSERT_EQ(Enable::ENABLED, view.GetAddressResolutionEnable());
+ le_impl_->le_address_manager_->OnCommandComplete(
+ ReturnCommandComplete(OpCode::LE_SET_ADDRESS_RESOLUTION_ENABLE, ErrorCode::SUCCESS));
+ }
+ ASSERT_TRUE(hci_layer_->IsPacketQueueEmpty());
+}
+
+TEST_F(LeImplWithConnectionTest, HACK_get_handle) {
+ sync_handler();
+
+ ASSERT_EQ(kHciHandle, le_impl_->HACK_get_handle(remote_address_));
+}
+
+TEST_F(LeImplTest, on_le_connection_canceled_on_pause) {
+ set_random_device_address_policy();
+ le_impl_->pause_connection = true;
+ le_impl_->on_le_connection_canceled_on_pause();
+ ASSERT_TRUE(le_impl_->arm_on_resume_);
+ ASSERT_EQ(ConnectabilityState::DISARMED, le_impl_->connectability_state_);
+}
+
+TEST_F(LeImplTest, on_create_connection_timeout) {
+ EXPECT_CALL(mock_le_connection_callbacks_, OnLeConnectFail(_, ErrorCode::CONNECTION_ACCEPT_TIMEOUT)).Times(1);
+ le_impl_->create_connection_timeout_alarms_.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(
+ remote_public_address_with_type_.GetAddress(), remote_public_address_with_type_.GetAddressType()),
+ std::forward_as_tuple(handler_));
+ le_impl_->on_create_connection_timeout(remote_public_address_with_type_);
+ sync_handler();
+ ASSERT_TRUE(le_impl_->create_connection_timeout_alarms_.empty());
+}
+
+TEST_F(LeImplTest, on_common_le_connection_complete__NoPriorConnection) {
+ auto log_capture = std::make_unique<LogCapture>();
+ le_impl_->on_common_le_connection_complete(remote_public_address_with_type_);
+ ASSERT_TRUE(le_impl_->connecting_le_.empty());
+ ASSERT_TRUE(log_capture->Rewind()->Find("No prior connection request for"));
+}
+
+TEST_F(LeImplTest, cancel_connect) {
+ le_impl_->create_connection_timeout_alarms_.emplace(
+ std::piecewise_construct,
+ std::forward_as_tuple(
+ remote_public_address_with_type_.GetAddress(), remote_public_address_with_type_.GetAddressType()),
+ std::forward_as_tuple(handler_));
+ le_impl_->cancel_connect(remote_public_address_with_type_);
+ sync_handler();
+ ASSERT_TRUE(le_impl_->create_connection_timeout_alarms_.empty());
+}
+
+TEST_F(LeImplTest, set_le_suggested_default_data_parameters) {
+ le_impl_->set_le_suggested_default_data_parameters(kLength, kTime);
+ sync_handler();
+ auto view =
+ CreateLeConnectionManagementCommandView<LeWriteSuggestedDefaultDataLengthView>(hci_layer_->DequeueCommandBytes());
+ ASSERT_TRUE(view.IsValid());
+ ASSERT_EQ(kLength, view.GetTxOctets());
+ ASSERT_EQ(kTime, view.GetTxTime());
+}
+
} // namespace acl_manager
} // namespace hci
} // namespace bluetooth
diff --git a/system/gd/hci/hci_layer.cc b/system/gd/hci/hci_layer.cc
index 48f05c4146..595c7b6564 100644
--- a/system/gd/hci/hci_layer.cc
+++ b/system/gd/hci/hci_layer.cc
@@ -49,6 +49,7 @@ using std::unique_ptr;
static void fail_if_reset_complete_not_success(CommandCompleteView complete) {
auto reset_complete = ResetCompleteView::Create(complete);
ASSERT(reset_complete.IsValid());
+ LOG_DEBUG("Reset completed with status: %s", ErrorCodeText(ErrorCode::SUCCESS).c_str());
ASSERT(reset_complete.GetStatus() == ErrorCode::SUCCESS);
}
diff --git a/system/gd/hci/hci_layer_unittest.cc b/system/gd/hci/hci_layer_unittest.cc
index 3d88f06b41..d8ac9de2b1 100644
--- a/system/gd/hci/hci_layer_unittest.cc
+++ b/system/gd/hci/hci_layer_unittest.cc
@@ -37,6 +37,25 @@
using namespace std::chrono_literals;
+namespace {
+constexpr size_t kBufSize = 512;
+constexpr char kOurAclEventHandlerWasInvoked[] = "Our ACL event handler was invoked.";
+constexpr char kOurCommandCompleteHandlerWasInvoked[] = "Our command complete handler was invoked.";
+constexpr char kOurCommandStatusHandlerWasInvoked[] = "Our command status handler was invoked.";
+constexpr char kOurDisconnectHandlerWasInvoked[] = "Our disconnect handler was invoked.";
+constexpr char kOurEventHandlerWasInvoked[] = "Our event handler was invoked.";
+constexpr char kOurLeAclEventHandlerWasInvoked[] = "Our LE ACL event handler was invoked.";
+constexpr char kOurLeAdvertisementEventHandlerWasInvoked[] = "Our LE advertisement event handler was invoked.";
+constexpr char kOurLeDisconnectHandlerWasInvoked[] = "Our LE disconnect handler was invoked.";
+constexpr char kOurLeEventHandlerWasInvoked[] = "Our LE event handler was invoked.";
+constexpr char kOurLeIsoEventHandlerWasInvoked[] = "Our LE ISO event handler was invoked.";
+constexpr char kOurLeReadRemoteVersionHandlerWasInvoked[] = "Our Read Remote Version complete handler was invoked.";
+constexpr char kOurLeScanningEventHandlerWasInvoked[] = "Our LE scanning event handler was invoked.";
+constexpr char kOurReadRemoteVersionHandlerWasInvoked[] = "Our Read Remote Version complete handler was invoked.";
+constexpr char kOurLeSecurityEventHandlerWasInvoked[] = "Our LE security event handler was invoked.";
+constexpr char kOurSecurityEventHandlerWasInvoked[] = "Our security event handler was invoked.";
+} // namespace
+
namespace bluetooth {
namespace hci {
@@ -68,7 +87,7 @@ class TestHciHal : public hal::HciHal {
TestHciHal() : hal::HciHal() {}
~TestHciHal() {
- ASSERT_LOG(callbacks == nullptr, "unregisterIncomingPacketCallback() must be called");
+ ASSERT(callbacks == nullptr);
}
void registerIncomingPacketCallback(hal::HciHalCallbacks* callback) override {
@@ -81,7 +100,8 @@ class TestHciHal : public hal::HciHal {
void sendHciCommand(hal::HciPacket command) override {
outgoing_commands_.push_back(std::move(command));
- LOG_DEBUG("Enqueued HCI command in HAL.");
+ sent_commands_++;
+ LOG_DEBUG("Enqueued HCI command %d in HAL.", sent_commands_);
}
void sendScoData(hal::HciPacket data) override {}
@@ -111,19 +131,24 @@ class TestHciHal : public hal::HciHal {
return outgoing_commands_.size();
}
- void InjectEvent(std::unique_ptr<packet::BasePacketBuilder> packet) {
- callbacks->hciEventReceived(GetPacketBytes(std::move(packet)));
- }
-
std::string ToString() const override {
return std::string("TestHciHal");
}
+ void InjectResetCompleteEventWithCode(ErrorCode code) {
+ auto reset_complete = ResetCompleteBuilder::Create(0x01, code);
+ InjectEvent(std::move(reset_complete));
+ }
+
+ void InjectEvent(std::unique_ptr<packet::BasePacketBuilder> packet) {
+ callbacks->hciEventReceived(GetPacketBytes(std::move(packet)));
+ }
static const ModuleFactory Factory;
private:
std::list<hal::HciPacket> outgoing_commands_;
std::unique_ptr<std::promise<void>> sent_command_promise_;
+ int sent_commands_{0};
};
const ModuleFactory TestHciHal::Factory = ModuleFactory([]() { return new TestHciHal(); });
@@ -152,8 +177,12 @@ class HciLayerTest : public ::testing::Test {
}
void FailIfResetNotSent() {
+ hci_handler_->BindOnceOn(this, &HciLayerTest::fail_if_reset_not_sent).Invoke();
+ }
+
+ void fail_if_reset_not_sent() {
std::promise<void> promise;
- log_capture_->WaitUntilLogContains(&promise, "Enqueued HCI command in HAL.");
+ log_capture_->WaitUntilLogContains(&promise, "Enqueued HCI command 1 in HAL.");
auto sent_command = hal_->GetSentCommand();
auto reset_view = ResetView::Create(CommandView::Create(sent_command));
ASSERT_TRUE(reset_view.IsValid());
@@ -177,7 +206,7 @@ TEST_F(HciLayerTest, controller_debug_info_requested_on_hci_timeout) {
FakeTimerAdvance(HciLayer::kHciTimeoutMs.count());
std::promise<void> promise;
- log_capture_->WaitUntilLogContains(&promise, "Enqueued HCI command in HAL.");
+ log_capture_->WaitUntilLogContains(&promise, "Enqueued HCI command 2 in HAL.");
auto sent_command = hal_->GetSentCommand();
auto debug_info_view = ControllerDebugInfoView::Create(VendorCommandView::Create(sent_command));
ASSERT_TRUE(debug_info_view.IsValid());
@@ -188,7 +217,7 @@ TEST_F(HciLayerTest, abort_after_hci_restart_timeout) {
FakeTimerAdvance(HciLayer::kHciTimeoutMs.count());
std::promise<void> promise;
- log_capture_->WaitUntilLogContains(&promise, "Enqueued HCI command in HAL.");
+ log_capture_->WaitUntilLogContains(&promise, "Enqueued HCI command 2 in HAL.");
auto sent_command = hal_->GetSentCommand();
auto debug_info_view = ControllerDebugInfoView::Create(VendorCommandView::Create(sent_command));
ASSERT_TRUE(debug_info_view.IsValid());
@@ -219,5 +248,315 @@ TEST_F(HciLayerTest, abort_on_root_inflammation_event) {
"");
}
+TEST_F(HciLayerTest, successful_reset) {
+ FailIfResetNotSent();
+ auto error_code = ErrorCode::SUCCESS;
+ hal_->InjectResetCompleteEventWithCode(error_code);
+ std::promise<void> promise;
+ auto buf = std::make_unique<char[]>(kBufSize);
+ std::snprintf(buf.get(), kBufSize, "Reset completed with status: %s", ErrorCodeText(error_code).c_str());
+ log_capture_->WaitUntilLogContains(&promise, buf.get());
+}
+
+TEST_F(HciLayerTest, abort_if_reset_complete_returns_error) {
+ ASSERT_DEATH(
+ {
+ FailIfResetNotSent();
+ auto error_code = ErrorCode::UNSPECIFIED_ERROR;
+ hal_->InjectResetCompleteEventWithCode(error_code);
+ auto buf = std::make_unique<char[]>(kBufSize);
+ std::snprintf(buf.get(), kBufSize, "Reset completed with status: %s", ErrorCodeText(error_code).c_str());
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, buf.get());
+ },
+ "");
+}
+
+TEST_F(HciLayerTest, event_handler_is_invoked) {
+ FailIfResetNotSent();
+ hci_->UnregisterEventHandler(EventCode::COMMAND_COMPLETE);
+ hci_->RegisterEventHandler(EventCode::COMMAND_COMPLETE, hci_handler_->Bind([](EventView view) {
+ LOG_DEBUG("%s", kOurEventHandlerWasInvoked);
+ }));
+ auto error_code = ErrorCode::SUCCESS;
+ hal_->InjectResetCompleteEventWithCode(error_code);
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurEventHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, le_event_handler_is_invoked) {
+ FailIfResetNotSent();
+ hci_->RegisterLeEventHandler(SubeventCode::ENHANCED_CONNECTION_COMPLETE, hci_handler_->Bind([](LeMetaEventView view) {
+ LOG_DEBUG("%s", kOurLeEventHandlerWasInvoked);
+ }));
+ hci::Address remote_address;
+ Address::FromString("D0:05:04:03:02:01", remote_address);
+ hal_->InjectEvent(LeEnhancedConnectionCompleteBuilder::Create(
+ ErrorCode::SUCCESS,
+ 0x0041,
+ Role::PERIPHERAL,
+ AddressType::PUBLIC_DEVICE_ADDRESS,
+ remote_address,
+ Address::kEmpty,
+ Address::kEmpty,
+ 0x0024,
+ 0x0000,
+ 0x0011,
+ ClockAccuracy::PPM_30));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurLeEventHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, abort_on_second_register_event_handler) {
+ ASSERT_DEATH(
+ {
+ FailIfResetNotSent();
+ hci_->RegisterEventHandler(EventCode::COMMAND_COMPLETE, hci_handler_->Bind([](EventView view) {}));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, "Can not register a second handler for");
+ },
+ "");
+}
+
+TEST_F(HciLayerTest, abort_on_second_register_le_event_handler) {
+ ASSERT_DEATH(
+ {
+ FailIfResetNotSent();
+ hci_->RegisterLeEventHandler(
+ SubeventCode::ENHANCED_CONNECTION_COMPLETE, hci_handler_->Bind([](LeMetaEventView view) {}));
+ hci_->RegisterLeEventHandler(
+ SubeventCode::ENHANCED_CONNECTION_COMPLETE, hci_handler_->Bind([](LeMetaEventView view) {}));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, "Can not register a second handler for");
+ },
+ "");
+}
+
+TEST_F(HciLayerTest, our_acl_event_callback_is_invoked) {
+ FailIfResetNotSent();
+ hci_->GetAclConnectionInterface(
+ hci_handler_->Bind([](EventView view) { LOG_DEBUG("%s", kOurAclEventHandlerWasInvoked); }),
+ hci_handler_->Bind([](uint16_t handle, ErrorCode reason) {}),
+ hci_handler_->Bind([](hci::ErrorCode hci_status,
+ uint16_t handle,
+ uint8_t version,
+ uint16_t manufacturer_name,
+ uint16_t sub_version) {}));
+ hal_->InjectEvent(ReadClockOffsetCompleteBuilder::Create(ErrorCode::SUCCESS, 0x0001, 0x0123));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurAclEventHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, our_disconnect_callback_is_invoked) {
+ FailIfResetNotSent();
+ hci_->GetAclConnectionInterface(
+ hci_handler_->Bind([](EventView view) {}),
+ hci_handler_->Bind([](uint16_t handle, ErrorCode reason) { LOG_DEBUG("%s", kOurDisconnectHandlerWasInvoked); }),
+ hci_handler_->Bind([](hci::ErrorCode hci_status,
+ uint16_t handle,
+ uint8_t version,
+ uint16_t manufacturer_name,
+ uint16_t sub_version) {}));
+ hal_->InjectEvent(
+ DisconnectionCompleteBuilder::Create(ErrorCode::SUCCESS, 0x0001, ErrorCode::REMOTE_USER_TERMINATED_CONNECTION));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurDisconnectHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, our_read_remote_version_callback_is_invoked) {
+ FailIfResetNotSent();
+ hci_->GetAclConnectionInterface(
+ hci_handler_->Bind([](EventView view) {}),
+ hci_handler_->Bind([](uint16_t handle, ErrorCode reason) {}),
+ hci_handler_->Bind([](hci::ErrorCode hci_status,
+ uint16_t handle,
+ uint8_t version,
+ uint16_t manufacturer_name,
+ uint16_t sub_version) { LOG_DEBUG("%s", kOurReadRemoteVersionHandlerWasInvoked); }));
+ hal_->InjectEvent(
+ ReadRemoteVersionInformationCompleteBuilder::Create(ErrorCode::SUCCESS, 0x0001, 0x0b, 0x000f, 0x0000));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurReadRemoteVersionHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, our_le_acl_event_callback_is_invoked) {
+ FailIfResetNotSent();
+ hci_->GetLeAclConnectionInterface(
+ hci_handler_->Bind([](LeMetaEventView view) { LOG_DEBUG("%s", kOurLeAclEventHandlerWasInvoked); }),
+ hci_handler_->Bind([](uint16_t handle, ErrorCode reason) {}),
+ hci_handler_->Bind([](hci::ErrorCode hci_status,
+ uint16_t handle,
+ uint8_t version,
+ uint16_t manufacturer_name,
+ uint16_t sub_version) {}));
+ hal_->InjectEvent(LeDataLengthChangeBuilder::Create(0x0001, 0x001B, 0x0148, 0x001B, 0x0148));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurLeAclEventHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, our_le_disconnect_callback_is_invoked) {
+ FailIfResetNotSent();
+ hci_->GetLeAclConnectionInterface(
+ hci_handler_->Bind([](LeMetaEventView view) {}),
+ hci_handler_->Bind([](uint16_t handle, ErrorCode reason) { LOG_DEBUG("%s", kOurLeDisconnectHandlerWasInvoked); }),
+ hci_handler_->Bind([](hci::ErrorCode hci_status,
+ uint16_t handle,
+ uint8_t version,
+ uint16_t manufacturer_name,
+ uint16_t sub_version) {}));
+ hal_->InjectEvent(
+ DisconnectionCompleteBuilder::Create(ErrorCode::SUCCESS, 0x0001, ErrorCode::REMOTE_USER_TERMINATED_CONNECTION));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurLeDisconnectHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, our_le_read_remote_version_callback_is_invoked) {
+ FailIfResetNotSent();
+ hci_->GetLeAclConnectionInterface(
+ hci_handler_->Bind([](LeMetaEventView view) {}),
+ hci_handler_->Bind([](uint16_t handle, ErrorCode reason) {}),
+ hci_handler_->Bind([](hci::ErrorCode hci_status,
+ uint16_t handle,
+ uint8_t version,
+ uint16_t manufacturer_name,
+ uint16_t sub_version) { LOG_DEBUG("%s", kOurLeReadRemoteVersionHandlerWasInvoked); }));
+ hal_->InjectEvent(
+ ReadRemoteVersionInformationCompleteBuilder::Create(ErrorCode::SUCCESS, 0x0001, 0x0b, 0x000f, 0x0000));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurLeReadRemoteVersionHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, our_security_callback_is_invoked) {
+ FailIfResetNotSent();
+ hci_->GetSecurityInterface(
+ hci_handler_->Bind([](EventView view) { LOG_DEBUG("%s", kOurSecurityEventHandlerWasInvoked); }));
+ hal_->InjectEvent(EncryptionChangeBuilder::Create(ErrorCode::SUCCESS, 0x0001, bluetooth::hci::EncryptionEnabled::ON));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurSecurityEventHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, our_le_security_callback_is_invoked) {
+ FailIfResetNotSent();
+ hci_->GetLeSecurityInterface(
+ hci_handler_->Bind([](LeMetaEventView view) { LOG_DEBUG("%s", kOurLeSecurityEventHandlerWasInvoked); }));
+ hal_->InjectEvent(LeLongTermKeyRequestBuilder::Create(0x0001, {0, 0, 0, 0, 0, 0, 0, 0}, 0));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurLeSecurityEventHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, our_le_advertising_callback_is_invoked) {
+ FailIfResetNotSent();
+ hci_->GetLeAdvertisingInterface(
+ hci_handler_->Bind([](LeMetaEventView view) { LOG_DEBUG("%s", kOurLeAdvertisementEventHandlerWasInvoked); }));
+ hal_->InjectEvent(LeAdvertisingSetTerminatedBuilder::Create(ErrorCode::SUCCESS, 0x01, 0x001, 0x01));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurLeAdvertisementEventHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, our_le_scanning_callback_is_invoked) {
+ FailIfResetNotSent();
+ hci_->GetLeScanningInterface(
+ hci_handler_->Bind([](LeMetaEventView view) { LOG_DEBUG("%s", kOurLeScanningEventHandlerWasInvoked); }));
+ hal_->InjectEvent(LeScanTimeoutBuilder::Create());
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurLeScanningEventHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, our_le_iso_callback_is_invoked) {
+ FailIfResetNotSent();
+ hci_->GetLeIsoInterface(
+ hci_handler_->Bind([](LeMetaEventView view) { LOG_DEBUG("%s", kOurLeIsoEventHandlerWasInvoked); }));
+ hal_->InjectEvent(LeCisRequestBuilder::Create(0x0001, 0x0001, 0x01, 0x01));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurLeIsoEventHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, our_command_complete_callback_is_invoked) {
+ FailIfResetNotSent();
+ auto error_code = ErrorCode::SUCCESS;
+ hal_->InjectResetCompleteEventWithCode(error_code);
+ hci_->EnqueueCommand(ResetBuilder::Create(), hci_handler_->BindOnce([](CommandCompleteView view) {
+ LOG_DEBUG("%s", kOurCommandCompleteHandlerWasInvoked);
+ }));
+ hal_->InjectResetCompleteEventWithCode(error_code);
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurCommandCompleteHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, our_command_status_callback_is_invoked) {
+ FailIfResetNotSent();
+ auto error_code = ErrorCode::SUCCESS;
+ hal_->InjectResetCompleteEventWithCode(error_code);
+ hci_->EnqueueCommand(ReadClockOffsetBuilder::Create(0x001), hci_handler_->BindOnce([](CommandStatusView view) {
+ LOG_DEBUG("%s", kOurCommandStatusHandlerWasInvoked);
+ }));
+ hal_->InjectEvent(ReadClockOffsetStatusBuilder::Create(ErrorCode::SUCCESS, 1));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, kOurCommandStatusHandlerWasInvoked);
+}
+
+TEST_F(HciLayerTest, command_complete_callback_is_invoked_with_an_opcode_that_does_not_match_command_queue) {
+ ASSERT_DEATH(
+ {
+ FailIfResetNotSent();
+ hci_->EnqueueCommand(
+ ReadClockOffsetBuilder::Create(0x001), hci_handler_->BindOnce([](CommandCompleteView view) {}));
+ hal_->InjectEvent(ReadClockOffsetStatusBuilder::Create(ErrorCode::SUCCESS, 1));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, "Waiting for 0x0c03 (RESET)");
+ },
+ "");
+}
+
+TEST_F(HciLayerTest, command_status_callback_is_invoked_with_an_opcode_that_does_not_match_command_queue) {
+ ASSERT_DEATH(
+ {
+ FailIfResetNotSent();
+ hci_->EnqueueCommand(
+ ReadClockOffsetBuilder::Create(0x001), hci_handler_->BindOnce([](CommandStatusView view) {}));
+ hal_->InjectEvent(ReadClockOffsetStatusBuilder::Create(ErrorCode::SUCCESS, 1));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, "Waiting for 0x0c03 (RESET)");
+ },
+ "");
+}
+
+TEST_F(HciLayerTest, command_complete_callback_is_invoked_but_command_queue_empty) {
+ ASSERT_DEATH(
+ {
+ FailIfResetNotSent();
+ auto error_code = ErrorCode::SUCCESS;
+ hal_->InjectResetCompleteEventWithCode(error_code);
+ hal_->InjectResetCompleteEventWithCode(error_code);
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, "Unexpected event complete with opcode:0x0c3");
+ },
+ "");
+}
+
+TEST_F(HciLayerTest, command_status_callback_is_invoked_but_command_queue_empty) {
+ ASSERT_DEATH(
+ {
+ FailIfResetNotSent();
+ auto error_code = ErrorCode::SUCCESS;
+ hal_->InjectResetCompleteEventWithCode(error_code);
+ hal_->InjectEvent(ReadClockOffsetStatusBuilder::Create(ErrorCode::SUCCESS, 1));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(&promise, "Unexpected event status with opcode:0x41f");
+ },
+ "");
+}
+
+TEST_F(HciLayerTest, command_status_callback_is_invoked_with_failure_status) {
+ FailIfResetNotSent();
+ auto error_code = ErrorCode::SUCCESS;
+ hal_->InjectResetCompleteEventWithCode(error_code);
+ hci_->EnqueueCommand(ReadClockOffsetBuilder::Create(0x001), hci_handler_->BindOnce([](CommandStatusView view) {}));
+ hal_->InjectEvent(ReadClockOffsetStatusBuilder::Create(ErrorCode::HARDWARE_FAILURE, 1));
+ std::promise<void> promise;
+ log_capture_->WaitUntilLogContains(
+ &promise, "Received UNEXPECTED command status:HARDWARE_FAILURE opcode:0x41f (READ_CLOCK_OFFSET)");
+}
+
} // namespace hci
} // namespace bluetooth
diff --git a/system/gd/hci/le_address_manager.cc b/system/gd/hci/le_address_manager.cc
index ece39f53de..703851fc3f 100644
--- a/system/gd/hci/le_address_manager.cc
+++ b/system/gd/hci/le_address_manager.cc
@@ -51,6 +51,18 @@ void LeAddressManager::SetPrivacyPolicyForInitiatorAddress(
bool supports_ble_privacy,
std::chrono::milliseconds minimum_rotation_time,
std::chrono::milliseconds maximum_rotation_time) {
+ // Handle repeated calls to the function
+ if (address_policy_ != AddressPolicy::POLICY_NOT_SET) {
+ // Need to update some parameteres like IRK if privacy is supported
+ if (supports_ble_privacy) {
+ LOG_INFO("Updating rotation parameters.");
+ rotation_irk_ = rotation_irk;
+ minimum_rotation_time_ = minimum_rotation_time;
+ maximum_rotation_time_ = maximum_rotation_time;
+ set_random_address();
+ }
+ return;
+ }
ASSERT(address_policy_ == AddressPolicy::POLICY_NOT_SET);
ASSERT(address_policy != AddressPolicy::POLICY_NOT_SET);
ASSERT_LOG(registered_clients_.empty(), "Policy must be set before clients are registered.");
diff --git a/system/gd/metrics/chromeos/metrics.cc b/system/gd/metrics/chromeos/metrics.cc
index 5e313f69d8..735cbade95 100644
--- a/system/gd/metrics/chromeos/metrics.cc
+++ b/system/gd/metrics/chromeos/metrics.cc
@@ -54,6 +54,7 @@ void LogMetricsAdapterStateChanged(uint32_t state) {
}
void LogMetricsBondCreateAttempt(RawAddress* addr, uint32_t device_type) {
+ ConnectionType connection_type;
int64_t boot_time;
std::string addr_string;
std::string boot_id;
@@ -62,26 +63,28 @@ void LogMetricsBondCreateAttempt(RawAddress* addr, uint32_t device_type) {
addr_string = addr->ToString();
boot_time = bluetooth::common::time_get_os_boottime_us();
+ connection_type = ToPairingDeviceType(addr_string, device_type);
LOG_DEBUG(
"PairingStateChanged: %s, %d, %s, %d, %d",
boot_id.c_str(),
boot_time,
addr_string.c_str(),
- device_type,
+ connection_type,
PairingState::PAIR_STARTING);
::metrics::structured::events::bluetooth::BluetoothPairingStateChanged()
.SetBootId(boot_id)
.SetSystemTime(boot_time)
.SetDeviceId(addr_string)
- .SetDeviceType(device_type)
+ .SetDeviceType((int64_t)connection_type)
.SetPairingState((int64_t)PairingState::PAIR_STARTING)
.Record();
}
void LogMetricsBondStateChanged(
RawAddress* addr, uint32_t device_type, uint32_t status, uint32_t bond_state, int32_t fail_reason) {
+ ConnectionType connection_type;
int64_t boot_time;
PairingState pairing_state;
std::string addr_string;
@@ -91,6 +94,7 @@ void LogMetricsBondStateChanged(
addr_string = addr->ToString();
boot_time = bluetooth::common::time_get_os_boottime_us();
+ connection_type = ToPairingDeviceType(addr_string, device_type);
pairing_state = ToPairingState(status, bond_state, fail_reason);
// Ignore the start of pairing event as its logged separated above.
@@ -101,14 +105,14 @@ void LogMetricsBondStateChanged(
boot_id.c_str(),
boot_time,
addr_string.c_str(),
- device_type,
+ connection_type,
pairing_state);
::metrics::structured::events::bluetooth::BluetoothPairingStateChanged()
.SetBootId(boot_id)
.SetSystemTime(boot_time)
.SetDeviceId(addr_string)
- .SetDeviceType(device_type)
+ .SetDeviceType((int64_t)connection_type)
.SetPairingState((int64_t)pairing_state)
.Record();
}
diff --git a/system/gd/metrics/chromeos/metrics_event.cc b/system/gd/metrics/chromeos/metrics_event.cc
index 2a4a5ef2a9..e8b0462dfe 100644
--- a/system/gd/metrics/chromeos/metrics_event.cc
+++ b/system/gd/metrics/chromeos/metrics_event.cc
@@ -20,6 +20,7 @@
#include "hci/hci_packets.h"
#include "include/hardware/bluetooth.h"
+#include "include/hardware/bt_av.h"
#include "include/hardware/bt_hh.h"
namespace bluetooth {
@@ -29,9 +30,19 @@ namespace metrics {
typedef bt_bond_state_t BtBondState;
// topshim::btif::BtStatus is a copy of hardware/bluetooth.h:bt_status_t
typedef bt_status_t BtStatus;
-// topshim::profile::hid_host::BthhConnectionState is a copy of hardware/bluetooth.h:bthh_connection_state_t
+// topshim::profile::a2dp::BtavConnectionState is a copy of hardware/bt_av.h:btav_connection_state_t
+typedef btav_connection_state_t BtavConnectionState;
+// topshim::profile::hid_host::BthhConnectionState is a copy of hardware/bt_hh.h:bthh_connection_state_t
typedef bthh_connection_state_t BthhConnectionState;
+// A copy of topshim::btif::BtDeviceType
+enum class BtDeviceType {
+ Unknown = 0,
+ Bredr,
+ Ble,
+ Dual,
+};
+
// A normalized connection state ENUM definition all profiles
enum class ProfilesConnectionState {
DISCONNECTED = 0,
@@ -173,6 +184,8 @@ static PairingState FailReasonToPairingState(int32_t fail_reason) {
return PairingState::PAIR_FAIL_AUTH_FAILED;
case hci::ErrorCode::ROLE_SWITCH_FAILED:
return PairingState::PAIR_FAIL_FAILED;
+ case hci::ErrorCode::HOST_BUSY:
+ return PairingState::PAIR_FAIL_BUSY;
case hci::ErrorCode::CONTROLLER_BUSY:
return PairingState::PAIR_FAIL_BUSY;
case hci::ErrorCode::CONNECTION_FAILED_ESTABLISHMENT:
@@ -188,6 +201,8 @@ static PairingState FailReasonToPairingState(int32_t fail_reason) {
case hci::ErrorCode::UNKNOWN_ADVERTISING_IDENTIFIER:
case hci::ErrorCode::STATUS_UNKNOWN:
return PairingState::PAIR_FAIL_UNKNOWN;
+ default:
+ return PairingState::PAIR_FAIL_UNKNOWN;
}
}
@@ -195,6 +210,28 @@ AdapterState ToAdapterState(uint32_t state) {
return state == 1 ? AdapterState::ON : AdapterState::OFF;
}
+ConnectionType ToPairingDeviceType(std::string addr, uint32_t device_type) {
+ // A map stores the pending ConnectionType used to match a pairing event with unknown type.
+ // map<address, type>
+ static std::map<std::string, ConnectionType> pending_type;
+
+ switch ((BtDeviceType)device_type) {
+ case BtDeviceType::Ble:
+ pending_type[addr] = ConnectionType::CONN_TYPE_LE;
+ return ConnectionType::CONN_TYPE_LE;
+ case BtDeviceType::Bredr:
+ pending_type[addr] = ConnectionType::CONN_TYPE_BREDR;
+ return ConnectionType::CONN_TYPE_BREDR;
+ case BtDeviceType::Dual:
+ case BtDeviceType::Unknown:
+ if (pending_type.find(addr) != pending_type.end()) {
+ return pending_type[addr];
+ } else {
+ return ConnectionType::CONN_TYPE_UNKNOWN;
+ }
+ }
+}
+
PairingState ToPairingState(uint32_t status, uint32_t bond_state, int32_t fail_reason) {
PairingState pairing_state = PairingState::PAIR_FAIL_UNKNOWN;
@@ -294,7 +331,26 @@ static std::pair<uint32_t, uint32_t> ToProfileConnectionState(uint32_t profile,
std::pair<uint32_t, uint32_t> output;
switch ((ProfilesFloss)profile) {
- // case ProfilesFloss::A2dpSink:
+ case ProfilesFloss::A2dpSink:
+ output.first = (uint32_t)Profile::A2DP;
+ switch ((BtavConnectionState)state) {
+ case BtavConnectionState::BTAV_CONNECTION_STATE_CONNECTED:
+ output.second = (uint32_t)ProfilesConnectionState::CONNECTED;
+ break;
+ case BtavConnectionState::BTAV_CONNECTION_STATE_CONNECTING:
+ output.second = (uint32_t)ProfilesConnectionState::CONNECTING;
+ break;
+ case BtavConnectionState::BTAV_CONNECTION_STATE_DISCONNECTED:
+ output.second = (uint32_t)ProfilesConnectionState::DISCONNECTED;
+ break;
+ case BtavConnectionState::BTAV_CONNECTION_STATE_DISCONNECTING:
+ output.second = (uint32_t)ProfilesConnectionState::DISCONNECTING;
+ break;
+ default:
+ output.second = (uint32_t)ProfilesConnectionState::UNKNOWN;
+ break;
+ }
+ break;
// case ProfilesFloss::A2dpSource:
// case ProfilesFloss::AdvAudioDist:
// case ProfilesFloss::Hsp:
diff --git a/system/gd/metrics/chromeos/metrics_event.h b/system/gd/metrics/chromeos/metrics_event.h
index a88c270197..769ecee238 100644
--- a/system/gd/metrics/chromeos/metrics_event.h
+++ b/system/gd/metrics/chromeos/metrics_event.h
@@ -25,6 +25,17 @@ namespace metrics {
// BluetoothAdapterStateChanged/AdapterState.
enum class AdapterState : int64_t { OFF = 0, ON = 1 };
+// ENUM definition for device/connection type that in sync with ChromeOS structured metrics
+// BluetoothPairingStateChanged/DeviceType and BlueZ metrics_conn_type. Note this is a non-optimal ENUM design that
+// mixed the connection transport type with the device type. The connection can only be LE or Classic, but the device
+// type can also be Dual.
+enum class ConnectionType : int64_t {
+ CONN_TYPE_UNKNOWN = 0,
+ CONN_TYPE_BREDR = 1,
+ CONN_TYPE_LE = 2,
+ CONN_TYPE_END = 3,
+};
+
// ENUM definition for pairing state that in sync with ChromeOS structured metrics
// BluetoothPairingStateChanged/PairingState and BlueZ metrics_pair_result.
enum class PairingState : int64_t {
@@ -131,6 +142,9 @@ struct ProfileConnectionEvent {
// Convert topshim::btif::BtState to AdapterState.
AdapterState ToAdapterState(uint32_t state);
+// Convert topshim::btif::BtDeviceType to ConnectionType
+ConnectionType ToPairingDeviceType(std::string addr, uint32_t device_type);
+
// Convert topshim::btif::bond_state info (status, addr, bond_state, and fail_reason) to PairingState
PairingState ToPairingState(uint32_t status, uint32_t bond_state, int32_t fail_reason);
diff --git a/system/gd/rust/common/src/init_flags.rs b/system/gd/rust/common/src/init_flags.rs
index 4fabae0e22..029283d6d6 100644
--- a/system/gd/rust/common/src/init_flags.rs
+++ b/system/gd/rust/common/src/init_flags.rs
@@ -74,10 +74,12 @@ macro_rules! init_flags {
init_flags!(
flags: {
+ sdp_serialization,
gd_core,
gd_security,
gd_l2cap,
- gatt_robust_caching,
+ gatt_robust_caching_client,
+ gatt_robust_caching_server,
btaa_hci,
gd_rust,
gd_link_policy
diff --git a/system/gd/rust/linux/client/src/command_handler.rs b/system/gd/rust/linux/client/src/command_handler.rs
index 94ad8e0813..af6cb16331 100644
--- a/system/gd/rust/linux/client/src/command_handler.rs
+++ b/system/gd/rust/linux/client/src/command_handler.rs
@@ -595,7 +595,16 @@ impl CommandHandler {
name: String::from("Classic Device"),
};
- let (name, alias, device_type, class, bonded, connection_state, uuids) = {
+ let (
+ name,
+ alias,
+ device_type,
+ class,
+ appearance,
+ bonded,
+ connection_state,
+ uuids,
+ ) = {
let ctx = self.context.lock().unwrap();
let adapter = ctx.adapter_dbus.as_ref().unwrap();
@@ -603,6 +612,7 @@ impl CommandHandler {
let device_type = adapter.get_remote_type(device.clone());
let alias = adapter.get_remote_alias(device.clone());
let class = adapter.get_remote_class(device.clone());
+ let appearance = adapter.get_remote_appearance(device.clone());
let bonded = adapter.get_bond_state(device.clone());
let connection_state = match adapter.get_connection_state(device.clone()) {
BtConnectionState::NotConnected => "Not Connected",
@@ -611,7 +621,16 @@ impl CommandHandler {
};
let uuids = adapter.get_remote_uuids(device.clone());
- (name, alias, device_type, class, bonded, connection_state, uuids)
+ (
+ name,
+ alias,
+ device_type,
+ class,
+ appearance,
+ bonded,
+ connection_state,
+ uuids,
+ )
};
let uuid_helper = UuidHelper::new();
@@ -620,6 +639,7 @@ impl CommandHandler {
print_info!("Alias: {}", alias);
print_info!("Type: {:?}", device_type);
print_info!("Class: {}", class);
+ print_info!("Appearance: {}", appearance);
print_info!("Bond State: {:?}", bonded);
print_info!("Connection State: {}", connection_state);
print_info!(
@@ -707,7 +727,7 @@ impl CommandHandler {
);
}
"client-connect" => {
- if args.len() < 3 {
+ if args.len() < 2 {
println!("usage: gatt client-connect <addr>");
return;
}
@@ -729,7 +749,7 @@ impl CommandHandler {
);
}
"client-disconnect" => {
- if args.len() < 3 {
+ if args.len() < 2 {
println!("usage: gatt client-disconnect <addr>");
return;
}
@@ -750,7 +770,7 @@ impl CommandHandler {
.client_disconnect(client_id.unwrap(), addr);
}
"client-read-phy" => {
- if args.len() < 3 {
+ if args.len() < 2 {
println!("usage: gatt client-read-phy <addr>");
return;
}
@@ -771,7 +791,7 @@ impl CommandHandler {
.client_read_phy(client_id.unwrap(), addr);
}
"client-discover-services" => {
- if args.len() < 3 {
+ if args.len() < 2 {
println!("usage: gatt client-discover-services <addr>");
return;
}
@@ -792,7 +812,7 @@ impl CommandHandler {
.discover_services(client_id.unwrap(), addr);
}
"configure-mtu" => {
- if args.len() < 4 {
+ if args.len() < 3 {
println!("usage: gatt configure-mtu <addr> <mtu>");
return;
}
diff --git a/system/gd/rust/linux/client/src/dbus_iface.rs b/system/gd/rust/linux/client/src/dbus_iface.rs
index 6937455095..83c837f193 100644
--- a/system/gd/rust/linux/client/src/dbus_iface.rs
+++ b/system/gd/rust/linux/client/src/dbus_iface.rs
@@ -538,6 +538,11 @@ impl IBluetooth for BluetoothDBus {
dbus_generated!()
}
+ #[dbus_method("GetRemoteAppearance")]
+ fn get_remote_appearance(&self, device: BluetoothDevice) -> u16 {
+ dbus_generated!()
+ }
+
#[dbus_method("GetRemoteConnected")]
fn get_remote_connected(&self, device: BluetoothDevice) -> bool {
dbus_generated!()
diff --git a/system/gd/rust/linux/service/Cargo.toml b/system/gd/rust/linux/service/Cargo.toml
index 0e0b9e3fb7..36dc6ce9ae 100644
--- a/system/gd/rust/linux/service/Cargo.toml
+++ b/system/gd/rust/linux/service/Cargo.toml
@@ -16,7 +16,9 @@ dbus-crossroads = "0.4.0"
dbus-tokio = "0.7.3"
env_logger = "0.8.3"
futures = "0.3.13"
+lazy_static = "1.4"
log = "0.4.14"
+nix = "0.19"
num-traits = "*"
tokio = { version = "1", features = ['bytes', 'fs', 'io-util', 'libc', 'macros', 'memchr', 'mio', 'net', 'num_cpus', 'rt', 'rt-multi-thread', 'sync', 'time', 'tokio-macros'] }
syslog = "4.0"
diff --git a/system/gd/rust/linux/service/src/iface_battery_manager.rs b/system/gd/rust/linux/service/src/iface_battery_manager.rs
index 88f4fe37cf..7ab0b0cb58 100644
--- a/system/gd/rust/linux/service/src/iface_battery_manager.rs
+++ b/system/gd/rust/linux/service/src/iface_battery_manager.rs
@@ -9,7 +9,7 @@ use crate::dbus_arg::{DBusArg, DBusArgError, RefArgToRust};
#[dbus_propmap(Battery)]
pub struct BatteryDBus {
- percentage: i32,
+ percentage: u32,
source_info: String,
variant: String,
}
@@ -28,22 +28,26 @@ struct IBatteryManagerDBus {}
#[generate_dbus_exporter(export_battery_manager_dbus_intf, "org.chromium.bluetooth.BatteryManager")]
impl IBatteryManager for IBatteryManagerDBus {
- #[dbus_method("GetBatteryInformation")]
- fn get_battery_information(&self, remote_address: String) -> Battery {
- dbus_generated!()
- }
-
#[dbus_method("RegisterBatteryCallback")]
fn register_battery_callback(
&mut self,
- remote_address: String,
battery_manager_callback: Box<dyn IBatteryManagerCallback + Send>,
- ) -> i32 {
+ ) -> u32 {
dbus_generated!()
}
#[dbus_method("UnregisterBatteryCallback")]
- fn unregister_battery_callback(&mut self, callback_id: i32) {
+ fn unregister_battery_callback(&mut self, callback_id: u32) {
+ dbus_generated!()
+ }
+
+ #[dbus_method("EnableNotifications")]
+ fn enable_notifications(&mut self, callback_id: u32, enable: bool) {
+ dbus_generated!()
+ }
+
+ #[dbus_method("GetBatteryInformation")]
+ fn get_battery_information(&self, remote_address: String) -> Option<Battery> {
dbus_generated!()
}
}
diff --git a/system/gd/rust/linux/service/src/iface_bluetooth.rs b/system/gd/rust/linux/service/src/iface_bluetooth.rs
index 00beb6c81b..7977ceab27 100644
--- a/system/gd/rust/linux/service/src/iface_bluetooth.rs
+++ b/system/gd/rust/linux/service/src/iface_bluetooth.rs
@@ -316,6 +316,11 @@ impl IBluetooth for IBluetoothDBus {
dbus_generated!()
}
+ #[dbus_method("GetRemoteAppearance")]
+ fn get_remote_appearance(&self, _device: BluetoothDevice) -> u16 {
+ dbus_generated!()
+ }
+
#[dbus_method("GetRemoteConnected")]
fn get_remote_connected(&self, _device: BluetoothDevice) -> bool {
dbus_generated!()
diff --git a/system/gd/rust/linux/service/src/main.rs b/system/gd/rust/linux/service/src/main.rs
index 5c2c2694bf..4cf0dde7f6 100644
--- a/system/gd/rust/linux/service/src/main.rs
+++ b/system/gd/rust/linux/service/src/main.rs
@@ -1,4 +1,6 @@
extern crate clap;
+#[macro_use]
+extern crate lazy_static;
use clap::{App, AppSettings, Arg};
use dbus::{channel::MatchingReceiver, message::MatchRule};
@@ -6,22 +8,27 @@ use dbus_crossroads::Crossroads;
use dbus_tokio::connection;
use futures::future;
use log::LevelFilter;
+use nix::sys::signal;
use std::error::Error;
-use std::sync::{Arc, Mutex};
+use std::sync::{Arc, Condvar, Mutex};
+use std::time::Duration;
use syslog::{BasicLogger, Facility, Formatter3164};
+use tokio::time;
use bt_topshim::{btif::get_btinterface, topstack};
use btstack::{
battery_manager::BatteryManager,
battery_provider_manager::BatteryProviderManager,
+ battery_service::BatteryService,
bluetooth::{get_bt_dispatcher, Bluetooth, IBluetooth},
bluetooth_gatt::BluetoothGatt,
bluetooth_media::BluetoothMedia,
socket_manager::BluetoothSocketManager,
suspend::Suspend,
- Stack,
+ Message, Stack,
};
use dbus_projection::DisconnectWatcher;
+use tokio::sync::mpsc::Sender;
mod dbus_arg;
mod iface_battery_manager;
@@ -100,6 +107,7 @@ fn main() -> Result<(), Box<dyn Error>> {
}
let (tx, rx) = Stack::create_channel();
+ let sig_notifier = Arc::new((Mutex::new(false), Condvar::new()));
let intf = Arc::new(Mutex::new(get_btinterface().unwrap()));
let bluetooth_gatt =
@@ -107,11 +115,15 @@ fn main() -> Result<(), Box<dyn Error>> {
let bluetooth_media =
Arc::new(Mutex::new(Box::new(BluetoothMedia::new(tx.clone(), intf.clone()))));
let battery_provider_manager = Arc::new(Mutex::new(Box::new(BatteryProviderManager::new())));
- let battery_manager = Arc::new(Mutex::new(Box::new(BatteryManager::new())));
+ let battery_service =
+ Arc::new(Mutex::new(Box::new(BatteryService::new(bluetooth_gatt.clone(), tx.clone()))));
+ let battery_manager =
+ Arc::new(Mutex::new(Box::new(BatteryManager::new(battery_service.clone(), tx.clone()))));
let bluetooth = Arc::new(Mutex::new(Box::new(Bluetooth::new(
tx.clone(),
intf.clone(),
bluetooth_media.clone(),
+ sig_notifier.clone(),
))));
let suspend = Arc::new(Mutex::new(Box::new(Suspend::new(
bluetooth.clone(),
@@ -156,6 +168,8 @@ fn main() -> Result<(), Box<dyn Error>> {
rx,
bluetooth.clone(),
bluetooth_gatt.clone(),
+ battery_service.clone(),
+ battery_manager.clone(),
bluetooth_media.clone(),
suspend.clone(),
bt_sock_mgr.clone(),
@@ -255,7 +269,7 @@ fn main() -> Result<(), Box<dyn Error>> {
battery_provider_manager.clone(),
);
cr.lock().unwrap().insert(
- make_object_name(adapter_index, "battery__manager"),
+ make_object_name(adapter_index, "battery_manager"),
&[battery_manager_iface],
battery_manager.clone(),
);
@@ -273,7 +287,28 @@ fn main() -> Result<(), Box<dyn Error>> {
bluetooth.enable();
bluetooth_gatt.lock().unwrap().init_profiles(tx.clone(), adapter.clone());
+ // TODO(b/247093293): Gatt topshim api is only usable some
+ // time after init. Investigate why this delay is needed
+ // and make it a blocking part of init before removing
+ // this.
+ tokio::spawn(async move {
+ time::sleep(Duration::from_millis(500)).await;
+ battery_service.lock().unwrap().init();
+ });
bt_sock_mgr.lock().unwrap().initialize(intf.clone());
+
+ // Install SIGTERM handler so that we can properly shutdown
+ *SIG_DATA.lock().unwrap() = Some((tx.clone(), sig_notifier.clone()));
+
+ let sig_action = signal::SigAction::new(
+ signal::SigHandler::Handler(handle_sigterm),
+ signal::SaFlags::empty(),
+ signal::SigSet::empty(),
+ );
+
+ unsafe {
+ signal::sigaction(signal::SIGTERM, &sig_action).unwrap();
+ }
}
// Serve clients forever.
@@ -281,3 +316,29 @@ fn main() -> Result<(), Box<dyn Error>> {
unreachable!()
})
}
+
+lazy_static! {
+ /// Data needed for signal handling.
+ static ref SIG_DATA: Mutex<Option<(Sender<Message>, Arc<(Mutex<bool>, Condvar)>)>> = Mutex::new(None);
+}
+
+extern "C" fn handle_sigterm(_signum: i32) {
+ let guard = SIG_DATA.lock().unwrap();
+ if let Some((tx, notifier)) = guard.as_ref() {
+ log::debug!("Handling SIGTERM by disabling the adapter!");
+ let txl = tx.clone();
+ tokio::spawn(async move {
+ // Send the shutdown message here.
+ let _ = txl.send(Message::Shutdown).await;
+ });
+
+ let guard = notifier.0.lock().unwrap();
+ if *guard {
+ log::debug!("Waiting for stack to turn off for 2s");
+ let _ = notifier.1.wait_timeout(guard, std::time::Duration::from_millis(2000));
+ }
+ }
+
+ log::debug!("Sigterm completed");
+ std::process::exit(0);
+}
diff --git a/system/gd/rust/linux/stack/src/battery_manager.rs b/system/gd/rust/linux/stack/src/battery_manager.rs
index 9bb39d41a3..4d16cbf58a 100644
--- a/system/gd/rust/linux/stack/src/battery_manager.rs
+++ b/system/gd/rust/linux/stack/src/battery_manager.rs
@@ -1,20 +1,24 @@
+use crate::battery_service::{
+ BatteryService, BatteryServiceStatus, IBatteryService, IBatteryServiceCallback,
+};
+use crate::callbacks::Callbacks;
+use crate::Message;
+use crate::RPCProxy;
+use std::collections::HashSet;
+use std::sync::{Arc, Mutex};
+use tokio::sync::mpsc::Sender;
+
+/// The primary representation of battery information for internal
+/// passing and external calls.
#[derive(Debug, Clone)]
pub struct Battery {
- pub percentage: i32,
+ pub percentage: u32,
pub source_info: String,
pub variant: String,
}
-pub struct BatteryManager {}
-
-impl BatteryManager {
- pub fn new() -> BatteryManager {
- BatteryManager {}
- }
-}
-
/// Callback for interacting with the BatteryManager.
-pub trait IBatteryManagerCallback {
+pub trait IBatteryManagerCallback: RPCProxy {
/// Invoked whenever battery information associated with the given remote changes.
fn on_battery_info_updated(&self, remote_address: String, battery: Battery);
}
@@ -25,31 +29,108 @@ pub trait IBatteryManager {
/// callback_id for future calls.
fn register_battery_callback(
&mut self,
- remote_address: String,
battery_manager_callback: Box<dyn IBatteryManagerCallback + Send>,
- ) -> i32;
+ ) -> u32;
/// Unregister a callback.
- fn unregister_battery_callback(&mut self, callback_id: i32);
+ fn unregister_battery_callback(&mut self, callback_id: u32);
+
+ /// Enables notifications for a given callback.
+ fn enable_notifications(&mut self, callback_id: u32, enable: bool);
/// Returns battery information for the remote, sourced from the highest priority origin.
- fn get_battery_information(&self, remote_address: String) -> Battery;
+ fn get_battery_information(&self, remote_address: String) -> Option<Battery>;
}
-impl IBatteryManager for BatteryManager {
- fn register_battery_callback(
- &mut self,
+/// Repesentation of the BatteryManager.
+pub struct BatteryManager {
+ bas: Arc<Mutex<Box<BatteryService>>>,
+ callbacks: Callbacks<dyn IBatteryManagerCallback + Send>,
+ /// List of callback IDs that have enabled notifications.
+ notifications_enabled: HashSet<u32>,
+}
+
+impl BatteryManager {
+ /// Construct a new BatteryManager with callbacks communicating on tx.
+ pub fn new(bas: Arc<Mutex<Box<BatteryService>>>, tx: Sender<Message>) -> BatteryManager {
+ let callbacks = Callbacks::new(tx.clone(), Message::BatteryManagerCallbackDisconnected);
+ let notifications_enabled = HashSet::new();
+ Self { bas, callbacks, notifications_enabled }
+ }
+
+ /// Invoked after BAS has been initialized.
+ pub fn init(&self) {
+ self.bas.lock().unwrap().register_callback(Box::new(BasCallback::new()));
+ }
+
+ /// Remove a callback due to disconnection or unregistration.
+ pub fn remove_callback(&mut self, callback_id: u32) {
+ self.callbacks.remove_callback(callback_id);
+ }
+}
+
+struct BasCallback {}
+
+impl BasCallback {
+ pub fn new() -> BasCallback {
+ Self {}
+ }
+}
+
+impl IBatteryServiceCallback for BasCallback {
+ fn on_battery_service_status_updated(
+ &self,
_remote_address: String,
- _battery_manager_callback: Box<dyn IBatteryManagerCallback + Send>,
- ) -> i32 {
+ _status: BatteryServiceStatus,
+ ) {
todo!()
}
- fn unregister_battery_callback(&mut self, _callback_id: i32) {
+ fn on_battery_level_updated(&self, _remote_address: String, _battery_level: u32) {
todo!()
}
- fn get_battery_information(&self, _remote_address: String) -> Battery {
+ fn on_battery_level_read(&self, _remote_address: String, _battery_level: u32) {
todo!()
}
}
+
+impl RPCProxy for BasCallback {
+ fn get_object_id(&self) -> String {
+ "BAS Callback".to_string()
+ }
+}
+
+impl IBatteryManager for BatteryManager {
+ fn register_battery_callback(
+ &mut self,
+ battery_manager_callback: Box<dyn IBatteryManagerCallback + Send>,
+ ) -> u32 {
+ self.callbacks.add_callback(battery_manager_callback)
+ }
+
+ fn unregister_battery_callback(&mut self, callback_id: u32) {
+ self.remove_callback(callback_id);
+ }
+
+ fn enable_notifications(&mut self, callback_id: u32, enable: bool) {
+ if self.callbacks.get_by_id(callback_id).is_none() {
+ return;
+ }
+ self.notifications_enabled.remove(&callback_id);
+ if enable {
+ self.notifications_enabled.insert(callback_id);
+ }
+ }
+
+ // TODO(b/233101174): update to use all available sources once
+ // BatteryProviderManager is implemented.
+ fn get_battery_information(&self, remote_address: String) -> Option<Battery> {
+ let battery_level = self.bas.lock().unwrap().get_battery_level(remote_address)?;
+ Some(Battery {
+ percentage: battery_level,
+ source_info: "BAS".to_string(),
+ variant: "".to_string(),
+ })
+ }
+}
diff --git a/system/gd/rust/linux/stack/src/battery_service.rs b/system/gd/rust/linux/stack/src/battery_service.rs
new file mode 100644
index 0000000000..3fe9a9bb2e
--- /dev/null
+++ b/system/gd/rust/linux/stack/src/battery_service.rs
@@ -0,0 +1,397 @@
+use crate::bluetooth_gatt::{
+ BluetoothGatt, BluetoothGattService, IBluetoothGatt, IBluetoothGattCallback,
+};
+use crate::callbacks::Callbacks;
+use crate::uuid;
+use crate::uuid::parse_uuid_string;
+use crate::Message;
+use crate::RPCProxy;
+use bt_topshim::btif::BtTransport;
+use bt_topshim::profiles::gatt::{GattStatus, LePhy};
+use log::debug;
+use std::collections::{HashMap, HashSet};
+use std::convert::TryInto;
+use std::iter;
+use std::sync::{Arc, Mutex};
+use tokio::sync::mpsc::Sender;
+
+/// The UUID corresponding to the BatteryLevel characteristic defined
+/// by the BatteryService specification.
+pub const CHARACTERISTIC_BATTERY_LEVEL: &str = "00002A1900001000800000805F9B34FB";
+
+/// Represents the Floss BatteryService implementation.
+pub struct BatteryService {
+ gatt: Arc<Mutex<Box<BluetoothGatt>>>,
+ /// Sender for callback communication with the main thread.
+ tx: Sender<Message>,
+ callbacks: Callbacks<dyn IBatteryServiceCallback + Send>,
+ /// The GATT client ID needed for GATT calls.
+ client_id: Option<i32>,
+ /// Cached battery levels keyed by remote device.
+ battery_levels: HashMap<String, u32>,
+ /// Callback IDs that have enabled notifications.
+ notifications_enabled: HashSet<u32>,
+ /// Found handles for battery levels. Required for faster
+ /// refreshes than initiating another search.
+ handles: HashMap<String, i32>,
+}
+
+/// Enum for GATT callbacks to relay messages to the main processing
+/// thread. Newly supported callbacks should add a corresponding entry
+/// here.
+pub enum GattBatteryCallbacks {
+ /// Params: status, client_id
+ OnClientRegistered(GattStatus, i32),
+ /// Params: status, client_id, connected, addr
+ OnClientConnectionState(GattStatus, i32, bool, String),
+ /// Params: addr, services, status
+ OnSearchComplete(String, Vec<BluetoothGattService>, GattStatus),
+ /// Params: addr, status, handle, value
+ OnCharacteristicRead(String, GattStatus, i32, Vec<u8>),
+ /// Params: addr, handle, value
+ OnNotify(String, i32, Vec<u8>),
+}
+
+/// API for Floss implementation of the Bluetooth Battery Service
+/// (BAS). BAS is built on GATT and this implementation wraps all of
+/// the GATT calls and handles tracking battery information for the
+/// client.
+pub trait IBatteryService {
+ /// Registers a callback for interacting with BatteryService.
+ fn register_callback(&mut self, callback: Box<dyn IBatteryServiceCallback + Send>) -> u32;
+
+ /// Unregisters a callback.
+ fn unregister_callback(&mut self, callback_id: u32);
+
+ /// Enables notifications for a given callback.
+ fn enable_notifications(&mut self, callback_id: u32, enable: bool);
+
+ /// Returns the battery level of the remove device if available in
+ /// BatteryService's cache. Call refresh_battery_level at least
+ /// once to ensure that BatteryService is tracking the device's
+ /// battery information.
+ fn get_battery_level(&self, remote_address: String) -> Option<u32>;
+
+ /// Forces an explicit read of the device's battery level,
+ /// including initiating battery level tracking if not yet
+ /// performed.
+ fn refresh_battery_level(&self, remote_address: String) -> bool;
+}
+
+/// Callback for interacting with BAS.
+pub trait IBatteryServiceCallback: RPCProxy {
+ /// Called when the status of BatteryService has changed. Trying
+ /// to read from devices that do not support BAS will result in
+ /// this method being called with BatteryServiceNotSupported.
+ fn on_battery_service_status_updated(
+ &self,
+ remote_address: String,
+ status: BatteryServiceStatus,
+ );
+
+ /// Invoked when battery level for a device has been changed due to notification.
+ fn on_battery_level_updated(&self, remote_address: String, battery_level: u32);
+
+ /// Invoked whenever an explicit read of a devices battery level completes.
+ fn on_battery_level_read(&self, remote_address: String, battery_level: u32);
+}
+
+impl BatteryService {
+ /// Construct a new BatteryService with callbacks relaying messages through tx.
+ pub fn new(gatt: Arc<Mutex<Box<BluetoothGatt>>>, tx: Sender<Message>) -> BatteryService {
+ let tx = tx.clone();
+ let callbacks = Callbacks::new(tx.clone(), Message::BatteryServiceCallbackDisconnected);
+ let client_id = None;
+ let battery_levels = HashMap::new();
+ let notifications_enabled = HashSet::new();
+ let handles = HashMap::new();
+ Self { gatt, tx, callbacks, client_id, battery_levels, notifications_enabled, handles }
+ }
+
+ /// Must be called after BluetoothGatt's init_profiles method has completed.
+ pub fn init(&self) {
+ self.gatt.lock().unwrap().register_client(
+ // TODO(b/233101174): make dynamic or decide on a static UUID
+ String::from("e4d2acffcfaa42198f494606b7412117"),
+ Box::new(GattCallback::new(self.tx.clone())),
+ false,
+ );
+ }
+
+ /// Handles all callback messages in a central location to avoid deadlocks.
+ pub fn handle_callback(&mut self, callback: GattBatteryCallbacks) {
+ match callback {
+ GattBatteryCallbacks::OnClientRegistered(_status, client_id) => {
+ self.client_id = Some(client_id);
+ }
+
+ GattBatteryCallbacks::OnClientConnectionState(_status, _client_id, connected, addr) => {
+ if !connected {
+ return;
+ }
+ let client_id = match self.client_id {
+ Some(id) => id,
+ None => {
+ return;
+ }
+ };
+ self.gatt.lock().unwrap().discover_services(client_id, addr);
+ }
+
+ GattBatteryCallbacks::OnSearchComplete(addr, services, status) => {
+ if status != GattStatus::Success {
+ debug!("GATT service discovery for {} failed with status {:?}", addr, status);
+ return;
+ }
+ let (bas_uuid, battery_level_uuid) = match (
+ parse_uuid_string(uuid::BAS),
+ parse_uuid_string(CHARACTERISTIC_BATTERY_LEVEL),
+ ) {
+ (Some(bas_uuid), Some(battery_level_uuid)) => (bas_uuid, battery_level_uuid),
+ _ => return,
+ };
+ // TODO(b/233101174): handle multiple instances of BAS
+ let bas = match services.iter().find(|service| service.uuid == bas_uuid.uu) {
+ Some(bas) => bas,
+ None => {
+ self.callbacks.for_all_callbacks(|callback| {
+ callback.on_battery_service_status_updated(
+ addr.clone(),
+ BatteryServiceStatus::BatteryServiceNotSupported,
+ )
+ });
+ return;
+ }
+ };
+ let battery_level = match bas
+ .characteristics
+ .iter()
+ .find(|characteristic| characteristic.uuid == battery_level_uuid.uu)
+ {
+ Some(battery_level) => battery_level,
+ None => {
+ debug!("Device {} has no BatteryLevel characteristic", addr);
+ return;
+ }
+ };
+ let client_id = match self.client_id {
+ Some(id) => id,
+ None => return,
+ };
+ let handle = battery_level.instance_id;
+ self.handles.insert(addr.clone(), handle.clone());
+ self.gatt.lock().unwrap().register_for_notification(
+ client_id,
+ addr.clone(),
+ handle,
+ true,
+ );
+ if let None = self.battery_levels.get(&addr) {
+ self.gatt.lock().unwrap().read_characteristic(
+ client_id,
+ addr,
+ battery_level.instance_id,
+ 0,
+ );
+ }
+ }
+
+ GattBatteryCallbacks::OnCharacteristicRead(addr, status, _handle, value) => {
+ if status != GattStatus::Success {
+ return;
+ }
+ let level = self.set_battery_level(addr.clone(), value.clone());
+ self.callbacks.for_all_callbacks(|callback| {
+ callback.on_battery_level_read(addr.clone(), level);
+ });
+ }
+
+ GattBatteryCallbacks::OnNotify(addr, _handle, value) => {
+ let level = self.set_battery_level(addr.clone(), value);
+ // TODO(b/247551256): expand Callbacks to allow direct
+ // filtering/exposing the underlying iter
+ let to_notify = self.notifications_enabled.clone();
+ to_notify.iter().for_each(|id| match self.callbacks.get_by_id(*id) {
+ Some(callback) => callback.on_battery_level_updated(addr.clone(), level),
+ None => (),
+ });
+ }
+ }
+ }
+
+ fn set_battery_level(&mut self, remote_address: String, value: Vec<u8>) -> u32 {
+ let level: Vec<_> = value.iter().cloned().chain(iter::repeat(0 as u8)).take(4).collect();
+ let level = u32::from_le_bytes(level.try_into().unwrap());
+ self.battery_levels.insert(remote_address, level);
+ level
+ }
+
+ fn init_device(&self, remote_address: String) {
+ let client_id = match self.client_id {
+ Some(id) => id,
+ None => return,
+ };
+ self.gatt.lock().unwrap().client_connect(
+ client_id,
+ remote_address,
+ false,
+ BtTransport::Le,
+ false,
+ LePhy::Phy1m,
+ );
+ }
+
+ /// Remove a callback due to disconnection or unregistration.
+ pub fn remove_callback(&mut self, callback_id: u32) {
+ self.callbacks.remove_callback(callback_id);
+ }
+}
+
+/// Status enum for relaying the state of BAS or a particular device.
+pub enum BatteryServiceStatus {
+ /// Device does not report support for BAS.
+ BatteryServiceNotSupported,
+}
+
+impl IBatteryService for BatteryService {
+ fn register_callback(&mut self, callback: Box<dyn IBatteryServiceCallback + Send>) -> u32 {
+ self.callbacks.add_callback(callback)
+ }
+
+ fn unregister_callback(&mut self, callback_id: u32) {
+ self.remove_callback(callback_id);
+ }
+
+ fn enable_notifications(&mut self, callback_id: u32, enable: bool) {
+ if self.callbacks.get_by_id(callback_id).is_none() {
+ return;
+ }
+ self.notifications_enabled.remove(&callback_id);
+ if enable {
+ self.notifications_enabled.insert(callback_id);
+ }
+ }
+
+ fn get_battery_level(&self, remote_address: String) -> Option<u32> {
+ self.battery_levels.get(&remote_address).cloned()
+ }
+
+ fn refresh_battery_level(&self, remote_address: String) -> bool {
+ let client_id = match self.client_id {
+ Some(id) => id,
+ None => return false,
+ };
+ let handle = match self.handles.get(&remote_address) {
+ Some(id) => *id,
+ None => {
+ self.init_device(remote_address);
+ return true;
+ }
+ };
+ self.gatt.lock().unwrap().read_characteristic(client_id, remote_address.clone(), handle, 0);
+ self.gatt.lock().unwrap().register_for_notification(
+ client_id,
+ remote_address,
+ handle,
+ true,
+ );
+ true
+ }
+}
+
+struct GattCallback {
+ tx: Sender<Message>,
+}
+
+impl GattCallback {
+ fn new(tx: Sender<Message>) -> Self {
+ Self { tx }
+ }
+}
+
+impl IBluetoothGattCallback for GattCallback {
+ // All callback methods relay messages through the stack receiver
+ // to allow BAS to operate on requests serially. This reduces
+ // overall complexity including removing the need to share state
+ // data with callbacks.
+
+ fn on_client_registered(&self, status: GattStatus, client_id: i32) {
+ let tx = self.tx.clone();
+ tokio::spawn(async move {
+ let _ = tx
+ .send(Message::BatteryServiceCallbacks(GattBatteryCallbacks::OnClientRegistered(
+ status, client_id,
+ )))
+ .await;
+ });
+ }
+
+ fn on_client_connection_state(
+ &self,
+ status: GattStatus,
+ client_id: i32,
+ connected: bool,
+ addr: String,
+ ) {
+ let tx = self.tx.clone();
+ tokio::spawn(async move {
+ let _ = tx
+ .send(Message::BatteryServiceCallbacks(
+ GattBatteryCallbacks::OnClientConnectionState(
+ status, client_id, connected, addr,
+ ),
+ ))
+ .await;
+ });
+ }
+
+ fn on_search_complete(
+ &self,
+ addr: String,
+ services: Vec<BluetoothGattService>,
+ status: GattStatus,
+ ) {
+ let tx = self.tx.clone();
+ tokio::spawn(async move {
+ let _ = tx
+ .send(Message::BatteryServiceCallbacks(GattBatteryCallbacks::OnSearchComplete(
+ addr, services, status,
+ )))
+ .await;
+ });
+ }
+
+ fn on_characteristic_read(
+ &self,
+ addr: String,
+ status: GattStatus,
+ handle: i32,
+ value: Vec<u8>,
+ ) {
+ let tx = self.tx.clone();
+ tokio::spawn(async move {
+ let _ = tx
+ .send(Message::BatteryServiceCallbacks(GattBatteryCallbacks::OnCharacteristicRead(
+ addr, status, handle, value,
+ )))
+ .await;
+ });
+ }
+
+ fn on_notify(&self, addr: String, handle: i32, value: Vec<u8>) {
+ let tx = self.tx.clone();
+ tokio::spawn(async move {
+ let _ = tx
+ .send(Message::BatteryServiceCallbacks(GattBatteryCallbacks::OnNotify(
+ addr, handle, value,
+ )))
+ .await;
+ });
+ }
+}
+
+impl RPCProxy for GattCallback {
+ fn get_object_id(&self) -> String {
+ "BAS Gatt Callback".to_string()
+ }
+}
diff --git a/system/gd/rust/linux/stack/src/bluetooth.rs b/system/gd/rust/linux/stack/src/bluetooth.rs
index 8dea15126f..67a70cc7ab 100644
--- a/system/gd/rust/linux/stack/src/bluetooth.rs
+++ b/system/gd/rust/linux/stack/src/bluetooth.rs
@@ -22,8 +22,7 @@ use log::{debug, error, warn};
use num_traits::cast::ToPrimitive;
use std::collections::HashMap;
use std::collections::VecDeque;
-use std::sync::Arc;
-use std::sync::Mutex;
+use std::sync::{Arc, Condvar, Mutex};
use std::time::Duration;
use std::time::Instant;
use tokio::sync::mpsc::Sender;
@@ -157,6 +156,9 @@ pub trait IBluetooth {
/// Gets the class of the remote device.
fn get_remote_class(&self, device: BluetoothDevice) -> u32;
+ /// Gets the appearance of the remote device.
+ fn get_remote_appearance(&self, device: BluetoothDevice) -> u16;
+
/// Gets whether the remote device is connected.
fn get_remote_connected(&self, device: BluetoothDevice) -> bool;
@@ -352,6 +354,9 @@ pub struct Bluetooth {
// Internal API members
internal_le_rand_queue: VecDeque<OneShotSender<u64>>,
discoverable_timeout: Option<JoinHandle<()>>,
+
+ /// Used to notify signal handler that we have turned off the stack.
+ sig_notifier: Arc<(Mutex<bool>, Condvar)>,
}
impl Bluetooth {
@@ -360,6 +365,7 @@ impl Bluetooth {
tx: Sender<Message>,
intf: Arc<Mutex<BluetoothInterface>>,
bluetooth_media: Arc<Mutex<Box<BluetoothMedia>>>,
+ sig_notifier: Arc<(Mutex<bool>, Condvar)>,
) -> Bluetooth {
Bluetooth {
bonded_devices: HashMap::new(),
@@ -387,6 +393,7 @@ impl Bluetooth {
// Internal API members
internal_le_rand_queue: VecDeque::<OneShotSender<u64>>::new(),
discoverable_timeout: None,
+ sig_notifier,
}
}
@@ -697,6 +704,10 @@ impl BtifBluetoothCallbacks for Bluetooth {
if self.state == BtState::Off {
self.properties.clear();
+
+ // Let the signal notifier know we are turned off.
+ *self.sig_notifier.0.lock().unwrap() = false;
+ self.sig_notifier.1.notify_all();
} else {
// Trigger properties update
self.intf.lock().unwrap().get_adapter_properties();
@@ -706,6 +717,10 @@ impl BtifBluetoothCallbacks for Bluetooth {
// Ensure device is connectable so that disconnected device can reconnect
self.set_connectable(true);
+
+ // Notify the signal notifier that we are turned on.
+ *self.sig_notifier.0.lock().unwrap() = true;
+ self.sig_notifier.1.notify_all();
}
}
@@ -1215,7 +1230,11 @@ impl IBluetooth for Bluetooth {
}
let address = addr.unwrap();
- let device_type = self.get_remote_type(device);
+ let device_type = match transport {
+ BtTransport::Bredr => BtDeviceType::Bredr,
+ BtTransport::Le => BtDeviceType::Ble,
+ _ => self.get_remote_type(device),
+ };
// We explicitly log the attempt to start the bonding separate from logging the bond state.
// The start of the attempt is critical to help identify a bonding/pairing session.
@@ -1401,6 +1420,13 @@ impl IBluetooth for Bluetooth {
}
}
+ fn get_remote_appearance(&self, device: BluetoothDevice) -> u16 {
+ match self.get_remote_device_property(&device, &BtPropertyType::Appearance) {
+ Some(BluetoothProperty::Appearance(appearance)) => appearance,
+ _ => 0,
+ }
+ }
+
fn get_remote_connected(&self, device: BluetoothDevice) -> bool {
self.get_connection_state(device) != BtConnectionState::NotConnected
}
diff --git a/system/gd/rust/linux/stack/src/bluetooth_gatt.rs b/system/gd/rust/linux/stack/src/bluetooth_gatt.rs
index 00ffd41973..f0fb53dc56 100644
--- a/system/gd/rust/linux/stack/src/bluetooth_gatt.rs
+++ b/system/gd/rust/linux/stack/src/bluetooth_gatt.rs
@@ -499,67 +499,84 @@ impl BluetoothGattService {
/// Callback for GATT Client API.
pub trait IBluetoothGattCallback: RPCProxy {
/// When the `register_client` request is done.
- fn on_client_registered(&self, status: GattStatus, client_id: i32);
+ fn on_client_registered(&self, _status: GattStatus, _client_id: i32) {}
/// When there is a change in the state of a GATT client connection.
fn on_client_connection_state(
&self,
- status: GattStatus,
- client_id: i32,
- connected: bool,
- addr: String,
- );
+ _status: GattStatus,
+ _client_id: i32,
+ _connected: bool,
+ _addr: String,
+ ) {
+ }
/// When there is a change of PHY.
- fn on_phy_update(&self, addr: String, tx_phy: LePhy, rx_phy: LePhy, status: GattStatus);
+ fn on_phy_update(&self, _addr: String, _tx_phy: LePhy, _rx_phy: LePhy, _status: GattStatus) {}
/// The completion of IBluetoothGatt::read_phy.
- fn on_phy_read(&self, addr: String, tx_phy: LePhy, rx_phy: LePhy, status: GattStatus);
+ fn on_phy_read(&self, _addr: String, _tx_phy: LePhy, _rx_phy: LePhy, _status: GattStatus) {}
/// When GATT db is available.
fn on_search_complete(
&self,
- addr: String,
- services: Vec<BluetoothGattService>,
- status: GattStatus,
- );
+ _addr: String,
+ _services: Vec<BluetoothGattService>,
+ _status: GattStatus,
+ ) {
+ }
/// The completion of IBluetoothGatt::read_characteristic.
- fn on_characteristic_read(&self, addr: String, status: GattStatus, handle: i32, value: Vec<u8>);
+ fn on_characteristic_read(
+ &self,
+ _addr: String,
+ _status: GattStatus,
+ _handle: i32,
+ _value: Vec<u8>,
+ ) {
+ }
/// The completion of IBluetoothGatt::write_characteristic.
- fn on_characteristic_write(&self, addr: String, status: GattStatus, handle: i32);
+ fn on_characteristic_write(&self, _addr: String, _status: GattStatus, _handle: i32) {}
/// When a reliable write is completed.
- fn on_execute_write(&self, addr: String, status: GattStatus);
+ fn on_execute_write(&self, _addr: String, _status: GattStatus) {}
/// The completion of IBluetoothGatt::read_descriptor.
- fn on_descriptor_read(&self, addr: String, status: GattStatus, handle: i32, value: Vec<u8>);
+ fn on_descriptor_read(
+ &self,
+ _addr: String,
+ _status: GattStatus,
+ _handle: i32,
+ _value: Vec<u8>,
+ ) {
+ }
/// The completion of IBluetoothGatt::write_descriptor.
- fn on_descriptor_write(&self, addr: String, status: GattStatus, handle: i32);
+ fn on_descriptor_write(&self, _addr: String, _status: GattStatus, _handle: i32) {}
/// When notification or indication is received.
- fn on_notify(&self, addr: String, handle: i32, value: Vec<u8>);
+ fn on_notify(&self, _addr: String, _handle: i32, _value: Vec<u8>) {}
/// The completion of IBluetoothGatt::read_remote_rssi.
- fn on_read_remote_rssi(&self, addr: String, rssi: i32, status: GattStatus);
+ fn on_read_remote_rssi(&self, _addr: String, _rssi: i32, _status: GattStatus) {}
/// The completion of IBluetoothGatt::configure_mtu.
- fn on_configure_mtu(&self, addr: String, mtu: i32, status: GattStatus);
+ fn on_configure_mtu(&self, _addr: String, _mtu: i32, _status: GattStatus) {}
/// When a connection parameter changes.
fn on_connection_updated(
&self,
- addr: String,
- interval: i32,
- latency: i32,
- timeout: i32,
- status: GattStatus,
- );
+ _addr: String,
+ _interval: i32,
+ _latency: i32,
+ _timeout: i32,
+ _status: GattStatus,
+ ) {
+ }
/// When there is an addition, removal, or change of a GATT service.
- fn on_service_changed(&self, addr: String);
+ fn on_service_changed(&self, _addr: String) {}
}
/// Interface for scanner callbacks to clients, passed to
@@ -1160,9 +1177,19 @@ impl IBluetoothGatt for BluetoothGatt {
callback: Box<dyn IBluetoothGattCallback + Send>,
eatt_support: bool,
) {
- let uuid = parse_uuid_string(app_uuid).unwrap();
+ let uuid = match parse_uuid_string(&app_uuid) {
+ Some(id) => id,
+ None => {
+ log::info!("Uuid is malformed: {}", app_uuid);
+ return;
+ }
+ };
self.context_map.add(&uuid.uu, callback);
- self.gatt.as_ref().unwrap().client.register_client(&uuid, eatt_support);
+ self.gatt
+ .as_ref()
+ .expect("GATT has not been initialized")
+ .client
+ .register_client(&uuid, eatt_support);
}
fn unregister_client(&mut self, client_id: i32) {
diff --git a/system/gd/rust/linux/stack/src/bluetooth_media.rs b/system/gd/rust/linux/stack/src/bluetooth_media.rs
index b065fa0bc7..0672126516 100644
--- a/system/gd/rust/linux/stack/src/bluetooth_media.rs
+++ b/system/gd/rust/linux/stack/src/bluetooth_media.rs
@@ -1,6 +1,6 @@
//! Anything related to audio and media API.
-use bt_topshim::btif::{BluetoothInterface, RawAddress};
+use bt_topshim::btif::{BluetoothInterface, BtStatus, RawAddress};
use bt_topshim::profiles::a2dp::{
A2dp, A2dpCallbacks, A2dpCallbacksDispatcher, A2dpCodecBitsPerSample, A2dpCodecChannelMode,
A2dpCodecConfig, A2dpCodecSampleRate, BtavAudioState, BtavConnectionState,
@@ -11,7 +11,7 @@ use bt_topshim::profiles::hfp::{
BthfAudioState, BthfConnectionState, Hfp, HfpCallbacks, HfpCallbacksDispatcher,
HfpCodecCapability,
};
-use bt_topshim::topstack;
+use bt_topshim::{metrics, topstack};
use bt_utils::uinput::UInput;
use log::{info, warn};
@@ -28,6 +28,7 @@ use tokio::time::{sleep, Duration, Instant};
use crate::bluetooth::{Bluetooth, BluetoothDevice, IBluetooth};
use crate::callbacks::Callbacks;
use crate::uuid;
+use crate::uuid::Profile;
use crate::{Message, RPCProxy};
// The timeout we have to wait for all supported profiles to connect after we
@@ -192,48 +193,75 @@ impl BluetoothMedia {
}
}
+ fn is_profile_connected(&self, addr: RawAddress, profile: uuid::Profile) -> bool {
+ if let Some(connected_profiles) = self.connected_profiles.get(&addr) {
+ return connected_profiles.contains(&profile);
+ }
+ return false;
+ }
+
+ fn add_connected_profile(&mut self, addr: RawAddress, profile: uuid::Profile) {
+ if self.is_profile_connected(addr, profile) {
+ warn!("[{}]: profile is already connected", addr.to_string());
+ return;
+ }
+
+ self.connected_profiles.entry(addr).or_insert_with(HashSet::new).insert(profile);
+
+ self.notify_media_capability_updated(addr);
+ }
+
+ fn rm_connected_profile(
+ &mut self,
+ addr: RawAddress,
+ profile: uuid::Profile,
+ is_profile_critical: bool,
+ ) {
+ if !self.is_profile_connected(addr, profile) {
+ warn!("[{}]: profile is already disconnected", addr.to_string());
+ return;
+ }
+
+ self.connected_profiles.entry(addr).or_insert_with(HashSet::new).remove(&profile);
+
+ if is_profile_critical {
+ self.notify_critical_profile_disconnected(addr);
+ }
+
+ self.notify_media_capability_updated(addr);
+ }
+
pub fn set_adapter(&mut self, adapter: Arc<Mutex<Box<Bluetooth>>>) {
self.adapter = Some(adapter);
}
pub fn dispatch_a2dp_callbacks(&mut self, cb: A2dpCallbacks) {
match cb {
- A2dpCallbacks::ConnectionState(addr, state) => {
+ A2dpCallbacks::ConnectionState(addr, state, error) => {
if !self.a2dp_states.get(&addr).is_none()
&& state == *self.a2dp_states.get(&addr).unwrap()
{
return;
}
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::A2dpSink as u32,
+ error.status,
+ state.clone() as u32,
+ );
match state {
BtavConnectionState::Connected => {
info!("[{}]: a2dp connected.", addr.to_string());
self.a2dp_states.insert(addr, state);
-
- self.connected_profiles
- .entry(addr)
- .or_insert_with(HashSet::new)
- .insert(uuid::Profile::A2dpSink);
-
- self.notify_media_capability_updated(addr);
+ self.add_connected_profile(addr, uuid::Profile::A2dpSink);
+ }
+ BtavConnectionState::Disconnected => {
+ info!("[{}]: a2dp disconnected.", addr.to_string());
+ self.a2dp_states.remove(&addr);
+ self.a2dp_caps.remove(&addr);
+ self.a2dp_audio_state.remove(&addr);
+ self.rm_connected_profile(addr, uuid::Profile::A2dpSink, true);
}
- BtavConnectionState::Disconnected => match self.a2dp_states.remove(&addr) {
- Some(_) => {
- info!("[{}]: a2dp disconnected.", addr.to_string());
- self.a2dp_caps.remove(&addr);
- self.a2dp_audio_state.remove(&addr);
-
- self.connected_profiles
- .entry(addr)
- .or_insert_with(HashSet::new)
- .remove(&uuid::Profile::A2dpSink);
-
- self.notify_critical_profile_disconnected(addr);
- self.notify_media_capability_updated(addr);
- }
- None => {
- warn!("[{}]: Unknown address a2dp disconnected.", addr.to_string());
- }
- },
_ => {
self.a2dp_states.insert(addr, state);
}
@@ -273,12 +301,14 @@ impl BluetoothMedia {
self.absolute_volume = supported;
- self.connected_profiles
- .entry(addr)
- .or_insert_with(HashSet::new)
- .insert(uuid::Profile::AvrcpController);
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::AvrcpController as u32,
+ BtStatus::Success,
+ BtavConnectionState::Connected as u32,
+ );
- self.notify_media_capability_updated(addr);
+ self.add_connected_profile(addr, uuid::Profile::AvrcpController);
}
AvrcpCallbacks::AvrcpDeviceDisconnected(addr) => {
info!("[{}]: avrcp disconnected.", addr.to_string());
@@ -288,18 +318,25 @@ impl BluetoothMedia {
// TODO: better support for multi-device
self.absolute_volume = false;
- self.connected_profiles
- .entry(addr)
- .or_insert_with(HashSet::new)
- .remove(&uuid::Profile::AvrcpController);
-
// This may be considered a critical profile in the extreme case
// where only AVRCP was connected.
- if self.connected_profiles.is_empty() {
- self.notify_critical_profile_disconnected(addr);
- }
+ let is_profile_critical = match self.connected_profiles.get(&addr) {
+ Some(profiles) => *profiles == HashSet::from([uuid::Profile::AvrcpController]),
+ None => false,
+ };
- self.notify_media_capability_updated(addr);
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::AvrcpController as u32,
+ BtStatus::Success,
+ BtavConnectionState::Disconnected as u32,
+ );
+
+ self.rm_connected_profile(
+ addr,
+ uuid::Profile::AvrcpController,
+ is_profile_critical,
+ );
}
AvrcpCallbacks::AvrcpAbsoluteVolumeUpdate(volume) => {
self.callbacks.lock().unwrap().for_all_callbacks(|callback| {
@@ -333,6 +370,12 @@ impl BluetoothMedia {
{
return;
}
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::Hfp as u32,
+ BtStatus::Success,
+ state.clone() as u32,
+ );
match state {
BthfConnectionState::Connected => {
info!("[{}]: hfp connected.", addr.to_string());
@@ -344,34 +387,14 @@ impl BluetoothMedia {
if !self.hfp_cap.contains_key(&addr) {
self.hfp_cap.insert(addr, HfpCodecCapability::CVSD);
}
-
- self.connected_profiles
- .entry(addr)
- .or_insert_with(HashSet::new)
- .insert(uuid::Profile::Hfp);
-
- self.notify_media_capability_updated(addr);
+ self.add_connected_profile(addr, uuid::Profile::Hfp);
}
BthfConnectionState::Disconnected => {
info!("[{}]: hfp disconnected.", addr.to_string());
- match self.hfp_states.remove(&addr) {
- Some(_) => {
- self.hfp_cap.remove(&addr);
- self.hfp_audio_state.remove(&addr);
-
- self.connected_profiles
- .entry(addr)
- .or_insert_with(HashSet::new)
- .remove(&uuid::Profile::Hfp);
-
- self.notify_critical_profile_disconnected(addr);
- self.notify_media_capability_updated(addr);
- }
- None => {
- warn!("[{}] Unknown address hfp disconnected.", addr.to_string())
- }
- }
- return;
+ self.hfp_states.remove(&addr);
+ self.hfp_cap.remove(&addr);
+ self.hfp_audio_state.remove(&addr);
+ self.rm_connected_profile(addr, uuid::Profile::Hfp, true);
}
BthfConnectionState::Connecting => {
info!("[{}]: hfp connecting.", addr.to_string());
@@ -725,24 +748,97 @@ impl IBluetoothMedia for BluetoothMedia {
for profile in missing_profiles {
match profile {
uuid::Profile::A2dpSink => {
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::A2dpSink as u32,
+ BtStatus::Success,
+ BtavConnectionState::Connecting as u32,
+ );
match self.a2dp.as_mut() {
- Some(a2dp) => a2dp.connect(addr),
- None => warn!("Uninitialized A2DP to connect {}", address),
+ Some(a2dp) => {
+ let status: BtStatus = a2dp.connect(addr);
+ if BtStatus::Success != status {
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::A2dpSink as u32,
+ status,
+ BtavConnectionState::Disconnected as u32,
+ );
+ }
+ }
+ None => {
+ warn!("Uninitialized A2DP to connect {}", address);
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::A2dpSink as u32,
+ BtStatus::NotReady,
+ BtavConnectionState::Disconnected as u32,
+ );
+ }
};
}
uuid::Profile::Hfp => {
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::Hfp as u32,
+ BtStatus::Success,
+ BtavConnectionState::Connecting as u32,
+ );
match self.hfp.as_mut() {
- Some(hfp) => hfp.connect(addr),
- None => warn!("Uninitialized HFP to connect {}", address),
+ Some(hfp) => {
+ let status: BtStatus = hfp.connect(addr);
+ if BtStatus::Success != status {
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::Hfp as u32,
+ status,
+ BthfConnectionState::Disconnected as u32,
+ );
+ }
+ }
+ None => {
+ warn!("Uninitialized HFP to connect {}", address);
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::Hfp as u32,
+ BtStatus::NotReady,
+ BthfConnectionState::Disconnected as u32,
+ );
+ }
};
}
uuid::Profile::AvrcpController => {
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::AvrcpController as u32,
+ BtStatus::Success,
+ BtavConnectionState::Connecting as u32,
+ );
match self.avrcp.as_mut() {
- Some(avrcp) => avrcp.connect(addr),
- None => warn!("Uninitialized AVRCP to connect {}", address),
+ Some(avrcp) => {
+ let status: BtStatus = avrcp.connect(addr);
+ if BtStatus::Success != status {
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::AvrcpController as u32,
+ status,
+ BtavConnectionState::Disconnected as u32,
+ );
+ }
+ }
+
+ None => {
+ warn!("Uninitialized AVRCP to connect {}", address);
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::AvrcpController as u32,
+ BtStatus::NotReady,
+ BtavConnectionState::Disconnected as u32,
+ );
+ }
};
}
- _ => warn!("Unknown profile."),
+ _ => warn!("Unknown profile: {:?}", profile),
}
}
}
@@ -774,24 +870,97 @@ impl IBluetoothMedia for BluetoothMedia {
for profile in connected_profiles {
match profile {
uuid::Profile::A2dpSink => {
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::A2dpSink as u32,
+ BtStatus::Success,
+ BtavConnectionState::Disconnecting as u32,
+ );
match self.a2dp.as_mut() {
- Some(a2dp) => a2dp.disconnect(addr),
- None => warn!("Uninitialized A2DP to disconnect {}", address),
+ Some(a2dp) => {
+ let status: BtStatus = a2dp.disconnect(addr);
+ if BtStatus::Success != status {
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::A2dpSource as u32,
+ status,
+ BtavConnectionState::Disconnected as u32,
+ );
+ }
+ }
+ None => {
+ warn!("Uninitialized A2DP to disconnect {}", address);
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::A2dpSource as u32,
+ BtStatus::NotReady,
+ BtavConnectionState::Disconnected as u32,
+ );
+ }
};
}
uuid::Profile::Hfp => {
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::Hfp as u32,
+ BtStatus::Success,
+ BthfConnectionState::Disconnecting as u32,
+ );
match self.hfp.as_mut() {
- Some(hfp) => hfp.disconnect(addr),
- None => warn!("Uninitialized HFP to disconnect {}", address),
+ Some(hfp) => {
+ let status: BtStatus = hfp.disconnect(addr);
+ if BtStatus::Success != status {
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::Hfp as u32,
+ status,
+ BthfConnectionState::Disconnected as u32,
+ );
+ }
+ }
+ None => {
+ warn!("Uninitialized HFP to disconnect {}", address);
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::Hfp as u32,
+ BtStatus::NotReady,
+ BthfConnectionState::Disconnected as u32,
+ );
+ }
};
}
uuid::Profile::AvrcpController => {
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::AvrcpController as u32,
+ BtStatus::Success,
+ BtavConnectionState::Disconnecting as u32,
+ );
match self.avrcp.as_mut() {
- Some(avrcp) => avrcp.disconnect(addr),
- None => warn!("Uninitialized AVRCP to disconnect {}", address),
+ Some(avrcp) => {
+ let status: BtStatus = avrcp.disconnect(addr);
+ if BtStatus::Success != status {
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::AvrcpController as u32,
+ status,
+ BtavConnectionState::Disconnected as u32,
+ );
+ }
+ }
+
+ None => {
+ warn!("Uninitialized AVRCP to disconnect {}", address);
+ metrics::profile_connection_state_changed(
+ addr,
+ Profile::AvrcpController as u32,
+ BtStatus::NotReady,
+ BtavConnectionState::Disconnected as u32,
+ );
+ }
};
}
- _ => warn!("Unknown profile."),
+ _ => warn!("Unknown profile: {:?}", profile),
}
}
}
diff --git a/system/gd/rust/linux/stack/src/lib.rs b/system/gd/rust/linux/stack/src/lib.rs
index 31b3975ef1..606eb99206 100644
--- a/system/gd/rust/linux/stack/src/lib.rs
+++ b/system/gd/rust/linux/stack/src/lib.rs
@@ -8,6 +8,7 @@ extern crate num_derive;
pub mod battery_manager;
pub mod battery_provider_manager;
+pub mod battery_service;
pub mod bluetooth;
pub mod bluetooth_adv;
pub mod bluetooth_gatt;
@@ -22,7 +23,9 @@ use std::sync::{Arc, Mutex};
use tokio::sync::mpsc::channel;
use tokio::sync::mpsc::{Receiver, Sender};
-use crate::bluetooth::Bluetooth;
+use crate::battery_manager::BatteryManager;
+use crate::battery_service::{BatteryService, GattBatteryCallbacks};
+use crate::bluetooth::{Bluetooth, IBluetooth};
use crate::bluetooth_gatt::BluetoothGatt;
use crate::bluetooth_media::{BluetoothMedia, MediaActions};
use crate::socket_manager::{BluetoothSocketManager, SocketActions};
@@ -39,6 +42,9 @@ use bt_topshim::{
/// Message types that are sent to the stack main dispatch loop.
pub enum Message {
+ // Shuts down the stack.
+ Shutdown,
+
// Callbacks from libbluetooth
A2dp(A2dpCallbacks),
Avrcp(AvrcpCallbacks),
@@ -77,6 +83,11 @@ pub enum Message {
SocketManagerActions(SocketActions),
SocketManagerCallbackDisconnected(u32),
+ // Battery related
+ BatteryServiceCallbackDisconnected(u32),
+ BatteryServiceCallbacks(GattBatteryCallbacks),
+ BatteryManagerCallbackDisconnected(u32),
+
GattClientCallbackDisconnected(u32),
}
@@ -106,6 +117,8 @@ impl Stack {
mut rx: Receiver<Message>,
bluetooth: Arc<Mutex<Box<Bluetooth>>>,
bluetooth_gatt: Arc<Mutex<Box<BluetoothGatt>>>,
+ battery_service: Arc<Mutex<Box<BatteryService>>>,
+ battery_manager: Arc<Mutex<Box<BatteryManager>>>,
bluetooth_media: Arc<Mutex<Box<BluetoothMedia>>>,
suspend: Arc<Mutex<Box<Suspend>>>,
bluetooth_socketmgr: Arc<Mutex<Box<BluetoothSocketManager>>>,
@@ -119,6 +132,10 @@ impl Stack {
}
match m.unwrap() {
+ Message::Shutdown => {
+ bluetooth.lock().unwrap().disable();
+ }
+
Message::A2dp(a) => {
bluetooth_media.lock().unwrap().dispatch_a2dp_callbacks(a);
}
@@ -210,6 +227,15 @@ impl Stack {
Message::SocketManagerCallbackDisconnected(id) => {
bluetooth_socketmgr.lock().unwrap().remove_callback(id);
}
+ Message::BatteryServiceCallbackDisconnected(id) => {
+ battery_service.lock().unwrap().remove_callback(id);
+ }
+ Message::BatteryServiceCallbacks(callback) => {
+ battery_service.lock().unwrap().handle_callback(callback);
+ }
+ Message::BatteryManagerCallbackDisconnected(id) => {
+ battery_manager.lock().unwrap().remove_callback(id);
+ }
Message::GattClientCallbackDisconnected(id) => {
bluetooth_gatt.lock().unwrap().remove_client_callback(id);
}
diff --git a/system/gd/rust/linux/stack/src/uuid.rs b/system/gd/rust/linux/stack/src/uuid.rs
index 6068405e1f..5c99d5292f 100644
--- a/system/gd/rust/linux/stack/src/uuid.rs
+++ b/system/gd/rust/linux/stack/src/uuid.rs
@@ -9,6 +9,7 @@ use bt_topshim::btif::{Uuid, Uuid128Bit};
pub const A2DP_SINK: &str = "0000110B-0000-1000-8000-00805F9B34FB";
pub const A2DP_SOURCE: &str = "0000110A-0000-1000-8000-00805F9B34FB";
pub const ADV_AUDIO_DIST: &str = "0000110D-0000-1000-8000-00805F9B34FB";
+pub const BAS: &str = "0000180F-0000-1000-8000-00805F9B34FB";
pub const HSP: &str = "00001108-0000-1000-8000-00805F9B34FB";
pub const HSP_AG: &str = "00001112-0000-1000-8000-00805F9B34FB";
pub const HFP: &str = "0000111E-0000-1000-8000-00805F9B34FB";
@@ -43,6 +44,7 @@ pub enum Profile {
A2dpSink,
A2dpSource,
AdvAudioDist,
+ Bas,
Hsp,
HspAg,
Hfp,
@@ -219,6 +221,11 @@ impl UuidHelper {
pub fn parse_uuid_string<T: Into<String>>(uuid: T) -> Option<Uuid> {
let uuid = uuid.into();
+ // Strip un-needed characters before parsing to handle the common
+ // case of including dashes in UUID strings. UUID expects only
+ // 0-9, a-f, A-F with no other characters. |is_digit| with radix
+ // 16 (hex) supports that exact behavior.
+ let uuid = uuid.chars().filter(|char| char.is_digit(16)).collect::<String>();
if uuid.len() != 32 {
return None;
}
@@ -226,11 +233,7 @@ pub fn parse_uuid_string<T: Into<String>>(uuid: T) -> Option<Uuid> {
let mut raw = [0; 16];
for i in 0..16 {
- let byte = u8::from_str_radix(&uuid[i * 2..i * 2 + 2], 16);
- if byte.is_err() {
- return None;
- }
- raw[i] = byte.unwrap();
+ raw[i] = u8::from_str_radix(&uuid[i * 2..i * 2 + 2], 16).ok()?;
}
Some(Uuid { uu: raw })
diff --git a/system/gd/rust/linux/utils/src/adv_parser.rs b/system/gd/rust/linux/utils/src/adv_parser.rs
index a319e71b2f..8f89fc0cfe 100644
--- a/system/gd/rust/linux/utils/src/adv_parser.rs
+++ b/system/gd/rust/linux/utils/src/adv_parser.rs
@@ -7,9 +7,13 @@ use bt_topshim::btif::Uuid128Bit;
// Advertising data types.
const FLAGS: u8 = 0x01;
+const COMPLETE_LIST_16_BIT_SERVICE_UUIDS: u8 = 0x03;
+const COMPLETE_LIST_32_BIT_SERVICE_UUIDS: u8 = 0x05;
const COMPLETE_LIST_128_BIT_SERVICE_UUIDS: u8 = 0x07;
const SHORTENED_LOCAL_NAME: u8 = 0x08;
const COMPLETE_LOCAL_NAME: u8 = 0x09;
+const SERVICE_DATA_16_BIT_UUID: u8 = 0x16;
+const SERVICE_DATA_32_BIT_UUID: u8 = 0x20;
const SERVICE_DATA_128_BIT_UUID: u8 = 0x21;
const MANUFACTURER_SPECIFIC_DATA: u8 = 0xff;
@@ -52,9 +56,19 @@ pub fn extract_flags(bytes: &[u8]) -> u8 {
// Helper function to extract service uuids (128bit) from advertising data
pub fn extract_service_uuids(bytes: &[u8]) -> Vec<Uuid128Bit> {
- iterate_adv_data(bytes, COMPLETE_LIST_128_BIT_SERVICE_UUIDS)
- .flat_map(|slice| slice.chunks(16))
+ iterate_adv_data(bytes, COMPLETE_LIST_16_BIT_SERVICE_UUIDS)
+ .flat_map(|slice| slice.chunks(2))
.filter_map(|chunk| Uuid::try_from(chunk.to_vec()).ok().map(|uuid| uuid.uu))
+ .chain(
+ iterate_adv_data(bytes, COMPLETE_LIST_32_BIT_SERVICE_UUIDS)
+ .flat_map(|slice| slice.chunks(4))
+ .filter_map(|chunk| Uuid::try_from(chunk.to_vec()).ok().map(|uuid| uuid.uu)),
+ )
+ .chain(
+ iterate_adv_data(bytes, COMPLETE_LIST_128_BIT_SERVICE_UUIDS)
+ .flat_map(|slice| slice.chunks(16))
+ .filter_map(|chunk| Uuid::try_from(chunk.to_vec()).ok().map(|uuid| uuid.uu)),
+ )
.collect()
}
@@ -68,12 +82,22 @@ pub fn extract_name(bytes: &[u8]) -> String {
// Helper function to extract service data from advertising data
pub fn extract_service_data(bytes: &[u8]) -> HashMap<String, Vec<u8>> {
- iterate_adv_data(bytes, SERVICE_DATA_128_BIT_UUID)
+ iterate_adv_data(bytes, SERVICE_DATA_16_BIT_UUID)
.filter_map(|slice| {
+ Uuid::try_from(slice.get(0..2)?.to_vec())
+ .ok()
+ .map(|uuid| (uuid.to_string(), slice[2..].to_vec()))
+ })
+ .chain(iterate_adv_data(bytes, SERVICE_DATA_32_BIT_UUID).filter_map(|slice| {
+ Uuid::try_from(slice.get(0..4)?.to_vec())
+ .ok()
+ .map(|uuid| (uuid.to_string(), slice[4..].to_vec()))
+ }))
+ .chain(iterate_adv_data(bytes, SERVICE_DATA_128_BIT_UUID).filter_map(|slice| {
Uuid::try_from(slice.get(0..16)?.to_vec())
.ok()
.map(|uuid| (uuid.to_string(), slice[16..].to_vec()))
- })
+ }))
.collect()
}
@@ -129,6 +153,16 @@ mod tests {
2,
FLAGS,
3,
+ 3,
+ COMPLETE_LIST_16_BIT_SERVICE_UUIDS,
+ 0xFE,
+ 0x2C,
+ 5,
+ COMPLETE_LIST_32_BIT_SERVICE_UUIDS,
+ 2,
+ 3,
+ 4,
+ 5,
17,
COMPLETE_LIST_128_BIT_SERVICE_UUIDS,
0,
@@ -149,9 +183,27 @@ mod tests {
15,
];
let uuids = extract_service_uuids(payload.as_slice());
- assert_eq!(uuids.len(), 1);
+ assert_eq!(uuids.len(), 3);
assert_eq!(
uuids[0],
+ Uuid::try_from(vec![
+ 0x0, 0x0, 0xFE, 0x2C, 0x0, 0x0, 0x10, 0x0, 0x80, 0x0, 0x0, 0x80, 0x5f, 0x9b, 0x34,
+ 0xfb
+ ])
+ .unwrap()
+ .uu
+ );
+ assert_eq!(
+ uuids[1],
+ Uuid::try_from(vec![
+ 0x2, 0x3, 0x4, 0x5, 0x0, 0x0, 0x10, 0x0, 0x80, 0x0, 0x0, 0x80, 0x5f, 0x9b, 0x34,
+ 0xfb
+ ])
+ .unwrap()
+ .uu
+ );
+ assert_eq!(
+ uuids[2],
Uuid::try_from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]).unwrap().uu
);
}
@@ -178,6 +230,18 @@ mod tests {
assert_eq!(service_data.len(), 0);
let payload: Vec<u8> = vec![
+ 4,
+ SERVICE_DATA_16_BIT_UUID,
+ 0xFE,
+ 0x2C,
+ 0xFF,
+ 6,
+ SERVICE_DATA_32_BIT_UUID,
+ 2,
+ 3,
+ 4,
+ 5,
+ 0xFE,
18,
SERVICE_DATA_128_BIT_UUID,
0,
@@ -217,7 +281,19 @@ mod tests {
16,
];
let service_data = extract_service_data(payload.as_slice());
- assert_eq!(service_data.len(), 2);
+ assert_eq!(service_data.len(), 4);
+ let expected_uuid = Uuid::try_from(vec![
+ 0x0, 0x0, 0xFE, 0x2C, 0x0, 0x0, 0x10, 0x0, 0x80, 0x0, 0x0, 0x80, 0x5f, 0x9b, 0x34, 0xfb,
+ ])
+ .unwrap()
+ .to_string();
+ assert_eq!(service_data.get(&expected_uuid), Some(&vec![0xFF]));
+ let expected_uuid = Uuid::try_from(vec![
+ 0x2, 0x3, 0x4, 0x5, 0x0, 0x0, 0x10, 0x0, 0x80, 0x0, 0x0, 0x80, 0x5f, 0x9b, 0x34, 0xfb,
+ ])
+ .unwrap()
+ .to_string();
+ assert_eq!(service_data.get(&expected_uuid), Some(&vec![0xFE]));
let expected_uuid =
Uuid::try_from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])
.unwrap()
diff --git a/system/gd/rust/packets/build.rs b/system/gd/rust/packets/build.rs
index be5d804b78..030fd67fa3 100644
--- a/system/gd/rust/packets/build.rs
+++ b/system/gd/rust/packets/build.rs
@@ -61,6 +61,7 @@ fn generate_packets() {
}
for i in 0..input_files.len() {
+ println!("cargo:rerun-if-changed={}", input_files[i].display());
let output = Command::new(packetgen.as_os_str().to_str().unwrap())
.arg("--source_root=".to_owned() + gd_root.as_os_str().to_str().unwrap())
.arg("--out=".to_owned() + out_dir.as_os_str().to_str().unwrap())
diff --git a/system/gd/rust/shim/src/init_flags.rs b/system/gd/rust/shim/src/init_flags.rs
index fd3015cc34..57152c5775 100644
--- a/system/gd/rust/shim/src/init_flags.rs
+++ b/system/gd/rust/shim/src/init_flags.rs
@@ -4,10 +4,12 @@ mod ffi {
fn load(flags: Vec<String>);
fn set_all_for_testing();
+ fn sdp_serialization_is_enabled() -> bool;
fn gd_core_is_enabled() -> bool;
fn gd_security_is_enabled() -> bool;
fn gd_l2cap_is_enabled() -> bool;
- fn gatt_robust_caching_is_enabled() -> bool;
+ fn gatt_robust_caching_client_is_enabled() -> bool;
+ fn gatt_robust_caching_server_is_enabled() -> bool;
fn btaa_hci_is_enabled() -> bool;
fn gd_rust_is_enabled() -> bool;
fn gd_link_policy_is_enabled() -> bool;
diff --git a/system/gd/rust/topshim/btav/btav_shim.cc b/system/gd/rust/topshim/btav/btav_shim.cc
index 7e4560ec22..157843dc10 100644
--- a/system/gd/rust/topshim/btav/btav_shim.cc
+++ b/system/gd/rust/topshim/btav/btav_shim.cc
@@ -152,10 +152,19 @@ static ::rust::Vec<A2dpCodecConfig> to_rust_codec_config_vec(const std::vector<b
return rconfigs;
}
-static void connection_state_cb(
- const RawAddress& bd_addr, btav_connection_state_t state, [[maybe_unused]] const btav_error_t& error) {
+static A2dpError to_rust_error(const btav_error_t& error) {
+ A2dpError a2dp_error = {
+ .status = error.status,
+ .error_code = error.error_code,
+ .error_msg = error.error_msg.value_or(""),
+ };
+ return a2dp_error;
+}
+
+static void connection_state_cb(const RawAddress& bd_addr, btav_connection_state_t state, const btav_error_t& error) {
RustRawAddress addr = rusty::CopyToRustAddress(bd_addr);
- rusty::connection_state_callback(addr, state);
+ A2dpError a2dp_error = to_rust_error(error);
+ rusty::connection_state_callback(addr, state, a2dp_error);
}
static void audio_state_cb(const RawAddress& bd_addr, btav_audio_state_t state) {
RustRawAddress addr = rusty::CopyToRustAddress(bd_addr);
@@ -208,11 +217,11 @@ int A2dpIntf::init() const {
return intf_->init(&internal::g_callbacks, 1, a, b);
}
-int A2dpIntf::connect(RustRawAddress bt_addr) const {
+uint32_t A2dpIntf::connect(RustRawAddress bt_addr) const {
RawAddress addr = rusty::CopyFromRustAddress(bt_addr);
return intf_->connect(addr);
}
-int A2dpIntf::disconnect(RustRawAddress bt_addr) const {
+uint32_t A2dpIntf::disconnect(RustRawAddress bt_addr) const {
RawAddress addr = rusty::CopyFromRustAddress(bt_addr);
return intf_->disconnect(addr);
}
@@ -286,11 +295,11 @@ void AvrcpIntf::cleanup() {
intf_->Cleanup();
}
-int AvrcpIntf::connect(RustRawAddress bt_addr) {
+uint32_t AvrcpIntf::connect(RustRawAddress bt_addr) {
RawAddress addr = rusty::CopyFromRustAddress(bt_addr);
return intf_->ConnectDevice(addr);
}
-int AvrcpIntf::disconnect(RustRawAddress bt_addr) {
+uint32_t AvrcpIntf::disconnect(RustRawAddress bt_addr) {
RawAddress addr = rusty::CopyFromRustAddress(bt_addr);
return intf_->DisconnectDevice(addr);
}
diff --git a/system/gd/rust/topshim/btav/btav_shim.h b/system/gd/rust/topshim/btav/btav_shim.h
index 1cabd9c15f..e6c1ce2096 100644
--- a/system/gd/rust/topshim/btav/btav_shim.h
+++ b/system/gd/rust/topshim/btav/btav_shim.h
@@ -39,8 +39,8 @@ class A2dpIntf {
// interface for Settings
int init() const;
- int connect(RustRawAddress bt_addr) const;
- int disconnect(RustRawAddress bt_addr) const;
+ uint32_t connect(RustRawAddress bt_addr) const;
+ uint32_t disconnect(RustRawAddress bt_addr) const;
int set_silence_device(RustRawAddress bt_addr, bool silent) const;
int set_active_device(RustRawAddress bt_addr) const;
int config_codec(RustRawAddress bt_addr, ::rust::Vec<A2dpCodecConfig> codec_preferences) const;
@@ -65,8 +65,8 @@ class AvrcpIntf {
void init();
void cleanup();
- int connect(RustRawAddress bt_addr);
- int disconnect(RustRawAddress bt_addr);
+ uint32_t connect(RustRawAddress bt_addr);
+ uint32_t disconnect(RustRawAddress bt_addr);
// interface for Audio server
void set_volume(int8_t volume);
diff --git a/system/gd/rust/topshim/facade/src/adapter_service.rs b/system/gd/rust/topshim/facade/src/adapter_service.rs
index 06123508db..11c5d5585c 100644
--- a/system/gd/rust/topshim/facade/src/adapter_service.rs
+++ b/system/gd/rust/topshim/facade/src/adapter_service.rs
@@ -1,11 +1,11 @@
//! Adapter service facade
use bt_topshim::btif;
-use bt_topshim::btif::{BaseCallbacks, BaseCallbacksDispatcher, BluetoothInterface, RawAddress};
+use bt_topshim::btif::{BaseCallbacks, BaseCallbacksDispatcher, BluetoothInterface};
use bt_topshim_facade_protobuf::empty::Empty;
use bt_topshim_facade_protobuf::facade::{
- EventType, FetchEventsRequest, FetchEventsResponse, RemoveBondRequest, SetDiscoveryModeRequest,
+ EventType, FetchEventsRequest, FetchEventsResponse, SetDiscoveryModeRequest,
SetDiscoveryModeResponse, ToggleStackRequest, ToggleStackResponse,
};
use bt_topshim_facade_protobuf::facade_grpc::{create_adapter_service, AdapterService};
@@ -173,14 +173,6 @@ impl AdapterService for AdapterServiceImpl {
})
}
- fn remove_bond(&mut self, ctx: RpcContext<'_>, req: RemoveBondRequest, sink: UnarySink<Empty>) {
- let raw_address = RawAddress::from_string(req.address).unwrap();
- self.btif_intf.lock().unwrap().remove_bond(&raw_address);
- ctx.spawn(async move {
- sink.success(Empty::default()).await.unwrap();
- })
- }
-
fn restore_filter_accept_list(
&mut self,
ctx: RpcContext<'_>,
diff --git a/system/gd/rust/topshim/facade/src/main.rs b/system/gd/rust/topshim/facade/src/main.rs
index 7610a46653..e971893f8c 100644
--- a/system/gd/rust/topshim/facade/src/main.rs
+++ b/system/gd/rust/topshim/facade/src/main.rs
@@ -21,6 +21,7 @@ use tokio::runtime::Runtime;
mod adapter_service;
mod gatt_service;
mod media_service;
+mod security_service;
// This is needed for linking, libbt_shim_bridge needs symbols defined by
// bt_shim, however bt_shim depends on rust crates (future, tokio) that
@@ -79,6 +80,9 @@ async fn async_main(rt: Arc<Runtime>, mut sigint: mpsc::UnboundedReceiver<()>) {
let adapter_service_impl =
adapter_service::AdapterServiceImpl::create(rt.clone(), btif_intf.clone());
+ let security_service_impl =
+ security_service::SecurityServiceImpl::create(rt.clone(), btif_intf.clone());
+
let gatt_service_impl = gatt_service::GattServiceImpl::create(rt.clone(), btif_intf.clone());
let media_service_impl = media_service::MediaServiceImpl::create(rt.clone(), btif_intf.clone());
@@ -91,6 +95,7 @@ async fn async_main(rt: Arc<Runtime>, mut sigint: mpsc::UnboundedReceiver<()>) {
let mut server = ServerBuilder::new(env)
.register_service(adapter_service_impl)
+ .register_service(security_service_impl)
.register_service(gatt_service_impl)
.register_service(media_service_impl)
.bind("0.0.0.0", grpc_port)
diff --git a/system/gd/rust/topshim/facade/src/security_service.rs b/system/gd/rust/topshim/facade/src/security_service.rs
new file mode 100644
index 0000000000..1e59658c94
--- /dev/null
+++ b/system/gd/rust/topshim/facade/src/security_service.rs
@@ -0,0 +1,41 @@
+//! Security service facade
+
+use bt_topshim::btif::BluetoothInterface;
+
+use bt_topshim_facade_protobuf::empty::Empty;
+use bt_topshim_facade_protobuf::facade::RemoveBondRequest;
+use bt_topshim_facade_protobuf::facade_grpc::{create_security_service, SecurityService};
+use grpcio::*;
+
+use std::sync::{Arc, Mutex};
+use tokio::runtime::Runtime;
+
+/// Main object for Adapter facade service
+#[derive(Clone)]
+pub struct SecurityServiceImpl {
+ #[allow(dead_code)]
+ rt: Arc<Runtime>,
+ #[allow(dead_code)]
+ btif_intf: Arc<Mutex<BluetoothInterface>>,
+}
+
+#[allow(dead_code)]
+impl SecurityServiceImpl {
+ /// Create a new instance of the root facade service
+ pub fn create(rt: Arc<Runtime>, btif_intf: Arc<Mutex<BluetoothInterface>>) -> grpcio::Service {
+ create_security_service(Self { rt, btif_intf })
+ }
+}
+
+impl SecurityService for SecurityServiceImpl {
+ fn remove_bond(
+ &mut self,
+ ctx: RpcContext<'_>,
+ _req: RemoveBondRequest,
+ sink: UnarySink<Empty>,
+ ) {
+ ctx.spawn(async move {
+ sink.success(Empty::default()).await.unwrap();
+ })
+ }
+}
diff --git a/system/gd/rust/topshim/hfp/hfp_shim.cc b/system/gd/rust/topshim/hfp/hfp_shim.cc
index 1d180356b6..9e124ad902 100644
--- a/system/gd/rust/topshim/hfp/hfp_shim.cc
+++ b/system/gd/rust/topshim/hfp/hfp_shim.cc
@@ -210,7 +210,7 @@ int HfpIntf::init() {
return intf_->Init(DBusHeadsetCallbacks::GetInstance(intf_), 1, false);
}
-int HfpIntf::connect(RustRawAddress bt_addr) {
+uint32_t HfpIntf::connect(RustRawAddress bt_addr) {
RawAddress addr = rusty::CopyFromRustAddress(bt_addr);
return intf_->Connect(&addr);
}
@@ -231,7 +231,7 @@ int HfpIntf::set_volume(int8_t volume, RustRawAddress bt_addr) {
return intf_->VolumeControl(headset::bthf_volume_type_t::BTHF_VOLUME_TYPE_SPK, volume, &addr);
}
-int HfpIntf::disconnect(RustRawAddress bt_addr) {
+uint32_t HfpIntf::disconnect(RustRawAddress bt_addr) {
RawAddress addr = rusty::CopyFromRustAddress(bt_addr);
return intf_->Disconnect(&addr);
}
diff --git a/system/gd/rust/topshim/hfp/hfp_shim.h b/system/gd/rust/topshim/hfp/hfp_shim.h
index 43d8b99b1d..99d78cd33f 100644
--- a/system/gd/rust/topshim/hfp/hfp_shim.h
+++ b/system/gd/rust/topshim/hfp/hfp_shim.h
@@ -33,11 +33,11 @@ class HfpIntf {
HfpIntf(headset::Interface* intf) : intf_(intf){};
int init();
- int connect(RustRawAddress bt_addr);
+ uint32_t connect(RustRawAddress bt_addr);
int connect_audio(RustRawAddress bt_addr, bool sco_offload, bool force_cvsd);
int set_active_device(RustRawAddress bt_addr);
int set_volume(int8_t volume, RustRawAddress bt_addr);
- int disconnect(RustRawAddress bt_addr);
+ uint32_t disconnect(RustRawAddress bt_addr);
int disconnect_audio(RustRawAddress bt_addr);
void cleanup();
diff --git a/system/gd/rust/topshim/src/btif.rs b/system/gd/rust/topshim/src/btif.rs
index 850f0005b0..92c59f14bd 100644
--- a/system/gd/rust/topshim/src/btif.rs
+++ b/system/gd/rust/topshim/src/btif.rs
@@ -321,12 +321,25 @@ impl TryFrom<Vec<u8>> for Uuid {
type Error = &'static str;
fn try_from(value: Vec<u8>) -> std::result::Result<Self, Self::Error> {
- if value.len() != 16 {
- Err("Vector size must be exactly 16.")
- } else {
- let mut uu: [u8; 16] = Default::default();
- uu.copy_from_slice(&value[0..16]);
- Ok(Uuid { uu })
+ // base UUID defined in the Bluetooth specification
+ let mut uu: [u8; 16] =
+ [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x0, 0x80, 0x0, 0x0, 0x80, 0x5f, 0x9b, 0x34, 0xfb];
+ match value.len() {
+ 2 => {
+ uu[2..4].copy_from_slice(&value[0..2]);
+ Ok(Uuid { uu })
+ }
+ 4 => {
+ uu[0..4].copy_from_slice(&value[0..4]);
+ Ok(Uuid { uu })
+ }
+ 16 => {
+ uu.copy_from_slice(&value[0..16]);
+ Ok(Uuid { uu })
+ }
+ _ => {
+ Err("Vector size must be exactly 2 (16 bit UUID), 4 (32 bit UUID), or 16 (128 bit UUID).")
+ }
}
}
}
diff --git a/system/gd/rust/topshim/src/profiles/a2dp.rs b/system/gd/rust/topshim/src/profiles/a2dp.rs
index aa69a3ec30..cd2593be07 100644
--- a/system/gd/rust/topshim/src/profiles/a2dp.rs
+++ b/system/gd/rust/topshim/src/profiles/a2dp.rs
@@ -1,11 +1,11 @@
-use crate::btif::{BluetoothInterface, RawAddress};
+use crate::btif::{BluetoothInterface, BtStatus, RawAddress};
use crate::topstack::get_dispatchers;
use num_traits::cast::FromPrimitive;
use std::sync::{Arc, Mutex};
use topshim_macros::cb_variant;
-#[derive(Debug, FromPrimitive, ToPrimitive, PartialEq, PartialOrd)]
+#[derive(Debug, FromPrimitive, ToPrimitive, PartialEq, PartialOrd, Clone)]
#[repr(u32)]
pub enum BtavConnectionState {
Disconnected = 0,
@@ -77,6 +77,18 @@ impl From<i32> for A2dpCodecPriority {
}
}
+#[derive(Debug)]
+pub struct A2dpError {
+ /// Standard BT status come from a function return or the cloest approximation to the real
+ /// error.
+ pub status: BtStatus,
+ /// An additional value to help explain the error. In the A2Dp context, this is often referring
+ /// to the BTA_AV_XXX status.
+ pub error: i32,
+ /// An optional error message that the lower layer wants to deliver.
+ pub error_message: Option<String>,
+}
+
bitflags! {
pub struct A2dpCodecSampleRate: i32 {
const RATE_NONE = 0x0;
@@ -154,6 +166,13 @@ pub mod ffi {
data_position_nsec: i32,
}
+ #[derive(Debug, Default)]
+ pub struct A2dpError {
+ status: u32,
+ error_code: u8,
+ error_msg: String,
+ }
+
unsafe extern "C++" {
include!("btav/btav_shim.h");
include!("btav_sink/btav_sink_shim.h");
@@ -164,8 +183,8 @@ pub mod ffi {
unsafe fn GetA2dpProfile(btif: *const u8) -> UniquePtr<A2dpIntf>;
fn init(self: &A2dpIntf) -> i32;
- fn connect(self: &A2dpIntf, bt_addr: RustRawAddress) -> i32;
- fn disconnect(self: &A2dpIntf, bt_addr: RustRawAddress) -> i32;
+ fn connect(self: &A2dpIntf, bt_addr: RustRawAddress) -> u32;
+ fn disconnect(self: &A2dpIntf, bt_addr: RustRawAddress) -> u32;
fn set_silence_device(self: &A2dpIntf, bt_addr: RustRawAddress, silent: bool) -> i32;
fn set_active_device(self: &A2dpIntf, bt_addr: RustRawAddress) -> i32;
fn config_codec(
@@ -189,7 +208,7 @@ pub mod ffi {
fn cleanup(self: &A2dpSinkIntf);
}
extern "Rust" {
- fn connection_state_callback(addr: RustRawAddress, state: u32);
+ fn connection_state_callback(addr: RustRawAddress, state: u32, error: A2dpError);
fn audio_state_callback(addr: RustRawAddress, state: u32);
fn audio_config_callback(
addr: RustRawAddress,
@@ -204,6 +223,7 @@ pub mod ffi {
pub type FfiAddress = ffi::RustRawAddress;
pub type A2dpCodecConfig = ffi::A2dpCodecConfig;
pub type PresentationPosition = ffi::RustPresentationPosition;
+pub type FfiA2dpError = ffi::A2dpError;
impl From<RawAddress> for FfiAddress {
fn from(addr: RawAddress) -> Self {
@@ -233,9 +253,18 @@ impl Default for A2dpCodecConfig {
}
}
+impl Into<A2dpError> for FfiA2dpError {
+ fn into(self) -> A2dpError {
+ A2dpError {
+ status: self.status.into(),
+ error: self.error_code as i32,
+ error_message: if self.error_msg == "" { None } else { Some(self.error_msg) },
+ }
+ }
+}
#[derive(Debug)]
pub enum A2dpCallbacks {
- ConnectionState(RawAddress, BtavConnectionState),
+ ConnectionState(RawAddress, BtavConnectionState, A2dpError),
AudioState(RawAddress, BtavAudioState),
AudioConfig(RawAddress, A2dpCodecConfig, Vec<A2dpCodecConfig>, Vec<A2dpCodecConfig>),
MandatoryCodecPreferred(RawAddress),
@@ -248,8 +277,9 @@ pub struct A2dpCallbacksDispatcher {
type A2dpCb = Arc<Mutex<A2dpCallbacksDispatcher>>;
cb_variant!(A2dpCb, connection_state_callback -> A2dpCallbacks::ConnectionState,
-FfiAddress -> RawAddress, u32 -> BtavConnectionState, {
+FfiAddress -> RawAddress, u32 -> BtavConnectionState, FfiA2dpError -> A2dpError,{
let _0 = _0.into();
+ let _2 = _2.into();
});
cb_variant!(A2dpCb, audio_state_callback -> A2dpCallbacks::AudioState,
@@ -293,16 +323,16 @@ impl A2dp {
true
}
- pub fn connect(&mut self, addr: RawAddress) {
- self.internal.connect(addr.into());
+ pub fn connect(&mut self, addr: RawAddress) -> BtStatus {
+ BtStatus::from(self.internal.connect(addr.into()))
}
pub fn set_active_device(&mut self, addr: RawAddress) {
self.internal.set_active_device(addr.into());
}
- pub fn disconnect(&mut self, addr: RawAddress) {
- self.internal.disconnect(addr.into());
+ pub fn disconnect(&mut self, addr: RawAddress) -> BtStatus {
+ BtStatus::from(self.internal.disconnect(addr.into()))
}
pub fn set_audio_config(&self, sample_rate: i32, bits_per_sample: i32, channel_mode: i32) {
diff --git a/system/gd/rust/topshim/src/profiles/avrcp.rs b/system/gd/rust/topshim/src/profiles/avrcp.rs
index 7e1bfcccf1..2a68ae2eea 100644
--- a/system/gd/rust/topshim/src/profiles/avrcp.rs
+++ b/system/gd/rust/topshim/src/profiles/avrcp.rs
@@ -1,4 +1,4 @@
-use crate::btif::{BluetoothInterface, RawAddress};
+use crate::btif::{BluetoothInterface, BtStatus, RawAddress};
use crate::topstack::get_dispatchers;
use std::sync::{Arc, Mutex};
@@ -20,8 +20,8 @@ pub mod ffi {
fn init(self: Pin<&mut AvrcpIntf>);
fn cleanup(self: Pin<&mut AvrcpIntf>);
- fn connect(self: Pin<&mut AvrcpIntf>, bt_addr: RustRawAddress) -> i32;
- fn disconnect(self: Pin<&mut AvrcpIntf>, bt_addr: RustRawAddress) -> i32;
+ fn connect(self: Pin<&mut AvrcpIntf>, bt_addr: RustRawAddress) -> u32;
+ fn disconnect(self: Pin<&mut AvrcpIntf>, bt_addr: RustRawAddress) -> u32;
fn set_volume(self: Pin<&mut AvrcpIntf>, volume: i8);
}
@@ -135,12 +135,12 @@ impl Avrcp {
true
}
- pub fn connect(&mut self, addr: RawAddress) {
- self.internal.pin_mut().connect(addr.into());
+ pub fn connect(&mut self, addr: RawAddress) -> BtStatus {
+ BtStatus::from(self.internal.pin_mut().connect(addr.into()))
}
- pub fn disconnect(&mut self, addr: RawAddress) {
- self.internal.pin_mut().disconnect(addr.into());
+ pub fn disconnect(&mut self, addr: RawAddress) -> BtStatus {
+ BtStatus::from(self.internal.pin_mut().disconnect(addr.into()))
}
pub fn set_volume(&mut self, volume: i8) {
diff --git a/system/gd/rust/topshim/src/profiles/hfp.rs b/system/gd/rust/topshim/src/profiles/hfp.rs
index 54cc7c73b9..5dbf260286 100644
--- a/system/gd/rust/topshim/src/profiles/hfp.rs
+++ b/system/gd/rust/topshim/src/profiles/hfp.rs
@@ -1,4 +1,4 @@
-use crate::btif::{BluetoothInterface, RawAddress};
+use crate::btif::{BluetoothInterface, BtStatus, RawAddress};
use crate::topstack::get_dispatchers;
use num_traits::cast::FromPrimitive;
@@ -6,7 +6,7 @@ use std::convert::{TryFrom, TryInto};
use std::sync::{Arc, Mutex};
use topshim_macros::cb_variant;
-#[derive(Debug, FromPrimitive, ToPrimitive, PartialEq, PartialOrd)]
+#[derive(Debug, FromPrimitive, ToPrimitive, PartialEq, PartialOrd, Clone)]
#[repr(u32)]
pub enum BthfConnectionState {
Disconnected = 0,
@@ -75,7 +75,7 @@ pub mod ffi {
unsafe fn GetHfpProfile(btif: *const u8) -> UniquePtr<HfpIntf>;
fn init(self: Pin<&mut HfpIntf>) -> i32;
- fn connect(self: Pin<&mut HfpIntf>, bt_addr: RustRawAddress) -> i32;
+ fn connect(self: Pin<&mut HfpIntf>, bt_addr: RustRawAddress) -> u32;
fn connect_audio(
self: Pin<&mut HfpIntf>,
bt_addr: RustRawAddress,
@@ -84,7 +84,7 @@ pub mod ffi {
) -> i32;
fn set_active_device(self: Pin<&mut HfpIntf>, bt_addr: RustRawAddress) -> i32;
fn set_volume(self: Pin<&mut HfpIntf>, volume: i8, bt_addr: RustRawAddress) -> i32;
- fn disconnect(self: Pin<&mut HfpIntf>, bt_addr: RustRawAddress) -> i32;
+ fn disconnect(self: Pin<&mut HfpIntf>, bt_addr: RustRawAddress) -> u32;
fn disconnect_audio(self: Pin<&mut HfpIntf>, bt_addr: RustRawAddress) -> i32;
fn cleanup(self: Pin<&mut HfpIntf>);
@@ -181,8 +181,8 @@ impl Hfp {
true
}
- pub fn connect(&mut self, addr: RawAddress) {
- self.internal.pin_mut().connect(addr.into());
+ pub fn connect(&mut self, addr: RawAddress) -> BtStatus {
+ BtStatus::from(self.internal.pin_mut().connect(addr.into()))
}
pub fn connect_audio(&mut self, addr: RawAddress, sco_offload: bool, force_cvsd: bool) -> i32 {
@@ -197,8 +197,8 @@ impl Hfp {
self.internal.pin_mut().set_volume(volume, addr.into())
}
- pub fn disconnect(&mut self, addr: RawAddress) {
- self.internal.pin_mut().disconnect(addr.into());
+ pub fn disconnect(&mut self, addr: RawAddress) -> BtStatus {
+ BtStatus::from(self.internal.pin_mut().disconnect(addr.into()))
}
pub fn disconnect_audio(&mut self, addr: RawAddress) -> i32 {
diff --git a/system/gd/rust/topshim/src/profiles/socket.rs b/system/gd/rust/topshim/src/profiles/socket.rs
index a8898743a1..9ead5f5beb 100644
--- a/system/gd/rust/topshim/src/profiles/socket.rs
+++ b/system/gd/rust/topshim/src/profiles/socket.rs
@@ -162,7 +162,7 @@ impl BtSocket {
};
let uuid_ptr = match uuid {
- Some(u) => &u as *const Uuid,
+ Some(ref u) => u as *const Uuid,
None => std::ptr::null(),
};
@@ -199,7 +199,7 @@ impl BtSocket {
};
let uuid_ptr = match uuid {
- Some(u) => &u as *const Uuid,
+ Some(ref u) => u as *const Uuid,
None => std::ptr::null(),
};
diff --git a/system/gd/security/pairing_handler_le.h b/system/gd/security/pairing_handler_le.h
index 051ce34c8e..ed29316250 100644
--- a/system/gd/security/pairing_handler_le.h
+++ b/system/gd/security/pairing_handler_le.h
@@ -318,7 +318,8 @@ class PairingHandlerLe {
std::optional<PairingEvent> WaitUiPairingAccept() {
PairingEvent e = WaitForEvent();
- if (e.type == PairingEvent::UI & e.ui_action == PairingEvent::PAIRING_ACCEPTED) {
+ if (e.type == PairingEvent::UI &&
+ e.ui_action == PairingEvent::PAIRING_ACCEPTED) {
return e;
} else {
return std::nullopt;
@@ -327,7 +328,8 @@ class PairingHandlerLe {
std::optional<PairingEvent> WaitUiConfirmYesNo() {
PairingEvent e = WaitForEvent();
- if (e.type == PairingEvent::UI & e.ui_action == PairingEvent::CONFIRM_YESNO) {
+ if (e.type == PairingEvent::UI &&
+ e.ui_action == PairingEvent::CONFIRM_YESNO) {
return e;
} else {
return std::nullopt;
@@ -362,7 +364,8 @@ class PairingHandlerLe {
e = WaitForEvent();
}
- if (e.type == PairingEvent::UI & e.ui_action == PairingEvent::PASSKEY) {
+ if (e.type == PairingEvent::UI &&
+ e.ui_action == PairingEvent::PASSKEY) {
return e;
} else {
return std::nullopt;
diff --git a/system/gd/storage/legacy_config_file.cc b/system/gd/storage/legacy_config_file.cc
index 4a46dd5961..214184af71 100644
--- a/system/gd/storage/legacy_config_file.cc
+++ b/system/gd/storage/legacy_config_file.cc
@@ -46,6 +46,10 @@ std::optional<ConfigCache> LegacyConfigFile::Read(size_t temp_devices_capacity)
while (std::getline(config_file, line)) {
++line_num;
line = common::StringTrim(std::move(line));
+ if (line.empty()) {
+ continue;
+ }
+
if (line.front() == '\0' || line.front() == '#') {
continue;
}
diff --git a/system/include/hardware/bt_le_audio.h b/system/include/hardware/bt_le_audio.h
index 34f8778772..978008b7d3 100644
--- a/system/include/hardware/bt_le_audio.h
+++ b/system/include/hardware/bt_le_audio.h
@@ -37,6 +37,7 @@ enum class ConnectionState {
enum class GroupStatus {
INACTIVE = 0,
ACTIVE,
+ TURNED_IDLE_DURING_CALL,
};
enum class GroupStreamStatus {
diff --git a/system/internal_include/stack_config.h b/system/internal_include/stack_config.h
index 080d40183a..efaec3f700 100644
--- a/system/internal_include/stack_config.h
+++ b/system/internal_include/stack_config.h
@@ -47,6 +47,7 @@ typedef struct {
int (*get_pts_l2cap_ecoc_send_num_of_sdu)(void);
bool (*get_pts_l2cap_ecoc_reconfigure)(void);
const std::string* (*get_pts_broadcast_audio_config_options)(void);
+ bool (*get_pts_le_audio_disable_ases_before_stopping)(void);
config_t* (*get_all)(void);
} stack_config_t;
diff --git a/system/main/shim/btm_api.cc b/system/main/shim/btm_api.cc
index 26a006c07a..720e9d5e81 100644
--- a/system/main/shim/btm_api.cc
+++ b/system/main/shim/btm_api.cc
@@ -37,6 +37,7 @@
#include "main/shim/shim.h"
#include "main/shim/stack.h"
#include "osi/include/allocator.h"
+#include "stack/btm/btm_ble_int.h"
#include "stack/btm/btm_int_types.h"
#include "stack/btm/btm_sec.h"
#include "stack/include/bt_hdr.h"
@@ -1392,3 +1393,8 @@ tBTM_STATUS bluetooth::shim::BTM_SetEventFilterInquiryResultAllDevices() {
controller_get_interface()->set_event_filter_inquiry_result_all_devices();
return BTM_SUCCESS;
}
+
+tBTM_STATUS bluetooth::shim::BTM_BleResetId() {
+ btm_ble_reset_id();
+ return BTM_SUCCESS;
+}
diff --git a/system/main/shim/btm_api.h b/system/main/shim/btm_api.h
index 3c56f2687d..8883140332 100644
--- a/system/main/shim/btm_api.h
+++ b/system/main/shim/btm_api.h
@@ -1924,6 +1924,15 @@ tBTM_STATUS BTM_SetDefaultEventMask(void);
*******************************************************************************/
tBTM_STATUS BTM_SetEventFilterInquiryResultAllDevices(void);
+/*******************************************************************************
+ *
+ * Function BTM_BleResetId
+ *
+ * Description Resets the local BLE keys
+ *
+ *******************************************************************************/
+tBTM_STATUS BTM_BleResetId(void);
+
/**
* Send remote name request to GD shim Name module
*/
diff --git a/system/main/shim/hci_layer.cc b/system/main/shim/hci_layer.cc
index 4eff5bedee..a3edcdc6aa 100644
--- a/system/main/shim/hci_layer.cc
+++ b/system/main/shim/hci_layer.cc
@@ -335,7 +335,6 @@ void OnTransmitPacketCommandComplete(command_complete_cb complete_callback,
bluetooth::hci::CommandCompleteView view) {
LOG_DEBUG("Received cmd complete for %s",
bluetooth::hci::OpCodeText(view.GetCommandOpCode()).c_str());
- std::vector<uint8_t> data(view.begin(), view.end());
BT_HDR* response = WrapPacketAndCopy(MSG_HC_TO_STACK_HCI_EVT, &view);
complete_callback(response, context);
}
diff --git a/system/main/stack_config.cc b/system/main/stack_config.cc
index 58994735c0..5c2921f1fb 100644
--- a/system/main/stack_config.cc
+++ b/system/main/stack_config.cc
@@ -51,6 +51,7 @@ const char* PTS_L2CAP_ECOC_SEND_NUM_OF_SDU = "PTS_L2capEcocSendNumOfSdu";
const char* PTS_L2CAP_ECOC_RECONFIGURE = "PTS_L2capEcocReconfigure";
const char* PTS_BROADCAST_AUDIO_CONFIG_OPTION =
"PTS_BroadcastAudioConfigOption";
+const char* PTS_LE_AUDIO_SUSPEND_STREAMING = "PTS_LeAudioSuspendStreaming";
static std::unique_ptr<config_t> config;
} // namespace
@@ -202,6 +203,11 @@ static const std::string* get_pts_broadcast_audio_config_options(void) {
PTS_BROADCAST_AUDIO_CONFIG_OPTION, NULL);
}
+static bool get_pts_le_audio_disable_ases_before_stopping(void) {
+ return config_get_bool(*config, CONFIG_DEFAULT_SECTION,
+ PTS_LE_AUDIO_SUSPEND_STREAMING, false);
+}
+
static config_t* get_all(void) { return config.get(); }
const stack_config_t interface = {
@@ -226,6 +232,7 @@ const stack_config_t interface = {
get_pts_l2cap_ecoc_send_num_of_sdu,
get_pts_l2cap_ecoc_reconfigure,
get_pts_broadcast_audio_config_options,
+ get_pts_le_audio_disable_ases_before_stopping,
get_all};
const stack_config_t* stack_config_get_interface(void) { return &interface; }
diff --git a/system/profile/avrcp/tests/avrcp_device_fuzz/avrcp_device_fuzz.cc b/system/profile/avrcp/tests/avrcp_device_fuzz/avrcp_device_fuzz.cc
index 42e9ba61ef..bca1f6f9ba 100644
--- a/system/profile/avrcp/tests/avrcp_device_fuzz/avrcp_device_fuzz.cc
+++ b/system/profile/avrcp/tests/avrcp_device_fuzz/avrcp_device_fuzz.cc
@@ -64,7 +64,8 @@ const stack_config_t interface = {nullptr, get_pts_avrcp_test,
nullptr, nullptr,
nullptr, nullptr,
nullptr, nullptr,
- nullptr, nullptr};
+ nullptr, nullptr,
+ nullptr};
void Callback(uint8_t, bool, std::unique_ptr<::bluetooth::PacketBuilder>) {}
diff --git a/system/profile/avrcp/tests/avrcp_device_test.cc b/system/profile/avrcp/tests/avrcp_device_test.cc
index f59330b287..e8058aa378 100644
--- a/system/profile/avrcp/tests/avrcp_device_test.cc
+++ b/system/profile/avrcp/tests/avrcp_device_test.cc
@@ -60,7 +60,8 @@ const stack_config_t interface = {nullptr, get_pts_avrcp_test,
nullptr, nullptr,
nullptr, nullptr,
nullptr, nullptr,
- nullptr, nullptr};
+ nullptr, nullptr,
+ nullptr};
// TODO (apanicke): All the tests below are just basic positive unit tests.
// Add more tests to increase code coverage.
diff --git a/system/stack/avrc/avrc_pars_ct.cc b/system/stack/avrc/avrc_pars_ct.cc
index 12aee4ce69..a5710428f4 100644
--- a/system/stack/avrc/avrc_pars_ct.cc
+++ b/system/stack/avrc/avrc_pars_ct.cc
@@ -237,7 +237,7 @@ static tAVRC_STS avrc_pars_browse_rsp(tAVRC_MSG_BROWSE* p_msg,
}
BE_STREAM_TO_UINT8(pdu, p);
uint16_t pkt_len;
- int min_len = 0;
+ uint16_t min_len = 0;
/* read the entire packet len */
BE_STREAM_TO_UINT16(pkt_len, p);
@@ -380,8 +380,14 @@ static tAVRC_STS avrc_pars_browse_rsp(tAVRC_MSG_BROWSE* p_msg,
/* Parse the name now */
BE_STREAM_TO_UINT16(attr_entry->name.charset_id, p);
BE_STREAM_TO_UINT16(attr_entry->name.str_len, p);
+ if (static_cast<uint16_t>(min_len + attr_entry->name.str_len) <
+ min_len) {
+ // Check for overflow
+ android_errorWriteLog(0x534e4554, "205570663");
+ }
+ if (pkt_len - min_len < attr_entry->name.str_len)
+ goto browse_length_error;
min_len += attr_entry->name.str_len;
- if (pkt_len < min_len) goto browse_length_error;
attr_entry->name.p_str = (uint8_t*)osi_malloc(
attr_entry->name.str_len * sizeof(uint8_t));
BE_STREAM_TO_ARRAY(p, attr_entry->name.p_str,
@@ -444,8 +450,14 @@ static tAVRC_STS avrc_pars_browse_rsp(tAVRC_MSG_BROWSE* p_msg,
BE_STREAM_TO_UINT32(attr_entry->attr_id, p);
BE_STREAM_TO_UINT16(attr_entry->name.charset_id, p);
BE_STREAM_TO_UINT16(attr_entry->name.str_len, p);
+ if (static_cast<uint16_t>(min_len + attr_entry->name.str_len) <
+ min_len) {
+ // Check for overflow
+ android_errorWriteLog(0x534e4554, "205570663");
+ }
+ if (pkt_len - min_len < attr_entry->name.str_len)
+ goto browse_length_error;
min_len += attr_entry->name.str_len;
- if (pkt_len < min_len) goto browse_length_error;
attr_entry->name.p_str =
(uint8_t*)osi_malloc(attr_entry->name.str_len * sizeof(uint8_t));
BE_STREAM_TO_ARRAY(p, attr_entry->name.p_str, attr_entry->name.str_len);
@@ -815,8 +827,12 @@ static tAVRC_STS avrc_ctrl_pars_vendor_rsp(tAVRC_MSG_VENDOR* p_msg,
BE_STREAM_TO_UINT32(p_attrs[i].attr_id, p);
BE_STREAM_TO_UINT16(p_attrs[i].name.charset_id, p);
BE_STREAM_TO_UINT16(p_attrs[i].name.str_len, p);
- min_len += p_attrs[i].name.str_len;
- if (len < min_len) {
+ if (static_cast<uint16_t>(min_len + p_attrs[i].name.str_len) <
+ min_len) {
+ // Check for overflow
+ android_errorWriteLog(0x534e4554, "205570663");
+ }
+ if (len - min_len < p_attrs[i].name.str_len) {
for (int j = 0; j < i; j++) {
osi_free(p_attrs[j].name.p_str);
}
@@ -824,6 +840,7 @@ static tAVRC_STS avrc_ctrl_pars_vendor_rsp(tAVRC_MSG_VENDOR* p_msg,
p_result->get_attrs.num_attrs = 0;
goto length_error;
}
+ min_len += p_attrs[i].name.str_len;
if (p_attrs[i].name.str_len > 0) {
p_attrs[i].name.p_str =
(uint8_t*)osi_calloc(p_attrs[i].name.str_len);
diff --git a/system/stack/btm/btm_ble.cc b/system/stack/btm/btm_ble.cc
index 5755ba2b94..57edb6190d 100644
--- a/system/stack/btm/btm_ble.cc
+++ b/system/stack/btm/btm_ble.cc
@@ -32,6 +32,7 @@
#include "main/shim/l2c_api.h"
#include "main/shim/shim.h"
#include "osi/include/allocator.h"
+#include "osi/include/properties.h"
#include "stack/btm/btm_dev.h"
#include "stack/btm/btm_int_types.h"
#include "stack/btm/security_device_record.h"
@@ -56,6 +57,11 @@ extern bool btm_ble_init_pseudo_addr(tBTM_SEC_DEV_REC* p_dev_rec,
extern void gatt_notify_phy_updated(tGATT_STATUS status, uint16_t handle,
uint8_t tx_phy, uint8_t rx_phy);
+
+#ifndef PROPERTY_BLE_PRIVACY_ENABLED
+#define PROPERTY_BLE_PRIVACY_ENABLED "bluetooth.core.gap.le.privacy.enabled"
+#endif
+
/******************************************************************************/
/* External Function to be called by other modules */
/******************************************************************************/
@@ -82,7 +88,7 @@ void BTM_SecAddBleDevice(const RawAddress& bd_addr, tBT_DEVICE_TYPE dev_type,
p_dev_rec->conn_params.peripheral_latency = BTM_BLE_CONN_PARAM_UNDEF;
LOG_DEBUG("Device added, handle=0x%x, p_dev_rec=%p, bd_addr=%s",
- p_dev_rec->ble_hci_handle, p_dev_rec, bd_addr.ToString().c_str());
+ p_dev_rec->ble_hci_handle, p_dev_rec, PRIVATE_ADDRESS(bd_addr));
}
memset(p_dev_rec->sec_bd_name, 0, sizeof(tBTM_BD_NAME));
@@ -2058,6 +2064,11 @@ static void btm_ble_reset_id_impl(const Octet16& rand1, const Octet16& rand2) {
/* proceed generate ER */
btm_cb.devcb.ble_encryption_key_value = rand2;
btm_notify_new_key(BTM_BLE_KEY_TYPE_ER);
+
+ /* if privacy is enabled, update the irk and RPA in the LE address manager */
+ if (btm_cb.ble_ctr_cb.privacy_mode != BTM_PRIVACY_NONE) {
+ BTM_BleConfigPrivacy(true);
+ }
}
struct reset_id_data {
diff --git a/system/stack/btm/btm_ble_gap.cc b/system/stack/btm/btm_ble_gap.cc
index 8266f632de..84341ad6a9 100644
--- a/system/stack/btm/btm_ble_gap.cc
+++ b/system/stack/btm/btm_ble_gap.cc
@@ -812,7 +812,7 @@ bool BTM_BleConfigPrivacy(bool privacy_mode) {
GAP_BleAttrDBUpdate(GATT_UUID_GAP_CENTRAL_ADDR_RESOL, &gap_ble_attr_value);
- bluetooth::shim::ACL_ConfigureLePrivacy(privacy_mode);
+ bluetooth::shim::ACL_ConfigureLePrivacy(privacy_mode);
return true;
}
diff --git a/system/stack/btm/btm_iso.cc b/system/stack/btm/btm_iso.cc
index 34bde1f6f1..ab92e7a8ce 100644
--- a/system/stack/btm/btm_iso.cc
+++ b/system/stack/btm/btm_iso.cc
@@ -71,8 +71,8 @@ void IsoManager::ReconfigureCig(
pimpl_->iso_impl_->reconfigure_cig(cig_id, std::move(cig_params));
}
-void IsoManager::RemoveCig(uint8_t cig_id) {
- pimpl_->iso_impl_->remove_cig(cig_id);
+void IsoManager::RemoveCig(uint8_t cig_id, bool force) {
+ pimpl_->iso_impl_->remove_cig(cig_id, force);
}
void IsoManager::EstablishCis(
diff --git a/system/stack/btm/btm_iso_impl.h b/system/stack/btm/btm_iso_impl.h
index 245a4ef696..358296e81e 100644
--- a/system/stack/btm/btm_iso_impl.h
+++ b/system/stack/btm/btm_iso_impl.h
@@ -201,8 +201,12 @@ struct iso_impl {
cig_callbacks_->OnCigEvent(kIsoEventCigOnRemoveCmpl, &evt);
}
- void remove_cig(uint8_t cig_id) {
- LOG_ASSERT(IsCigKnown(cig_id)) << "No such cig: " << +cig_id;
+ void remove_cig(uint8_t cig_id, bool force) {
+ if (!force) {
+ LOG_ASSERT(IsCigKnown(cig_id)) << "No such cig: " << +cig_id;
+ } else {
+ LOG_WARN("Forcing to remove CIG %d", cig_id);
+ }
btsnd_hcic_remove_cig(cig_id, base::BindOnce(&iso_impl::on_remove_cig,
base::Unretained(this)));
diff --git a/system/stack/eatt/eatt.cc b/system/stack/eatt/eatt.cc
index a023026309..8e88be96bc 100644
--- a/system/stack/eatt/eatt.cc
+++ b/system/stack/eatt/eatt.cc
@@ -175,7 +175,7 @@ bool EattExtension::IsOutstandingMsgInSendQueue(const RawAddress& bd_addr) {
return pimpl_->eatt_impl_->is_outstanding_msg_in_send_queue(bd_addr);
}
-EattChannel* EattExtension::GetChannelWithQueuedData(
+EattChannel* EattExtension::GetChannelWithQueuedDataToSend(
const RawAddress& bd_addr) {
return pimpl_->eatt_impl_->get_channel_with_queued_data(bd_addr);
}
diff --git a/system/stack/eatt/eatt.h b/system/stack/eatt/eatt.h
index 828d17ac0e..40542b00ce 100644
--- a/system/stack/eatt/eatt.h
+++ b/system/stack/eatt/eatt.h
@@ -224,7 +224,8 @@ class EattExtension {
*
* @return pointer to EATT channel.
*/
- virtual EattChannel* GetChannelWithQueuedData(const RawAddress& bd_addr);
+ virtual EattChannel* GetChannelWithQueuedDataToSend(
+ const RawAddress& bd_addr);
/**
* Get EATT channel available to send GATT request.
diff --git a/system/stack/eatt/eatt_impl.h b/system/stack/eatt/eatt_impl.h
index a59afa999f..4c3a561552 100644
--- a/system/stack/eatt/eatt_impl.h
+++ b/system/stack/eatt/eatt_impl.h
@@ -706,7 +706,10 @@ struct eatt_impl {
auto iter = find_if(
eatt_dev->eatt_channels.begin(), eatt_dev->eatt_channels.end(),
[](const std::pair<uint16_t, std::shared_ptr<EattChannel>>& el) {
- return !el.second->cl_cmd_q_.empty();
+ if (el.second->cl_cmd_q_.empty()) return false;
+
+ tGATT_CMD_Q& cmd = el.second->cl_cmd_q_.front();
+ return cmd.to_send;
});
return (iter != eatt_dev->eatt_channels.end());
}
@@ -718,7 +721,10 @@ struct eatt_impl {
auto iter = find_if(
eatt_dev->eatt_channels.begin(), eatt_dev->eatt_channels.end(),
[](const std::pair<uint16_t, std::shared_ptr<EattChannel>>& el) {
- return !el.second->cl_cmd_q_.empty();
+ if (el.second->cl_cmd_q_.empty()) return false;
+
+ tGATT_CMD_Q& cmd = el.second->cl_cmd_q_.front();
+ return cmd.to_send;
});
return (iter == eatt_dev->eatt_channels.end()) ? nullptr
: iter->second.get();
diff --git a/system/stack/gatt/gatt_attr.cc b/system/stack/gatt/gatt_attr.cc
index ed97e72214..2ea6e89b9e 100644
--- a/system/stack/gatt/gatt_attr.cc
+++ b/system/stack/gatt/gatt_attr.cc
@@ -79,6 +79,8 @@ static void gatt_cl_op_cmpl_cback(uint16_t conn_id, tGATTC_OPTYPE op,
static void gatt_cl_start_config_ccc(tGATT_PROFILE_CLCB* p_clcb);
+static bool gatt_cl_is_robust_caching_enabled();
+
static bool gatt_sr_is_robust_caching_enabled();
static bool read_sr_supported_feat_req(
@@ -430,7 +432,7 @@ void gatt_profile_db_init(void) {
gatt_cb.gatt_svr_supported_feat_mask |= BLE_GATT_SVR_SUP_FEAT_EATT_BITMASK;
gatt_cb.gatt_cl_supported_feat_mask |= BLE_GATT_CL_ANDROID_SUP_FEAT;
- if (gatt_sr_is_robust_caching_enabled())
+ if (gatt_cl_is_robust_caching_enabled())
gatt_cb.gatt_cl_supported_feat_mask |= BLE_GATT_CL_SUP_FEAT_CACHING_BITMASK;
VLOG(1) << __func__ << ": gatt_if=" << gatt_cb.gatt_if << " EATT supported";
@@ -848,6 +850,19 @@ bool gatt_profile_get_eatt_support(const RawAddress& remote_bda) {
/*******************************************************************************
*
+ * Function gatt_cl_is_robust_caching_enabled
+ *
+ * Description Check if Robust Caching is enabled on client side.
+ *
+ * Returns true if enabled in gd flag, otherwise false
+ *
+ ******************************************************************************/
+static bool gatt_cl_is_robust_caching_enabled() {
+ return bluetooth::common::init_flags::gatt_robust_caching_client_is_enabled();
+}
+
+/*******************************************************************************
+ *
* Function gatt_sr_is_robust_caching_enabled
*
* Description Check if Robust Caching is enabled on server side.
@@ -856,7 +871,7 @@ bool gatt_profile_get_eatt_support(const RawAddress& remote_bda) {
*
******************************************************************************/
static bool gatt_sr_is_robust_caching_enabled() {
- return bluetooth::common::init_flags::gatt_robust_caching_is_enabled();
+ return bluetooth::common::init_flags::gatt_robust_caching_server_is_enabled();
}
/*******************************************************************************
diff --git a/system/stack/gatt/gatt_cl.cc b/system/stack/gatt/gatt_cl.cc
index d62a0872d1..215ea2abdf 100644
--- a/system/stack/gatt/gatt_cl.cc
+++ b/system/stack/gatt/gatt_cl.cc
@@ -1138,7 +1138,8 @@ bool gatt_cl_send_next_cmd_inq(tGATT_TCB& tcb) {
cl_cmd_q = &tcb.cl_cmd_q;
} else {
EattChannel* channel =
- EattExtension::GetInstance()->GetChannelWithQueuedData(tcb.peer_bda);
+ EattExtension::GetInstance()->GetChannelWithQueuedDataToSend(
+ tcb.peer_bda);
cl_cmd_q = &channel->cl_cmd_q_;
}
diff --git a/system/stack/include/btm_iso_api.h b/system/stack/include/btm_iso_api.h
index 221dfedb85..f0300366a5 100644
--- a/system/stack/include/btm_iso_api.h
+++ b/system/stack/include/btm_iso_api.h
@@ -107,8 +107,9 @@ class IsoManager {
* Initiates removing of connected isochronous group (CIG).
*
* @param cig_id connected isochronous group id
+ * @param force do not check if CIG exist
*/
- virtual void RemoveCig(uint8_t cig_id);
+ virtual void RemoveCig(uint8_t cig_id, bool force = false);
/**
* Initiates creation of connected isochronous stream (CIS).
diff --git a/system/stack/l2cap/l2c_fcr.cc b/system/stack/l2cap/l2c_fcr.cc
index 4960858da8..98ebd2e12c 100644
--- a/system/stack/l2cap/l2c_fcr.cc
+++ b/system/stack/l2cap/l2c_fcr.cc
@@ -736,6 +736,10 @@ void l2c_lcc_proc_pdu(tL2C_CCB* p_ccb, BT_HDR* p_buf) {
} else {
p_data = p_ccb->ble_sdu;
+ if (p_data == NULL) {
+ osi_free(p_buf);
+ return;
+ }
if (p_buf->len > (p_ccb->ble_sdu_length - p_data->len)) {
L2CAP_TRACE_ERROR("%s: buffer length=%d too big. max=%d. Dropped",
__func__, p_data->len,
diff --git a/system/stack/sdp/sdp_main.cc b/system/stack/sdp/sdp_main.cc
index 1cdeb9673a..9719d0faeb 100644
--- a/system/stack/sdp/sdp_main.cc
+++ b/system/stack/sdp/sdp_main.cc
@@ -22,8 +22,10 @@
*
******************************************************************************/
+#include <base/logging.h>
#include <string.h> // memset
+#include "gd/common/init_flags.h"
#include "osi/include/allocator.h"
#include "osi/include/osi.h" // UNUSED_ATTR
#include "stack/include/bt_hdr.h"
@@ -34,8 +36,6 @@
#include "stack/sdp/sdpint.h"
#include "types/raw_address.h"
-#include <base/logging.h>
-
/******************************************************************************/
/* G L O B A L S D P D A T A */
/******************************************************************************/
@@ -351,7 +351,8 @@ tCONN_CB* sdp_conn_originate(const RawAddress& p_bd_addr) {
// Look for any active sdp connection on the remote device
cid = sdpu_get_active_ccb_cid(p_bd_addr);
- if (cid == 0) {
+ if (!bluetooth::common::init_flags::sdp_serialization_is_enabled() ||
+ cid == 0) {
p_ccb->con_state = SDP_STATE_CONN_SETUP;
cid = L2CA_ConnectReq2(BT_PSM_SDP, p_bd_addr, BTM_SEC_NONE);
} else {
diff --git a/system/stack/test/btm/stack_btm_test.cc b/system/stack/test/btm/stack_btm_test.cc
index 150012eee9..b9285e643a 100644
--- a/system/stack/test/btm/stack_btm_test.cc
+++ b/system/stack/test/btm/stack_btm_test.cc
@@ -88,6 +88,7 @@ bool get_pts_l2cap_ecoc_reconfigure(void) { return false; }
const std::string* get_pts_broadcast_audio_config_options(void) {
return &kBroadcastAudioConfigOptions;
}
+bool get_pts_le_audio_disable_ases_before_stopping(void) { return false; }
config_t* get_all(void) { return nullptr; }
const packet_fragmenter_t* packet_fragmenter_get_interface() { return nullptr; }
@@ -119,6 +120,8 @@ stack_config_t mock_stack_config{
.get_pts_l2cap_ecoc_reconfigure = get_pts_l2cap_ecoc_reconfigure,
.get_pts_broadcast_audio_config_options =
get_pts_broadcast_audio_config_options,
+ .get_pts_le_audio_disable_ases_before_stopping =
+ get_pts_le_audio_disable_ases_before_stopping,
.get_all = get_all,
};
const stack_config_t* stack_config_get_interface(void) {
diff --git a/system/stack/test/btm_iso_test.cc b/system/stack/test/btm_iso_test.cc
index e18932b44e..a5554a06a7 100644
--- a/system/stack/test/btm_iso_test.cc
+++ b/system/stack/test/btm_iso_test.cc
@@ -770,6 +770,14 @@ TEST_F(IsoManagerDeathTest, RemoveCigWithNoSuchCig) {
::testing::KilledBySignal(SIGABRT), "No such cig");
}
+TEST_F(IsoManagerDeathTest, RemoveCigForceNoSuchCig) {
+ EXPECT_CALL(hcic_interface_,
+ RemoveCig(volatile_test_cig_create_cmpl_evt_.cig_id, _))
+ .Times(1);
+ IsoManager::GetInstance()->RemoveCig(
+ volatile_test_cig_create_cmpl_evt_.cig_id, true);
+}
+
TEST_F(IsoManagerDeathTest, RemoveSameCigTwice) {
IsoManager::GetInstance()->CreateCig(
volatile_test_cig_create_cmpl_evt_.cig_id, kDefaultCigParams);
diff --git a/system/stack/test/common/mock_eatt.cc b/system/stack/test/common/mock_eatt.cc
index 6e07bf5469..dc9867fb36 100644
--- a/system/stack/test/common/mock_eatt.cc
+++ b/system/stack/test/common/mock_eatt.cc
@@ -86,9 +86,9 @@ bool EattExtension::IsOutstandingMsgInSendQueue(const RawAddress& bd_addr) {
return pimpl_->IsOutstandingMsgInSendQueue(bd_addr);
}
-EattChannel* EattExtension::GetChannelWithQueuedData(
+EattChannel* EattExtension::GetChannelWithQueuedDataToSend(
const RawAddress& bd_addr) {
- return pimpl_->GetChannelWithQueuedData(bd_addr);
+ return pimpl_->GetChannelWithQueuedDataToSend(bd_addr);
}
EattChannel* EattExtension::GetChannelAvailableForClientRequest(
diff --git a/system/stack/test/common/mock_eatt.h b/system/stack/test/common/mock_eatt.h
index 6d1682befc..bbeb502297 100644
--- a/system/stack/test/common/mock_eatt.h
+++ b/system/stack/test/common/mock_eatt.h
@@ -52,7 +52,7 @@ class MockEattExtension : public EattExtension {
(const RawAddress& bd_addr));
MOCK_METHOD((void), FreeGattResources, (const RawAddress& bd_addr));
MOCK_METHOD((bool), IsOutstandingMsgInSendQueue, (const RawAddress& bd_addr));
- MOCK_METHOD((EattChannel*), GetChannelWithQueuedData,
+ MOCK_METHOD((EattChannel*), GetChannelWithQueuedDataToSend,
(const RawAddress& bd_addr));
MOCK_METHOD((EattChannel*), GetChannelAvailableForClientRequest,
(const RawAddress& bd_addr));
diff --git a/system/stack/test/sdp/stack_sdp_test.cc b/system/stack/test/sdp/stack_sdp_test.cc
index 0251aab54f..98bb1365e4 100644
--- a/system/stack/test/sdp/stack_sdp_test.cc
+++ b/system/stack/test/sdp/stack_sdp_test.cc
@@ -110,6 +110,8 @@ tCONN_CB* find_ccb(uint16_t cid, uint8_t state) {
}
TEST_F(StackSdpMainTest, sdp_service_search_request_queuing) {
+ bluetooth::common::InitFlags::SetAllForTesting();
+
ASSERT_TRUE(SDP_ServiceSearchRequest(addr, sdp_db, nullptr));
const int cid = L2CA_ConnectReq2_cid;
tCONN_CB* p_ccb1 = find_ccb(cid, SDP_STATE_CONN_SETUP);
diff --git a/system/stack/test/stack_avrcp_test.cc b/system/stack/test/stack_avrcp_test.cc
index 72ec45f290..e731e98b76 100644
--- a/system/stack/test/stack_avrcp_test.cc
+++ b/system/stack/test/stack_avrcp_test.cc
@@ -27,6 +27,56 @@ class StackAvrcpTest : public ::testing::Test {
virtual ~StackAvrcpTest() = default;
};
+TEST_F(StackAvrcpTest, test_avrcp_ctrl_parse_vendor_rsp) {
+ uint8_t scratch_buf[512]{};
+ uint16_t scratch_buf_len = 512;
+ tAVRC_MSG msg{};
+ tAVRC_RESPONSE result{};
+ uint8_t vendor_rsp_buf[512]{};
+
+ msg.hdr.opcode = AVRC_OP_VENDOR;
+ msg.hdr.ctype = AVRC_CMD_STATUS;
+
+ memset(vendor_rsp_buf, 0, sizeof(vendor_rsp_buf));
+ vendor_rsp_buf[0] = AVRC_PDU_GET_ELEMENT_ATTR;
+ uint8_t* p = &vendor_rsp_buf[2];
+ UINT16_TO_BE_STREAM(p, 0x0009); // parameter length
+ UINT8_TO_STREAM(p, 0x01); // number of attributes
+ UINT32_TO_STREAM(p, 0x00000000); // attribute ID
+ UINT16_TO_STREAM(p, 0x0000); // character set ID
+ UINT16_TO_STREAM(p, 0xffff); // attribute value length
+ msg.vendor.p_vendor_data = vendor_rsp_buf;
+ msg.vendor.vendor_len = 13;
+ EXPECT_EQ(
+ AVRC_Ctrl_ParsResponse(&msg, &result, scratch_buf, &scratch_buf_len),
+ AVRC_STS_INTERNAL_ERR);
+}
+
+TEST_F(StackAvrcpTest, test_avrcp_parse_browse_rsp) {
+ uint8_t scratch_buf[512]{};
+ uint16_t scratch_buf_len = 512;
+ tAVRC_MSG msg{};
+ tAVRC_RESPONSE result{};
+ uint8_t browse_rsp_buf[512]{};
+
+ msg.hdr.opcode = AVRC_OP_BROWSE;
+
+ memset(browse_rsp_buf, 0, sizeof(browse_rsp_buf));
+ browse_rsp_buf[0] = AVRC_PDU_GET_ITEM_ATTRIBUTES;
+ uint8_t* p = &browse_rsp_buf[1];
+ UINT16_TO_BE_STREAM(p, 0x000a); // parameter length;
+ UINT8_TO_STREAM(p, 0x04); // status
+ UINT8_TO_STREAM(p, 0x01); // number of attribute
+ UINT32_TO_STREAM(p, 0x00000000); // attribute ID
+ UINT16_TO_STREAM(p, 0x0000); // character set ID
+ UINT16_TO_STREAM(p, 0xffff); // attribute value length
+ msg.browse.p_browse_data = browse_rsp_buf;
+ msg.browse.browse_len = 13;
+ EXPECT_EQ(
+ AVRC_Ctrl_ParsResponse(&msg, &result, scratch_buf, &scratch_buf_len),
+ AVRC_STS_BAD_CMD);
+}
+
TEST_F(StackAvrcpTest, test_avrcp_parse_browse_cmd) {
uint8_t scratch_buf[512]{};
tAVRC_MSG msg{};
diff --git a/system/stack/test/stack_smp_test.cc b/system/stack/test/stack_smp_test.cc
index d0c7c0fabc..85a16f9797 100644
--- a/system/stack/test/stack_smp_test.cc
+++ b/system/stack/test/stack_smp_test.cc
@@ -63,6 +63,7 @@ bool get_pts_l2cap_ecoc_reconfigure(void) { return false; }
const std::string* get_pts_broadcast_audio_config_options(void) {
return &kBroadcastAudioConfigOptions;
}
+bool get_pts_le_audio_disable_ases_before_stopping(void) { return false; }
config_t* get_all(void) { return nullptr; }
const packet_fragmenter_t* packet_fragmenter_get_interface() { return nullptr; }
@@ -95,6 +96,8 @@ stack_config_t mock_stack_config{
.get_pts_l2cap_ecoc_reconfigure = get_pts_l2cap_ecoc_reconfigure,
.get_pts_broadcast_audio_config_options =
get_pts_broadcast_audio_config_options,
+ .get_pts_le_audio_disable_ases_before_stopping =
+ get_pts_le_audio_disable_ases_before_stopping,
.get_all = get_all,
};
const stack_config_t* stack_config_get_interface(void) {
diff --git a/system/test/common/stack_config.cc b/system/test/common/stack_config.cc
index 300f0d6442..bb783f9f64 100644
--- a/system/test/common/stack_config.cc
+++ b/system/test/common/stack_config.cc
@@ -48,6 +48,7 @@ bool get_pts_l2cap_ecoc_reconfigure(void) { return false; }
const std::string* get_pts_broadcast_audio_config_options(void) {
return &kBroadcastAudioConfigOptions;
}
+bool get_pts_le_audio_disable_ases_before_stopping(void) { return false; }
struct config_t;
config_t* get_all(void) { return nullptr; }
struct packet_fragmenter_t;
@@ -82,6 +83,8 @@ stack_config_t mock_stack_config{
.get_pts_l2cap_ecoc_reconfigure = get_pts_l2cap_ecoc_reconfigure,
.get_pts_broadcast_audio_config_options =
get_pts_broadcast_audio_config_options,
+ .get_pts_le_audio_disable_ases_before_stopping =
+ get_pts_le_audio_disable_ases_before_stopping,
.get_all = get_all,
};
diff --git a/system/test/mock/mock_bta_dm_act.h b/system/test/mock/mock_bta_dm_act.h
index 5a566dcb7d..af586006e6 100644
--- a/system/test/mock/mock_bta_dm_act.h
+++ b/system/test/mock/mock_bta_dm_act.h
@@ -264,6 +264,15 @@ struct bta_dm_clear_event_filter {
};
extern struct bta_dm_clear_event_filter bta_dm_clear_event_filter;
+// Name: bta_dm_ble_reset_id
+// Params: None
+// Return: void
+struct bta_dm_ble_reset_id {
+ std::function<void()> body{[]() {}};
+ void operator()() { body(); };
+};
+extern struct bta_dm_ble_reset_id bta_dm_ble_reset_id;
+
// Name: bta_dm_ble_passkey_reply
// Params: const RawAddress& bd_addr, bool accept, uint32_t passkey
// Return: void
diff --git a/system/test/mock/mock_main_shim_btm_api.cc b/system/test/mock/mock_main_shim_btm_api.cc
index 84c19ae40a..89ce392638 100644
--- a/system/test/mock/mock_main_shim_btm_api.cc
+++ b/system/test/mock/mock_main_shim_btm_api.cc
@@ -475,3 +475,8 @@ tBTM_STATUS bluetooth::shim::BTM_SetEventFilterInquiryResultAllDevices() {
mock_function_count_map[__func__]++;
return BTM_SUCCESS;
}
+
+tBTM_STATUS bluetooth::shim::BTM_BleResetId() {
+ mock_function_count_map[__func__]++;
+ return BTM_SUCCESS;
+}
diff --git a/system/test/mock/mock_stack_btm_iso.cc b/system/test/mock/mock_stack_btm_iso.cc
index 4f99d66579..6c5cda35d2 100644
--- a/system/test/mock/mock_stack_btm_iso.cc
+++ b/system/test/mock/mock_stack_btm_iso.cc
@@ -18,7 +18,7 @@ void IsoManager::CreateCig(uint8_t cig_id,
struct iso_manager::cig_create_params cig_params) {}
void IsoManager::ReconfigureCig(
uint8_t cig_id, struct iso_manager::cig_create_params cig_params) {}
-void IsoManager::RemoveCig(uint8_t cig_id) {}
+void IsoManager::RemoveCig(uint8_t cig_id, bool force) {}
void IsoManager::EstablishCis(
struct iso_manager::cis_establish_params conn_params) {}
void IsoManager::DisconnectCis(uint16_t cis_handle, uint8_t reason) {}
diff --git a/system/test/rootcanal/Android.bp b/system/test/rootcanal/Android.bp
index 38a6cdd5f1..bf81f49929 100644
--- a/system/test/rootcanal/Android.bp
+++ b/system/test/rootcanal/Android.bp
@@ -51,9 +51,6 @@ cc_binary {
],
cflags: [
"-fvisibility=hidden",
- "-Wall",
- "-Wextra",
- "-Werror",
"-DHAS_NO_BDROID_BUILDCFG",
],
generated_headers: [
@@ -106,9 +103,6 @@ cc_library_shared {
"libprotobuf-cpp-lite",
],
cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
"-DHAS_NO_BDROID_BUILDCFG",
],
generated_headers: [
diff --git a/system/tools/scripts/dump_le_audio.py b/system/tools/scripts/dump_le_audio.py
index 806cdfb357..048facbfa8 100755
--- a/system/tools/scripts/dump_le_audio.py
+++ b/system/tools/scripts/dump_le_audio.py
@@ -79,6 +79,13 @@ OPCODE_RELEASE = 0x08
# opcode for hci command
OPCODE_HCI_CREATE_CIS = 0x2064
OPCODE_REMOVE_ISO_DATA_PATH = 0x206F
+OPCODE_LE_SET_PERIODIC_ADVERTISING_DATA = 0x203F
+OPCODE_LE_CREATE_BIG = 0x2068
+OPCODE_LE_SETUP_ISO_DATA_PATH = 0x206E
+
+# HCI event
+EVENT_CODE_LE_META_EVENT = 0x3E
+SUBEVENT_CODE_LE_CREATE_BIG_COMPLETE = 0x1B
TYPE_STREAMING_AUDIO_CONTEXTS = 0x02
@@ -114,6 +121,9 @@ AUDIO_LOCATION_LEFT = 0x01
AUDIO_LOCATION_RIGHT = 0x02
AUDIO_LOCATION_CENTER = 0x04
+AD_TYPE_SERVICE_DATA_16_BIT = 0x16
+BASIC_AUDIO_ANNOUNCEMENT_SERVICE = 0x1851
+
packet_number = 0
debug_enable = False
add_header = False
@@ -158,35 +168,77 @@ class AseStream:
print("octets_per_frame: " + str(self.octets_per_frame))
+class Broadcast:
+
+ def __init__(self):
+ self.num_of_bis = defaultdict(int) # subgroup - num_of_bis
+ self.bis = defaultdict(BisStream) # bis_index - codec_config
+ self.bis_index_handle_map = defaultdict(int) # bis_index - bis_handle
+ self.bis_index_list = []
+
+ def dump(self):
+ for bis_index, iso_stream in self.bis.items():
+ print("bis_index: " + str(bis_index) + " bis handle: " + str(self.bis_index_handle_map[bis_index]))
+ iso_stream.dump()
+
+
+class BisStream:
+
+ def __init__(self):
+ self.sampling_frequencies = 0xFF
+ self.frame_duration = 0xFF
+ self.channel_allocation = 0xFFFFFFFF
+ self.octets_per_frame = 0xFFFF
+ self.output_dump = []
+ self.start_time = 0xFFFFFFFF
+
+ def dump(self):
+ print("start_time: " + str(self.start_time))
+ print("sampling_frequencies: " + str(self.sampling_frequencies))
+ print("frame_duration: " + str(self.frame_duration))
+ print("channel_allocation: " + str(self.channel_allocation))
+ print("octets_per_frame: " + str(self.octets_per_frame))
+
+
connection_map = defaultdict(Connection)
cis_acl_map = defaultdict(int)
+broadcast_map = defaultdict(Broadcast)
+big_adv_map = defaultdict(int)
+bis_stream_map = defaultdict(BisStream)
+
+
+def generate_header(file, stream, is_cis):
+ sf_case = {
+ SAMPLE_FREQUENCY_8000: 80,
+ SAMPLE_FREQUENCY_11025: 110,
+ SAMPLE_FREQUENCY_16000: 160,
+ SAMPLE_FREQUENCY_22050: 220,
+ SAMPLE_FREQUENCY_24000: 240,
+ SAMPLE_FREQUENCY_32000: 320,
+ SAMPLE_FREQUENCY_44100: 441,
+ SAMPLE_FREQUENCY_48000: 480,
+ SAMPLE_FREQUENCY_88200: 882,
+ SAMPLE_FREQUENCY_96000: 960,
+ SAMPLE_FREQUENCY_176400: 1764,
+ SAMPLE_FREQUENCY_192000: 1920,
+ SAMPLE_FREQUENCY_384000: 2840,
+ }
+ fd_case = {FRAME_DURATION_7_5: 7.5, FRAME_DURATION_10: 10}
+ al_case = {AUDIO_LOCATION_MONO: 1, AUDIO_LOCATION_LEFT: 1, AUDIO_LOCATION_RIGHT: 1, AUDIO_LOCATION_CENTER: 2}
-
-def generate_header(file, connection):
header = bytearray.fromhex('1ccc1200')
- for ase in connection.ase.values():
- sf_case = {
- SAMPLE_FREQUENCY_8000: 80,
- SAMPLE_FREQUENCY_11025: 110,
- SAMPLE_FREQUENCY_16000: 160,
- SAMPLE_FREQUENCY_22050: 220,
- SAMPLE_FREQUENCY_24000: 240,
- SAMPLE_FREQUENCY_32000: 320,
- SAMPLE_FREQUENCY_44100: 441,
- SAMPLE_FREQUENCY_48000: 480,
- SAMPLE_FREQUENCY_88200: 882,
- SAMPLE_FREQUENCY_96000: 960,
- SAMPLE_FREQUENCY_176400: 1764,
- SAMPLE_FREQUENCY_192000: 1920,
- SAMPLE_FREQUENCY_384000: 2840,
- }
- header = header + struct.pack("<H", sf_case[ase.sampling_frequencies])
- fd_case = {FRAME_DURATION_7_5: 7.5, FRAME_DURATION_10: 10}
- header = header + struct.pack("<H", int(ase.octets_per_frame * 8 * 10 / fd_case[ase.frame_duration]))
- al_case = {AUDIO_LOCATION_MONO: 1, AUDIO_LOCATION_LEFT: 1, AUDIO_LOCATION_RIGHT: 1, AUDIO_LOCATION_CENTER: 2}
- header = header + struct.pack("<HHHL", al_case[ase.channel_allocation], fd_case[ase.frame_duration] * 100, 0,
- 48000000)
- break
+ if is_cis:
+ for ase in stream.ase.values():
+ header = header + struct.pack("<H", sf_case[ase.sampling_frequencies])
+ header = header + struct.pack("<H", int(ase.octets_per_frame * 8 * 10 / fd_case[ase.frame_duration]))
+ header = header + struct.pack("<HHHL", al_case[ase.channel_allocation], fd_case[ase.frame_duration] * 100,
+ 0, 48000000)
+ break
+ else:
+ header = header + struct.pack("<H", sf_case[stream.sampling_frequencies])
+ header = header + struct.pack("<H", int(stream.octets_per_frame * 8 * 10 / fd_case[stream.frame_duration]))
+ header = header + struct.pack("<HHHL", al_case[stream.channel_allocation], fd_case[stream.frame_duration] * 100,
+ 0, 48000000)
file.write(header)
@@ -206,7 +258,7 @@ def parse_codec_information(connection_handle, ase_id, packet):
ase.frame_duration = value
elif config_type == TYPE_CHANNEL_ALLOCATION:
ase.channel_allocation = value
- elif TYPE_OCTETS_PER_FRAME:
+ elif config_type == TYPE_OCTETS_PER_FRAME:
ase.octets_per_frame = value
length -= (config_length + 1)
@@ -284,6 +336,64 @@ def parse_att_packet(packet, connection_handle, flags, timestamp):
packet_handle.get((opcode, flags), lambda x, y, z: None)(packet, connection_handle, timestamp)
+def parse_big_codec_information(adv_handle, packet):
+ # Ignore presentation delay
+ packet = unpack_data(packet, 3, True)
+ number_of_subgroup, packet = unpack_data(packet, 1, False)
+ for subgroup in range(number_of_subgroup):
+ num_of_bis, packet = unpack_data(packet, 1, False)
+ broadcast_map[adv_handle].num_of_bis[subgroup] = num_of_bis
+ # Ignore codec id
+ packet = unpack_data(packet, 5, True)
+ length, packet = unpack_data(packet, 1, False)
+ if len(packet) < length:
+ print("Invalid subgroup codec information length")
+ return
+
+ while length > 0:
+ config_length, packet = unpack_data(packet, 1, False)
+ config_type, packet = unpack_data(packet, 1, False)
+ value, packet = unpack_data(packet, config_length - 1, False)
+ if config_type == TYPE_SAMPLING_FREQUENCIES:
+ sampling_frequencies = value
+ elif config_type == TYPE_FRAME_DURATION:
+ frame_duration = value
+ elif config_type == TYPE_OCTETS_PER_FRAME:
+ octets_per_frame = value
+ else:
+ print("Unknown config type")
+ length -= (config_length + 1)
+
+ # Ignore metadata
+ metadata_length, packet = unpack_data(packet, 1, False)
+ packet = unpack_data(packet, metadata_length, True)
+
+ for count in range(num_of_bis):
+ bis_index, packet = unpack_data(packet, 1, False)
+ broadcast_map[adv_handle].bis_index_list.append(bis_index)
+ length, packet = unpack_data(packet, 1, False)
+ if len(packet) < length:
+ print("Invalid level 3 codec information length")
+ return
+
+ while length > 0:
+ config_length, packet = unpack_data(packet, 1, False)
+ config_type, packet = unpack_data(packet, 1, False)
+ value, packet = unpack_data(packet, config_length - 1, False)
+ if config_type == TYPE_CHANNEL_ALLOCATION:
+ channel_allocation = value
+ else:
+ print("Ignored config type")
+ length -= (config_length + 1)
+
+ broadcast_map[adv_handle].bis[bis_index].sampling_frequencies = sampling_frequencies
+ broadcast_map[adv_handle].bis[bis_index].frame_duration = frame_duration
+ broadcast_map[adv_handle].bis[bis_index].octets_per_frame = octets_per_frame
+ broadcast_map[adv_handle].bis[bis_index].channel_allocation = channel_allocation
+
+ return packet
+
+
def debug_print(log):
global packet_number
print("#" + str(packet_number) + ": " + log)
@@ -303,7 +413,7 @@ def unpack_data(data, byte, ignore):
return value, data[byte:]
-def parse_command_packet(packet):
+def parse_command_packet(packet, timestamp):
opcode, packet = unpack_data(packet, 2, False)
if opcode == OPCODE_HCI_CREATE_CIS:
debug_print("OPCODE_HCI_CREATE_CIS")
@@ -330,9 +440,96 @@ def parse_command_packet(packet):
debug_print("Invalid cmd length")
return
- cis_handle, packet = unpack_data(packet, 2, False)
- acl_handle = cis_acl_map[cis_handle]
- dump_audio_data_to_file(acl_handle)
+ iso_handle, packet = unpack_data(packet, 2, False)
+ # CIS stream
+ if iso_handle in cis_acl_map:
+ acl_handle = cis_acl_map[iso_handle]
+ dump_cis_audio_data_to_file(acl_handle)
+ # To Do: BIS stream
+ elif iso_handle in bis_stream_map:
+ dump_bis_audio_data_to_file(iso_handle)
+ elif opcode == OPCODE_LE_SET_PERIODIC_ADVERTISING_DATA:
+ debug_print("OPCODE_LE_SET_PERIODIC_ADVERTISING_DATA")
+
+ length, packet = unpack_data(packet, 1, False)
+ if length != len(packet):
+ debug_print("Invalid cmd length")
+ return
+
+ if length < 21:
+ debug_print("Ignored. Not basic audio announcement")
+ return
+
+ adv_hdl, packet = unpack_data(packet, 1, False)
+ #ignore operation, advertising_data_length
+ packet = unpack_data(packet, 2, True)
+ length, packet = unpack_data(packet, 1, False)
+ if length != len(packet):
+ debug_print("Invalid AD element length")
+ return
+
+ ad_type, packet = unpack_data(packet, 1, False)
+ service, packet = unpack_data(packet, 2, False)
+ if ad_type != AD_TYPE_SERVICE_DATA_16_BIT or service != BASIC_AUDIO_ANNOUNCEMENT_SERVICE:
+ debug_print("Ignored. Not basic audio announcement")
+ return
+
+ packet = parse_big_codec_information(adv_hdl, packet)
+ elif opcode == OPCODE_LE_CREATE_BIG:
+ debug_print("OPCODE_LE_CREATE_BIG")
+
+ length, packet = unpack_data(packet, 1, False)
+ if length != len(packet) and length < 31:
+ debug_print("Invalid Create BIG command length")
+ return
+
+ big_handle, packet = unpack_data(packet, 1, False)
+ adv_handle, packet = unpack_data(packet, 1, False)
+ big_adv_map[big_handle] = adv_handle
+ elif opcode == OPCODE_LE_SETUP_ISO_DATA_PATH:
+ debug_print("OPCODE_LE_SETUP_ISO_DATA_PATH")
+ length, packet = unpack_data(packet, 1, False)
+ if len(packet) != length:
+ debug_print("Invalid LE SETUP ISO DATA PATH command length")
+ return
+
+ iso_handle, packet = unpack_data(packet, 2, False)
+ if iso_handle in bis_stream_map:
+ bis_stream_map[iso_handle].start_time = timestamp
+
+
+def parse_event_packet(packet):
+ event_code, packet = unpack_data(packet, 1, False)
+ if event_code != EVENT_CODE_LE_META_EVENT:
+ return
+
+ length, packet = unpack_data(packet, 1, False)
+ if len(packet) != length:
+ print("Invalid LE mata event length")
+ return
+
+ subevent_code, packet = unpack_data(packet, 1, False)
+ if subevent_code != SUBEVENT_CODE_LE_CREATE_BIG_COMPLETE:
+ return
+
+ status, packet = unpack_data(packet, 1, False)
+ if status != 0x00:
+ debug_print("Create_BIG failed")
+ return
+
+ big_handle, packet = unpack_data(packet, 1, False)
+ if big_handle not in big_adv_map:
+ print("Invalid BIG handle")
+ return
+ adv_handle = big_adv_map[big_handle]
+ # Ignore, we don't care these parameter
+ packet = unpack_data(packet, 15, True)
+ num_of_bis, packet = unpack_data(packet, 1, False)
+ for count in range(num_of_bis):
+ bis_handle, packet = unpack_data(packet, 2, False)
+ bis_index = broadcast_map[adv_handle].bis_index_list[count]
+ broadcast_map[adv_handle].bis_index_handle_map[bis_index] = bis_handle
+ bis_stream_map[bis_handle] = broadcast_map[adv_handle].bis[bis_index]
def convert_time_str(timestamp):
@@ -348,7 +545,7 @@ def convert_time_str(timestamp):
return full_str_format
-def dump_audio_data_to_file(acl_handle):
+def dump_cis_audio_data_to_file(acl_handle):
if debug_enable:
connection_map[acl_handle].dump()
file_name = ""
@@ -389,20 +586,20 @@ def dump_audio_data_to_file(acl_handle):
break
if connection_map[acl_handle].input_dump != []:
- debug_print("Dump input...")
+ debug_print("Dump unicast input...")
f = open(file_name + "_input.bin", 'wb')
if add_header == True:
- generate_header(f, connection_map[acl_handle])
+ generate_header(f, connection_map[acl_handle], True)
arr = bytearray(connection_map[acl_handle].input_dump)
f.write(arr)
f.close()
connection_map[acl_handle].input_dump = []
if connection_map[acl_handle].output_dump != []:
- debug_print("Dump output...")
+ debug_print("Dump unicast output...")
f = open(file_name + "_output.bin", 'wb')
if add_header == True:
- generate_header(f, connection_map[acl_handle])
+ generate_header(f, connection_map[acl_handle], True)
arr = bytearray(connection_map[acl_handle].output_dump)
f.write(arr)
f.close()
@@ -411,6 +608,51 @@ def dump_audio_data_to_file(acl_handle):
return
+def dump_bis_audio_data_to_file(iso_handle):
+ if debug_enable:
+ bis_stream_map[iso_handle].dump()
+ file_name = "broadcast"
+ sf_case = {
+ SAMPLE_FREQUENCY_8000: "8000",
+ SAMPLE_FREQUENCY_11025: "11025",
+ SAMPLE_FREQUENCY_16000: "16000",
+ SAMPLE_FREQUENCY_22050: "22050",
+ SAMPLE_FREQUENCY_24000: "24000",
+ SAMPLE_FREQUENCY_32000: "32000",
+ SAMPLE_FREQUENCY_44100: "44100",
+ SAMPLE_FREQUENCY_48000: "48000",
+ SAMPLE_FREQUENCY_88200: "88200",
+ SAMPLE_FREQUENCY_96000: "96000",
+ SAMPLE_FREQUENCY_176400: "176400",
+ SAMPLE_FREQUENCY_192000: "192000",
+ SAMPLE_FREQUENCY_384000: "284000"
+ }
+ file_name += ("_sf" + sf_case[bis_stream_map[iso_handle].sampling_frequencies])
+ fd_case = {FRAME_DURATION_7_5: "7_5", FRAME_DURATION_10: "10"}
+ file_name += ("_fd" + fd_case[bis_stream_map[iso_handle].frame_duration])
+ al_case = {
+ AUDIO_LOCATION_MONO: "mono",
+ AUDIO_LOCATION_LEFT: "left",
+ AUDIO_LOCATION_RIGHT: "right",
+ AUDIO_LOCATION_CENTER: "center"
+ }
+ file_name += ("_" + al_case[bis_stream_map[iso_handle].channel_allocation])
+ file_name += ("_frame" + str(bis_stream_map[iso_handle].octets_per_frame))
+ file_name += ("_" + convert_time_str(bis_stream_map[iso_handle].start_time))
+
+ if bis_stream_map[iso_handle].output_dump != []:
+ debug_print("Dump broadcast output...")
+ f = open(file_name + "_output.bin", 'wb')
+ if add_header == True:
+ generate_header(f, bis_stream_map[iso_handle], False)
+ arr = bytearray(bis_stream_map[iso_handle].output_dump)
+ f.write(arr)
+ f.close()
+ bis_stream_map[iso_handle].output_dump = []
+
+ return
+
+
def parse_acl_packet(packet, flags, timestamp):
# Check the minimum acl length, HCI leader (4 bytes)
# + L2CAP header (4 bytes)
@@ -441,8 +683,8 @@ def parse_acl_packet(packet, flags, timestamp):
def parse_iso_packet(packet, flags):
- cis_handle, packet = unpack_data(packet, 2, False)
- cis_handle &= 0x0EFF
+ iso_handle, packet = unpack_data(packet, 2, False)
+ iso_handle &= 0x0EFF
iso_data_load_length, packet = unpack_data(packet, 2, False)
if iso_data_load_length != len(packet):
debug_print("Invalid iso data load length")
@@ -457,13 +699,18 @@ def parse_iso_packet(packet, flags):
debug_print("Invalid iso sdu length")
return
- acl_handle = cis_acl_map[cis_handle]
- if flags == SENT:
- connection_map[acl_handle].output_dump.extend(struct.pack("<H", len(packet)))
- connection_map[acl_handle].output_dump.extend(list(packet))
- elif flags == RECEIVED:
- connection_map[acl_handle].input_dump.extend(struct.pack("<H", len(packet)))
- connection_map[acl_handle].input_dump.extend(list(packet))
+ # CIS stream
+ if iso_handle in cis_acl_map:
+ acl_handle = cis_acl_map[iso_handle]
+ if flags == SENT:
+ connection_map[acl_handle].output_dump.extend(struct.pack("<H", len(packet)))
+ connection_map[acl_handle].output_dump.extend(list(packet))
+ elif flags == RECEIVED:
+ connection_map[acl_handle].input_dump.extend(struct.pack("<H", len(packet)))
+ connection_map[acl_handle].input_dump.extend(list(packet))
+ elif iso_handle in bis_stream_map:
+ bis_stream_map[iso_handle].output_dump.extend(struct.pack("<H", len(packet)))
+ bis_stream_map[iso_handle].output_dump.extend(list(packet))
def parse_next_packet(btsnoop_file):
@@ -490,10 +737,10 @@ def parse_next_packet(btsnoop_file):
return False
packet_handle = {
- COMMADN_PACKET: (lambda x, y, z: parse_command_packet(x)),
+ COMMADN_PACKET: (lambda x, y, z: parse_command_packet(x, z)),
ACL_PACKET: (lambda x, y, z: parse_acl_packet(x, y, z)),
SCO_PACKET: (lambda x, y, z: None),
- EVENT_PACKET: (lambda x, y, z: None),
+ EVENT_PACKET: (lambda x, y, z: parse_event_packet(x)),
ISO_PACKET: (lambda x, y, z: parse_iso_packet(x, y))
}
packet_handle.get(type, lambda x, y, z: None)(packet, flags, timestamp)
@@ -535,7 +782,10 @@ def main():
break
for handle in connection_map.keys():
- dump_audio_data_to_file(handle)
+ dump_cis_audio_data_to_file(handle)
+
+ for handle in bis_stream_map.keys():
+ dump_bis_audio_data_to_file(handle)
if __name__ == "__main__":
diff --git a/system/vendor_libs/linux/interface/Android.bp b/system/vendor_libs/linux/interface/Android.bp
index 85fde7d473..a92124e77f 100644
--- a/system/vendor_libs/linux/interface/Android.bp
+++ b/system/vendor_libs/linux/interface/Android.bp
@@ -24,6 +24,7 @@ package {
cc_binary {
name: "android.hardware.bluetooth@1.1-service.btlinux",
+ defaults: ["fluoride_common_options"],
proprietary: true,
relative_install_path: "hw",
srcs: [
@@ -32,10 +33,6 @@ cc_binary {
"bluetooth_hci.cc",
"service.cc",
],
- cflags: [
- "-Wall",
- "-Werror",
- ],
header_libs: ["libbluetooth_headers"],
shared_libs: [
"android.hardware.bluetooth@1.0",
@@ -57,14 +54,11 @@ cc_binary {
cc_library_static {
name: "async_fd_watcher",
+ defaults: ["fluoride_common_options"],
proprietary: true,
srcs: [
"async_fd_watcher.cc",
],
- cflags: [
- "-Wall",
- "-Werror",
- ],
shared_libs: [
"liblog",
],
diff --git a/tools/pdl/Android.bp b/tools/pdl/Android.bp
index 80473ac6a2..663972c9a8 100644
--- a/tools/pdl/Android.bp
+++ b/tools/pdl/Android.bp
@@ -56,6 +56,8 @@ rust_test_host {
"tests/generated/packet_decl_empty.rs",
"tests/generated/packet_decl_simple_little_endian.rs",
"tests/generated/packet_decl_simple_big_endian.rs",
+ "tests/generated/packet_decl_complex_little_endian.rs",
+ "tests/generated/packet_decl_complex_big_endian.rs",
],
}
diff --git a/tools/pdl/doc/reference.md b/tools/pdl/doc/reference.md
index f30db0816d..3cbd256590 100644
--- a/tools/pdl/doc/reference.md
+++ b/tools/pdl/doc/reference.md
@@ -563,11 +563,13 @@ packet CRCedBrew {
> padding_field:\
> &nbsp;&nbsp; `_padding_` `[` [INTEGER](#integer) `]`
-A *\_padding\_* field adds a number of **octet** of padding.
+A *\_padding\_* field immediately following an array field pads the array field with `0`s to the
+specified number of **octets**.
```
-packet Padded {
- _padding_[1] // 1 octet/8bit of padding
+packet PaddedCoffee {
+ additions: CoffeeAddition[],
+ _padding_[100]
}
```
diff --git a/tools/pdl/scripts/generate_python_backend.py b/tools/pdl/scripts/generate_python_backend.py
index 34497a844a..172998ab7a 100755
--- a/tools/pdl/scripts/generate_python_backend.py
+++ b/tools/pdl/scripts/generate_python_backend.py
@@ -177,6 +177,8 @@ class FieldParser:
"""Parse the selected array field."""
array_size = core.get_array_field_size(field)
element_width = core.get_array_element_size(field)
+ padded_size = field.padded_size
+
if element_width:
if element_width % 8 != 0:
raise Exception('Array element size is not a multiple of 8')
@@ -202,6 +204,12 @@ class FieldParser:
if field.size_modifier and size:
self.append_(f"{size} = {size} - {field.size_modifier}")
+ # Parse from the padded array if padding is present.
+ if padded_size:
+ self.check_size_(padded_size)
+ self.append_(f"remaining_span = span[{padded_size}:]")
+ self.append_(f"span = span[:{padded_size}]")
+
# 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.
if element_width is None and size is not None:
@@ -263,6 +271,10 @@ class FieldParser:
if size is not None:
self.append_(f"span = span[{size}:]")
+ # Drop the padding
+ if padded_size:
+ self.append_(f"span = remaining_span")
+
def parse_bit_field_(self, field: ast.Field):
"""Parse the selected field as a bit field.
The field is added to the current chunk. When a byte boundary
@@ -346,7 +358,7 @@ class FieldParser:
if self.shift != 0:
raise Exception('Padding field does not start on an octet boundary')
- self.offset += field.width
+ self.offset += field.size
def parse_payload_field_(self, field: Union[ast.BodyField, ast.PayloadField]):
"""Parse body and payload fields."""
@@ -535,6 +547,9 @@ class FieldSerializer:
def serialize_array_field_(self, field: ast.ArrayField):
"""Serialize the selected array field."""
+ if field.padded_size:
+ self.append_(f"_{field.id}_start = len(_span)")
+
if field.width == 8:
self.append_(f"_span.extend(self.{field.id})")
else:
@@ -543,6 +558,9 @@ class FieldSerializer:
self.serialize_array_element_(field)
self.unindent_()
+ if field.padded_size:
+ self.append_(f"_span.extend([0] * ({field.padded_size} - len(_span) + _{field.id}_start))")
+
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
diff --git a/tools/pdl/scripts/pdl/ast.py b/tools/pdl/scripts/pdl/ast.py
index 7bab412ac5..266635f48b 100644
--- a/tools/pdl/scripts/pdl/ast.py
+++ b/tools/pdl/scripts/pdl/ast.py
@@ -59,7 +59,7 @@ class ChecksumField(Field):
@node('padding_field')
class PaddingField(Field):
- width: int
+ size: int
@node('size_field')
@@ -109,6 +109,7 @@ class ArrayField(Field):
type_id: Optional[str]
size_modifier: Optional[str]
size: Optional[int]
+ padded_size: Optional[int] = field(init=False, default=None)
@property
def type(self) -> Optional['Declaration']:
diff --git a/tools/pdl/scripts/pdl/core.py b/tools/pdl/scripts/pdl/core.py
index f6ad1f730c..a4609c8f1c 100644
--- a/tools/pdl/scripts/pdl/core.py
+++ b/tools/pdl/scripts/pdl/core.py
@@ -2,7 +2,7 @@ from typing import Optional, List, Dict, Union, Tuple
from .ast import *
-def desugar_field_(field: Field, constraints: Dict[str, Constraint]) -> List[Field]:
+def desugar_field_(field: Field, previous: Field, constraints: Dict[str, Constraint]) -> List[Field]:
"""Inline group and constrained fields.
Constrained fields are transformed into fixed fields.
Group fields are inlined and recursively desugared."""
@@ -13,6 +13,10 @@ def desugar_field_(field: Field, constraints: Dict[str, Constraint]) -> List[Fie
fixed.parent = field.parent
return [fixed]
+ elif isinstance(field, PaddingField):
+ previous.padded_size = field.size
+ return []
+
elif isinstance(field, TypedefField) and field.id in constraints:
tag_id = constraints[field.id].tag_id
fixed = FixedField(kind='fixed_field', loc=field.loc, enum_id=field.type_id, tag_id=tag_id)
@@ -24,7 +28,8 @@ def desugar_field_(field: Field, constraints: Dict[str, Constraint]) -> List[Fie
constraints = dict([(c.id, c) for c in field.constraints])
fields = []
for f in group.fields:
- fields.extend(desugar_field_(f, constraints))
+ fields.extend(desugar_field_(f, previous, constraints))
+ previous = f
return fields
else:
@@ -45,7 +50,7 @@ def desugar(file: File):
if isinstance(d, (PacketDeclaration, StructDeclaration)):
fields = []
for f in d.fields:
- fields.extend(desugar_field_(f, {}))
+ fields.extend(desugar_field_(f, fields[-1] if len(fields) > 0 else None, {}))
d.fields = fields
declarations.append(d)
diff --git a/tools/pdl/scripts/pdl/parser.py b/tools/pdl/scripts/pdl/parser.py
index a896b4be8e..d3fa4c2463 100644
--- a/tools/pdl/scripts/pdl/parser.py
+++ b/tools/pdl/scripts/pdl/parser.py
@@ -67,8 +67,8 @@ def parse_fields(data):
'field_id': m['field_id'],
})
elif name == '_padding_':
- m = re.match(rule(fr' \[ {g(integer, "width")} \]'), rest)
- fields.append({'kind': 'padding_field', 'width': int(m['width'], 0)})
+ m = re.match(rule(fr' \[ {g(integer, "size")} \]'), rest)
+ fields.append({'kind': 'padding_field', 'size': int(m['size'], 0)})
elif name == '_size_':
m = re.match(rule(fr' \( {g(identifier, "field_id")} \) : {g(integer, "width")}'), rest)
fields.append({'kind': 'size_field', 'field_id': m['field_id'], 'width': int(m['width'], 0)})
diff --git a/tools/pdl/src/ast.rs b/tools/pdl/src/ast.rs
index 33d5537392..4792f11bbb 100644
--- a/tools/pdl/src/ast.rs
+++ b/tools/pdl/src/ast.rs
@@ -73,7 +73,7 @@ pub enum Field {
#[serde(rename = "checksum_field")]
Checksum { loc: SourceRange, field_id: String },
#[serde(rename = "padding_field")]
- Padding { loc: SourceRange, width: usize },
+ Padding { loc: SourceRange, size: usize },
#[serde(rename = "size_field")]
Size { loc: SourceRange, field_id: String, width: usize },
#[serde(rename = "count_field")]
diff --git a/tools/pdl/src/backends.rs b/tools/pdl/src/backends.rs
new file mode 100644
index 0000000000..7c159f2178
--- /dev/null
+++ b/tools/pdl/src/backends.rs
@@ -0,0 +1,4 @@
+//! Compiler backends.
+
+pub mod json;
+pub mod rust;
diff --git a/tools/pdl/src/backends/json.rs b/tools/pdl/src/backends/json.rs
new file mode 100644
index 0000000000..f113cbc560
--- /dev/null
+++ b/tools/pdl/src/backends/json.rs
@@ -0,0 +1,9 @@
+//! Rust compiler backend.
+
+use crate::ast;
+
+/// Turn the AST into a JSON representation.
+pub fn generate(file: &ast::File) -> Result<String, String> {
+ serde_json::to_string_pretty(&file)
+ .map_err(|err| format!("could not JSON serialize grammar: {err}"))
+}
diff --git a/tools/pdl/src/backends/rust.rs b/tools/pdl/src/backends/rust.rs
new file mode 100644
index 0000000000..13b30ea811
--- /dev/null
+++ b/tools/pdl/src/backends/rust.rs
@@ -0,0 +1,1028 @@
+//! Rust compiler backend.
+
+// The `format-push-string` lint was briefly enabled present in Rust
+// 1.62. It is now moved the disabled "restriction" category instead.
+// See https://github.com/rust-lang/rust-clippy/issues/9077 for the
+// problems with this lint.
+//
+// Remove this when we use Rust 1.63 or later.
+#![allow(clippy::format_push_string)]
+
+use crate::ast;
+use quote::{format_ident, quote};
+use std::collections::HashMap;
+use std::path::Path;
+use syn::parse_quote;
+
+mod preamble;
+mod types;
+
+/// Generate a block of code.
+///
+/// Like `quote!`, but the code block will be followed by an empty
+/// line of code. This makes the generated code more readable.
+#[macro_export]
+macro_rules! quote_block {
+ ($($tt:tt)*) => {
+ format!("{}\n\n", ::quote::quote!($($tt)*))
+ }
+}
+
+fn generate_field(field: &ast::Field, visibility: syn::Visibility) -> proc_macro2::TokenStream {
+ match field {
+ ast::Field::Scalar { id, width, .. } => {
+ let field_name = format_ident!("{id}");
+ let field_type = types::Integer::new(*width);
+ quote! {
+ #visibility #field_name: #field_type
+ }
+ }
+ _ => todo!("unsupported field: {:?}", field),
+ }
+}
+
+fn generate_field_getter(packet_name: &syn::Ident, field: &ast::Field) -> proc_macro2::TokenStream {
+ match field {
+ ast::Field::Scalar { id, width, .. } => {
+ // TODO(mgeisler): refactor with generate_field above.
+ let getter_name = format_ident!("get_{id}");
+ let field_name = format_ident!("{id}");
+ let field_type = types::Integer::new(*width);
+ quote! {
+ pub fn #getter_name(&self) -> #field_type {
+ self.#packet_name.as_ref().#field_name
+ }
+ }
+ }
+ _ => todo!("unsupported field: {:?}", field),
+ }
+}
+
+/// Find byte indices covering `offset..offset+width` bits.
+fn get_field_range(offset: usize, width: usize) -> std::ops::Range<usize> {
+ let start = offset / 8;
+ let mut end = (offset + width) / 8;
+ if (offset + width) % 8 != 0 {
+ end += 1;
+ }
+ start..end
+}
+
+fn get_chunk_width(fields: &[ast::Field]) -> usize {
+ fields.iter().map(get_field_width).sum()
+}
+
+/// Read data for a byte-aligned chunk.
+fn generate_chunk_read(
+ packet_name: &str,
+ endianness_value: ast::EndiannessValue,
+ offset: usize,
+ chunk: &[ast::Field],
+) -> proc_macro2::TokenStream {
+ assert!(offset % 8 == 0, "Chunks must be byte-aligned, got offset: {offset}");
+ let getter = match endianness_value {
+ ast::EndiannessValue::BigEndian => format_ident!("from_be_bytes"),
+ ast::EndiannessValue::LittleEndian => format_ident!("from_le_bytes"),
+ };
+
+ // Work directly with the field name if we are reading a single
+ // field. This generates simpler code.
+ let chunk_name = match chunk {
+ [ast::Field::Scalar { id: field_name, .. }] => format_ident!("{}", field_name),
+ _ => format_ident!("chunk"),
+ };
+ let chunk_width = get_chunk_width(chunk);
+ let chunk_type = types::Integer::new(chunk_width);
+ assert!(chunk_width % 8 == 0, "Chunks must have a byte size, got width: {chunk_width}");
+
+ let range = get_field_range(offset, chunk_width);
+ let indices = range.map(syn::Index::from).collect::<Vec<_>>();
+
+ let mut field_offset = offset;
+ let mut last_field_range_end = 0;
+ // TODO(mgeisler): emit just a single length check per chunk. We
+ // could even emit a single length check per packet.
+ let length_checks = chunk.iter().map(|field| match field {
+ ast::Field::Scalar { id, width, .. } => {
+ let field_range = get_field_range(field_offset, *width);
+ field_offset += *width;
+ if field_range.end == last_field_range_end {
+ None // Suppress redundant length check.
+ } else {
+ last_field_range_end = field_range.end;
+ let range_end = syn::Index::from(field_range.end);
+ Some(quote! {
+ if bytes.len() < #range_end {
+ return Err(Error::InvalidLengthError {
+ obj: #packet_name.to_string(),
+ field: #id.to_string(),
+ wanted: #range_end,
+ got: bytes.len(),
+ });
+ }
+ })
+ }
+ }
+ _ => todo!("unsupported field: {:?}", field),
+ });
+
+ // When the chunk_type.width is larger than chunk_width (e.g.
+ // chunk_width is 24 but chunk_type.width is 32), then we need
+ // zero padding.
+ let zero_padding_len = (chunk_type.width - chunk_width) / 8;
+ // We need the padding on the MSB side of the payload, so for
+ // big-endian, we need to padding on the left, for little-endian
+ // we need it on the right.
+ let (zero_padding_before, zero_padding_after) = match endianness_value {
+ ast::EndiannessValue::BigEndian => (vec![syn::Index::from(0); zero_padding_len], vec![]),
+ ast::EndiannessValue::LittleEndian => (vec![], vec![syn::Index::from(0); zero_padding_len]),
+ };
+
+ quote! {
+ #(#length_checks)*
+ let #chunk_name = #chunk_type::#getter([
+ #(#zero_padding_before,)* #(bytes[#indices]),* #(, #zero_padding_after)*
+ ]);
+ }
+}
+
+fn generate_chunk_read_field_adjustments(fields: &[ast::Field]) -> proc_macro2::TokenStream {
+ // If there is a single field in the chunk, then we don't have to
+ // shift, mask, or cast.
+ if fields.len() == 1 {
+ return quote! {};
+ }
+
+ let chunk_width = get_chunk_width(fields);
+ let chunk_type = types::Integer::new(chunk_width);
+
+ let mut field_parsers = Vec::new();
+ let mut field_offset = 0;
+ for field in fields {
+ match field {
+ ast::Field::Scalar { id, width, .. } => {
+ let field_name = format_ident!("{id}");
+ let field_type = types::Integer::new(*width);
+
+ let mut field = quote! {
+ chunk
+ };
+ if field_offset > 0 {
+ let field_offset = syn::Index::from(field_offset);
+ let op = syn::parse_str::<syn::BinOp>(">>").unwrap();
+ field = quote! {
+ (#field #op #field_offset)
+ };
+ }
+
+ if *width < field_type.width {
+ let bit_mask = mask_bits(*width);
+ field = quote! {
+ (#field & #bit_mask)
+ };
+ }
+
+ if field_type.width < chunk_type.width {
+ field = quote! {
+ #field as #field_type;
+ };
+ }
+
+ field_offset += width;
+ field_parsers.push(quote! {
+ let #field_name = #field;
+ });
+ }
+ _ => todo!("unsupported field: {:?}", field),
+ }
+ }
+
+ quote! {
+ #(#field_parsers)*
+ }
+}
+
+fn generate_chunk_write_field_adjustments(chunk: &[ast::Field]) -> proc_macro2::TokenStream {
+ // Work directly with the field name if we are writing a single
+ // field. This generates simpler code.
+ if let [ast::Field::Scalar { id, .. }] = chunk {
+ // If there is a single field in the chunk, then we don't have to
+ // shift, mask, or cast.
+ let field_name = format_ident!("{id}");
+ return quote! {
+ let #field_name = self.#field_name;
+ };
+ }
+
+ let chunk_width = get_chunk_width(chunk);
+ let chunk_type = types::Integer::new(chunk_width);
+
+ let mut field_parsers = Vec::new();
+ let mut field_offset = 0;
+ for field in chunk {
+ match field {
+ ast::Field::Scalar { id, width, .. } => {
+ let field_name = format_ident!("{id}");
+ let field_type = types::Integer::new(*width);
+
+ let mut field = quote! {
+ self.#field_name
+ };
+
+ if field_type.width < chunk_type.width {
+ field = quote! {
+ (#field as #chunk_type)
+ };
+ }
+
+ if *width < field_type.width {
+ let bit_mask = mask_bits(*width);
+ field = quote! {
+ (#field & #bit_mask)
+ };
+ }
+
+ if field_offset > 0 {
+ let field_offset = syn::Index::from(field_offset);
+ let op = syn::parse_str::<syn::BinOp>("<<").unwrap();
+ field = quote! {
+ (#field #op #field_offset)
+ };
+ }
+
+ field_offset += width;
+ field_parsers.push(quote! {
+ let chunk = chunk | #field;
+ });
+ }
+ _ => todo!("unsupported field: {:?}", field),
+ }
+ }
+
+ quote! {
+ let chunk = 0;
+ #(#field_parsers)*
+ }
+}
+
+/// Generate a bit-mask which masks out `n` least significant bits.
+fn mask_bits(n: usize) -> syn::LitInt {
+ syn::parse_str::<syn::LitInt>(&format!("{:#x}", (1u64 << n) - 1)).unwrap()
+}
+
+fn generate_chunk_write(
+ endianness_value: ast::EndiannessValue,
+ offset: usize,
+ chunk: &[ast::Field],
+) -> proc_macro2::TokenStream {
+ let writer = match endianness_value {
+ ast::EndiannessValue::BigEndian => format_ident!("to_be_bytes"),
+ ast::EndiannessValue::LittleEndian => format_ident!("to_le_bytes"),
+ };
+
+ // Work directly with the field name if we are writing a single
+ // field. This generates simpler code.
+ let chunk_name = match chunk {
+ [ast::Field::Scalar { id, .. }] => format_ident!("{id}"),
+ _ => format_ident!("chunk"),
+ };
+ let chunk_width = get_chunk_width(chunk);
+ assert!(chunk_width % 8 == 0, "Chunks must have a byte size, got width: {chunk_width}");
+
+ let range = get_field_range(offset, chunk_width);
+ let start = syn::Index::from(range.start);
+ let end = syn::Index::from(range.end);
+ // TODO(mgeisler): let slice = (chunk_type_width > chunk_width).then( ... )
+ let chunk_byte_width = syn::Index::from(chunk_width / 8);
+ quote! {
+ buffer[#start..#end].copy_from_slice(&#chunk_name.#writer()[0..#chunk_byte_width]);
+ }
+}
+
+/// Field size in bits.
+fn get_field_width(field: &ast::Field) -> usize {
+ match field {
+ ast::Field::Scalar { width, .. } => *width,
+ _ => todo!("unsupported field: {:?}", field),
+ }
+}
+
+/// Generate code for an `ast::Decl::Packet` enum value.
+fn generate_packet_decl(
+ file: &ast::File,
+ packets: &HashMap<&str, &ast::Decl>,
+ child_ids: &[&str],
+ id: &str,
+ fields: &[ast::Field],
+ parent_id: &Option<String>,
+) -> String {
+ // TODO(mgeisler): use the convert_case crate to convert between
+ // `FooBar` and `foo_bar` in the code below.
+ let mut code = String::new();
+
+ let has_children = !child_ids.is_empty();
+ let child_idents = child_ids.iter().map(|id| format_ident!("{id}")).collect::<Vec<_>>();
+
+ let ident = format_ident!("{}", id.to_lowercase());
+ let data_child_ident = format_ident!("{id}DataChild");
+ let child_decl_packet_name =
+ child_idents.iter().map(|ident| format_ident!("{ident}Packet")).collect::<Vec<_>>();
+ let child_name = format_ident!("{id}Child");
+ if has_children {
+ let child_data_idents = child_idents.iter().map(|ident| format_ident!("{ident}Data"));
+ code.push_str(&quote_block! {
+ #[derive(Debug)]
+ enum #data_child_ident {
+ #(#child_idents(Arc<#child_data_idents>),)*
+ None,
+ }
+
+ impl #data_child_ident {
+ fn get_total_size(&self) -> usize {
+ // TODO(mgeisler): use Self instad of #data_child_ident.
+ match self {
+ #(#data_child_ident::#child_idents(value) => value.get_total_size(),)*
+ #data_child_ident::None => 0,
+ }
+ }
+ }
+
+ #[derive(Debug)]
+ pub enum #child_name {
+ #(#child_idents(#child_decl_packet_name),)*
+ None,
+ }
+ });
+ }
+
+ let data_name = format_ident!("{id}Data");
+ let child_field = has_children.then(|| {
+ quote! {
+ child: #data_child_ident,
+ }
+ });
+ let plain_fields = fields.iter().map(|field| generate_field(field, parse_quote!()));
+ code.push_str(&quote_block! {
+ #[derive(Debug)]
+ struct #data_name {
+ #(#plain_fields,)*
+ #child_field
+ }
+ });
+
+ let parent = parent_id.as_ref().map(|parent_id| match packets.get(parent_id.as_str()) {
+ Some(ast::Decl::Packet { id, .. }) => {
+ let parent_ident = format_ident!("{}", id.to_lowercase());
+ let parent_data = format_ident!("{id}Data");
+ quote! {
+ #parent_ident: Arc<#parent_data>,
+ }
+ }
+ _ => panic!("Could not find {parent_id}"),
+ });
+
+ let packet_name = format_ident!("{id}Packet");
+ code.push_str(&quote_block! {
+ #[derive(Debug, Clone)]
+ pub struct #packet_name {
+ #parent
+ #ident: Arc<#data_name>,
+ }
+ });
+
+ let builder_name = format_ident!("{id}Builder");
+ let pub_fields = fields.iter().map(|field| generate_field(field, parse_quote!(pub)));
+ code.push_str(&quote_block! {
+ #[derive(Debug)]
+ pub struct #builder_name {
+ #(#pub_fields,)*
+ }
+ });
+
+ let mut chunk_width = 0;
+ let chunks = fields.split_inclusive(|field| {
+ chunk_width += get_field_width(field);
+ chunk_width % 8 == 0
+ });
+ let mut field_parsers = Vec::new();
+ let mut field_writers = Vec::new();
+ let mut offset = 0;
+ for chunk in chunks {
+ field_parsers.push(generate_chunk_read(id, file.endianness.value, offset, chunk));
+ field_parsers.push(generate_chunk_read_field_adjustments(chunk));
+
+ field_writers.push(generate_chunk_write_field_adjustments(chunk));
+ field_writers.push(generate_chunk_write(file.endianness.value, offset, chunk));
+
+ offset += get_chunk_width(chunk);
+ }
+
+ let field_names = fields
+ .iter()
+ .map(|field| match field {
+ ast::Field::Scalar { id, .. } => format_ident!("{id}"),
+ _ => todo!("unsupported field: {:?}", field),
+ })
+ .collect::<Vec<_>>();
+
+ let packet_size_bits = get_chunk_width(fields);
+ if packet_size_bits % 8 != 0 {
+ panic!("packet {id} does not end on a byte boundary, size: {packet_size_bits} bits",);
+ }
+ let packet_size_bytes = syn::Index::from(packet_size_bits / 8);
+ let get_size_adjustment = (packet_size_bytes.index > 0).then(|| {
+ Some(quote! {
+ let ret = ret + #packet_size_bytes;
+ })
+ });
+
+ code.push_str(&quote_block! {
+ impl #data_name {
+ fn conforms(bytes: &[u8]) -> bool {
+ // TODO(mgeisler): return Boolean expression directly.
+ // TODO(mgeisler): skip when total_field_size == 0.
+ if bytes.len() < #packet_size_bytes {
+ return false;
+ }
+ true
+ }
+
+ fn parse(bytes: &[u8]) -> Result<Self> {
+ #(#field_parsers)*
+ Ok(Self { #(#field_names),* })
+ }
+
+ fn write_to(&self, buffer: &mut BytesMut) {
+ #(#field_writers)*
+ }
+
+ fn get_total_size(&self) -> usize {
+ self.get_size()
+ }
+
+ fn get_size(&self) -> usize {
+ let ret = 0;
+ #get_size_adjustment
+ ret
+ }
+ }
+ });
+
+ code.push_str(&quote_block! {
+ impl Packet for #packet_name {
+ fn to_bytes(self) -> Bytes {
+ let mut buffer = BytesMut::new();
+ buffer.resize(self.#ident.get_total_size(), 0);
+ self.#ident.write_to(&mut buffer);
+ buffer.freeze()
+ }
+ fn to_vec(self) -> Vec<u8> {
+ self.to_bytes().to_vec()
+ }
+ }
+ impl From<#packet_name> for Bytes {
+ fn from(packet: #packet_name) -> Self {
+ packet.to_bytes()
+ }
+ }
+ impl From<#packet_name> for Vec<u8> {
+ fn from(packet: #packet_name) -> Self {
+ packet.to_vec()
+ }
+ }
+ });
+
+ let specialize = has_children.then(|| {
+ quote! {
+ pub fn specialize(&self) -> #child_name {
+ match &self.#ident.child {
+ #(#data_child_ident::#child_idents(_) =>
+ #child_name::#child_idents(
+ #child_decl_packet_name::new(self.#ident.clone()).unwrap()),)*
+ #data_child_ident::None => #child_name::None,
+ }
+ }
+ }
+ });
+ let field_getters = fields.iter().map(|field| generate_field_getter(&ident, field));
+ code.push_str(&quote_block! {
+ impl #packet_name {
+ pub fn parse(bytes: &[u8]) -> Result<Self> {
+ Ok(Self::new(Arc::new(#data_name::parse(bytes)?)).unwrap())
+ }
+
+ #specialize
+
+ fn new(root: Arc<#data_name>) -> std::result::Result<Self, &'static str> {
+ let #ident = root;
+ Ok(Self { #ident })
+ }
+
+ #(#field_getters)*
+ }
+ });
+
+ let child = has_children.then(|| {
+ quote! {
+ child: #data_child_ident::None,
+ }
+ });
+ code.push_str(&quote_block! {
+ impl #builder_name {
+ pub fn build(self) -> #packet_name {
+ let #ident = Arc::new(#data_name {
+ #(#field_names: self.#field_names,)*
+ #child
+ });
+ #packet_name::new(#ident).unwrap()
+ }
+ }
+ });
+
+ code
+}
+
+fn generate_decl(
+ file: &ast::File,
+ packets: &HashMap<&str, &ast::Decl>,
+ children: &HashMap<&str, Vec<&str>>,
+ decl: &ast::Decl,
+) -> String {
+ let empty: Vec<&str> = vec![];
+ match decl {
+ ast::Decl::Packet { id, fields, parent_id, .. } => generate_packet_decl(
+ file,
+ packets,
+ children.get(id.as_str()).unwrap_or(&empty),
+ id,
+ fields,
+ parent_id,
+ ),
+ _ => todo!("unsupported Decl::{:?}", decl),
+ }
+}
+
+/// Generate Rust code from an AST.
+///
+/// The code is not formatted, pipe it through `rustfmt` to get
+/// readable source code.
+pub fn generate(sources: &ast::SourceDatabase, file: &ast::File) -> String {
+ let source = sources.get(file.file).expect("could not read source");
+
+ let mut children = HashMap::new();
+ let mut packets = HashMap::new();
+ for decl in &file.declarations {
+ if let ast::Decl::Packet { id, parent_id, .. } = decl {
+ packets.insert(id.as_str(), decl);
+ if let Some(parent_id) = parent_id {
+ children.entry(parent_id.as_str()).or_insert_with(Vec::new).push(id.as_str());
+ }
+ }
+ }
+
+ let mut code = String::new();
+
+ code.push_str(&preamble::generate(Path::new(source.name())));
+
+ for decl in &file.declarations {
+ code.push_str(&generate_decl(file, &packets, &children, decl));
+ code.push_str("\n\n");
+ }
+
+ code
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::ast;
+ use crate::parser::parse_inline;
+ use crate::test_utils::{assert_eq_with_diff, assert_snapshot_eq, rustfmt};
+
+ /// Parse a string fragment as a PDL file.
+ ///
+ /// # Panics
+ ///
+ /// Panics on parse errors.
+ pub fn parse_str(text: &str) -> ast::File {
+ let mut db = ast::SourceDatabase::new();
+ parse_inline(&mut db, String::from("stdin"), String::from(text)).expect("parse error")
+ }
+
+ #[test]
+ fn test_generate_packet_decl_empty() {
+ let file = parse_str(
+ r#"
+ big_endian_packets
+ packet Foo {}
+ "#,
+ );
+ let packets = HashMap::new();
+ let children = HashMap::new();
+ let decl = &file.declarations[0];
+ let actual_code = generate_decl(&file, &packets, &children, decl);
+ assert_snapshot_eq("tests/generated/packet_decl_empty.rs", &rustfmt(&actual_code));
+ }
+
+ #[test]
+ fn test_generate_packet_decl_simple_little_endian() {
+ let file = parse_str(
+ r#"
+ little_endian_packets
+
+ packet Foo {
+ x: 8,
+ y: 16,
+ z: 24,
+ }
+ "#,
+ );
+ let packets = HashMap::new();
+ let children = HashMap::new();
+ let decl = &file.declarations[0];
+ let actual_code = generate_decl(&file, &packets, &children, decl);
+ assert_snapshot_eq(
+ "tests/generated/packet_decl_simple_little_endian.rs",
+ &rustfmt(&actual_code),
+ );
+ }
+
+ #[test]
+ fn test_generate_packet_decl_simple_big_endian() {
+ let file = parse_str(
+ r#"
+ big_endian_packets
+
+ packet Foo {
+ x: 8,
+ y: 16,
+ z: 24,
+ }
+ "#,
+ );
+ let packets = HashMap::new();
+ let children = HashMap::new();
+ let decl = &file.declarations[0];
+ let actual_code = generate_decl(&file, &packets, &children, decl);
+ assert_snapshot_eq(
+ "tests/generated/packet_decl_simple_big_endian.rs",
+ &rustfmt(&actual_code),
+ );
+ }
+
+ #[test]
+ fn test_generate_packet_decl_complex_little_endian() {
+ let grammar = parse_str(
+ r#"
+ little_endian_packets
+
+ packet Foo {
+ a: 3,
+ b: 8,
+ c: 5,
+ d: 24,
+ e: 12,
+ f: 4,
+ }
+ "#,
+ );
+ let packets = HashMap::new();
+ let children = HashMap::new();
+ let decl = &grammar.declarations[0];
+ let actual_code = generate_decl(&grammar, &packets, &children, decl);
+ assert_snapshot_eq(
+ "tests/generated/packet_decl_complex_little_endian.rs",
+ &rustfmt(&actual_code),
+ );
+ }
+
+ #[test]
+ fn test_generate_packet_decl_complex_big_endian() {
+ let grammar = parse_str(
+ r#"
+ big_endian_packets
+
+ packet Foo {
+ a: 3,
+ b: 8,
+ c: 5,
+ d: 24,
+ e: 12,
+ f: 4,
+ }
+ "#,
+ );
+ let packets = HashMap::new();
+ let children = HashMap::new();
+ let decl = &grammar.declarations[0];
+ let actual_code = generate_decl(&grammar, &packets, &children, decl);
+ assert_snapshot_eq(
+ "tests/generated/packet_decl_complex_big_endian.rs",
+ &rustfmt(&actual_code),
+ );
+ }
+
+ #[test]
+ fn test_get_field_range() {
+ // Zero widths will give you an empty slice iff the offset is
+ // byte aligned. In both cases, the slice covers the empty
+ // width. In practice, PDL doesn't allow zero-width fields.
+ assert_eq!(get_field_range(/*offset=*/ 0, /*width=*/ 0), (0..0));
+ assert_eq!(get_field_range(/*offset=*/ 5, /*width=*/ 0), (0..1));
+ assert_eq!(get_field_range(/*offset=*/ 8, /*width=*/ 0), (1..1));
+ assert_eq!(get_field_range(/*offset=*/ 9, /*width=*/ 0), (1..2));
+
+ // Non-zero widths work as expected.
+ assert_eq!(get_field_range(/*offset=*/ 0, /*width=*/ 1), (0..1));
+ assert_eq!(get_field_range(/*offset=*/ 0, /*width=*/ 5), (0..1));
+ assert_eq!(get_field_range(/*offset=*/ 0, /*width=*/ 8), (0..1));
+ assert_eq!(get_field_range(/*offset=*/ 0, /*width=*/ 20), (0..3));
+
+ assert_eq!(get_field_range(/*offset=*/ 5, /*width=*/ 1), (0..1));
+ assert_eq!(get_field_range(/*offset=*/ 5, /*width=*/ 3), (0..1));
+ assert_eq!(get_field_range(/*offset=*/ 5, /*width=*/ 4), (0..2));
+ assert_eq!(get_field_range(/*offset=*/ 5, /*width=*/ 20), (0..4));
+ }
+
+ // Assert that an expression equals the given expression.
+ //
+ // Both expressions are wrapped in a `main` function (so we can
+ // format it with `rustfmt`) and a diff is be shown if they
+ // differ.
+ #[track_caller]
+ fn assert_expr_eq(left: proc_macro2::TokenStream, right: proc_macro2::TokenStream) {
+ let left = quote! {
+ fn main() { #left }
+ };
+ let right = quote! {
+ fn main() { #right }
+ };
+ assert_eq_with_diff(
+ "left",
+ &rustfmt(&left.to_string()),
+ "right",
+ &rustfmt(&right.to_string()),
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_read_8bit() {
+ let loc = ast::SourceRange::default();
+ let fields = &[ast::Field::Scalar { loc, id: String::from("a"), width: 8 }];
+ assert_expr_eq(
+ generate_chunk_read("Foo", ast::EndiannessValue::BigEndian, 80, fields),
+ quote! {
+ if bytes.len() < 11 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "a".to_string(),
+ wanted: 11,
+ got: bytes.len(),
+ });
+ }
+ let a = u8::from_be_bytes([bytes[10]]);
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_read_16bit_le() {
+ let loc = ast::SourceRange::default();
+ let fields = &[ast::Field::Scalar { loc, id: String::from("a"), width: 16 }];
+ assert_expr_eq(
+ generate_chunk_read("Foo", ast::EndiannessValue::LittleEndian, 80, fields),
+ quote! {
+ if bytes.len() < 12 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "a".to_string(),
+ wanted: 12,
+ got: bytes.len(),
+ });
+ }
+ let a = u16::from_le_bytes([bytes[10], bytes[11]]);
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_read_16bit_be() {
+ let loc = ast::SourceRange::default();
+ let fields = &[ast::Field::Scalar { loc, id: String::from("a"), width: 16 }];
+ assert_expr_eq(
+ generate_chunk_read("Foo", ast::EndiannessValue::BigEndian, 80, fields),
+ quote! {
+ if bytes.len() < 12 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "a".to_string(),
+ wanted: 12,
+ got: bytes.len(),
+ });
+ }
+ let a = u16::from_be_bytes([bytes[10], bytes[11]]);
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_read_24bit_le() {
+ let loc = ast::SourceRange::default();
+ let fields = &[ast::Field::Scalar { loc, id: String::from("a"), width: 24 }];
+ assert_expr_eq(
+ generate_chunk_read("Foo", ast::EndiannessValue::LittleEndian, 80, fields),
+ quote! {
+ if bytes.len() < 13 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "a".to_string(),
+ wanted: 13,
+ got: bytes.len(),
+ });
+ }
+ let a = u32::from_le_bytes([bytes[10], bytes[11], bytes[12], 0]);
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_read_24bit_be() {
+ let loc = ast::SourceRange::default();
+ let fields = &[ast::Field::Scalar { loc, id: String::from("a"), width: 24 }];
+ assert_expr_eq(
+ generate_chunk_read("Foo", ast::EndiannessValue::BigEndian, 80, fields),
+ quote! {
+ if bytes.len() < 13 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "a".to_string(),
+ wanted: 13,
+ got: bytes.len(),
+ });
+ }
+ let a = u32::from_be_bytes([0, bytes[10], bytes[11], bytes[12]]);
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_read_multiple_fields() {
+ let loc = ast::SourceRange::default();
+ let fields = &[
+ ast::Field::Scalar { loc, id: String::from("a"), width: 16 },
+ ast::Field::Scalar { loc, id: String::from("b"), width: 24 },
+ ];
+ assert_expr_eq(
+ generate_chunk_read("Foo", ast::EndiannessValue::BigEndian, 80, fields),
+ quote! {
+ if bytes.len() < 12 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "a".to_string(),
+ wanted: 12,
+ got: bytes.len(),
+ });
+ }
+ if bytes.len() < 15 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "b".to_string(),
+ wanted: 15,
+ got: bytes.len(),
+ });
+ }
+ let chunk =
+ u64::from_be_bytes([0, 0, 0, bytes[10], bytes[11], bytes[12], bytes[13], bytes[14]]);
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_read_field_adjustments_8bit() {
+ let loc = ast::SourceRange::default();
+ let fields = vec![
+ ast::Field::Scalar { loc, id: String::from("a"), width: 3 },
+ ast::Field::Scalar { loc, id: String::from("b"), width: 5 },
+ ];
+ assert_expr_eq(
+ generate_chunk_read_field_adjustments(&fields),
+ quote! {
+ let a = (chunk & 0x7);
+ let b = ((chunk >> 3) & 0x1f);
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_read_field_adjustments_48bit() {
+ let loc = ast::SourceRange::default();
+ let fields = vec![
+ ast::Field::Scalar { loc, id: String::from("a"), width: 3 },
+ ast::Field::Scalar { loc, id: String::from("b"), width: 8 },
+ ast::Field::Scalar { loc, id: String::from("c"), width: 10 },
+ ast::Field::Scalar { loc, id: String::from("d"), width: 18 },
+ ast::Field::Scalar { loc, id: String::from("e"), width: 9 },
+ ];
+ assert_expr_eq(
+ generate_chunk_read_field_adjustments(&fields),
+ quote! {
+ let a = (chunk & 0x7) as u8;
+ let b = (chunk >> 3) as u8;
+ let c = ((chunk >> 11) & 0x3ff) as u16;
+ let d = ((chunk >> 21) & 0x3ffff) as u32;
+ let e = ((chunk >> 39) & 0x1ff) as u16;
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_write_8bit() {
+ let loc = ast::SourceRange::default();
+ let fields = &[ast::Field::Scalar { loc, id: String::from("a"), width: 8 }];
+ assert_expr_eq(
+ generate_chunk_write(ast::EndiannessValue::BigEndian, 80, fields),
+ quote! {
+ buffer[10..11].copy_from_slice(&a.to_be_bytes()[0..1]);
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_write_16bit() {
+ let loc = ast::SourceRange::default();
+ let fields = &[ast::Field::Scalar { loc, id: String::from("a"), width: 16 }];
+ assert_expr_eq(
+ generate_chunk_write(ast::EndiannessValue::BigEndian, 80, fields),
+ quote! {
+ buffer[10..12].copy_from_slice(&a.to_be_bytes()[0..2]);
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_write_24bit() {
+ let loc = ast::SourceRange::default();
+ let fields = &[ast::Field::Scalar { loc, id: String::from("a"), width: 24 }];
+ assert_expr_eq(
+ generate_chunk_write(ast::EndiannessValue::BigEndian, 80, fields),
+ quote! {
+ buffer[10..13].copy_from_slice(&a.to_be_bytes()[0..3]);
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_write_multiple_fields() {
+ let loc = ast::SourceRange::default();
+ let fields = &[
+ ast::Field::Scalar { loc, id: String::from("a"), width: 16 },
+ ast::Field::Scalar { loc, id: String::from("b"), width: 24 },
+ ];
+ assert_expr_eq(
+ generate_chunk_write(ast::EndiannessValue::BigEndian, 80, fields),
+ quote! {
+ buffer[10..15].copy_from_slice(&chunk.to_be_bytes()[0..5]);
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_write_field_adjustments_8bit() {
+ let loc = ast::SourceRange::default();
+ let fields = vec![
+ ast::Field::Scalar { loc, id: String::from("a"), width: 3 },
+ ast::Field::Scalar { loc, id: String::from("b"), width: 5 },
+ ];
+ assert_expr_eq(
+ generate_chunk_write_field_adjustments(&fields),
+ quote! {
+ let chunk = 0;
+ let chunk = chunk | (self.a & 0x7) ;
+ let chunk = chunk | ((self.b & 0x1f) << 3);
+ },
+ );
+ }
+
+ #[test]
+ fn test_generate_chunk_write_field_adjustments_48bit() {
+ let loc = ast::SourceRange::default();
+ let fields = vec![
+ ast::Field::Scalar { loc, id: String::from("a"), width: 3 },
+ ast::Field::Scalar { loc, id: String::from("b"), width: 8 },
+ ast::Field::Scalar { loc, id: String::from("c"), width: 10 },
+ ast::Field::Scalar { loc, id: String::from("d"), width: 18 },
+ ast::Field::Scalar { loc, id: String::from("e"), width: 9 },
+ ];
+ assert_expr_eq(
+ generate_chunk_write_field_adjustments(&fields),
+ quote! {
+ let chunk = 0;
+ let chunk = chunk | ((self.a as u64) & 0x7);
+ let chunk = chunk | ((self.b as u64) << 3);
+ let chunk = chunk | (((self.c as u64) & 0x3ff) << 11);
+ let chunk = chunk | (((self.d as u64) & 0x3ffff) << 21);
+ let chunk = chunk | (((self.e as u64) & 0x1ff) << 39);
+ },
+ );
+ }
+}
diff --git a/tools/pdl/src/backends/rust/preamble.rs b/tools/pdl/src/backends/rust/preamble.rs
new file mode 100644
index 0000000000..62fab56e2f
--- /dev/null
+++ b/tools/pdl/src/backends/rust/preamble.rs
@@ -0,0 +1,67 @@
+use std::path::Path;
+
+use crate::quote_block;
+
+/// Generate the file preamble.
+pub fn generate(path: &Path) -> String {
+ let mut code = String::new();
+ let filename = path.file_name().unwrap().to_str().expect("non UTF-8 filename");
+ code.push_str(&format!("// @generated rust packets from {filename}\n\n"));
+
+ code.push_str(&quote_block! {
+ use bytes::{BufMut, Bytes, BytesMut};
+ use num_derive::{FromPrimitive, ToPrimitive};
+ use num_traits::{FromPrimitive, ToPrimitive};
+ use std::convert::{TryFrom, TryInto};
+ use std::fmt;
+ use std::sync::Arc;
+ use thiserror::Error;
+ });
+
+ code.push_str(&quote_block! {
+ type Result<T> = std::result::Result<T, Error>;
+ });
+
+ code.push_str(&quote_block! {
+ #[derive(Debug, Error)]
+ pub enum Error {
+ #[error("Packet parsing failed")]
+ InvalidPacketError,
+ #[error("{field} was {value:x}, which is not known")]
+ ConstraintOutOfBounds { field: String, value: u64 },
+ #[error("when parsing {obj}.{field} needed length of {wanted} but got {got}")]
+ InvalidLengthError { obj: String, field: String, wanted: usize, got: usize },
+ #[error("Due to size restrictions a struct could not be parsed.")]
+ ImpossibleStructError,
+ #[error("when parsing field {obj}.{field}, {value} is not a valid {type_} value")]
+ InvalidEnumValueError { obj: String, field: String, value: u64, type_: String },
+ }
+ });
+
+ code.push_str(&quote_block! {
+ #[derive(Debug, Error)]
+ #[error("{0}")]
+ pub struct TryFromError(&'static str);
+ });
+
+ code.push_str(&quote_block! {
+ pub trait Packet {
+ fn to_bytes(self) -> Bytes;
+ fn to_vec(self) -> Vec<u8>;
+ }
+ });
+
+ code
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use crate::test_utils::{assert_snapshot_eq, rustfmt};
+
+ #[test]
+ fn test_generate_preamble() {
+ let actual_code = generate(Path::new("some/path/foo.pdl"));
+ assert_snapshot_eq("tests/generated/preamble.rs", &rustfmt(&actual_code));
+ }
+}
diff --git a/tools/pdl/src/backends/rust/types.rs b/tools/pdl/src/backends/rust/types.rs
new file mode 100644
index 0000000000..d53101daeb
--- /dev/null
+++ b/tools/pdl/src/backends/rust/types.rs
@@ -0,0 +1,49 @@
+//! Utility functions for dealing with Rust integer types.
+
+/// A Rust integer type such as `u8`.
+pub struct Integer {
+ pub width: usize,
+}
+
+impl Integer {
+ /// Get the Rust integer type for the given bit width.
+ ///
+ /// This will round up the size to the nearest Rust integer size.
+ /// PDL supports integers up to 64 bit, so it is an error to call
+ /// this with a width larger than 64.
+ pub fn new(width: usize) -> Integer {
+ for integer_width in [8, 16, 32, 64] {
+ if width <= integer_width {
+ return Integer { width: integer_width };
+ }
+ }
+ panic!("Cannot construct Integer with width: {width}")
+ }
+}
+
+impl quote::ToTokens for Integer {
+ fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
+ let t: syn::Type = syn::parse_str(&format!("u{}", self.width))
+ .expect("Could not parse integer, unsupported width?");
+ t.to_tokens(tokens);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_integer_new() {
+ assert_eq!(Integer::new(0).width, 8);
+ assert_eq!(Integer::new(8).width, 8);
+ assert_eq!(Integer::new(9).width, 16);
+ assert_eq!(Integer::new(64).width, 64);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_integer_new_panics_on_large_width() {
+ Integer::new(65);
+ }
+}
diff --git a/tools/pdl/src/generator.rs b/tools/pdl/src/generator.rs
deleted file mode 100644
index a1bff766e9..0000000000
--- a/tools/pdl/src/generator.rs
+++ /dev/null
@@ -1,685 +0,0 @@
-// The `format-push-string` lint was briefly enabled present in Rust
-// 1.62. It is now moved the disabled "restriction" category instead.
-// See https://github.com/rust-lang/rust-clippy/issues/9077 for the
-// problems with this lint.
-//
-// Remove this when we use Rust 1.63 or later.
-#![allow(clippy::format_push_string)]
-
-use crate::ast;
-use quote::{format_ident, quote};
-use std::collections::HashMap;
-use std::path::Path;
-use syn::parse_quote;
-
-/// Generate a block of code.
-///
-/// Like `quote!`, but the code block will be followed by an empty
-/// line of code. This makes the generated code more readable.
-macro_rules! quote_block {
- ($($tt:tt)*) => {
- format!("{}\n\n", quote!($($tt)*))
- }
-}
-
-/// Generate the file preamble.
-fn generate_preamble(path: &Path) -> String {
- let mut code = String::new();
- let filename = path.file_name().unwrap().to_str().expect("non UTF-8 filename");
- code.push_str(&format!("// @generated rust packets from {filename}\n\n"));
-
- code.push_str(&quote_block! {
- use bytes::{BufMut, Bytes, BytesMut};
- use num_derive::{FromPrimitive, ToPrimitive};
- use num_traits::{FromPrimitive, ToPrimitive};
- use std::convert::{TryFrom, TryInto};
- use std::fmt;
- use std::sync::Arc;
- use thiserror::Error;
- });
-
- code.push_str(&quote_block! {
- type Result<T> = std::result::Result<T, Error>;
- });
-
- code.push_str(&quote_block! {
- #[derive(Debug, Error)]
- pub enum Error {
- #[error("Packet parsing failed")]
- InvalidPacketError,
- #[error("{field} was {value:x}, which is not known")]
- ConstraintOutOfBounds { field: String, value: u64 },
- #[error("when parsing {obj}.{field} needed length of {wanted} but got {got}")]
- InvalidLengthError { obj: String, field: String, wanted: usize, got: usize },
- #[error("Due to size restrictions a struct could not be parsed.")]
- ImpossibleStructError,
- #[error("when parsing field {obj}.{field}, {value} is not a valid {type_} value")]
- InvalidEnumValueError { obj: String, field: String, value: u64, type_: String },
- }
- });
-
- code.push_str(&quote_block! {
- #[derive(Debug, Error)]
- #[error("{0}")]
- pub struct TryFromError(&'static str);
- });
-
- code.push_str(&quote_block! {
- pub trait Packet {
- fn to_bytes(self) -> Bytes;
- fn to_vec(self) -> Vec<u8>;
- }
- });
-
- code
-}
-
-/// Round up the bit width to a Rust integer size.
-fn round_bit_width(width: usize) -> usize {
- match width {
- 8 => 8,
- 16 => 16,
- 24 | 32 => 32,
- 40 | 48 | 56 | 64 => 64,
- _ => todo!("unsupported field width: {width}"),
- }
-}
-
-/// Generate a Rust unsigned integer type large enough to hold
-/// integers of the given bit width.
-fn type_for_width(width: usize) -> syn::Type {
- let rounded_width = round_bit_width(width);
- syn::parse_str(&format!("u{rounded_width}")).unwrap()
-}
-
-fn generate_field(field: &ast::Field, visibility: syn::Visibility) -> proc_macro2::TokenStream {
- match field {
- ast::Field::Scalar { id, width, .. } => {
- let field_name = format_ident!("{id}");
- let field_type = type_for_width(*width);
- quote! {
- #visibility #field_name: #field_type
- }
- }
- _ => todo!("unsupported field: {:?}", field),
- }
-}
-
-fn generate_field_getter(packet_name: &syn::Ident, field: &ast::Field) -> proc_macro2::TokenStream {
- match field {
- ast::Field::Scalar { id, width, .. } => {
- // TODO(mgeisler): refactor with generate_field above.
- let getter_name = format_ident!("get_{id}");
- let field_name = format_ident!("{id}");
- let field_type = type_for_width(*width);
- quote! {
- pub fn #getter_name(&self) -> #field_type {
- self.#packet_name.as_ref().#field_name
- }
- }
- }
- _ => todo!("unsupported field: {:?}", field),
- }
-}
-
-/// Mask and rebind the field value (if necessary).
-fn mask_field_value(field: &ast::Field) -> Option<proc_macro2::TokenStream> {
- match field {
- ast::Field::Scalar { id, width, .. } => {
- let field_name = format_ident!("{id}");
- let type_width = round_bit_width(*width);
- if *width != type_width {
- let mask =
- syn::parse_str::<syn::LitInt>(&format!("{:#x}", (1u64 << *width) - 1)).unwrap();
- Some(quote! {
- let #field_name = #field_name & #mask;
- })
- } else {
- None
- }
- }
- _ => todo!("unsupported field: {:?}", field),
- }
-}
-
-fn generate_field_parser(
- endianness_value: ast::EndiannessValue,
- packet_name: &str,
- field: &ast::Field,
- offset: usize,
-) -> proc_macro2::TokenStream {
- match field {
- ast::Field::Scalar { id, width, .. } => {
- let field_name = format_ident!("{id}");
- let type_width = round_bit_width(*width);
- let field_type = type_for_width(*width);
-
- let getter = match endianness_value {
- ast::EndiannessValue::BigEndian => format_ident!("from_be_bytes"),
- ast::EndiannessValue::LittleEndian => format_ident!("from_le_bytes"),
- };
-
- // We need the padding on the MSB side of the payload, so
- // for big-endian, we need to padding on the left, for
- // little-endian we need it on the right.
- let padding = vec![syn::Index::from(0); (type_width - width) / 8];
- let (padding_before, padding_after) = match endianness_value {
- ast::EndiannessValue::BigEndian => (padding, vec![]),
- ast::EndiannessValue::LittleEndian => (vec![], padding),
- };
-
- let wanted_len = syn::Index::from(offset + width / 8);
- let indices = (offset..offset + width / 8).map(syn::Index::from);
- let mask = mask_field_value(field);
-
- quote! {
- // TODO(mgeisler): call a function instead to avoid
- // generating so much code for this.
- if bytes.len() < #wanted_len {
- return Err(Error::InvalidLengthError {
- obj: #packet_name.to_string(),
- field: #id.to_string(),
- wanted: #wanted_len,
- got: bytes.len(),
- });
- }
- let #field_name = #field_type::#getter([
- #(#padding_before,)* #(bytes[#indices]),* #(, #padding_after)*
- ]);
- #mask
- }
- }
- _ => todo!("unsupported field: {:?}", field),
- }
-}
-
-fn generate_field_writer(
- file: &ast::File,
- field: &ast::Field,
- offset: usize,
-) -> proc_macro2::TokenStream {
- match field {
- ast::Field::Scalar { id, width, .. } => {
- let field_name = format_ident!("{id}");
- let start = syn::Index::from(offset);
- let end = syn::Index::from(offset + width / 8);
- let byte_width = syn::Index::from(width / 8);
- let mask = mask_field_value(field);
- let writer = match file.endianness.value {
- ast::EndiannessValue::BigEndian => format_ident!("to_be_bytes"),
- ast::EndiannessValue::LittleEndian => format_ident!("to_le_bytes"),
- };
- quote! {
- let #field_name = self.#field_name;
- #mask
- buffer[#start..#end].copy_from_slice(&#field_name.#writer()[0..#byte_width]);
- }
- }
- _ => todo!("unsupported field: {:?}", field),
- }
-}
-
-fn get_field_size(field: &ast::Field) -> usize {
- match field {
- ast::Field::Scalar { width, .. } => width / 8,
- _ => todo!("unsupported field: {:?}", field),
- }
-}
-
-/// Generate code for an `ast::Decl::Packet` enum value.
-fn generate_packet_decl(
- file: &ast::File,
- packets: &HashMap<&str, &ast::Decl>,
- child_ids: &[&str],
- id: &str,
- fields: &[ast::Field],
- parent_id: &Option<String>,
-) -> String {
- // TODO(mgeisler): use the convert_case crate to convert between
- // `FooBar` and `foo_bar` in the code below.
- let mut code = String::new();
-
- let has_children = !child_ids.is_empty();
- let child_idents = child_ids.iter().map(|id| format_ident!("{id}")).collect::<Vec<_>>();
-
- let ident = format_ident!("{}", id.to_lowercase());
- let data_child_ident = format_ident!("{id}DataChild");
- let child_decl_packet_name =
- child_idents.iter().map(|ident| format_ident!("{ident}Packet")).collect::<Vec<_>>();
- let child_name = format_ident!("{id}Child");
- if has_children {
- let child_data_idents = child_idents.iter().map(|ident| format_ident!("{ident}Data"));
- code.push_str(&quote_block! {
- #[derive(Debug)]
- enum #data_child_ident {
- #(#child_idents(Arc<#child_data_idents>),)*
- None,
- }
-
- impl #data_child_ident {
- fn get_total_size(&self) -> usize {
- // TODO(mgeisler): use Self instad of #data_child_ident.
- match self {
- #(#data_child_ident::#child_idents(value) => value.get_total_size(),)*
- #data_child_ident::None => 0,
- }
- }
- }
-
- #[derive(Debug)]
- pub enum #child_name {
- #(#child_idents(#child_decl_packet_name),)*
- None,
- }
- });
- }
-
- let data_name = format_ident!("{id}Data");
- let child_field = has_children.then(|| {
- quote! {
- child: #data_child_ident,
- }
- });
- let plain_fields = fields.iter().map(|field| generate_field(field, parse_quote!()));
- code.push_str(&quote_block! {
- #[derive(Debug)]
- struct #data_name {
- #(#plain_fields,)*
- #child_field
- }
- });
-
- let parent = parent_id.as_ref().map(|parent_id| match packets.get(parent_id.as_str()) {
- Some(ast::Decl::Packet { id, .. }) => {
- let parent_ident = format_ident!("{}", id.to_lowercase());
- let parent_data = format_ident!("{id}Data");
- quote! {
- #parent_ident: Arc<#parent_data>,
- }
- }
- _ => panic!("Could not find {parent_id}"),
- });
-
- let packet_name = format_ident!("{id}Packet");
- code.push_str(&quote_block! {
- #[derive(Debug, Clone)]
- pub struct #packet_name {
- #parent
- #ident: Arc<#data_name>,
- }
- });
-
- let builder_name = format_ident!("{id}Builder");
- let pub_fields = fields.iter().map(|field| generate_field(field, parse_quote!(pub)));
- code.push_str(&quote_block! {
- #[derive(Debug)]
- pub struct #builder_name {
- #(#pub_fields,)*
- }
- });
-
- // TODO(mgeisler): use the `Buf` trait instead of tracking
- // the offset manually.
- let mut offset = 0;
- let field_parsers = fields.iter().map(|field| {
- let parser = generate_field_parser(file.endianness.value, id, field, offset);
- offset += get_field_size(field);
- parser
- });
- let field_names = fields
- .iter()
- .map(|field| match field {
- ast::Field::Scalar { id, .. } => format_ident!("{id}"),
- _ => todo!("unsupported field: {:?}", field),
- })
- .collect::<Vec<_>>();
- let mut offset = 0;
- let field_writers = fields.iter().map(|field| {
- let writer = generate_field_writer(file, field, offset);
- offset += get_field_size(field);
- writer
- });
-
- let total_field_size = syn::Index::from(fields.iter().map(get_field_size).sum::<usize>());
- let get_size_adjustment = (total_field_size.index > 0).then(|| {
- Some(quote! {
- let ret = ret + #total_field_size;
- })
- });
-
- code.push_str(&quote_block! {
- impl #data_name {
- fn conforms(bytes: &[u8]) -> bool {
- // TODO(mgeisler): return Boolean expression directly.
- // TODO(mgeisler): skip when total_field_size == 0.
- if bytes.len() < #total_field_size {
- return false;
- }
- true
- }
-
- fn parse(bytes: &[u8]) -> Result<Self> {
- #(#field_parsers)*
- Ok(Self { #(#field_names),* })
- }
-
- fn write_to(&self, buffer: &mut BytesMut) {
- #(#field_writers)*
- }
-
- fn get_total_size(&self) -> usize {
- self.get_size()
- }
-
- fn get_size(&self) -> usize {
- let ret = 0;
- #get_size_adjustment
- ret
- }
- }
- });
-
- code.push_str(&quote_block! {
- impl Packet for #packet_name {
- fn to_bytes(self) -> Bytes {
- let mut buffer = BytesMut::new();
- buffer.resize(self.#ident.get_total_size(), 0);
- self.#ident.write_to(&mut buffer);
- buffer.freeze()
- }
- fn to_vec(self) -> Vec<u8> {
- self.to_bytes().to_vec()
- }
- }
- impl From<#packet_name> for Bytes {
- fn from(packet: #packet_name) -> Self {
- packet.to_bytes()
- }
- }
- impl From<#packet_name> for Vec<u8> {
- fn from(packet: #packet_name) -> Self {
- packet.to_vec()
- }
- }
- });
-
- let specialize = has_children.then(|| {
- quote! {
- pub fn specialize(&self) -> #child_name {
- match &self.#ident.child {
- #(#data_child_ident::#child_idents(_) =>
- #child_name::#child_idents(
- #child_decl_packet_name::new(self.#ident.clone()).unwrap()),)*
- #data_child_ident::None => #child_name::None,
- }
- }
- }
- });
- let field_getters = fields.iter().map(|field| generate_field_getter(&ident, field));
- code.push_str(&quote_block! {
- impl #packet_name {
- pub fn parse(bytes: &[u8]) -> Result<Self> {
- Ok(Self::new(Arc::new(#data_name::parse(bytes)?)).unwrap())
- }
-
- #specialize
-
- fn new(root: Arc<#data_name>) -> std::result::Result<Self, &'static str> {
- let #ident = root;
- Ok(Self { #ident })
- }
-
- #(#field_getters)*
- }
- });
-
- let child = has_children.then(|| {
- quote! {
- child: #data_child_ident::None,
- }
- });
- code.push_str(&quote_block! {
- impl #builder_name {
- pub fn build(self) -> #packet_name {
- let #ident = Arc::new(#data_name {
- #(#field_names: self.#field_names,)*
- #child
- });
- #packet_name::new(#ident).unwrap()
- }
- }
- });
-
- code
-}
-
-fn generate_decl(
- file: &ast::File,
- packets: &HashMap<&str, &ast::Decl>,
- children: &HashMap<&str, Vec<&str>>,
- decl: &ast::Decl,
-) -> String {
- let empty: Vec<&str> = vec![];
- match decl {
- ast::Decl::Packet { id, fields, parent_id, .. } => generate_packet_decl(
- file,
- packets,
- children.get(id.as_str()).unwrap_or(&empty),
- id,
- fields,
- parent_id,
- ),
- _ => todo!("unsupported Decl::{:?}", decl),
- }
-}
-
-/// Generate Rust code from an AST.
-///
-/// The code is not formatted, pipe it through `rustfmt` to get
-/// readable source code.
-pub fn generate_rust(sources: &ast::SourceDatabase, file: &ast::File) -> String {
- let source = sources.get(file.file).expect("could not read source");
-
- let mut children = HashMap::new();
- let mut packets = HashMap::new();
- for decl in &file.declarations {
- if let ast::Decl::Packet { id, parent_id, .. } = decl {
- packets.insert(id.as_str(), decl);
- if let Some(parent_id) = parent_id {
- children.entry(parent_id.as_str()).or_insert_with(Vec::new).push(id.as_str());
- }
- }
- }
-
- let mut code = String::new();
-
- code.push_str(&generate_preamble(Path::new(source.name())));
-
- for decl in &file.declarations {
- code.push_str(&generate_decl(file, &packets, &children, decl));
- code.push_str("\n\n");
- }
-
- code
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use crate::ast;
- use crate::parser::parse_inline;
- use crate::test_utils::{assert_eq_with_diff, assert_snapshot_eq, rustfmt};
-
- /// Parse a string fragment as a PDL file.
- ///
- /// # Panics
- ///
- /// Panics on parse errors.
- pub fn parse_str(text: &str) -> ast::File {
- let mut db = ast::SourceDatabase::new();
- parse_inline(&mut db, String::from("stdin"), String::from(text)).expect("parse error")
- }
-
- #[test]
- fn test_generate_preamble() {
- let actual_code = generate_preamble(Path::new("some/path/foo.pdl"));
- assert_snapshot_eq("tests/generated/preamble.rs", &rustfmt(&actual_code));
- }
-
- #[test]
- fn test_generate_packet_decl_empty() {
- let file = parse_str(
- r#"
- big_endian_packets
- packet Foo {}
- "#,
- );
- let packets = HashMap::new();
- let children = HashMap::new();
- let decl = &file.declarations[0];
- let actual_code = generate_decl(&file, &packets, &children, decl);
- assert_snapshot_eq("tests/generated/packet_decl_empty.rs", &rustfmt(&actual_code));
- }
-
- #[test]
- fn test_generate_packet_decl_little_endian() {
- let file = parse_str(
- r#"
- little_endian_packets
-
- packet Foo {
- x: 8,
- y: 16,
- z: 24,
- }
- "#,
- );
- let packets = HashMap::new();
- let children = HashMap::new();
- let decl = &file.declarations[0];
- let actual_code = generate_decl(&file, &packets, &children, decl);
- assert_snapshot_eq(
- "tests/generated/packet_decl_simple_little_endian.rs",
- &rustfmt(&actual_code),
- );
- }
-
- #[test]
- fn test_generate_packet_decl_simple_big_endian() {
- let file = parse_str(
- r#"
- big_endian_packets
-
- packet Foo {
- x: 8,
- y: 16,
- z: 24,
- }
- "#,
- );
- let packets = HashMap::new();
- let children = HashMap::new();
- let decl = &file.declarations[0];
- let actual_code = generate_decl(&file, &packets, &children, decl);
- assert_snapshot_eq(
- "tests/generated/packet_decl_simple_big_endian.rs",
- &rustfmt(&actual_code),
- );
- }
-
- // Assert that an expression equals the given expression.
- //
- // Both expressions are wrapped in a `main` function (so we can
- // format it with `rustfmt`) and a diff is be shown if they
- // differ.
- #[track_caller]
- fn assert_expr_eq(left: proc_macro2::TokenStream, right: proc_macro2::TokenStream) {
- let left = quote! {
- fn main() { #left }
- };
- let right = quote! {
- fn main() { #right }
- };
- assert_eq_with_diff(
- "left",
- &rustfmt(&left.to_string()),
- "right",
- &rustfmt(&right.to_string()),
- );
- }
-
- #[test]
- fn test_mask_field_value() {
- let loc = ast::SourceRange::default();
- let field = ast::Field::Scalar { loc, id: String::from("a"), width: 8 };
- assert_eq!(mask_field_value(&field).map(|m| m.to_string()), None);
-
- let field = ast::Field::Scalar { loc, id: String::from("a"), width: 24 };
- assert_expr_eq(mask_field_value(&field).unwrap(), quote! { let a = a & 0xffffff; });
- }
-
- #[test]
- fn test_generate_field_parser_no_padding() {
- let loc = ast::SourceRange::default();
- let field = ast::Field::Scalar { loc, id: String::from("a"), width: 8 };
-
- assert_expr_eq(
- generate_field_parser(ast::EndiannessValue::BigEndian, "Foo", &field, 10),
- quote! {
- if bytes.len() < 11 {
- return Err(Error::InvalidLengthError {
- obj: "Foo".to_string(),
- field: "a".to_string(),
- wanted: 11,
- got: bytes.len(),
- });
- }
- let a = u8::from_be_bytes([bytes[10]]);
- },
- );
- }
-
- #[test]
- fn test_generate_field_parser_little_endian_padding() {
- // Test with width != type width.
- let loc = ast::SourceRange::default();
- let field = ast::Field::Scalar { loc, id: String::from("a"), width: 24 };
- assert_expr_eq(
- generate_field_parser(ast::EndiannessValue::LittleEndian, "Foo", &field, 10),
- quote! {
- if bytes.len() < 13 {
- return Err(Error::InvalidLengthError {
- obj: "Foo".to_string(),
- field: "a".to_string(),
- wanted: 13,
- got: bytes.len(),
- });
- }
- let a = u32::from_le_bytes([bytes[10], bytes[11], bytes[12], 0]);
- let a = a & 0xffffff;
- },
- );
- }
-
- #[test]
- fn test_generate_field_parser_big_endian_padding() {
- // Test with width != type width.
- let loc = ast::SourceRange::default();
- let field = ast::Field::Scalar { loc, id: String::from("a"), width: 24 };
- assert_expr_eq(
- generate_field_parser(ast::EndiannessValue::BigEndian, "Foo", &field, 10),
- quote! {
- if bytes.len() < 13 {
- return Err(Error::InvalidLengthError {
- obj: "Foo".to_string(),
- field: "a".to_string(),
- wanted: 13,
- got: bytes.len(),
- });
- }
- let a = u32::from_be_bytes([0, bytes[10], bytes[11], bytes[12]]);
- let a = a & 0xffffff;
- },
- );
- }
-}
diff --git a/tools/pdl/src/lint.rs b/tools/pdl/src/lint.rs
index b2aa237fe6..3841799c49 100644
--- a/tools/pdl/src/lint.rs
+++ b/tools/pdl/src/lint.rs
@@ -3,6 +3,7 @@ use codespan_reporting::files;
use codespan_reporting::term;
use codespan_reporting::term::termcolor;
use std::collections::HashMap;
+use std::ptr;
use crate::ast::*;
@@ -347,6 +348,19 @@ impl<'d> PacketScope<'d> {
}
}
+ /// Return the field immediately preceding the selected field, or None
+ /// if no such field exists.
+ fn get_preceding_field(&self, searched_field: &FieldPath) -> Option<&FieldPath> {
+ let mut preceding_field: Option<&FieldPath> = None;
+ for field in self.fields.iter() {
+ if ptr::eq(field, searched_field) {
+ break;
+ }
+ preceding_field = Some(field);
+ }
+ preceding_field
+ }
+
/// Cleanup scope after processing all fields.
fn finalize(&mut self, result: &mut LintDiagnostics) {
// Check field shadowing.
@@ -996,6 +1010,42 @@ fn lint_array(
}
}
+// Helper for linting padding fields.
+fn lint_padding(
+ _scope: &Scope,
+ packet_scope: &PacketScope,
+ path: &FieldPath,
+ _size: usize,
+ result: &mut LintDiagnostics,
+) {
+ // The padding field must follow an array field.
+
+ let padding_loc = path.loc();
+
+ match packet_scope.get_preceding_field(path).map(|f| f.0.last().unwrap()) {
+ None => result.push(
+ Diagnostic::error()
+ .with_message("padding field cannot be the first field of a packet")
+ .with_labels(vec![padding_loc.primary()])
+ .with_notes(vec![
+ "hint: padding fields must be placed after an array field".to_owned()
+ ]),
+ ),
+ Some(Field::Array { .. }) => (),
+ Some(preceding_field) => result.push(
+ Diagnostic::error()
+ .with_message(format!(
+ "padding field cannot be placed after {} field",
+ preceding_field.kind()
+ ))
+ .with_labels(vec![padding_loc.primary(), preceding_field.loc().secondary()])
+ .with_notes(vec![
+ "hint: padding fields must be placed after an array field".to_owned()
+ ]),
+ ),
+ }
+}
+
// Helper for linting typedef fields.
fn lint_typedef(
scope: &Scope,
@@ -1059,8 +1109,8 @@ fn lint_field(
lint_array(scope, packet_scope, field, width, type_id, size_modifier, size, result)
}
Field::Typedef { type_id, .. } => lint_typedef(scope, packet_scope, field, type_id, result),
- Field::Padding { .. }
- | Field::Reserved { .. }
+ Field::Padding { size, .. } => lint_padding(scope, packet_scope, field, *size, result),
+ Field::Reserved { .. }
| Field::Scalar { .. }
| Field::Body { .. }
| Field::Payload { .. } => (),
@@ -1256,43 +1306,81 @@ mod test {
use crate::lint::Lintable;
use crate::parser::parse_inline;
- macro_rules! parse {
- ($db:expr, $text:literal) => {
- parse_inline($db, "stdin".to_owned(), $text.to_owned()).expect("parsing failure")
+ macro_rules! lint_success {
+ ($name:ident, $text:literal) => {
+ #[test]
+ fn $name() {
+ let mut db = SourceDatabase::new();
+ let file = parse_inline(&mut db, "stdin".to_owned(), $text.to_owned())
+ .expect("parsing failure");
+ assert!(file.lint().diagnostics.is_empty());
+ }
+ };
+ }
+
+ macro_rules! lint_failure {
+ ($name:ident, $text:literal) => {
+ #[test]
+ fn $name() {
+ let mut db = SourceDatabase::new();
+ let file = parse_inline(&mut db, "stdin".to_owned(), $text.to_owned())
+ .expect("parsing failure");
+ assert!(!file.lint().diagnostics.is_empty());
+ }
};
}
- #[test]
- fn test_packet_redeclared() {
- let mut db = SourceDatabase::new();
- let file = parse!(
- &mut db,
- r#"
+ lint_failure!(
+ test_packet_redeclared,
+ r#"
little_endian_packets
struct Name { }
packet Name { }
"#
- );
- let result = file.lint();
- assert!(!result.diagnostics.is_empty());
- }
+ );
- #[test]
- fn test_packet_checksum_start() {
- let mut db = SourceDatabase::new();
- let file = parse!(
- &mut db,
- r#"
- little_endian_packets
- checksum Checksum : 8 "Checksum"
- packet P {
- _checksum_start_(crc),
- a: 16,
- crc: Checksum,
- }
- "#
- );
- let result = file.lint();
- assert!(dbg!(result.diagnostics).is_empty());
- }
+ lint_success!(
+ test_packet_checksum_start,
+ r#"
+ little_endian_packets
+ checksum Checksum : 8 "Checksum"
+ packet P {
+ _checksum_start_(crc),
+ a: 16,
+ crc: Checksum,
+ }
+ "#
+ );
+
+ lint_failure!(
+ test_padding_cannot_be_first_field,
+ r#"
+ little_endian_packets
+ struct Test {
+ _padding_[10],
+ }
+ "#
+ );
+
+ lint_failure!(
+ test_padding_cannot_follow_scalar_field,
+ r#"
+ little_endian_packets
+ struct Test {
+ scalar: 8,
+ _padding_[10],
+ }
+ "#
+ );
+
+ lint_success!(
+ test_padding,
+ r#"
+ little_endian_packets
+ struct Test {
+ array: 8[],
+ _padding_[10],
+ }
+ "#
+ );
}
diff --git a/tools/pdl/src/main.rs b/tools/pdl/src/main.rs
index 042f3858ee..afd9c70936 100644
--- a/tools/pdl/src/main.rs
+++ b/tools/pdl/src/main.rs
@@ -4,7 +4,7 @@ use codespan_reporting::term::{self, termcolor};
use structopt::StructOpt;
mod ast;
-mod generator;
+mod backends;
mod lint;
mod parser;
#[cfg(test)]
@@ -67,10 +67,10 @@ fn main() -> std::process::ExitCode {
match opt.output_format {
OutputFormat::JSON => {
- println!("{}", serde_json::to_string_pretty(&file).unwrap())
+ println!("{}", backends::json::generate(&file).unwrap())
}
OutputFormat::Rust => {
- println!("{}", generator::generate_rust(&sources, &file))
+ println!("{}", backends::rust::generate(&sources, &file))
}
}
std::process::ExitCode::SUCCESS
diff --git a/tools/pdl/src/parser.rs b/tools/pdl/src/parser.rs
index cb24a7ae45..f66774b0c6 100644
--- a/tools/pdl/src/parser.rs
+++ b/tools/pdl/src/parser.rs
@@ -308,8 +308,8 @@ fn parse_field(node: Node<'_>, context: &Context) -> Result<ast::Field, String>
ast::Field::Checksum { loc, field_id }
}
Rule::padding_field => {
- let width = parse_integer(&mut children)?;
- ast::Field::Padding { loc, width }
+ let size = parse_integer(&mut children)?;
+ ast::Field::Padding { loc, size }
}
Rule::size_field => {
let field_id = match children.next() {
diff --git a/tools/pdl/tests/canonical/be_test_file.pdl b/tools/pdl/tests/canonical/be_test_file.pdl
index 10b743056f..41c16b2002 100644
--- a/tools/pdl/tests/canonical/be_test_file.pdl
+++ b/tools/pdl/tests/canonical/be_test_file.pdl
@@ -206,14 +206,6 @@ packet Packet_Struct_Field {
b: UnsizedStruct,
}
-// The parser must be able to handle padding fields.
-// The parser should generate a static size guard.
-packet Packet_Padding_Field {
- a: 8,
- _padding_ [1],
- b: 8,
-}
-
// The parser must be able to handle custom fields of constant size.
// The parser should generate a static size guard.
packet Packet_Custom_Field_ConstantSize {
@@ -306,6 +298,21 @@ packet Packet_Array_Field_UnsizedElement_SizeModifier {
array: UnsizedStruct[+2],
}
+// The parser must be able to handle arrays with padded size.
+packet Packet_Array_Field_SizedElement_VariableSize_Padded {
+ _size_(array) : 4,
+ _reserved_: 4,
+ array: 16[],
+ _padding_ [16],
+}
+
+// The parser must be able to handle arrays with padded size.
+packet Packet_Array_Field_UnsizedElement_VariableCount_Padded {
+ _count_(array) : 8,
+ array: UnsizedStruct[],
+ _padding_ [16],
+}
+
// Packet inheritance
// The parser must handle specialization into
@@ -472,17 +479,6 @@ packet Struct_Struct_Field {
b: UnsizedStruct,
}
-// The parser must be able to handle padding fields.
-// The parser should generate a static size guard.
-struct Struct_Padding_Field_ {
- a: 8,
- _padding_ [1],
- b: 8,
-}
-packet Struct_Padding_Field {
- s: Struct_Padding_Field_,
-}
-
// The parser must be able to handle custom fields of constant size.
// The parser should generate a static size guard.
struct Struct_Custom_Field_ConstantSize_ {
@@ -613,3 +609,24 @@ struct Struct_Array_Field_UnsizedElement_SizeModifier_ {
packet Struct_Array_Field_UnsizedElement_SizeModifier {
s: Struct_Array_Field_UnsizedElement_SizeModifier_,
}
+
+// The parser must be able to handle arrays with padded size.
+struct Struct_Array_Field_SizedElement_VariableSize_Padded_ {
+ _size_(array) : 4,
+ _reserved_: 4,
+ array: 16[],
+ _padding_ [16],
+}
+packet Struct_Array_Field_SizedElement_VariableSize_Padded {
+ s: Struct_Array_Field_SizedElement_VariableSize_Padded_,
+}
+
+// The parser must be able to handle arrays with padded size.
+struct Struct_Array_Field_UnsizedElement_VariableCount_Padded_ {
+ _count_(array) : 8,
+ array: UnsizedStruct[],
+ _padding_ [16],
+}
+packet Struct_Array_Field_UnsizedElement_VariableCount_Padded {
+ s: Struct_Array_Field_UnsizedElement_VariableCount_Padded_,
+}
diff --git a/tools/pdl/tests/canonical/be_test_vectors.json b/tools/pdl/tests/canonical/be_test_vectors.json
index de0b2b6782..b7b47e4027 100644
--- a/tools/pdl/tests/canonical/be_test_vectors.json
+++ b/tools/pdl/tests/canonical/be_test_vectors.json
@@ -383,18 +383,6 @@
]
},
{
- "packet": "Packet_Padding_Field",
- "tests": [
- {
- "packed": "7e007f",
- "unpacked": {
- "a": 126,
- "b": 127
- }
- }
- ]
- },
- {
"packet": "Packet_Array_Field_ScalarElement",
"tests": [
{
@@ -613,6 +601,31 @@
]
},
{
+ "packet": "Packet_Array_Field_SizedElement_VariableSize_Padded",
+ "tests": [
+ {
+ "packed": "0000000000000000000000000000000000",
+ "unpacked": {
+ "array": []
+ }
+ },
+ {
+ "packed": "0e000102030405060708090a0b0c0d0000",
+ "unpacked": {
+ "array": [
+ 1,
+ 515,
+ 1029,
+ 1543,
+ 2057,
+ 2571,
+ 3085
+ ]
+ }
+ }
+ ]
+ },
+ {
"packet": "ScalarParent",
"tests": [
{
@@ -897,20 +910,6 @@
]
},
{
- "packet": "Struct_Padding_Field",
- "tests": [
- {
- "packed": "2f0030",
- "unpacked": {
- "s": {
- "a": 47,
- "b": 48
- }
- }
- }
- ]
- },
- {
"packet": "Struct_Array_Field_ScalarElement",
"tests": [
{
@@ -1147,5 +1146,34 @@
}
}
]
+ },
+ {
+ "packet": "Struct_Array_Field_SizedElement_VariableSize_Padded",
+ "tests": [
+ {
+ "packed": "0000000000000000000000000000000000",
+ "unpacked": {
+ "s": {
+ "array": []
+ }
+ }
+ },
+ {
+ "packed": "0e000102030405060708090a0b0c0d0000",
+ "unpacked": {
+ "s": {
+ "array": [
+ 1,
+ 515,
+ 1029,
+ 1543,
+ 2057,
+ 2571,
+ 3085
+ ]
+ }
+ }
+ }
+ ]
}
]
diff --git a/tools/pdl/tests/canonical/le_test_file.pdl b/tools/pdl/tests/canonical/le_test_file.pdl
index 0c6b181c6b..fa06829864 100644
--- a/tools/pdl/tests/canonical/le_test_file.pdl
+++ b/tools/pdl/tests/canonical/le_test_file.pdl
@@ -216,14 +216,6 @@ packet Packet_Struct_Field {
b: UnsizedStruct,
}
-// The parser must be able to handle padding fields.
-// The parser should generate a static size guard.
-packet Packet_Padding_Field {
- a: 8,
- _padding_ [1],
- b: 8,
-}
-
// The parser must be able to handle custom fields of constant size.
// The parser should generate a static size guard.
packet Packet_Custom_Field_ConstantSize {
@@ -316,6 +308,21 @@ packet Packet_Array_Field_UnsizedElement_SizeModifier {
array: UnsizedStruct[+2],
}
+// The parser must be able to handle arrays with padded size.
+packet Packet_Array_Field_SizedElement_VariableSize_Padded {
+ _size_(array) : 4,
+ _reserved_: 4,
+ array: 16[],
+ _padding_ [16],
+}
+
+// The parser must be able to handle arrays with padded size.
+packet Packet_Array_Field_UnsizedElement_VariableCount_Padded {
+ _count_(array) : 8,
+ array: UnsizedStruct[],
+ _padding_ [16],
+}
+
// Packet inheritance
// The parser must handle specialization into
@@ -510,17 +517,6 @@ packet Struct_Struct_Field {
b: UnsizedStruct,
}
-// The parser must be able to handle padding fields.
-// The parser should generate a static size guard.
-struct Struct_Padding_Field_ {
- a: 8,
- _padding_ [1],
- b: 8,
-}
-packet Struct_Padding_Field {
- s: Struct_Padding_Field_,
-}
-
// The parser must be able to handle custom fields of constant size.
// The parser should generate a static size guard.
struct Struct_Custom_Field_ConstantSize_ {
@@ -651,3 +647,24 @@ struct Struct_Array_Field_UnsizedElement_SizeModifier_ {
packet Struct_Array_Field_UnsizedElement_SizeModifier {
s: Struct_Array_Field_UnsizedElement_SizeModifier_,
}
+
+// The parser must be able to handle arrays with padded size.
+struct Struct_Array_Field_SizedElement_VariableSize_Padded_ {
+ _size_(array) : 4,
+ _reserved_: 4,
+ array: 16[],
+ _padding_ [16],
+}
+packet Struct_Array_Field_SizedElement_VariableSize_Padded {
+ s: Struct_Array_Field_SizedElement_VariableSize_Padded_,
+}
+
+// The parser must be able to handle arrays with padded size.
+struct Struct_Array_Field_UnsizedElement_VariableCount_Padded_ {
+ _count_(array) : 8,
+ array: UnsizedStruct[],
+ _padding_ [16],
+}
+packet Struct_Array_Field_UnsizedElement_VariableCount_Padded {
+ s: Struct_Array_Field_UnsizedElement_VariableCount_Padded_,
+}
diff --git a/tools/pdl/tests/canonical/le_test_vectors.json b/tools/pdl/tests/canonical/le_test_vectors.json
index 8eff0837ac..7df394e8cf 100644
--- a/tools/pdl/tests/canonical/le_test_vectors.json
+++ b/tools/pdl/tests/canonical/le_test_vectors.json
@@ -383,18 +383,6 @@
]
},
{
- "packet": "Packet_Padding_Field",
- "tests": [
- {
- "packed": "7e007f",
- "unpacked": {
- "a": 126,
- "b": 127
- }
- }
- ]
- },
- {
"packet": "Packet_Array_Field_ScalarElement",
"tests": [
{
@@ -613,6 +601,31 @@
]
},
{
+ "packet": "Packet_Array_Field_SizedElement_VariableSize_Padded",
+ "tests": [
+ {
+ "packed": "0000000000000000000000000000000000",
+ "unpacked": {
+ "array": []
+ }
+ },
+ {
+ "packed": "0e010003020504070609080b0a0d0c0000",
+ "unpacked": {
+ "array": [
+ 1,
+ 515,
+ 1029,
+ 1543,
+ 2057,
+ 2571,
+ 3085
+ ]
+ }
+ }
+ ]
+ },
+ {
"packet": "ScalarParent",
"tests": [
{
@@ -939,20 +952,6 @@
]
},
{
- "packet": "Struct_Padding_Field",
- "tests": [
- {
- "packed": "2f0030",
- "unpacked": {
- "s": {
- "a": 47,
- "b": 48
- }
- }
- }
- ]
- },
- {
"packet": "Struct_Array_Field_ScalarElement",
"tests": [
{
@@ -1189,5 +1188,34 @@
}
}
]
+ },
+ {
+ "packet": "Struct_Array_Field_SizedElement_VariableSize_Padded",
+ "tests": [
+ {
+ "packed": "0000000000000000000000000000000000",
+ "unpacked": {
+ "s": {
+ "array": []
+ }
+ }
+ },
+ {
+ "packed": "0e010003020504070609080b0a0d0c0000",
+ "unpacked": {
+ "s": {
+ "array": [
+ 1,
+ 515,
+ 1029,
+ 1543,
+ 2057,
+ 2571,
+ 3085
+ ]
+ }
+ }
+ }
+ ]
}
]
diff --git a/tools/pdl/tests/generated/packet_decl_complex_big_endian.rs b/tools/pdl/tests/generated/packet_decl_complex_big_endian.rs
new file mode 100644
index 0000000000..f1cf8afebf
--- /dev/null
+++ b/tools/pdl/tests/generated/packet_decl_complex_big_endian.rs
@@ -0,0 +1,155 @@
+#[derive(Debug)]
+struct FooData {
+ a: u8,
+ b: u8,
+ c: u8,
+ d: u32,
+ e: u16,
+ f: u8,
+}
+
+#[derive(Debug, Clone)]
+pub struct FooPacket {
+ foo: Arc<FooData>,
+}
+
+#[derive(Debug)]
+pub struct FooBuilder {
+ pub a: u8,
+ pub b: u8,
+ pub c: u8,
+ pub d: u32,
+ pub e: u16,
+ pub f: u8,
+}
+
+impl FooData {
+ fn conforms(bytes: &[u8]) -> bool {
+ if bytes.len() < 7 {
+ return false;
+ }
+ true
+ }
+ fn parse(bytes: &[u8]) -> Result<Self> {
+ if bytes.len() < 1 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "a".to_string(),
+ wanted: 1,
+ got: bytes.len(),
+ });
+ }
+ if bytes.len() < 2 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "b".to_string(),
+ wanted: 2,
+ got: bytes.len(),
+ });
+ }
+ let chunk = u16::from_be_bytes([bytes[0], bytes[1]]);
+ let a = (chunk & 0x7) as u8;
+ let b = (chunk >> 3) as u8;
+ let c = ((chunk >> 11) & 0x1f) as u8;
+ if bytes.len() < 5 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "d".to_string(),
+ wanted: 5,
+ got: bytes.len(),
+ });
+ }
+ let d = u32::from_be_bytes([0, bytes[2], bytes[3], bytes[4]]);
+ if bytes.len() < 7 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "e".to_string(),
+ wanted: 7,
+ got: bytes.len(),
+ });
+ }
+ let chunk = u16::from_be_bytes([bytes[5], bytes[6]]);
+ let e = (chunk & 0xfff);
+ let f = ((chunk >> 12) & 0xf) as u8;
+ Ok(Self { a, b, c, d, e, f })
+ }
+ fn write_to(&self, buffer: &mut BytesMut) {
+ let chunk = 0;
+ let chunk = chunk | ((self.a as u16) & 0x7);
+ let chunk = chunk | ((self.b as u16) << 3);
+ let chunk = chunk | (((self.c as u16) & 0x1f) << 11);
+ buffer[0..2].copy_from_slice(&chunk.to_be_bytes()[0..2]);
+ let d = self.d;
+ buffer[2..5].copy_from_slice(&d.to_be_bytes()[0..3]);
+ let chunk = 0;
+ let chunk = chunk | (self.e & 0xfff);
+ let chunk = chunk | (((self.f as u16) & 0xf) << 12);
+ buffer[5..7].copy_from_slice(&chunk.to_be_bytes()[0..2]);
+ }
+ fn get_total_size(&self) -> usize {
+ self.get_size()
+ }
+ fn get_size(&self) -> usize {
+ let ret = 0;
+ let ret = ret + 7;
+ ret
+ }
+}
+
+impl Packet for FooPacket {
+ fn to_bytes(self) -> Bytes {
+ let mut buffer = BytesMut::new();
+ buffer.resize(self.foo.get_total_size(), 0);
+ self.foo.write_to(&mut buffer);
+ buffer.freeze()
+ }
+ fn to_vec(self) -> Vec<u8> {
+ self.to_bytes().to_vec()
+ }
+}
+impl From<FooPacket> for Bytes {
+ fn from(packet: FooPacket) -> Self {
+ packet.to_bytes()
+ }
+}
+impl From<FooPacket> for Vec<u8> {
+ fn from(packet: FooPacket) -> Self {
+ packet.to_vec()
+ }
+}
+
+impl FooPacket {
+ pub fn parse(bytes: &[u8]) -> Result<Self> {
+ Ok(Self::new(Arc::new(FooData::parse(bytes)?)).unwrap())
+ }
+ fn new(root: Arc<FooData>) -> std::result::Result<Self, &'static str> {
+ let foo = root;
+ Ok(Self { foo })
+ }
+ pub fn get_a(&self) -> u8 {
+ self.foo.as_ref().a
+ }
+ pub fn get_b(&self) -> u8 {
+ self.foo.as_ref().b
+ }
+ pub fn get_c(&self) -> u8 {
+ self.foo.as_ref().c
+ }
+ pub fn get_d(&self) -> u32 {
+ self.foo.as_ref().d
+ }
+ pub fn get_e(&self) -> u16 {
+ self.foo.as_ref().e
+ }
+ pub fn get_f(&self) -> u8 {
+ self.foo.as_ref().f
+ }
+}
+
+impl FooBuilder {
+ pub fn build(self) -> FooPacket {
+ let foo =
+ Arc::new(FooData { a: self.a, b: self.b, c: self.c, d: self.d, e: self.e, f: self.f });
+ FooPacket::new(foo).unwrap()
+ }
+}
diff --git a/tools/pdl/tests/generated/packet_decl_complex_little_endian.rs b/tools/pdl/tests/generated/packet_decl_complex_little_endian.rs
new file mode 100644
index 0000000000..8c985d29a5
--- /dev/null
+++ b/tools/pdl/tests/generated/packet_decl_complex_little_endian.rs
@@ -0,0 +1,155 @@
+#[derive(Debug)]
+struct FooData {
+ a: u8,
+ b: u8,
+ c: u8,
+ d: u32,
+ e: u16,
+ f: u8,
+}
+
+#[derive(Debug, Clone)]
+pub struct FooPacket {
+ foo: Arc<FooData>,
+}
+
+#[derive(Debug)]
+pub struct FooBuilder {
+ pub a: u8,
+ pub b: u8,
+ pub c: u8,
+ pub d: u32,
+ pub e: u16,
+ pub f: u8,
+}
+
+impl FooData {
+ fn conforms(bytes: &[u8]) -> bool {
+ if bytes.len() < 7 {
+ return false;
+ }
+ true
+ }
+ fn parse(bytes: &[u8]) -> Result<Self> {
+ if bytes.len() < 1 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "a".to_string(),
+ wanted: 1,
+ got: bytes.len(),
+ });
+ }
+ if bytes.len() < 2 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "b".to_string(),
+ wanted: 2,
+ got: bytes.len(),
+ });
+ }
+ let chunk = u16::from_le_bytes([bytes[0], bytes[1]]);
+ let a = (chunk & 0x7) as u8;
+ let b = (chunk >> 3) as u8;
+ let c = ((chunk >> 11) & 0x1f) as u8;
+ if bytes.len() < 5 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "d".to_string(),
+ wanted: 5,
+ got: bytes.len(),
+ });
+ }
+ let d = u32::from_le_bytes([bytes[2], bytes[3], bytes[4], 0]);
+ if bytes.len() < 7 {
+ return Err(Error::InvalidLengthError {
+ obj: "Foo".to_string(),
+ field: "e".to_string(),
+ wanted: 7,
+ got: bytes.len(),
+ });
+ }
+ let chunk = u16::from_le_bytes([bytes[5], bytes[6]]);
+ let e = (chunk & 0xfff);
+ let f = ((chunk >> 12) & 0xf) as u8;
+ Ok(Self { a, b, c, d, e, f })
+ }
+ fn write_to(&self, buffer: &mut BytesMut) {
+ let chunk = 0;
+ let chunk = chunk | ((self.a as u16) & 0x7);
+ let chunk = chunk | ((self.b as u16) << 3);
+ let chunk = chunk | (((self.c as u16) & 0x1f) << 11);
+ buffer[0..2].copy_from_slice(&chunk.to_le_bytes()[0..2]);
+ let d = self.d;
+ buffer[2..5].copy_from_slice(&d.to_le_bytes()[0..3]);
+ let chunk = 0;
+ let chunk = chunk | (self.e & 0xfff);
+ let chunk = chunk | (((self.f as u16) & 0xf) << 12);
+ buffer[5..7].copy_from_slice(&chunk.to_le_bytes()[0..2]);
+ }
+ fn get_total_size(&self) -> usize {
+ self.get_size()
+ }
+ fn get_size(&self) -> usize {
+ let ret = 0;
+ let ret = ret + 7;
+ ret
+ }
+}
+
+impl Packet for FooPacket {
+ fn to_bytes(self) -> Bytes {
+ let mut buffer = BytesMut::new();
+ buffer.resize(self.foo.get_total_size(), 0);
+ self.foo.write_to(&mut buffer);
+ buffer.freeze()
+ }
+ fn to_vec(self) -> Vec<u8> {
+ self.to_bytes().to_vec()
+ }
+}
+impl From<FooPacket> for Bytes {
+ fn from(packet: FooPacket) -> Self {
+ packet.to_bytes()
+ }
+}
+impl From<FooPacket> for Vec<u8> {
+ fn from(packet: FooPacket) -> Self {
+ packet.to_vec()
+ }
+}
+
+impl FooPacket {
+ pub fn parse(bytes: &[u8]) -> Result<Self> {
+ Ok(Self::new(Arc::new(FooData::parse(bytes)?)).unwrap())
+ }
+ fn new(root: Arc<FooData>) -> std::result::Result<Self, &'static str> {
+ let foo = root;
+ Ok(Self { foo })
+ }
+ pub fn get_a(&self) -> u8 {
+ self.foo.as_ref().a
+ }
+ pub fn get_b(&self) -> u8 {
+ self.foo.as_ref().b
+ }
+ pub fn get_c(&self) -> u8 {
+ self.foo.as_ref().c
+ }
+ pub fn get_d(&self) -> u32 {
+ self.foo.as_ref().d
+ }
+ pub fn get_e(&self) -> u16 {
+ self.foo.as_ref().e
+ }
+ pub fn get_f(&self) -> u8 {
+ self.foo.as_ref().f
+ }
+}
+
+impl FooBuilder {
+ pub fn build(self) -> FooPacket {
+ let foo =
+ Arc::new(FooData { a: self.a, b: self.b, c: self.c, d: self.d, e: self.e, f: self.f });
+ FooPacket::new(foo).unwrap()
+ }
+}
diff --git a/tools/pdl/tests/generated/packet_decl_simple_big_endian.rs b/tools/pdl/tests/generated/packet_decl_simple_big_endian.rs
index 6c5c7fb3e6..d2fc18663f 100644
--- a/tools/pdl/tests/generated/packet_decl_simple_big_endian.rs
+++ b/tools/pdl/tests/generated/packet_decl_simple_big_endian.rs
@@ -52,7 +52,6 @@ impl FooData {
});
}
let z = u32::from_be_bytes([0, bytes[3], bytes[4], bytes[5]]);
- let z = z & 0xffffff;
Ok(Self { x, y, z })
}
fn write_to(&self, buffer: &mut BytesMut) {
@@ -61,7 +60,6 @@ impl FooData {
let y = self.y;
buffer[1..3].copy_from_slice(&y.to_be_bytes()[0..2]);
let z = self.z;
- let z = z & 0xffffff;
buffer[3..6].copy_from_slice(&z.to_be_bytes()[0..3]);
}
fn get_total_size(&self) -> usize {
diff --git a/tools/pdl/tests/generated/packet_decl_simple_little_endian.rs b/tools/pdl/tests/generated/packet_decl_simple_little_endian.rs
index 468dd70eaf..02cacbc16c 100644
--- a/tools/pdl/tests/generated/packet_decl_simple_little_endian.rs
+++ b/tools/pdl/tests/generated/packet_decl_simple_little_endian.rs
@@ -52,7 +52,6 @@ impl FooData {
});
}
let z = u32::from_le_bytes([bytes[3], bytes[4], bytes[5], 0]);
- let z = z & 0xffffff;
Ok(Self { x, y, z })
}
fn write_to(&self, buffer: &mut BytesMut) {
@@ -61,7 +60,6 @@ impl FooData {
let y = self.y;
buffer[1..3].copy_from_slice(&y.to_le_bytes()[0..2]);
let z = self.z;
- let z = z & 0xffffff;
buffer[3..6].copy_from_slice(&z.to_le_bytes()[0..3]);
}
fn get_total_size(&self) -> usize {
diff --git a/tools/rootcanal/Android.bp b/tools/rootcanal/Android.bp
index 90841bd44a..702ddd3b8d 100644
--- a/tools/rootcanal/Android.bp
+++ b/tools/rootcanal/Android.bp
@@ -16,14 +16,12 @@ package {
cc_defaults {
name: "rootcanal_defaults",
defaults: [
+ "fluoride_common_options",
"gd_defaults",
"gd_clang_tidy",
"gd_clang_tidy_ignore_android",
],
cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
"-fvisibility=hidden",
"-DROOTCANAL_LMP",
],
@@ -117,10 +115,47 @@ cc_library_static {
srcs: ["model/devices/scripted_beacon_ble_payload.proto"],
}
+cc_test_host {
+ name: "rootcanal_hci_test",
+ defaults: [
+ "clang_file_coverage",
+ "clang_coverage_bin",
+ "rootcanal_defaults",
+ ],
+ srcs: [
+ "test/controller/le/le_clear_filter_accept_list_test.cc",
+ "test/controller/le/le_add_device_to_filter_accept_list_test.cc",
+ "test/controller/le/le_remove_device_from_filter_accept_list_test.cc",
+ "test/controller/le/le_add_device_to_resolving_list_test.cc",
+ "test/controller/le/le_clear_resolving_list_test.cc",
+ "test/controller/le/le_remove_device_from_resolving_list_test.cc",
+ "test/controller/le/le_set_address_resolution_enable_test.cc",
+ ],
+ header_libs: [
+ "libbluetooth_headers",
+ ],
+ local_include_dirs: [
+ "include",
+ ".",
+ ],
+ include_dirs: [
+ "packages/modules/Bluetooth/system",
+ "packages/modules/Bluetooth/system/gd",
+ ],
+ shared_libs: [
+ "liblog",
+ ],
+ static_libs: [
+ "libbt-rootcanal",
+ "libjsoncpp",
+ ],
+}
+
// test-vendor unit tests for host
cc_test_host {
name: "rootcanal_test_host",
defaults: [
+ "fluoride_common_options",
"clang_file_coverage",
"clang_coverage_bin",
],
@@ -149,9 +184,6 @@ cc_test_host {
"libbt-rootcanal",
],
cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
"-fvisibility=hidden",
"-DLOG_NDEBUG=1",
],
diff --git a/tools/rootcanal/lmp/build.rs b/tools/rootcanal/lmp/build.rs
index 131103037b..a7eb2bc9c0 100644
--- a/tools/rootcanal/lmp/build.rs
+++ b/tools/rootcanal/lmp/build.rs
@@ -51,6 +51,7 @@ fn generate_packets() {
);
}
+ println!("cargo:rerun-if-changed=lmp_packets.pdl");
let output = Command::new(packetgen.as_os_str().to_str().unwrap())
.arg("--out=".to_owned() + out_dir.as_os_str().to_str().unwrap())
.arg("--include=.")
diff --git a/tools/rootcanal/lmp/src/procedure/encryption.rs b/tools/rootcanal/lmp/src/procedure/encryption.rs
index df44124e76..841b20f4b6 100644
--- a/tools/rootcanal/lmp/src/procedure/encryption.rs
+++ b/tools/rootcanal/lmp/src/procedure/encryption.rs
@@ -1,9 +1,13 @@
// Bluetooth Core, Vol 2, Part C, 4.2.5
+use super::features;
use crate::num_hci_command_packets;
use crate::packets::{hci, lmp};
use crate::procedure::Context;
+use hci::LMPFeaturesPage1Bits::SecureConnectionsHostSupport;
+use hci::LMPFeaturesPage2Bits::SecureConnectionsControllerSupport;
+
pub async fn initiate(ctx: &impl Context) {
// TODO: handle turn off
let _ = ctx.receive_hci_command::<hci::SetConnectionEncryptionPacket>().await;
@@ -36,11 +40,18 @@ pub async fn initiate(ctx: &impl Context) {
)
.await;
+ let aes_ccm = features::supported_on_both_page1(ctx, SecureConnectionsHostSupport).await
+ && features::supported_on_both_page2(ctx, SecureConnectionsControllerSupport).await;
+
ctx.send_hci_event(
hci::EncryptionChangeBuilder {
status: hci::ErrorCode::Success,
connection_handle: ctx.peer_handle(),
- encryption_enabled: hci::EncryptionEnabled::On,
+ encryption_enabled: if aes_ccm {
+ hci::EncryptionEnabled::BrEdrAesCcm
+ } else {
+ hci::EncryptionEnabled::On
+ },
}
.build(),
);
@@ -72,12 +83,70 @@ pub async fn respond(ctx: &impl Context) {
.build(),
);
+ let aes_ccm = features::supported_on_both_page1(ctx, SecureConnectionsHostSupport).await
+ && features::supported_on_both_page2(ctx, SecureConnectionsControllerSupport).await;
+
ctx.send_hci_event(
hci::EncryptionChangeBuilder {
status: hci::ErrorCode::Success,
connection_handle: ctx.peer_handle(),
- encryption_enabled: hci::EncryptionEnabled::On,
+ encryption_enabled: if aes_ccm {
+ hci::EncryptionEnabled::BrEdrAesCcm
+ } else {
+ hci::EncryptionEnabled::On
+ },
}
.build(),
);
}
+
+#[cfg(test)]
+mod tests {
+ use super::initiate;
+ use super::respond;
+ use crate::procedure::Context;
+ use crate::test::{sequence, TestContext};
+
+ use crate::packets::hci::LMPFeaturesPage1Bits::SecureConnectionsHostSupport;
+ use crate::packets::hci::LMPFeaturesPage2Bits::SecureConnectionsControllerSupport;
+
+ #[test]
+ fn accept_encryption() {
+ let context = TestContext::new();
+ let procedure = respond;
+
+ include!("../../test/ENC/BV-01-C.in");
+ }
+
+ #[test]
+ fn initiate_encryption() {
+ let context = TestContext::new();
+ let procedure = initiate;
+
+ include!("../../test/ENC/BV-05-C.in");
+ }
+
+ #[test]
+ fn accept_aes_ccm_encryption_request() {
+ let context = TestContext::new()
+ .with_page_1_feature(SecureConnectionsHostSupport)
+ .with_page_2_feature(SecureConnectionsControllerSupport)
+ .with_peer_page_1_feature(SecureConnectionsHostSupport)
+ .with_peer_page_2_feature(SecureConnectionsControllerSupport);
+ let procedure = respond;
+
+ include!("../../test/ENC/BV-26-C.in");
+ }
+
+ #[test]
+ fn initiate_aes_ccm_encryption() {
+ let context = TestContext::new()
+ .with_page_1_feature(SecureConnectionsHostSupport)
+ .with_page_2_feature(SecureConnectionsControllerSupport)
+ .with_peer_page_1_feature(SecureConnectionsHostSupport)
+ .with_peer_page_2_feature(SecureConnectionsControllerSupport);
+ let procedure = initiate;
+
+ include!("../../test/ENC/BV-34-C.in");
+ }
+}
diff --git a/tools/rootcanal/lmp/src/procedure/features.rs b/tools/rootcanal/lmp/src/procedure/features.rs
index 5de641cc76..d5a2eeab98 100644
--- a/tools/rootcanal/lmp/src/procedure/features.rs
+++ b/tools/rootcanal/lmp/src/procedure/features.rs
@@ -1,5 +1,7 @@
// Bluetooth Core, Vol 2, Part C, 4.3.4
+use num_traits::ToPrimitive;
+
use crate::packets::lmp;
use crate::procedure::Context;
@@ -34,21 +36,30 @@ pub async fn respond(ctx: &impl Context) {
);
}
-pub async fn supported_on_both_page1(
- ctx: &impl Context,
- feature: crate::packets::hci::LMPFeaturesPage1Bits,
-) -> bool {
- use num_traits::ToPrimitive;
- let feature_mask = feature.to_u64().unwrap();
- let local_supported = ctx.extended_features(1) & feature_mask != 0;
+async fn supported_on_both_page(ctx: &impl Context, page_number: u8, feature_mask: u64) -> bool {
+ let local_supported = ctx.extended_features(page_number) & feature_mask != 0;
// Lazy peer features
let peer_supported = async move {
- let page = if let Some(page) = ctx.peer_extended_features(1) {
+ let page = if let Some(page) = ctx.peer_extended_features(page_number) {
page
} else {
- crate::procedure::features::initiate(ctx, 1).await
+ crate::procedure::features::initiate(ctx, page_number).await
};
page & feature_mask != 0
};
local_supported && peer_supported.await
}
+
+pub async fn supported_on_both_page1(
+ ctx: &impl Context,
+ feature: crate::packets::hci::LMPFeaturesPage1Bits,
+) -> bool {
+ supported_on_both_page(ctx, 1, feature.to_u64().unwrap()).await
+}
+
+pub async fn supported_on_both_page2(
+ ctx: &impl Context,
+ feature: crate::packets::hci::LMPFeaturesPage2Bits,
+) -> bool {
+ supported_on_both_page(ctx, 2, feature.to_u64().unwrap()).await
+}
diff --git a/tools/rootcanal/lmp/src/test/context.rs b/tools/rootcanal/lmp/src/test/context.rs
index c784269be4..262b97f805 100644
--- a/tools/rootcanal/lmp/src/test/context.rs
+++ b/tools/rootcanal/lmp/src/test/context.rs
@@ -5,6 +5,8 @@ use std::future::Future;
use std::pin::Pin;
use std::task::{self, Poll};
+use num_traits::ToPrimitive;
+
use crate::ec::PrivateKey;
use crate::packets::{hci, lmp};
@@ -17,11 +19,35 @@ pub struct TestContext {
pub hci_events: RefCell<VecDeque<hci::EventPacket>>,
pub hci_commands: RefCell<VecDeque<hci::CommandPacket>>,
private_key: RefCell<Option<PrivateKey>>,
+ features_pages: [u64; 3],
+ peer_features_pages: [u64; 3],
}
impl TestContext {
pub fn new() -> Self {
- Default::default()
+ Self::default()
+ .with_page_1_feature(hci::LMPFeaturesPage1Bits::SecureSimplePairingHostSupport)
+ .with_peer_page_1_feature(hci::LMPFeaturesPage1Bits::SecureSimplePairingHostSupport)
+ }
+
+ pub fn with_page_1_feature(mut self, feature: hci::LMPFeaturesPage1Bits) -> Self {
+ self.features_pages[1] |= feature.to_u64().unwrap();
+ self
+ }
+
+ pub fn with_page_2_feature(mut self, feature: hci::LMPFeaturesPage2Bits) -> Self {
+ self.features_pages[2] |= feature.to_u64().unwrap();
+ self
+ }
+
+ pub fn with_peer_page_1_feature(mut self, feature: hci::LMPFeaturesPage1Bits) -> Self {
+ self.peer_features_pages[1] |= feature.to_u64().unwrap();
+ self
+ }
+
+ pub fn with_peer_page_2_feature(mut self, feature: hci::LMPFeaturesPage2Bits) -> Self {
+ self.peer_features_pages[2] |= feature.to_u64().unwrap();
+ self
}
}
@@ -67,19 +93,11 @@ impl Context for TestContext {
}
fn peer_extended_features(&self, features_page: u8) -> Option<u64> {
- if features_page == 1 {
- Some(1)
- } else {
- Some(0)
- }
+ Some(self.peer_features_pages[features_page as usize])
}
fn extended_features(&self, features_page: u8) -> u64 {
- if features_page == 1 {
- 1
- } else {
- 0
- }
+ self.features_pages[features_page as usize]
}
fn get_private_key(&self) -> Option<PrivateKey> {
diff --git a/tools/rootcanal/lmp/test/ENC/BV-01-C.in b/tools/rootcanal/lmp/test/ENC/BV-01-C.in
new file mode 100644
index 0000000000..cee42505db
--- /dev/null
+++ b/tools/rootcanal/lmp/test/ENC/BV-01-C.in
@@ -0,0 +1,32 @@
+sequence! { procedure, context,
+ // ACL Connection Established
+ Lower Tester -> IUT: EncryptionModeReq {
+ transaction_id: 0,
+ encryption_mode: 0x01,
+ }
+ IUT ->Lower Tester: Accepted {
+ transaction_id: 0,
+ accepted_opcode: Opcode::EncryptionModeReq,
+ }
+ Lower Tester -> IUT: EncryptionKeySizeReq {
+ transaction_id: 0,
+ key_size: 0x10,
+ }
+ IUT -> Lower Tester: Accepted {
+ transaction_id: 0,
+ accepted_opcode: Opcode::EncryptionKeySizeReq,
+ }
+ Lower Tester -> IUT: StartEncryptionReq {
+ transaction_id: 0,
+ random_number: [0; 16],
+ }
+ IUT -> Lower Tester: Accepted {
+ transaction_id: 0,
+ accepted_opcode: Opcode::StartEncryptionReq,
+ }
+ IUT -> Upper Tester: EncryptionChange {
+ status: ErrorCode::Success,
+ connection_handle: context.peer_handle(),
+ encryption_enabled: EncryptionEnabled::On,
+ }
+}
diff --git a/tools/rootcanal/lmp/test/ENC/BV-05-C.in b/tools/rootcanal/lmp/test/ENC/BV-05-C.in
new file mode 100644
index 0000000000..b25d81ef90
--- /dev/null
+++ b/tools/rootcanal/lmp/test/ENC/BV-05-C.in
@@ -0,0 +1,40 @@
+sequence! { procedure, context,
+ // ACL Connection Established
+ Upper Tester -> IUT: SetConnectionEncryption {
+ connection_handle: context.peer_handle(),
+ encryption_enable: Enable::Enabled
+ }
+ IUT -> Upper Tester: SetConnectionEncryptionStatus {
+ num_hci_command_packets: 1,
+ status: ErrorCode::Success,
+ }
+ IUT -> Lower Tester: EncryptionModeReq {
+ transaction_id: 0,
+ encryption_mode: 0x01,
+ }
+ Lower Tester -> IUT: Accepted {
+ transaction_id: 0,
+ accepted_opcode: Opcode::EncryptionModeReq,
+ }
+ IUT -> Lower Tester: EncryptionKeySizeReq {
+ transaction_id: 0,
+ key_size: 0x10,
+ }
+ Lower Tester -> IUT: Accepted {
+ transaction_id: 0,
+ accepted_opcode: Opcode::EncryptionKeySizeReq,
+ }
+ IUT -> Lower Tester: StartEncryptionReq {
+ transaction_id: 0,
+ random_number: [0; 16],
+ }
+ Lower Tester -> IUT: Accepted {
+ transaction_id: 0,
+ accepted_opcode: Opcode::StartEncryptionReq,
+ }
+ IUT -> Upper Tester: EncryptionChange {
+ status: ErrorCode::Success,
+ connection_handle: context.peer_handle(),
+ encryption_enabled: EncryptionEnabled::On,
+ }
+}
diff --git a/tools/rootcanal/lmp/test/ENC/BV-26-C.in b/tools/rootcanal/lmp/test/ENC/BV-26-C.in
new file mode 100644
index 0000000000..01df56eb54
--- /dev/null
+++ b/tools/rootcanal/lmp/test/ENC/BV-26-C.in
@@ -0,0 +1,32 @@
+sequence! { procedure, context,
+ // ACL Connection Established
+ Lower Tester -> IUT: EncryptionModeReq {
+ transaction_id: 0,
+ encryption_mode: 0x01,
+ }
+ IUT ->Lower Tester: Accepted {
+ transaction_id: 0,
+ accepted_opcode: Opcode::EncryptionModeReq,
+ }
+ Lower Tester -> IUT: EncryptionKeySizeReq {
+ transaction_id: 0,
+ key_size: 0x10,
+ }
+ IUT -> Lower Tester: Accepted {
+ transaction_id: 0,
+ accepted_opcode: Opcode::EncryptionKeySizeReq,
+ }
+ Lower Tester -> IUT: StartEncryptionReq {
+ transaction_id: 0,
+ random_number: [0; 16],
+ }
+ IUT -> Lower Tester: Accepted {
+ transaction_id: 0,
+ accepted_opcode: Opcode::StartEncryptionReq,
+ }
+ IUT -> Upper Tester: EncryptionChange {
+ status: ErrorCode::Success,
+ connection_handle: context.peer_handle(),
+ encryption_enabled: EncryptionEnabled::BrEdrAesCcm,
+ }
+}
diff --git a/tools/rootcanal/lmp/test/ENC/BV-34-C.in b/tools/rootcanal/lmp/test/ENC/BV-34-C.in
new file mode 100644
index 0000000000..ea03d98702
--- /dev/null
+++ b/tools/rootcanal/lmp/test/ENC/BV-34-C.in
@@ -0,0 +1,40 @@
+sequence! { procedure, context,
+ // ACL Connection Established
+ Upper Tester -> IUT: SetConnectionEncryption {
+ connection_handle: context.peer_handle(),
+ encryption_enable: Enable::Enabled
+ }
+ IUT -> Upper Tester: SetConnectionEncryptionStatus {
+ num_hci_command_packets: 1,
+ status: ErrorCode::Success,
+ }
+ IUT -> Lower Tester: EncryptionModeReq {
+ transaction_id: 0,
+ encryption_mode: 0x01,
+ }
+ Lower Tester -> IUT: Accepted {
+ transaction_id: 0,
+ accepted_opcode: Opcode::EncryptionModeReq,
+ }
+ IUT -> Lower Tester: EncryptionKeySizeReq {
+ transaction_id: 0,
+ key_size: 0x10,
+ }
+ Lower Tester -> IUT: Accepted {
+ transaction_id: 0,
+ accepted_opcode: Opcode::EncryptionKeySizeReq,
+ }
+ IUT -> Lower Tester: StartEncryptionReq {
+ transaction_id: 0,
+ random_number: [0; 16],
+ }
+ Lower Tester -> IUT: Accepted {
+ transaction_id: 0,
+ accepted_opcode: Opcode::StartEncryptionReq,
+ }
+ IUT -> Upper Tester: EncryptionChange {
+ status: ErrorCode::Success,
+ connection_handle: context.peer_handle(),
+ encryption_enabled: EncryptionEnabled::BrEdrAesCcm,
+ }
+}
diff --git a/tools/rootcanal/model/controller/acl_connection.cc b/tools/rootcanal/model/controller/acl_connection.cc
index e23b412f8c..35d2108e4b 100644
--- a/tools/rootcanal/model/controller/acl_connection.cc
+++ b/tools/rootcanal/model/controller/acl_connection.cc
@@ -20,11 +20,12 @@ namespace rootcanal {
AclConnection::AclConnection(AddressWithType address,
AddressWithType own_address,
AddressWithType resolved_address,
- Phy::Type phy_type)
+ Phy::Type phy_type, bluetooth::hci::Role role)
: address_(address),
own_address_(own_address),
resolved_address_(resolved_address),
type_(phy_type),
+ role_(role),
last_packet_timestamp_(std::chrono::steady_clock::now()),
timeout_(std::chrono::seconds(1)) {}
diff --git a/tools/rootcanal/model/controller/acl_connection.h b/tools/rootcanal/model/controller/acl_connection.h
index 40c8fcd6fc..c2c23c7eee 100644
--- a/tools/rootcanal/model/controller/acl_connection.h
+++ b/tools/rootcanal/model/controller/acl_connection.h
@@ -30,7 +30,8 @@ using ::bluetooth::hci::AddressWithType;
class AclConnection {
public:
AclConnection(AddressWithType address, AddressWithType own_address,
- AddressWithType resolved_address, Phy::Type phy_type);
+ AddressWithType resolved_address, Phy::Type phy_type,
+ bluetooth::hci::Role role);
virtual ~AclConnection() = default;
diff --git a/tools/rootcanal/model/controller/acl_connection_handler.cc b/tools/rootcanal/model/controller/acl_connection_handler.cc
index a38b2724c7..9992121907 100644
--- a/tools/rootcanal/model/controller/acl_connection_handler.cc
+++ b/tools/rootcanal/model/controller/acl_connection_handler.cc
@@ -127,20 +127,22 @@ uint16_t AclConnectionHandler::CreateConnection(Address addr,
AclConnection{
AddressWithType{addr, AddressType::PUBLIC_DEVICE_ADDRESS},
AddressWithType{own_addr, AddressType::PUBLIC_DEVICE_ADDRESS},
- AddressWithType(), Phy::Type::BR_EDR});
+ AddressWithType(), Phy::Type::BR_EDR,
+ bluetooth::hci::Role::CENTRAL});
return handle;
}
return kReservedHandle;
}
uint16_t AclConnectionHandler::CreateLeConnection(AddressWithType addr,
- AddressWithType own_addr) {
+ AddressWithType own_addr,
+ bluetooth::hci::Role role) {
AddressWithType resolved_peer = pending_le_connection_resolved_address_;
if (CancelPendingLeConnection(addr)) {
uint16_t handle = GetUnusedHandle();
- acl_connections_.emplace(
- handle,
- AclConnection{addr, own_addr, resolved_peer, Phy::Type::LOW_ENERGY});
+ acl_connections_.emplace(handle,
+ AclConnection{addr, own_addr, resolved_peer,
+ Phy::Type::LOW_ENERGY, role});
return handle;
}
return kReservedHandle;
diff --git a/tools/rootcanal/model/controller/acl_connection_handler.h b/tools/rootcanal/model/controller/acl_connection_handler.h
index d601728ec0..2b0de43a23 100644
--- a/tools/rootcanal/model/controller/acl_connection_handler.h
+++ b/tools/rootcanal/model/controller/acl_connection_handler.h
@@ -67,7 +67,8 @@ class AclConnectionHandler {
uint16_t CreateConnection(bluetooth::hci::Address addr,
bluetooth::hci::Address own_addr);
uint16_t CreateLeConnection(bluetooth::hci::AddressWithType addr,
- bluetooth::hci::AddressWithType own_addr);
+ bluetooth::hci::AddressWithType own_addr,
+ bluetooth::hci::Role role);
bool Disconnect(uint16_t handle);
bool HasHandle(uint16_t handle) const;
bool HasScoHandle(uint16_t handle) const;
diff --git a/tools/rootcanal/model/controller/controller_properties.cc b/tools/rootcanal/model/controller/controller_properties.cc
index 41dfd8d1e0..7a5856eeb8 100644
--- a/tools/rootcanal/model/controller/controller_properties.cc
+++ b/tools/rootcanal/model/controller/controller_properties.cc
@@ -285,9 +285,6 @@ ControllerProperties::ControllerProperties(const std::string& file_name)
ParseUint(root, "ManufacturerName", company_identifier);
ParseHex64(root["LeSupportedFeatures"], &le_features);
- ParseUint(root, "LeConnectListIgnoreReasons", le_connect_list_ignore_reasons);
- ParseUint(root, "LeResolvingListIgnoreReasons",
- le_resolving_list_ignore_reasons);
// Configuration options.
@@ -310,6 +307,7 @@ ControllerProperties::ControllerProperties(const std::string& file_name)
ParseUint(root, "total_num_le_acl_data_packets ",
total_num_le_acl_data_packets);
ParseUint(root, "total_num_iso_data_packets ", total_num_iso_data_packets);
+ ParseUint(root, "num_supported_iac", num_supported_iac);
ParseUintArray(root, "lmp_features", lmp_features);
ParseUintVector(root, "supported_standard_codecs", supported_standard_codecs);
diff --git a/tools/rootcanal/model/controller/controller_properties.h b/tools/rootcanal/model/controller/controller_properties.h
index 9f551fd34f..5ec91a6dcd 100644
--- a/tools/rootcanal/model/controller/controller_properties.h
+++ b/tools/rootcanal/model/controller/controller_properties.h
@@ -76,6 +76,9 @@ struct ControllerProperties {
uint8_t total_num_le_acl_data_packets{20};
uint8_t total_num_iso_data_packets{12};
+ // Number of Supported IAC (Vol 4, Part E § 7.3.43).
+ uint8_t num_supported_iac{4};
+
// Supported Codecs (Vol 4, Part E § 7.4.8).
// Implements the [v1] version only.
std::vector<uint8_t> supported_standard_codecs{0};
@@ -93,15 +96,6 @@ struct ControllerProperties {
// Vendor Information.
// Provide parameters returned by vendor specific commands.
std::vector<uint8_t> le_vendor_capabilities{};
-
- // LE Workarounds
- uint16_t le_connect_list_ignore_reasons{0};
- uint16_t le_resolving_list_ignore_reasons{0};
-
- // Workaround for misbehaving stacks
- static constexpr uint8_t kLeListIgnoreScanEnable = 0x1;
- static constexpr uint8_t kLeListIgnoreConnections = 0x2;
- static constexpr uint8_t kLeListIgnoreAdvertising = 0x4;
};
} // namespace rootcanal
diff --git a/tools/rootcanal/model/controller/dual_mode_controller.cc b/tools/rootcanal/model/controller/dual_mode_controller.cc
index 9a26337c95..c1a58fc4f8 100644
--- a/tools/rootcanal/model/controller/dual_mode_controller.cc
+++ b/tools/rootcanal/model/controller/dual_mode_controller.cc
@@ -1494,7 +1494,7 @@ void DualModeController::ReadPageTimeout(CommandView command) {
auto command_view = gd_hci::ReadPageTimeoutView::Create(
gd_hci::DiscoveryCommandView::Create(command));
ASSERT(command_view.IsValid());
- uint16_t page_timeout = 0x2000;
+ uint16_t page_timeout = link_layer_controller_.GetPageTimeout();
send_event_(bluetooth::hci::ReadPageTimeoutCompleteBuilder::Create(
kNumCommandPackets, ErrorCode::SUCCESS, page_timeout));
}
@@ -1503,6 +1503,7 @@ void DualModeController::WritePageTimeout(CommandView command) {
auto command_view = gd_hci::WritePageTimeoutView::Create(
gd_hci::DiscoveryCommandView::Create(command));
ASSERT(command_view.IsValid());
+ link_layer_controller_.SetPageTimeout(command_view.GetPageTimeout());
send_event_(bluetooth::hci::WritePageTimeoutCompleteBuilder::Create(
kNumCommandPackets, ErrorCode::SUCCESS));
}
@@ -1744,25 +1745,24 @@ void DualModeController::ReadNumberOfSupportedIac(CommandView command) {
auto command_view = gd_hci::ReadNumberOfSupportedIacView::Create(
gd_hci::DiscoveryCommandView::Create(command));
ASSERT(command_view.IsValid());
- uint8_t num_support_iac = 0x1;
send_event_(bluetooth::hci::ReadNumberOfSupportedIacCompleteBuilder::Create(
- kNumCommandPackets, ErrorCode::SUCCESS, num_support_iac));
+ kNumCommandPackets, ErrorCode::SUCCESS, properties_.num_supported_iac));
}
void DualModeController::ReadCurrentIacLap(CommandView command) {
auto command_view = gd_hci::ReadCurrentIacLapView::Create(
gd_hci::DiscoveryCommandView::Create(command));
ASSERT(command_view.IsValid());
- gd_hci::Lap lap;
- lap.lap_ = 0x30;
send_event_(bluetooth::hci::ReadCurrentIacLapCompleteBuilder::Create(
- kNumCommandPackets, ErrorCode::SUCCESS, {lap}));
+ kNumCommandPackets, ErrorCode::SUCCESS,
+ link_layer_controller_.ReadCurrentIacLap()));
}
void DualModeController::WriteCurrentIacLap(CommandView command) {
auto command_view = gd_hci::WriteCurrentIacLapView::Create(
gd_hci::DiscoveryCommandView::Create(command));
ASSERT(command_view.IsValid());
+ link_layer_controller_.WriteCurrentIacLap(command_view.GetLapsToWrite());
send_event_(bluetooth::hci::WriteCurrentIacLapCompleteBuilder::Create(
kNumCommandPackets, ErrorCode::SUCCESS));
}
@@ -1807,8 +1807,19 @@ void DualModeController::ReadScanEnable(CommandView command) {
auto command_view = gd_hci::ReadScanEnableView::Create(
gd_hci::DiscoveryCommandView::Create(command));
ASSERT(command_view.IsValid());
+
+ bool inquiry_scan = link_layer_controller_.GetInquiryScanEnable();
+ bool page_scan = link_layer_controller_.GetPageScanEnable();
+
+ bluetooth::hci::ScanEnable scan_enable =
+ inquiry_scan && page_scan
+ ? bluetooth::hci::ScanEnable::INQUIRY_AND_PAGE_SCAN
+ : inquiry_scan ? bluetooth::hci::ScanEnable::INQUIRY_SCAN_ONLY
+ : page_scan ? bluetooth::hci::ScanEnable::PAGE_SCAN_ONLY
+ : bluetooth::hci::ScanEnable::NO_SCANS;
+
send_event_(bluetooth::hci::ReadScanEnableCompleteBuilder::Create(
- kNumCommandPackets, ErrorCode::SUCCESS, gd_hci::ScanEnable::NO_SCANS));
+ kNumCommandPackets, ErrorCode::SUCCESS, scan_enable));
}
void DualModeController::WriteScanEnable(CommandView command) {
@@ -2041,7 +2052,7 @@ void DualModeController::LeSetAddressResolutionEnable(CommandView command) {
gd_hci::LeSecurityCommandView::Create(
gd_hci::SecurityCommandView::Create(command)));
ASSERT(command_view.IsValid());
- auto status = link_layer_controller_.LeSetAddressResolutionEnable(
+ ErrorCode status = link_layer_controller_.LeSetAddressResolutionEnable(
command_view.GetAddressResolutionEnable() ==
bluetooth::hci::Enable::ENABLED);
send_event_(
@@ -2097,7 +2108,7 @@ void DualModeController::LeSetAdvertisingParameters(CommandView command) {
static_cast<uint8_t>(command_view.GetOwnAddressType()),
static_cast<uint8_t>(command_view.GetPeerAddressType()), peer_address,
command_view.GetAdvertisingChannelMap(),
- static_cast<uint8_t>(command_view.GetAdvertisingFilterPolicy()));
+ command_view.GetAdvertisingFilterPolicy());
send_event_(bluetooth::hci::LeSetAdvertisingParametersCompleteBuilder::Create(
kNumCommandPackets, ErrorCode::SUCCESS));
@@ -2165,7 +2176,7 @@ void DualModeController::LeSetScanParameters(CommandView command) {
link_layer_controller_.SetLeScanWindow(command_view.GetLeScanWindow());
link_layer_controller_.SetLeAddressType(command_view.GetOwnAddressType());
link_layer_controller_.SetLeScanFilterPolicy(
- static_cast<uint8_t>(command_view.GetScanningFilterPolicy()));
+ command_view.GetScanningFilterPolicy());
send_event_(bluetooth::hci::LeSetScanParametersCompleteBuilder::Create(
kNumCommandPackets, ErrorCode::SUCCESS));
}
@@ -2194,13 +2205,15 @@ void DualModeController::LeCreateConnection(CommandView command) {
gd_hci::LeConnectionManagementCommandView::Create(
gd_hci::AclCommandView::Create(command)));
ASSERT(command_view.IsValid());
+ auto initiator_filter_policy = command_view.GetInitiatorFilterPolicy();
+
link_layer_controller_.SetLeScanInterval(command_view.GetLeScanInterval());
link_layer_controller_.SetLeScanWindow(command_view.GetLeScanWindow());
- uint8_t initiator_filter_policy =
- static_cast<uint8_t>(command_view.GetInitiatorFilterPolicy());
link_layer_controller_.SetLeInitiatorFilterPolicy(initiator_filter_policy);
- if (initiator_filter_policy == 0) { // Connect list not used
+ if (initiator_filter_policy ==
+ bluetooth::hci::InitiatorFilterPolicy::USE_PEER_ADDRESS) {
+ // Connect list not used
uint8_t peer_address_type =
static_cast<uint8_t>(command_view.GetPeerAddressType());
Address peer_address = command_view.GetPeerAddress();
@@ -2326,9 +2339,9 @@ void DualModeController::LeClearFilterAcceptList(CommandView command) {
gd_hci::LeConnectionManagementCommandView::Create(
gd_hci::AclCommandView::Create(command)));
ASSERT(command_view.IsValid());
- link_layer_controller_.LeFilterAcceptListClear();
+ ErrorCode status = link_layer_controller_.LeClearFilterAcceptList();
send_event_(bluetooth::hci::LeClearFilterAcceptListCompleteBuilder::Create(
- kNumCommandPackets, ErrorCode::SUCCESS));
+ kNumCommandPackets, status));
}
void DualModeController::LeAddDeviceToFilterAcceptList(CommandView command) {
@@ -2336,17 +2349,11 @@ void DualModeController::LeAddDeviceToFilterAcceptList(CommandView command) {
gd_hci::LeConnectionManagementCommandView::Create(
gd_hci::AclCommandView::Create(command)));
ASSERT(command_view.IsValid());
-
- ErrorCode result = ErrorCode::INVALID_HCI_COMMAND_PARAMETERS;
- if (command_view.GetAddressType() !=
- bluetooth::hci::FilterAcceptListAddressType::ANONYMOUS_ADVERTISERS) {
- result = link_layer_controller_.LeFilterAcceptListAddDevice(
- command_view.GetAddress(), static_cast<bluetooth::hci::AddressType>(
- command_view.GetAddressType()));
- }
+ ErrorCode status = link_layer_controller_.LeAddDeviceToFilterAcceptList(
+ command_view.GetAddressType(), command_view.GetAddress());
send_event_(
bluetooth::hci::LeAddDeviceToFilterAcceptListCompleteBuilder::Create(
- kNumCommandPackets, result));
+ kNumCommandPackets, status));
}
void DualModeController::LeRemoveDeviceFromFilterAcceptList(
@@ -2355,16 +2362,8 @@ void DualModeController::LeRemoveDeviceFromFilterAcceptList(
gd_hci::LeConnectionManagementCommandView::Create(
gd_hci::AclCommandView::Create(command)));
ASSERT(command_view.IsValid());
-
- ErrorCode status = ErrorCode::SUCCESS;
- if (command_view.GetAddressType() !=
- bluetooth::hci::FilterAcceptListAddressType::ANONYMOUS_ADVERTISERS) {
- link_layer_controller_.LeFilterAcceptListAddDevice(
- command_view.GetAddress(), static_cast<bluetooth::hci::AddressType>(
- command_view.GetAddressType()));
- } else {
- status = ErrorCode::INVALID_HCI_COMMAND_PARAMETERS;
- }
+ ErrorCode status = link_layer_controller_.LeRemoveDeviceFromFilterAcceptList(
+ command_view.GetAddressType(), command_view.GetAddress());
send_event_(
bluetooth::hci::LeRemoveDeviceFromFilterAcceptListCompleteBuilder::Create(
kNumCommandPackets, status));
@@ -2374,9 +2373,9 @@ void DualModeController::LeClearResolvingList(CommandView command) {
auto command_view = gd_hci::LeClearResolvingListView::Create(
gd_hci::LeSecurityCommandView::Create(command));
ASSERT(command_view.IsValid());
- link_layer_controller_.LeResolvingListClear();
+ ErrorCode status = link_layer_controller_.LeClearResolvingList();
send_event_(bluetooth::hci::LeClearResolvingListCompleteBuilder::Create(
- kNumCommandPackets, ErrorCode::SUCCESS));
+ kNumCommandPackets, status));
}
void DualModeController::LeReadResolvingListSize(CommandView command) {
@@ -2438,17 +2437,17 @@ void DualModeController::LeAddDeviceToResolvingList(CommandView command) {
auto command_view = gd_hci::LeAddDeviceToResolvingListView::Create(
gd_hci::LeSecurityCommandView::Create(command));
ASSERT(command_view.IsValid());
- AddressType peer_address_type;
+ AddressType peer_identity_address_type;
switch (command_view.GetPeerIdentityAddressType()) {
case bluetooth::hci::PeerAddressType::PUBLIC_DEVICE_OR_IDENTITY_ADDRESS:
- peer_address_type = AddressType::PUBLIC_DEVICE_ADDRESS;
+ peer_identity_address_type = AddressType::PUBLIC_DEVICE_ADDRESS;
break;
case bluetooth::hci::PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS:
- peer_address_type = AddressType::RANDOM_DEVICE_ADDRESS;
+ peer_identity_address_type = AddressType::RANDOM_DEVICE_ADDRESS;
break;
}
- auto status = link_layer_controller_.LeResolvingListAddDevice(
- command_view.GetPeerIdentityAddress(), peer_address_type,
+ ErrorCode status = link_layer_controller_.LeAddDeviceToResolvingList(
+ peer_identity_address_type, command_view.GetPeerIdentityAddress(),
command_view.GetPeerIrk(), command_view.GetLocalIrk());
send_event_(bluetooth::hci::LeAddDeviceToResolvingListCompleteBuilder::Create(
kNumCommandPackets, status));
@@ -2459,20 +2458,20 @@ void DualModeController::LeRemoveDeviceFromResolvingList(CommandView command) {
gd_hci::LeSecurityCommandView::Create(command));
ASSERT(command_view.IsValid());
- AddressType peer_address_type;
+ AddressType peer_identity_address_type;
switch (command_view.GetPeerIdentityAddressType()) {
case bluetooth::hci::PeerAddressType::PUBLIC_DEVICE_OR_IDENTITY_ADDRESS:
- peer_address_type = AddressType::PUBLIC_DEVICE_ADDRESS;
+ peer_identity_address_type = AddressType::PUBLIC_DEVICE_ADDRESS;
break;
case bluetooth::hci::PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS:
- peer_address_type = AddressType::RANDOM_DEVICE_ADDRESS;
+ peer_identity_address_type = AddressType::RANDOM_DEVICE_ADDRESS;
break;
}
- link_layer_controller_.LeResolvingListRemoveDevice(
- command_view.GetPeerIdentityAddress(), peer_address_type);
+ ErrorCode status = link_layer_controller_.LeRemoveDeviceFromResolvingList(
+ peer_identity_address_type, command_view.GetPeerIdentityAddress());
send_event_(
bluetooth::hci::LeRemoveDeviceFromResolvingListCompleteBuilder::Create(
- kNumCommandPackets, ErrorCode::SUCCESS));
+ kNumCommandPackets, status));
}
void DualModeController::LeSetExtendedScanParameters(CommandView command) {
@@ -2492,7 +2491,7 @@ void DualModeController::LeSetExtendedScanParameters(CommandView command) {
link_layer_controller_.SetLeScanWindow(parameters[0].le_scan_window_);
link_layer_controller_.SetLeAddressType(command_view.GetOwnAddressType());
link_layer_controller_.SetLeScanFilterPolicy(
- static_cast<uint8_t>(command_view.GetScanningFilterPolicy()));
+ command_view.GetScanningFilterPolicy());
} else {
status = ErrorCode::COMMAND_DISALLOWED;
}
@@ -2524,11 +2523,11 @@ void DualModeController::LeExtendedCreateConnection(CommandView command) {
ASSERT(command_view.IsValid());
ASSERT_LOG(command_view.GetInitiatingPhys() == 1, "Only LE_1M is supported");
auto params = command_view.GetPhyScanParameters();
+ auto initiator_filter_policy = command_view.GetInitiatorFilterPolicy();
+
link_layer_controller_.SetLeScanInterval(params[0].scan_interval_);
link_layer_controller_.SetLeScanWindow(params[0].scan_window_);
- auto initiator_filter_policy = command_view.GetInitiatorFilterPolicy();
- link_layer_controller_.SetLeInitiatorFilterPolicy(
- static_cast<uint8_t>(initiator_filter_policy));
+ link_layer_controller_.SetLeInitiatorFilterPolicy(initiator_filter_policy);
if (initiator_filter_policy ==
gd_hci::InitiatorFilterPolicy::USE_PEER_ADDRESS) {
@@ -2571,7 +2570,7 @@ void DualModeController::LeSetPrivacyMode(CommandView command) {
break;
}
if (link_layer_controller_.LeResolvingListContainsDevice(
- peer_identity_address, peer_identity_address_type)) {
+ peer_identity_address_type, peer_identity_address)) {
link_layer_controller_.LeSetPrivacyMode(
peer_identity_address_type, peer_identity_address, privacy_mode);
}
diff --git a/tools/rootcanal/model/controller/le_advertiser.cc b/tools/rootcanal/model/controller/le_advertiser.cc
index 412f4ca562..3dac3771a1 100644
--- a/tools/rootcanal/model/controller/le_advertiser.cc
+++ b/tools/rootcanal/model/controller/le_advertiser.cc
@@ -23,7 +23,7 @@ namespace rootcanal {
void LeAdvertiser::Initialize(OwnAddressType address_type,
AddressWithType public_address,
AddressWithType peer_address,
- LeScanningFilterPolicy filter_policy,
+ AdvertisingFilterPolicy filter_policy,
AdvertisingType type,
const std::vector<uint8_t>& advertisement,
const std::vector<uint8_t>& scan_response,
@@ -44,7 +44,7 @@ void LeAdvertiser::Initialize(OwnAddressType address_type,
void LeAdvertiser::InitializeExtended(
unsigned advertising_handle, OwnAddressType address_type,
AddressWithType public_address, AddressWithType peer_address,
- LeScanningFilterPolicy filter_policy, AdvertisingType type,
+ AdvertisingFilterPolicy filter_policy, AdvertisingType type,
std::chrono::steady_clock::duration interval, uint8_t tx_power,
const std::function<bluetooth::hci::Address()>& get_address) {
get_address_ = get_address;
@@ -64,7 +64,7 @@ void LeAdvertiser::InitializeExtended(
void LeAdvertiser::Clear() {
address_ = AddressWithType{};
peer_address_ = AddressWithType{};
- filter_policy_ = LeScanningFilterPolicy::ACCEPT_ALL;
+ filter_policy_ = AdvertisingFilterPolicy::ALL_DEVICES;
type_ = AdvertisingType::ADV_IND;
advertisement_.clear();
scan_response_.clear();
@@ -256,24 +256,25 @@ LeAdvertiser::GetAdvertisement(std::chrono::steady_clock::time_point now) {
std::unique_ptr<model::packets::LinkLayerPacketBuilder>
LeAdvertiser::GetScanResponse(bluetooth::hci::Address scanned,
- bluetooth::hci::Address scanner) {
+ bluetooth::hci::Address scanner,
+ bool scanner_in_filter_accept_list) {
+ (void)scanner;
if (scanned != address_.GetAddress() || !enabled_) {
return nullptr;
}
+
switch (filter_policy_) {
- case bluetooth::hci::LeScanningFilterPolicy::
- FILTER_ACCEPT_LIST_AND_INITIATORS_IDENTITY:
- case bluetooth::hci::LeScanningFilterPolicy::FILTER_ACCEPT_LIST_ONLY:
- LOG_WARN("ScanResponses don't handle connect list filters");
- return nullptr;
- case bluetooth::hci::LeScanningFilterPolicy::CHECK_INITIATORS_IDENTITY:
- if (scanner != peer_address_.GetAddress()) {
+ case bluetooth::hci::AdvertisingFilterPolicy::ALL_DEVICES:
+ case bluetooth::hci::AdvertisingFilterPolicy::LISTED_CONNECT:
+ break;
+ case bluetooth::hci::AdvertisingFilterPolicy::LISTED_SCAN:
+ case bluetooth::hci::AdvertisingFilterPolicy::LISTED_SCAN_AND_CONNECT:
+ if (!scanner_in_filter_accept_list) {
return nullptr;
}
break;
- case bluetooth::hci::LeScanningFilterPolicy::ACCEPT_ALL:
- break;
}
+
if (tx_power_ == kTxPowerUnavailable) {
return model::packets::LeScanResponseBuilder::Create(
address_.GetAddress(), peer_address_.GetAddress(),
diff --git a/tools/rootcanal/model/controller/le_advertiser.h b/tools/rootcanal/model/controller/le_advertiser.h
index bfebe1cd02..a90d2c5955 100644
--- a/tools/rootcanal/model/controller/le_advertiser.h
+++ b/tools/rootcanal/model/controller/le_advertiser.h
@@ -35,7 +35,7 @@ class LeAdvertiser {
void Initialize(bluetooth::hci::OwnAddressType address_type,
bluetooth::hci::AddressWithType public_address,
bluetooth::hci::AddressWithType peer_address,
- bluetooth::hci::LeScanningFilterPolicy filter_policy,
+ bluetooth::hci::AdvertisingFilterPolicy filter_policy,
bluetooth::hci::AdvertisingType type,
const std::vector<uint8_t>& advertisement,
const std::vector<uint8_t>& scan_response,
@@ -45,7 +45,7 @@ class LeAdvertiser {
unsigned advertising_handle, bluetooth::hci::OwnAddressType address_type,
bluetooth::hci::AddressWithType public_address,
bluetooth::hci::AddressWithType peer_address,
- bluetooth::hci::LeScanningFilterPolicy filter_policy,
+ bluetooth::hci::AdvertisingFilterPolicy filter_policy,
bluetooth::hci::AdvertisingType type,
std::chrono::steady_clock::duration interval, uint8_t tx_power,
const std::function<bluetooth::hci::Address()>& get_address);
@@ -67,13 +67,18 @@ class LeAdvertiser {
std::unique_ptr<model::packets::LinkLayerPacketBuilder> GetScanResponse(
bluetooth::hci::Address scanned_address,
- bluetooth::hci::Address scanner_address);
+ bluetooth::hci::Address scanner_address,
+ bool scanner_in_filter_accept_list);
void Clear();
void Disable();
void Enable();
void EnableExtended(std::chrono::milliseconds duration);
+ bluetooth::hci::AdvertisingFilterPolicy GetAdvertisingFilterPolicy() const {
+ return filter_policy_;
+ }
+
bool IsEnabled() const;
bool IsExtended() const;
bool IsConnectable() const;
@@ -91,7 +96,7 @@ class LeAdvertiser {
bluetooth::hci::OwnAddressType own_address_type_;
bluetooth::hci::AddressWithType
peer_address_{}; // For directed advertisements
- bluetooth::hci::LeScanningFilterPolicy filter_policy_{};
+ bluetooth::hci::AdvertisingFilterPolicy filter_policy_{};
bluetooth::hci::AdvertisingType type_{};
std::vector<uint8_t> advertisement_;
std::vector<uint8_t> scan_response_;
diff --git a/tools/rootcanal/model/controller/link_layer_controller.cc b/tools/rootcanal/model/controller/link_layer_controller.cc
index 00a7fcaec7..698b40f300 100644
--- a/tools/rootcanal/model/controller/link_layer_controller.cc
+++ b/tools/rootcanal/model/controller/link_layer_controller.cc
@@ -67,6 +67,124 @@ bool LinkLayerController::IsLeEventUnmasked(SubeventCode subevent) const {
(le_event_mask_ & bit) != 0;
}
+bool LinkLayerController::FilterAcceptListBusy() {
+ // Filter Accept List cannot be modified when
+ // • any advertising filter policy uses the Filter Accept List and
+ // advertising is enabled,
+ for (auto const& advertiser : advertisers_) {
+ if (advertiser.IsEnabled() &&
+ advertiser.GetAdvertisingFilterPolicy() !=
+ bluetooth::hci::AdvertisingFilterPolicy::ALL_DEVICES) {
+ return true;
+ }
+ }
+
+ // • the scanning filter policy uses the Filter Accept List and scanning
+ // is enabled,
+ if (le_scan_enable_ != bluetooth::hci::OpCode::NONE &&
+ (le_scan_filter_policy_ ==
+ bluetooth::hci::LeScanningFilterPolicy::FILTER_ACCEPT_LIST_ONLY ||
+ le_scan_filter_policy_ ==
+ bluetooth::hci::LeScanningFilterPolicy::
+ FILTER_ACCEPT_LIST_AND_INITIATORS_IDENTITY)) {
+ return true;
+ }
+
+ // • the initiator filter policy uses the Filter Accept List and an
+ // HCI_LE_Create_Connection or HCI_LE_Extended_Create_Connection
+ // command is pending.
+ if (le_connect_ &&
+ le_initiator_filter_policy_ ==
+ bluetooth::hci::InitiatorFilterPolicy::USE_FILTER_ACCEPT_LIST) {
+ return true;
+ }
+
+ return false;
+}
+
+bool LinkLayerController::LeFilterAcceptListContainsDevice(
+ FilterAcceptListAddressType address_type, Address address) {
+ for (auto const& entry : le_filter_accept_list_) {
+ if (entry.address_type == address_type &&
+ (address_type == FilterAcceptListAddressType::ANONYMOUS_ADVERTISERS ||
+ entry.address == address)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool LinkLayerController::LeFilterAcceptListContainsDevice(
+ AddressType address_type, Address address) {
+ FilterAcceptListAddressType filter_accept_list_address_type;
+ switch (address_type) {
+ case AddressType::PUBLIC_DEVICE_ADDRESS:
+ case AddressType::PUBLIC_IDENTITY_ADDRESS:
+ filter_accept_list_address_type = FilterAcceptListAddressType::PUBLIC;
+ break;
+ case AddressType::RANDOM_DEVICE_ADDRESS:
+ case AddressType::RANDOM_IDENTITY_ADDRESS:
+ filter_accept_list_address_type = FilterAcceptListAddressType::RANDOM;
+ break;
+ }
+
+ return LeFilterAcceptListContainsDevice(filter_accept_list_address_type,
+ address);
+}
+
+bool LinkLayerController::ResolvingListBusy() {
+ // The resolving list cannot be modified when
+ // • Advertising (other than periodic advertising) is enabled,
+ for (auto const& advertiser : advertisers_) {
+ if (advertiser.IsEnabled()) {
+ return true;
+ }
+ }
+
+ // • Scanning is enabled,
+ if (le_scan_enable_ != bluetooth::hci::OpCode::NONE) {
+ return true;
+ }
+
+ // • an HCI_LE_Create_Connection, HCI_LE_Extended_Create_Connection, or
+ // HCI_LE_Periodic_Advertising_Create_Sync command is pending.
+ if (le_connect_) {
+ return true;
+ }
+
+ return false;
+}
+
+bool LinkLayerController::LeResolvingListContainsDevice(
+ AddressType peer_identity_address_type, Address peer_identity_address) {
+ for (auto const& entry : le_resolving_list_) {
+ if (entry.peer_identity_address_type == peer_identity_address_type &&
+ entry.peer_identity_address == peer_identity_address) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool LinkLayerController::LeResolvingListContainsDevice(
+ bluetooth::hci::PeerAddressType peer_identity_address_type,
+ Address peer_identity_address) {
+ AddressType peer_address_type;
+ switch (peer_identity_address_type) {
+ case bluetooth::hci::PeerAddressType::PUBLIC_DEVICE_OR_IDENTITY_ADDRESS:
+ peer_address_type = AddressType::PUBLIC_DEVICE_ADDRESS;
+ break;
+ case bluetooth::hci::PeerAddressType::RANDOM_DEVICE_OR_IDENTITY_ADDRESS:
+ peer_address_type = AddressType::RANDOM_DEVICE_ADDRESS;
+ break;
+ }
+
+ return LeResolvingListContainsDevice(peer_address_type,
+ peer_identity_address);
+}
+
// =============================================================================
// General LE Coommands
// =============================================================================
@@ -130,6 +248,213 @@ ErrorCode LinkLayerController::LeSetHostFeature(uint8_t bit_number,
return ErrorCode::SUCCESS;
}
+// =============================================================================
+// LE Resolving List
+// =============================================================================
+
+// HCI command LE_Add_Device_To_Resolving_List (Vol 4, Part E § 7.8.38).
+ErrorCode LinkLayerController::LeAddDeviceToResolvingList(
+ AddressType peer_identity_address_type, Address peer_identity_address,
+ std::array<uint8_t, kIrkSize> peer_irk,
+ std::array<uint8_t, kIrkSize> local_irk) {
+ // This command shall not be used when address resolution is enabled in the
+ // Controller and:
+ // • Advertising (other than periodic advertising) is enabled,
+ // • Scanning is enabled, or
+ // • an HCI_LE_Create_Connection, HCI_LE_Extended_Create_Connection, or
+ // HCI_LE_Periodic_Advertising_Create_Sync command is pending.
+ if (le_resolving_list_enabled_ && ResolvingListBusy()) {
+ LOG_INFO(
+ "device is currently advertising, scanning, or establishing an"
+ " LE connection");
+ return ErrorCode::COMMAND_DISALLOWED;
+ }
+
+ // When a Controller cannot add a device to the list because there is no space
+ // available, it shall return the error code Memory Capacity Exceeded (0x07).
+ if (le_resolving_list_.size() >= properties_.le_resolving_list_size) {
+ LOG_INFO("resolving list is full");
+ return ErrorCode::MEMORY_CAPACITY_EXCEEDED;
+ }
+
+ // If there is an existing entry in the resolving list with the same
+ // Peer_Identity_Address and Peer_Identity_Address_Type, or with the same
+ // Peer_IRK, the Controller should return the error code Invalid HCI Command
+ // Parameters (0x12).
+ for (auto const& entry : le_resolving_list_) {
+ if ((entry.peer_identity_address_type == peer_identity_address_type &&
+ entry.peer_identity_address == peer_identity_address) ||
+ entry.peer_irk == peer_irk) {
+ LOG_INFO("device is already present in the resolving list");
+ return ErrorCode::INVALID_HCI_COMMAND_PARAMETERS;
+ }
+ }
+
+ le_resolving_list_.emplace_back(ResolvingListEntry{
+ peer_identity_address_type, peer_identity_address, peer_irk, local_irk});
+ return ErrorCode::SUCCESS;
+}
+
+// HCI command LE_Remove_Device_From_Resolving_List (Vol 4, Part E § 7.8.39).
+ErrorCode LinkLayerController::LeRemoveDeviceFromResolvingList(
+ AddressType peer_identity_address_type, Address peer_identity_address) {
+ // This command shall not be used when address resolution is enabled in the
+ // Controller and:
+ // • Advertising (other than periodic advertising) is enabled,
+ // • Scanning is enabled, or
+ // • an HCI_LE_Create_Connection, HCI_LE_Extended_Create_Connection, or
+ // HCI_LE_Periodic_Advertising_Create_Sync command is pending.
+ if (le_resolving_list_enabled_ && ResolvingListBusy()) {
+ LOG_INFO(
+ "device is currently advertising, scanning, or establishing an"
+ " LE connection");
+ return ErrorCode::COMMAND_DISALLOWED;
+ }
+
+ for (auto it = le_resolving_list_.begin(); it != le_resolving_list_.end();
+ it++) {
+ if (it->peer_identity_address_type == peer_identity_address_type &&
+ it->peer_identity_address == peer_identity_address) {
+ le_resolving_list_.erase(it);
+ return ErrorCode::SUCCESS;
+ }
+ }
+
+ // When a Controller cannot remove a device from the resolving list because
+ // it is not found, it shall return the error code
+ // Unknown Connection Identifier (0x02).
+ LOG_INFO("peer address not found in the resolving list");
+ return ErrorCode::UNKNOWN_CONNECTION;
+}
+
+// HCI command LE_Clear_Resolving_List (Vol 4, Part E § 7.8.40).
+ErrorCode LinkLayerController::LeClearResolvingList() {
+ // This command shall not be used when address resolution is enabled in the
+ // Controller and:
+ // • Advertising (other than periodic advertising) is enabled,
+ // • Scanning is enabled, or
+ // • an HCI_LE_Create_Connection, HCI_LE_Extended_Create_Connection, or
+ // HCI_LE_Periodic_Advertising_Create_Sync command is pending.
+ if (le_resolving_list_enabled_ && ResolvingListBusy()) {
+ LOG_INFO(
+ "device is currently advertising, scanning,"
+ " or establishing an LE connection");
+ return ErrorCode::COMMAND_DISALLOWED;
+ }
+
+ le_resolving_list_.clear();
+ return ErrorCode::SUCCESS;
+}
+
+// HCI command LE_Set_Address_Resolution_Enable (Vol 4, Part E § 7.8.44).
+ErrorCode LinkLayerController::LeSetAddressResolutionEnable(bool enable) {
+ // This command shall not be used when:
+ // • Advertising (other than periodic advertising) is enabled,
+ // • Scanning is enabled, or
+ // • an HCI_LE_Create_Connection, HCI_LE_Extended_Create_Connection, or
+ // HCI_LE_Periodic_Advertising_Create_Sync command is pending.
+ if (ResolvingListBusy()) {
+ LOG_INFO(
+ "device is currently advertising, scanning,"
+ " or establishing an LE connection");
+ return ErrorCode::COMMAND_DISALLOWED;
+ }
+
+ le_resolving_list_enabled_ = enable;
+ return ErrorCode::SUCCESS;
+}
+
+// =============================================================================
+// LE Filter Accept List
+// =============================================================================
+
+// HCI command LE_Clear_Filter_Accept_List (Vol 4, Part E § 7.8.15).
+ErrorCode LinkLayerController::LeClearFilterAcceptList() {
+ // This command shall not be used when:
+ // • any advertising filter policy uses the Filter Accept List and
+ // advertising is enabled,
+ // • the scanning filter policy uses the Filter Accept List and scanning
+ // is enabled, or
+ // • the initiator filter policy uses the Filter Accept List and an
+ // HCI_LE_Create_Connection or HCI_LE_Extended_Create_Connection
+ // command is pending.
+ if (FilterAcceptListBusy()) {
+ LOG_INFO(
+ "device is currently advertising, scanning,"
+ " or establishing an LE connection using the filter accept list");
+ return ErrorCode::COMMAND_DISALLOWED;
+ }
+
+ le_filter_accept_list_.clear();
+ return ErrorCode::SUCCESS;
+}
+
+// HCI command LE_Add_Device_To_Filter_Accept_List (Vol 4, Part E § 7.8.16).
+ErrorCode LinkLayerController::LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType address_type, Address address) {
+ // This command shall not be used when:
+ // • any advertising filter policy uses the Filter Accept List and
+ // advertising is enabled,
+ // • the scanning filter policy uses the Filter Accept List and scanning
+ // is enabled, or
+ // • the initiator filter policy uses the Filter Accept List and an
+ // HCI_LE_Create_Connection or HCI_LE_Extended_Create_Connection
+ // command is pending.
+ if (FilterAcceptListBusy()) {
+ LOG_INFO(
+ "device is currently advertising, scanning,"
+ " or establishing an LE connection using the filter accept list");
+ return ErrorCode::COMMAND_DISALLOWED;
+ }
+
+ // When a Controller cannot add a device to the Filter Accept List
+ // because there is no space available, it shall return the error code
+ // Memory Capacity Exceeded (0x07).
+ if (le_filter_accept_list_.size() >= properties_.le_filter_accept_list_size) {
+ LOG_INFO("filter accept list is full");
+ return ErrorCode::MEMORY_CAPACITY_EXCEEDED;
+ }
+
+ le_filter_accept_list_.emplace_back(
+ FilterAcceptListEntry{address_type, address});
+ return ErrorCode::SUCCESS;
+}
+
+// HCI command LE_Remove_Device_From_Filter_Accept_List (Vol 4, Part E
+// § 7.8.17).
+ErrorCode LinkLayerController::LeRemoveDeviceFromFilterAcceptList(
+ FilterAcceptListAddressType address_type, Address address) {
+ // This command shall not be used when:
+ // • any advertising filter policy uses the Filter Accept List and
+ // advertising is enabled,
+ // • the scanning filter policy uses the Filter Accept List and scanning
+ // is enabled, or
+ // • the initiator filter policy uses the Filter Accept List and an
+ // HCI_LE_Create_Connection or HCI_LE_Extended_Create_Connection
+ // command is pending.
+ if (FilterAcceptListBusy()) {
+ LOG_INFO(
+ "device is currently advertising, scanning,"
+ " or establishing an LE connection using the filter accept list");
+ return ErrorCode::COMMAND_DISALLOWED;
+ }
+
+ for (auto it = le_filter_accept_list_.begin();
+ it != le_filter_accept_list_.end(); it++) {
+ // Address shall be ignored when Address_Type is set to 0xFF.
+ if (it->address_type == address_type &&
+ (address_type == FilterAcceptListAddressType::ANONYMOUS_ADVERTISERS ||
+ it->address == address)) {
+ le_filter_accept_list_.erase(it);
+ return ErrorCode::SUCCESS;
+ }
+ }
+
+ // Note: this case is not documented.
+ LOG_INFO("address not found in the filter accept list");
+ return ErrorCode::SUCCESS;
+}
+
void LinkLayerController::SetSecureSimplePairingSupport(bool enable) {
uint64_t bit = 0x1;
secure_simple_pairing_host_support_ = enable;
@@ -171,7 +496,7 @@ void LinkLayerController::SetLeAdvertisingParameters(
uint16_t interval_min, uint16_t interval_max,
bluetooth::hci::AdvertisingType advertising_type, uint8_t own_address_type,
uint8_t peer_address_type, Address peer_address, uint8_t channel_map,
- uint8_t filter_policy) {
+ bluetooth::hci::AdvertisingFilterPolicy filter_policy) {
le_advertising_type_ = advertising_type;
le_advertising_interval_min_ = interval_min;
le_advertising_interval_max_ = interval_max;
@@ -967,6 +1292,14 @@ void LinkLayerController::IncomingInquiryPacket(
ASSERT(inquiry.IsValid());
Address peer = incoming.GetSourceAddress();
+ uint8_t lap = inquiry.GetLap();
+
+ // Filter out inquiry packets with IAC not present in the
+ // list Current_IAC_LAP.
+ if (std::none_of(current_iac_lap_list_.cbegin(), current_iac_lap_list_.cend(),
+ [lap](auto iac_lap) { return iac_lap.lap_ == lap; })) {
+ return;
+ }
switch (inquiry.GetInquiryType()) {
case (model::packets::InquiryType::STANDARD): {
@@ -1596,10 +1929,10 @@ void LinkLayerController::IncomingLeLegacyAdvertisingPdu(
for (const auto& entry : le_resolving_list_) {
if (rpa_matches_irk(advertising_address, entry.peer_irk)) {
LOG_INFO("Matched against IRK for %s",
- entry.address.ToString().c_str());
+ entry.peer_identity_address.ToString().c_str());
resolved = true;
- resolved_address = entry.address;
- resolved_address_type = entry.address_type;
+ resolved_address = entry.peer_identity_address;
+ resolved_address_type = entry.peer_identity_address_type;
rpa = generate_rpa(entry.local_irk);
}
}
@@ -1609,11 +1942,11 @@ void LinkLayerController::IncomingLeLegacyAdvertisingPdu(
if ((le_peer_address_ == advertising_address &&
le_peer_address_type_ ==
static_cast<uint8_t>(advertising_address_type)) ||
- (LeFilterAcceptListContainsDevice(
- advertising_address,
- static_cast<AddressType>(advertising_address_type))) ||
- (resolved && LeFilterAcceptListContainsDevice(resolved_address,
- resolved_address_type))) {
+ LeFilterAcceptListContainsDevice(
+ static_cast<AddressType>(advertising_address_type),
+ advertising_address) ||
+ (resolved && LeFilterAcceptListContainsDevice(resolved_address_type,
+ resolved_address))) {
Address own_address;
auto own_address_type =
static_cast<bluetooth::hci::OwnAddressType>(le_address_type_);
@@ -1801,15 +2134,15 @@ void LinkLayerController::IncomingLmpPacket(
#endif /* ROOTCANAL_LMP */
uint16_t LinkLayerController::HandleLeConnection(
- AddressWithType address, AddressWithType own_address, uint8_t role,
- uint16_t connection_interval, uint16_t connection_latency,
- uint16_t supervision_timeout,
+ AddressWithType address, AddressWithType own_address,
+ bluetooth::hci::Role role, uint16_t connection_interval,
+ uint16_t connection_latency, uint16_t supervision_timeout,
bool send_le_channel_selection_algorithm_event) {
// Note: the HCI_LE_Connection_Complete event is not sent if the
// HCI_LE_Enhanced_Connection_Complete event (see Section 7.7.65.10) is
// unmasked.
- uint16_t handle = connections_.CreateLeConnection(address, own_address);
+ uint16_t handle = connections_.CreateLeConnection(address, own_address, role);
if (handle == kReservedHandle) {
LOG_WARN("No pending connection for connection from %s",
address.ToString().c_str());
@@ -1844,17 +2177,15 @@ uint16_t LinkLayerController::HandleLeConnection(
}
send_event_(bluetooth::hci::LeEnhancedConnectionCompleteBuilder::Create(
- ErrorCode::SUCCESS, handle, static_cast<bluetooth::hci::Role>(role),
- peer_address_type, connection_address, local_resolved_address,
- peer_resolvable_private_address, connection_interval,
- connection_latency, supervision_timeout,
+ ErrorCode::SUCCESS, handle, role, peer_address_type, connection_address,
+ local_resolved_address, peer_resolvable_private_address,
+ connection_interval, connection_latency, supervision_timeout,
static_cast<bluetooth::hci::ClockAccuracy>(0x00)));
} else if (IsLeEventUnmasked(SubeventCode::CONNECTION_COMPLETE)) {
send_event_(bluetooth::hci::LeConnectionCompleteBuilder::Create(
- ErrorCode::SUCCESS, handle, static_cast<bluetooth::hci::Role>(role),
- address.GetAddressType(), address.GetAddress(), connection_interval,
- connection_latency, supervision_timeout,
- static_cast<bluetooth::hci::ClockAccuracy>(0x00)));
+ ErrorCode::SUCCESS, handle, role, address.GetAddressType(),
+ address.GetAddress(), connection_interval, connection_latency,
+ supervision_timeout, static_cast<bluetooth::hci::ClockAccuracy>(0x00)));
}
// Note: the HCI_LE_Connection_Complete event is immediately followed by
@@ -1925,8 +2256,8 @@ void LinkLayerController::IncomingLeConnectPacket(
AddressWithType(
incoming.GetSourceAddress(),
static_cast<bluetooth::hci::AddressType>(connect.GetAddressType())),
- my_address, static_cast<uint8_t>(bluetooth::hci::Role::PERIPHERAL),
- connection_interval, connect.GetLeConnectionLatency(),
+ my_address, bluetooth::hci::Role::PERIPHERAL, connection_interval,
+ connect.GetLeConnectionLatency(),
connect.GetLeConnectionSupervisionTimeout(), false);
SendLeLinkLayerPacket(model::packets::LeConnectCompleteBuilder::Create(
@@ -1957,8 +2288,8 @@ void LinkLayerController::IncomingLeConnectCompletePacket(
AddressWithType(
incoming.GetDestinationAddress(),
static_cast<bluetooth::hci::AddressType>(le_address_type_)),
- static_cast<uint8_t>(bluetooth::hci::Role::CENTRAL),
- complete.GetLeConnectionInterval(), complete.GetLeConnectionLatency(),
+ bluetooth::hci::Role::CENTRAL, complete.GetLeConnectionInterval(),
+ complete.GetLeConnectionLatency(),
complete.GetLeConnectionSupervisionTimeout(), le_extended_connect_);
le_connect_ = false;
le_extended_connect_ = false;
@@ -1978,11 +2309,21 @@ void LinkLayerController::IncomingLeConnectionParameterRequest(
peer.ToString().c_str());
return;
}
- if (IsLeEventUnmasked(SubeventCode::CONNECTION_UPDATE_COMPLETE)) {
+
+ if (IsLeEventUnmasked(SubeventCode::REMOTE_CONNECTION_PARAMETER_REQUEST)) {
send_event_(
bluetooth::hci::LeRemoteConnectionParameterRequestBuilder::Create(
handle, request.GetIntervalMin(), request.GetIntervalMax(),
request.GetLatency(), request.GetTimeout()));
+ } else {
+ // If the request is being indicated to the Host and the event to the Host
+ // is masked, then the Link Layer shall issue an LL_REJECT_EXT_IND PDU with
+ // the ErrorCode set to Unsupported Remote Feature (0x1A).
+ SendLeLinkLayerPacket(
+ model::packets::LeConnectionParameterUpdateBuilder::Create(
+ request.GetDestinationAddress(), request.GetSourceAddress(),
+ static_cast<uint8_t>(ErrorCode::UNSUPPORTED_REMOTE_OR_LMP_FEATURE),
+ 0, 0, 0));
}
}
@@ -2106,8 +2447,11 @@ void LinkLayerController::IncomingLeReadRemoteFeaturesResponse(
void LinkLayerController::IncomingLeScanPacket(
model::packets::LinkLayerPacketView incoming) {
for (auto& advertiser : advertisers_) {
- auto to_send = advertiser.GetScanResponse(incoming.GetDestinationAddress(),
- incoming.GetSourceAddress());
+ // TODO address type should be provided by the LL SCAN_REQ PDU.
+ auto to_send = advertiser.GetScanResponse(
+ incoming.GetDestinationAddress(), incoming.GetSourceAddress(),
+ LeFilterAcceptListContainsDevice(AddressType::PUBLIC_DEVICE_ADDRESS,
+ incoming.GetSourceAddress()));
if (to_send != nullptr) {
SendLeLinkLayerPacket(std::move(to_send));
}
@@ -2345,6 +2689,7 @@ void LinkLayerController::IncomingPageResponsePacket(
LOG_WARN("No free handles");
return;
}
+ CancelScheduledTask(page_timeout_task_id_);
#ifdef ROOTCANAL_LMP
ASSERT(link_manager_add_link(
lm_.get(), reinterpret_cast<const uint8_t(*)[6]>(peer.data())));
@@ -2915,6 +3260,22 @@ ErrorCode LinkLayerController::SetConnectionEncryption(
}
#endif /* ROOTCANAL_LMP */
+std::vector<bluetooth::hci::Lap> const& LinkLayerController::ReadCurrentIacLap()
+ const {
+ return current_iac_lap_list_;
+}
+
+void LinkLayerController::WriteCurrentIacLap(
+ std::vector<bluetooth::hci::Lap> iac_lap) {
+ current_iac_lap_list_.swap(iac_lap);
+
+ // If Num_Current_IAC is greater than Num_Supported_IAC then only the first
+ // Num_Supported_IAC shall be stored in the Controller
+ if (current_iac_lap_list_.size() > properties_.num_supported_iac) {
+ current_iac_lap_list_.resize(properties_.num_supported_iac);
+ }
+}
+
ErrorCode LinkLayerController::AcceptConnectionRequest(const Address& bd_addr,
bool try_role_switch) {
if (connections_.HasPendingConnection(bd_addr)) {
@@ -3030,6 +3391,14 @@ ErrorCode LinkLayerController::CreateConnection(const Address& addr, uint16_t,
return ErrorCode::CONTROLLER_BUSY;
}
+ page_timeout_task_id_ = ScheduleTask(
+ duration_cast<milliseconds>(page_timeout_ * microseconds(625)),
+ [this, addr] {
+ send_event_(bluetooth::hci::ConnectionCompleteBuilder::Create(
+ ErrorCode::PAGE_TIMEOUT, 0xeff, addr, bluetooth::hci::LinkType::ACL,
+ bluetooth::hci::Enable::DISABLED));
+ });
+
SendLinkLayerPacket(model::packets::PageBuilder::Create(
GetAddress(), addr, class_of_device_, allow_role_switch));
@@ -3040,6 +3409,7 @@ ErrorCode LinkLayerController::CreateConnectionCancel(const Address& addr) {
if (!connections_.CancelPendingConnection(addr)) {
return ErrorCode::UNKNOWN_CONNECTION;
}
+ CancelScheduledTask(page_timeout_task_id_);
return ErrorCode::SUCCESS;
}
@@ -3386,31 +3756,11 @@ ErrorCode LinkLayerController::SetLeExtendedAdvertisingParameters(
bluetooth::hci::PeerAddressTypeText(peer_address_type).c_str());
LOG_INFO("peer_address %s", peer_address.ToString().c_str());
- bluetooth::hci::LeScanningFilterPolicy scanning_filter_policy;
- switch (filter_policy) {
- case bluetooth::hci::AdvertisingFilterPolicy::ALL_DEVICES:
- scanning_filter_policy =
- bluetooth::hci::LeScanningFilterPolicy::ACCEPT_ALL;
- break;
- case bluetooth::hci::AdvertisingFilterPolicy::LISTED_SCAN:
- scanning_filter_policy =
- bluetooth::hci::LeScanningFilterPolicy::FILTER_ACCEPT_LIST_ONLY;
- break;
- case bluetooth::hci::AdvertisingFilterPolicy::LISTED_CONNECT:
- scanning_filter_policy =
- bluetooth::hci::LeScanningFilterPolicy::CHECK_INITIATORS_IDENTITY;
- break;
- case bluetooth::hci::AdvertisingFilterPolicy::LISTED_SCAN_AND_CONNECT:
- scanning_filter_policy = bluetooth::hci::LeScanningFilterPolicy::
- FILTER_ACCEPT_LIST_AND_INITIATORS_IDENTITY;
- break;
- }
-
advertisers_[set].InitializeExtended(
set, own_address_type,
bluetooth::hci::AddressWithType(
GetAddress(), bluetooth::hci::AddressType::PUBLIC_DEVICE_ADDRESS),
- directed_address, scanning_filter_policy, advertising_type,
+ directed_address, filter_policy, advertising_type,
std::chrono::milliseconds(interval_ms), tx_power,
[this, own_address_type, peer_address]() {
if (own_address_type ==
@@ -3418,8 +3768,9 @@ ErrorCode LinkLayerController::SetLeExtendedAdvertisingParameters(
own_address_type ==
bluetooth::hci::OwnAddressType::RESOLVABLE_OR_RANDOM_ADDRESS) {
for (const auto& entry : le_resolving_list_) {
- if (entry.address == peer_address.GetAddress() &&
- entry.address_type == peer_address.GetAddressType()) {
+ if (entry.peer_identity_address == peer_address.GetAddress() &&
+ entry.peer_identity_address_type ==
+ peer_address.GetAddressType()) {
return generate_rpa(entry.local_irk);
}
}
@@ -3486,10 +3837,30 @@ ErrorCode LinkLayerController::LeConnectionUpdate(
return ErrorCode::UNKNOWN_CONNECTION;
}
- SendLeLinkLayerPacket(LeConnectionParameterRequestBuilder::Create(
- connections_.GetOwnAddress(handle).GetAddress(),
- connections_.GetAddress(handle).GetAddress(), interval_min, interval_max,
- latency, supervision_timeout));
+ bluetooth::hci::Role role = connections_.GetAclRole(handle);
+
+ if (role == bluetooth::hci::Role::CENTRAL) {
+ // As Central, it is allowed to directly send
+ // LL_CONNECTION_PARAM_UPDATE_IND to update the parameters.
+ SendLeLinkLayerPacket(LeConnectionParameterUpdateBuilder::Create(
+ connections_.GetOwnAddress(handle).GetAddress(),
+ connections_.GetAddress(handle).GetAddress(),
+ static_cast<uint8_t>(ErrorCode::SUCCESS), interval_max, latency,
+ supervision_timeout));
+
+ if (IsLeEventUnmasked(SubeventCode::CONNECTION_UPDATE_COMPLETE)) {
+ send_event_(bluetooth::hci::LeConnectionUpdateCompleteBuilder::Create(
+ ErrorCode::SUCCESS, handle, interval_max, latency,
+ supervision_timeout));
+ }
+ } else {
+ // Send LL_CONNECTION_PARAM_REQ and wait for LL_CONNECTION_PARAM_RSP
+ // in return.
+ SendLeLinkLayerPacket(LeConnectionParameterRequestBuilder::Create(
+ connections_.GetOwnAddress(handle).GetAddress(),
+ connections_.GetAddress(handle).GetAddress(), interval_min,
+ interval_max, latency, supervision_timeout));
+ }
return ErrorCode::SUCCESS;
}
@@ -3531,64 +3902,6 @@ ErrorCode LinkLayerController::LeRemoteConnectionParameterRequestNegativeReply(
return ErrorCode::SUCCESS;
}
-ErrorCode LinkLayerController::LeFilterAcceptListClear() {
- if (FilterAcceptListBusy()) {
- return ErrorCode::COMMAND_DISALLOWED;
- }
-
- le_connect_list_.clear();
- return ErrorCode::SUCCESS;
-}
-
-ErrorCode LinkLayerController::LeSetAddressResolutionEnable(bool enable) {
- if (ResolvingListBusy()) {
- return ErrorCode::COMMAND_DISALLOWED;
- }
-
- le_resolving_list_enabled_ = enable;
- return ErrorCode::SUCCESS;
-}
-
-ErrorCode LinkLayerController::LeResolvingListClear() {
- if (ResolvingListBusy()) {
- return ErrorCode::COMMAND_DISALLOWED;
- }
-
- le_resolving_list_.clear();
- return ErrorCode::SUCCESS;
-}
-
-ErrorCode LinkLayerController::LeFilterAcceptListAddDevice(
- Address addr, AddressType addr_type) {
- if (FilterAcceptListBusy()) {
- return ErrorCode::COMMAND_DISALLOWED;
- }
- for (auto dev : le_connect_list_) {
- if (dev.address == addr && dev.address_type == addr_type) {
- return ErrorCode::SUCCESS;
- }
- }
- if (LeFilterAcceptListFull()) {
- return ErrorCode::MEMORY_CAPACITY_EXCEEDED;
- }
- le_connect_list_.emplace_back(ConnectListEntry{addr, addr_type});
- return ErrorCode::SUCCESS;
-}
-
-ErrorCode LinkLayerController::LeResolvingListAddDevice(
- Address addr, AddressType addr_type, std::array<uint8_t, kIrkSize> peerIrk,
- std::array<uint8_t, kIrkSize> localIrk) {
- if (ResolvingListBusy()) {
- return ErrorCode::COMMAND_DISALLOWED;
- }
- if (LeResolvingListFull()) {
- return ErrorCode::MEMORY_CAPACITY_EXCEEDED;
- }
- le_resolving_list_.emplace_back(
- ResolvingListEntry{addr, addr_type, peerIrk, localIrk});
- return ErrorCode::SUCCESS;
-}
-
bool LinkLayerController::HasAclConnection() {
return (connections_.GetAclHandles().size() > 0);
}
@@ -3864,10 +4177,8 @@ ErrorCode LinkLayerController::SetLeAdvertisingEnable(
bluetooth::hci::AddressWithType(GetLeAdvertisingPeerAddress(),
static_cast<bluetooth::hci::AddressType>(
le_advertising_peer_address_type_)),
- static_cast<bluetooth::hci::LeScanningFilterPolicy>(
- GetLeAdvertisingFilterPolicy()),
- le_advertising_type_, GetLeAdvertisingData(), GetLeScanResponseData(),
- interval);
+ GetLeAdvertisingFilterPolicy(), le_advertising_type_,
+ GetLeAdvertisingData(), GetLeScanResponseData(), interval);
advertisers_[0].Enable();
return ErrorCode::SUCCESS;
}
@@ -3901,101 +4212,9 @@ ErrorCode LinkLayerController::SetLeExtendedAdvertisingEnable(
return ErrorCode::SUCCESS;
}
-bool LinkLayerController::ListBusy(uint16_t ignore) {
- (void)ignore;
- if (le_connect_) {
- LOG_INFO("le_connect_");
- if (!(ignore & ControllerProperties::kLeListIgnoreConnections)) {
- return true;
- }
- }
- if (le_scan_enable_ != bluetooth::hci::OpCode::NONE) {
- LOG_INFO("le_scan_enable");
- if (!(ignore & ControllerProperties::kLeListIgnoreScanEnable)) {
- return true;
- }
- }
- for (auto advertiser : advertisers_) {
- if (advertiser.IsEnabled()) {
- LOG_INFO("Advertising");
- if (!(ignore & ControllerProperties::kLeListIgnoreAdvertising)) {
- return true;
- }
- }
- }
- // TODO: Add HCI_LE_Periodic_Advertising_Create_Sync
- return false;
-}
-
-bool LinkLayerController::FilterAcceptListBusy() {
- return ListBusy(properties_.le_connect_list_ignore_reasons);
-}
-
-bool LinkLayerController::ResolvingListBusy() {
- return ListBusy(properties_.le_resolving_list_ignore_reasons);
-}
-
-ErrorCode LinkLayerController::LeFilterAcceptListRemoveDevice(
- Address addr, AddressType addr_type) {
- if (FilterAcceptListBusy()) {
- return ErrorCode::COMMAND_DISALLOWED;
- }
- for (size_t i = 0; i < le_connect_list_.size(); i++) {
- if (le_connect_list_[i].address == addr &&
- le_connect_list_[i].address_type == addr_type) {
- le_connect_list_.erase(le_connect_list_.begin() + i);
- }
- }
- return ErrorCode::SUCCESS;
-}
-
-ErrorCode LinkLayerController::LeResolvingListRemoveDevice(
- Address addr, AddressType addr_type) {
- if (ResolvingListBusy()) {
- return ErrorCode::COMMAND_DISALLOWED;
- }
- for (size_t i = 0; i < le_resolving_list_.size(); i++) {
- auto curr = le_resolving_list_[i];
- if (curr.address == addr && curr.address_type == addr_type) {
- le_resolving_list_.erase(le_resolving_list_.begin() + i);
- }
- }
- return ErrorCode::SUCCESS;
-}
-
-bool LinkLayerController::LeFilterAcceptListContainsDevice(
- Address addr, AddressType addr_type) {
- for (size_t i = 0; i < le_connect_list_.size(); i++) {
- if (le_connect_list_[i].address == addr &&
- le_connect_list_[i].address_type == addr_type) {
- return true;
- }
- }
- return false;
-}
-
-bool LinkLayerController::LeResolvingListContainsDevice(Address addr,
- AddressType addr_type) {
- for (size_t i = 0; i < le_connect_list_.size(); i++) {
- auto curr = le_connect_list_[i];
- if (curr.address == addr && curr.address_type == addr_type) {
- return true;
- }
- }
- return false;
-}
-
-bool LinkLayerController::LeFilterAcceptListFull() {
- return le_connect_list_.size() >= properties_.le_filter_accept_list_size;
-}
-
-bool LinkLayerController::LeResolvingListFull() {
- return le_resolving_list_.size() >= properties_.le_resolving_list_size;
-}
-
void LinkLayerController::Reset() {
connections_ = AclConnectionHandler();
- le_connect_list_.clear();
+ le_filter_accept_list_.clear();
le_resolving_list_.clear();
le_resolving_list_enabled_ = false;
le_connecting_rpa_ = Address();
@@ -4011,6 +4230,12 @@ void LinkLayerController::Reset() {
last_inquiry_ = steady_clock::now();
page_scan_enable_ = false;
inquiry_scan_enable_ = false;
+
+ bluetooth::hci::Lap general_iac;
+ general_iac.lap_ = 0x33; // 0x9E8B33
+ current_iac_lap_list_.clear();
+ current_iac_lap_list_.emplace_back(general_iac);
+
#ifdef ROOTCANAL_LMP
lm_.reset(link_manager_create(ops_));
#endif
@@ -4055,7 +4280,7 @@ void LinkLayerController::Inquiry() {
}
SendLinkLayerPacket(model::packets::InquiryBuilder::Create(
- GetAddress(), Address::kEmpty, inquiry_mode_));
+ GetAddress(), Address::kEmpty, inquiry_mode_, inquiry_lap_));
last_inquiry_ = now;
}
@@ -4067,6 +4292,12 @@ void LinkLayerController::SetPageScanEnable(bool enable) {
page_scan_enable_ = enable;
}
+uint16_t LinkLayerController::GetPageTimeout() { return page_timeout_; }
+
+void LinkLayerController::SetPageTimeout(uint16_t page_timeout) {
+ page_timeout_ = page_timeout;
+}
+
ErrorCode LinkLayerController::AddScoConnection(uint16_t connection_handle,
uint16_t packet_type) {
if (!connections_.HasHandle(connection_handle)) {
diff --git a/tools/rootcanal/model/controller/link_layer_controller.h b/tools/rootcanal/model/controller/link_layer_controller.h
index 9a024767b9..9b1e83dffb 100644
--- a/tools/rootcanal/model/controller/link_layer_controller.h
+++ b/tools/rootcanal/model/controller/link_layer_controller.h
@@ -41,6 +41,7 @@ using ::bluetooth::hci::AddressType;
using ::bluetooth::hci::AuthenticationEnable;
using ::bluetooth::hci::ClassOfDevice;
using ::bluetooth::hci::ErrorCode;
+using ::bluetooth::hci::FilterAcceptListAddressType;
using ::bluetooth::hci::OpCode;
using ::bluetooth::hci::PageScanRepetitionMode;
@@ -48,12 +49,6 @@ class LinkLayerController {
public:
static constexpr size_t kIrkSize = 16;
- // HCI LE Set Random Address command (Vol 4, Part E § 7.8.4).
- ErrorCode LeSetRandomAddress(Address random_address);
-
- // HCI LE Set Host Feature command (Vol 4, Part E § 7.8.115).
- ErrorCode LeSetHostFeature(uint8_t bit_number, uint8_t bit_value);
-
LinkLayerController(const Address& address,
const ControllerProperties& properties);
@@ -109,6 +104,9 @@ class LinkLayerController {
ErrorCode AuthenticationRequested(uint16_t handle);
#endif /* ROOTCANAL_LMP */
+ std::vector<bluetooth::hci::Lap> const& ReadCurrentIacLap() const;
+ void WriteCurrentIacLap(std::vector<bluetooth::hci::Lap> iac_lap);
+
ErrorCode AcceptConnectionRequest(const Address& addr, bool try_role_switch);
void MakePeripheralConnection(const Address& addr, bool try_role_switch);
ErrorCode RejectConnectionRequest(const Address& addr, uint8_t reason);
@@ -205,28 +203,25 @@ class LinkLayerController {
ErrorCode LeRemoteConnectionParameterRequestNegativeReply(
uint16_t connection_handle, bluetooth::hci::ErrorCode reason);
uint16_t HandleLeConnection(AddressWithType addr, AddressWithType own_addr,
- uint8_t role, uint16_t connection_interval,
+ bluetooth::hci::Role role,
+ uint16_t connection_interval,
uint16_t connection_latency,
uint16_t supervision_timeout,
bool send_le_channel_selection_algorithm_event);
- bool ListBusy(uint16_t ignore_mask);
-
bool FilterAcceptListBusy();
- ErrorCode LeFilterAcceptListClear();
- ErrorCode LeFilterAcceptListAddDevice(Address addr, AddressType addr_type);
- ErrorCode LeFilterAcceptListRemoveDevice(Address addr, AddressType addr_type);
- bool LeFilterAcceptListContainsDevice(Address addr, AddressType addr_type);
- bool LeFilterAcceptListFull();
+ bool LeFilterAcceptListContainsDevice(
+ FilterAcceptListAddressType address_type, Address address);
+ bool LeFilterAcceptListContainsDevice(AddressType address_type,
+ Address address);
+
bool ResolvingListBusy();
- ErrorCode LeSetAddressResolutionEnable(bool enable);
- ErrorCode LeResolvingListClear();
- ErrorCode LeResolvingListAddDevice(Address addr, AddressType addr_type,
- std::array<uint8_t, kIrkSize> peerIrk,
- std::array<uint8_t, kIrkSize> localIrk);
- ErrorCode LeResolvingListRemoveDevice(Address addr, AddressType addr_type);
- bool LeResolvingListContainsDevice(Address addr, AddressType addr_type);
- bool LeResolvingListFull();
+ bool LeResolvingListContainsDevice(AddressType peer_identity_address_type,
+ Address peer_identity_address);
+ bool LeResolvingListContainsDevice(
+ bluetooth::hci::PeerAddressType peer_identity_address_type,
+ Address peer_identity_address);
+
void LeSetPrivacyMode(AddressType address_type, Address addr, uint8_t mode);
void LeReadIsoTxSync(uint16_t handle);
@@ -300,7 +295,8 @@ class LinkLayerController {
void SetLeScanWindow(uint16_t le_scan_window) {
le_scan_window_ = le_scan_window;
}
- void SetLeScanFilterPolicy(uint8_t le_scan_filter_policy) {
+ void SetLeScanFilterPolicy(
+ bluetooth::hci::LeScanningFilterPolicy le_scan_filter_policy) {
le_scan_filter_policy_ = le_scan_filter_policy;
}
void SetLeFilterDuplicates(uint8_t le_scan_filter_duplicates) {
@@ -336,7 +332,8 @@ class LinkLayerController {
void SetLeMaximumCeLength(uint16_t max) {
le_connection_maximum_ce_length_ = max;
}
- void SetLeInitiatorFilterPolicy(uint8_t le_initiator_filter_policy) {
+ void SetLeInitiatorFilterPolicy(
+ bluetooth::hci::InitiatorFilterPolicy le_initiator_filter_policy) {
le_initiator_filter_policy_ = le_initiator_filter_policy;
}
void SetLePeerAddressType(uint8_t peer_address_type) {
@@ -355,9 +352,15 @@ class LinkLayerController {
void SetInquiryMaxResponses(uint8_t max);
void Inquiry();
+ bool GetInquiryScanEnable() { return inquiry_scan_enable_; }
void SetInquiryScanEnable(bool enable);
+
+ bool GetPageScanEnable() { return page_scan_enable_; }
void SetPageScanEnable(bool enable);
+ uint16_t GetPageTimeout();
+ void SetPageTimeout(uint16_t page_timeout);
+
ErrorCode ChangeConnectionPacketType(uint16_t handle, uint16_t types);
ErrorCode ChangeConnectionLinkKey(uint16_t handle);
ErrorCode CentralLinkKey(uint8_t key_flag);
@@ -401,6 +404,46 @@ class LinkLayerController {
void HandleIso(bluetooth::hci::IsoView iso);
+ // LE Commands
+
+ // HCI LE Set Random Address command (Vol 4, Part E § 7.8.4).
+ ErrorCode LeSetRandomAddress(Address random_address);
+
+ // HCI LE Set Host Feature command (Vol 4, Part E § 7.8.115).
+ ErrorCode LeSetHostFeature(uint8_t bit_number, uint8_t bit_value);
+
+ // LE Filter Accept List
+
+ // HCI command LE_Clear_Filter_Accept_List (Vol 4, Part E § 7.8.15).
+ ErrorCode LeClearFilterAcceptList();
+
+ // HCI command LE_Add_Device_To_Filter_Accept_List (Vol 4, Part E § 7.8.16).
+ ErrorCode LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType address_type, Address address);
+
+ // HCI command LE_Remove_Device_From_Filter_Accept_List (Vol 4, Part E
+ // § 7.8.17).
+ ErrorCode LeRemoveDeviceFromFilterAcceptList(
+ FilterAcceptListAddressType address_type, Address address);
+
+ // LE Address Resolving
+
+ // HCI command LE_Add_Device_To_Resolving_List (Vol 4, Part E § 7.8.38).
+ ErrorCode LeAddDeviceToResolvingList(AddressType peer_identity_address_type,
+ Address peer_address,
+ std::array<uint8_t, kIrkSize> peer_irk,
+ std::array<uint8_t, kIrkSize> local_irk);
+
+ // HCI command LE_Remove_Device_From_Resolving_List (Vol 4, Part E § 7.8.39).
+ ErrorCode LeRemoveDeviceFromResolvingList(
+ AddressType peer_identity_address_type, Address peer_identity_address);
+
+ // HCI command LE_Clear_Resolving_List (Vol 4, Part E § 7.8.40).
+ ErrorCode LeClearResolvingList();
+
+ // HCI command LE_Set_Address_Resolution_Enable (Vol 4, Part E § 7.8.44).
+ ErrorCode LeSetAddressResolutionEnable(bool enable);
+
protected:
void SendLeLinkLayerPacketWithRssi(
Address source, Address dest, uint8_t rssi,
@@ -586,7 +629,8 @@ class LinkLayerController {
uint16_t interval_min, uint16_t interval_max,
bluetooth::hci::AdvertisingType advertising_type,
uint8_t own_address_type, uint8_t peer_address_type, Address peer_address,
- uint8_t channel_map, uint8_t filter_policy);
+ uint8_t channel_map,
+ bluetooth::hci::AdvertisingFilterPolicy filter_policy);
void SetConnectionAcceptTimeout(uint16_t timeout) {
connection_accept_timeout_ = timeout;
}
@@ -621,7 +665,7 @@ class LinkLayerController {
return le_advertising_channel_map_;
}
- uint8_t GetLeAdvertisingFilterPolicy() const {
+ bluetooth::hci::AdvertisingFilterPolicy GetLeAdvertisingFilterPolicy() const {
return le_advertising_filter_policy_;
}
@@ -698,6 +742,9 @@ class LinkLayerController {
// Other configuration parameters.
+ // Current IAC LAP (Vol 4, Part E § 7.3.44).
+ std::vector<bluetooth::hci::Lap> current_iac_lap_list_{};
+
// Min Encryption Key Size (Vol 4, Part E § 7.3.102).
uint8_t min_encryption_key_size_{16};
@@ -727,8 +774,8 @@ class LinkLayerController {
uint8_t le_advertising_peer_address_type_{0x0}; // Public Device Address
Address le_advertising_peer_address_{};
uint8_t le_advertising_channel_map_{0x7}; // All channels enabled
- uint8_t le_advertising_filter_policy_{0x0}; // Process scan and connection
- // requests from all devices
+ bluetooth::hci::AdvertisingFilterPolicy le_advertising_filter_policy_{
+ bluetooth::hci::AdvertisingFilterPolicy::ALL_DEVICES};
AclConnectionHandler connections_;
@@ -755,18 +802,20 @@ class LinkLayerController {
uint32_t oob_id_ = 1;
uint32_t key_id_ = 1;
- // LE state
- struct ConnectListEntry {
+ struct FilterAcceptListEntry {
+ FilterAcceptListAddressType address_type;
Address address;
- AddressType address_type;
};
- std::vector<ConnectListEntry> le_connect_list_;
+
+ std::vector<FilterAcceptListEntry> le_filter_accept_list_;
+
struct ResolvingListEntry {
- Address address;
- AddressType address_type;
+ AddressType peer_identity_address_type;
+ Address peer_identity_address;
std::array<uint8_t, kIrkSize> peer_irk;
std::array<uint8_t, kIrkSize> local_irk;
};
+
std::vector<ResolvingListEntry> le_resolving_list_;
bool le_resolving_list_enabled_{false};
@@ -778,7 +827,7 @@ class LinkLayerController {
uint8_t le_scan_type_{};
uint16_t le_scan_interval_{};
uint16_t le_scan_window_{};
- uint8_t le_scan_filter_policy_{};
+ bluetooth::hci::LeScanningFilterPolicy le_scan_filter_policy_{};
uint8_t le_scan_filter_duplicates_{};
bluetooth::hci::OwnAddressType le_address_type_{};
@@ -791,7 +840,7 @@ class LinkLayerController {
uint16_t le_connection_supervision_timeout_{};
uint16_t le_connection_minimum_ce_length_{};
uint16_t le_connection_maximum_ce_length_{};
- uint8_t le_initiator_filter_policy_{};
+ bluetooth::hci::InitiatorFilterPolicy le_initiator_filter_policy_{};
Address le_peer_address_{};
uint8_t le_peer_address_type_{};
@@ -803,6 +852,9 @@ class LinkLayerController {
#else
SecurityManager security_manager_{10};
#endif /* ROOTCANAL_LMP */
+
+ AsyncTaskId page_timeout_task_id_ = kInvalidTaskId;
+
std::chrono::steady_clock::time_point last_inquiry_;
model::packets::InquiryType inquiry_mode_{
model::packets::InquiryType::STANDARD};
diff --git a/tools/rootcanal/packets/link_layer_packets.pdl b/tools/rootcanal/packets/link_layer_packets.pdl
index 66101bbc7d..0f6ab02549 100644
--- a/tools/rootcanal/packets/link_layer_packets.pdl
+++ b/tools/rootcanal/packets/link_layer_packets.pdl
@@ -100,6 +100,7 @@ enum InquiryType : 8 {
packet Inquiry : LinkLayerPacket (type = INQUIRY) {
inquiry_type : InquiryType,
+ lap : 8, // The IAC is derived from the LAP
}
packet BasicInquiryResponse : LinkLayerPacket(type = INQUIRY_RESPONSE) {
diff --git a/tools/rootcanal/test/controller/le/le_add_device_to_filter_accept_list_test.cc b/tools/rootcanal/test/controller/le/le_add_device_to_filter_accept_list_test.cc
new file mode 100644
index 0000000000..93fe0cc838
--- /dev/null
+++ b/tools/rootcanal/test/controller/le/le_add_device_to_filter_accept_list_test.cc
@@ -0,0 +1,104 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "model/controller/link_layer_controller.h"
+
+namespace rootcanal {
+
+using namespace bluetooth::hci;
+
+class LeAddDeviceToFilterAcceptListTest : public ::testing::Test {
+ public:
+ LeAddDeviceToFilterAcceptListTest() {
+ // Reduce the size of the filter accept list to simplify testing.
+ properties_.le_filter_accept_list_size = 2;
+ }
+
+ ~LeAddDeviceToFilterAcceptListTest() override = default;
+
+ protected:
+ Address address_{0};
+ ControllerProperties properties_{};
+ LinkLayerController controller_{address_, properties_};
+};
+
+TEST_F(LeAddDeviceToFilterAcceptListTest, Success) {
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::RANDOM, Address{1}),
+ ErrorCode::SUCCESS);
+}
+
+TEST_F(LeAddDeviceToFilterAcceptListTest, ListFull) {
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{2}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{3}),
+ ErrorCode::MEMORY_CAPACITY_EXCEEDED);
+}
+
+TEST_F(LeAddDeviceToFilterAcceptListTest, ScanningActive) {
+ controller_.SetLeScanFilterPolicy(
+ LeScanningFilterPolicy::FILTER_ACCEPT_LIST_ONLY);
+ controller_.SetLeScanEnable(OpCode::LE_SET_SCAN_ENABLE);
+
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeAddDeviceToFilterAcceptListTest, LegacyAdvertisingActive) {
+ controller_.SetLeAdvertisingParameters(
+ 0x0800, 0x0800, AdvertisingType::ADV_IND, 0, 0, Address::kEmpty, 0x7,
+ AdvertisingFilterPolicy::LISTED_SCAN);
+ ASSERT_EQ(controller_.SetLeAdvertisingEnable(1), ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeAddDeviceToFilterAcceptListTest, ExtendedAdvertisingActive) {
+ EnabledSet enabled_set;
+ enabled_set.advertising_handle_ = 1;
+ enabled_set.duration_ = 0;
+ ASSERT_EQ(controller_.SetLeExtendedAdvertisingParameters(
+ 1, 0, 0, LegacyAdvertisingEventProperties::ADV_IND,
+ OwnAddressType::PUBLIC_DEVICE_ADDRESS,
+ PeerAddressType::PUBLIC_DEVICE_OR_IDENTITY_ADDRESS,
+ Address::kEmpty, AdvertisingFilterPolicy::LISTED_SCAN, 0x70),
+ ErrorCode::SUCCESS);
+ ASSERT_EQ(controller_.SetLeExtendedAdvertisingEnable(Enable::ENABLED,
+ {enabled_set}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+} // namespace rootcanal
diff --git a/tools/rootcanal/test/controller/le/le_add_device_to_resolving_list_test.cc b/tools/rootcanal/test/controller/le/le_add_device_to_resolving_list_test.cc
new file mode 100644
index 0000000000..345a713803
--- /dev/null
+++ b/tools/rootcanal/test/controller/le/le_add_device_to_resolving_list_test.cc
@@ -0,0 +1,128 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "model/controller/link_layer_controller.h"
+
+namespace rootcanal {
+
+using namespace bluetooth::hci;
+
+class LeAddDeviceToResolvingListTest : public ::testing::Test {
+ public:
+ LeAddDeviceToResolvingListTest() {
+ // Reduce the size of the resolving list to simplify testing.
+ properties_.le_resolving_list_size = 2;
+ }
+
+ ~LeAddDeviceToResolvingListTest() override = default;
+
+ protected:
+ Address address_{0};
+ ControllerProperties properties_{};
+ LinkLayerController controller_{address_, properties_};
+};
+
+TEST_F(LeAddDeviceToResolvingListTest, Success) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::RANDOM_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{2}, std::array<uint8_t, 16>{2}),
+ ErrorCode::SUCCESS);
+}
+
+TEST_F(LeAddDeviceToResolvingListTest, ListFull) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{2},
+ std::array<uint8_t, 16>{2}, std::array<uint8_t, 16>{2}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{3},
+ std::array<uint8_t, 16>{3}, std::array<uint8_t, 16>{3}),
+ ErrorCode::MEMORY_CAPACITY_EXCEEDED);
+}
+
+TEST_F(LeAddDeviceToResolvingListTest, ScanningActive) {
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true), ErrorCode::SUCCESS);
+ controller_.SetLeScanEnable(OpCode::LE_SET_SCAN_ENABLE);
+
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeAddDeviceToResolvingListTest, LegacyAdvertisingActive) {
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true), ErrorCode::SUCCESS);
+ ASSERT_EQ(controller_.SetLeAdvertisingEnable(1), ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeAddDeviceToResolvingListTest, ExtendedAdvertisingActive) {
+ EnabledSet enabled_set;
+ enabled_set.advertising_handle_ = 1;
+ enabled_set.duration_ = 0;
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true), ErrorCode::SUCCESS);
+ ASSERT_EQ(controller_.SetLeExtendedAdvertisingEnable(Enable::ENABLED,
+ {enabled_set}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeAddDeviceToResolvingListTest, PeerAddressDuplicate) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{2}, std::array<uint8_t, 16>{2}),
+ ErrorCode::INVALID_HCI_COMMAND_PARAMETERS);
+}
+
+TEST_F(LeAddDeviceToResolvingListTest, PeerIrkDuplicate) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::RANDOM_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::INVALID_HCI_COMMAND_PARAMETERS);
+}
+
+} // namespace rootcanal
diff --git a/tools/rootcanal/test/controller/le/le_clear_filter_accept_list_test.cc b/tools/rootcanal/test/controller/le/le_clear_filter_accept_list_test.cc
new file mode 100644
index 0000000000..cca1a78360
--- /dev/null
+++ b/tools/rootcanal/test/controller/le/le_clear_filter_accept_list_test.cc
@@ -0,0 +1,97 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "model/controller/link_layer_controller.h"
+
+namespace rootcanal {
+
+using namespace bluetooth::hci;
+
+class LeClearFilterAcceptListTest : public ::testing::Test {
+ public:
+ LeClearFilterAcceptListTest() {
+ // Reduce the size of the filter accept list to simplify testing.
+ properties_.le_filter_accept_list_size = 2;
+ }
+
+ ~LeClearFilterAcceptListTest() override = default;
+
+ protected:
+ Address address_{0};
+ ControllerProperties properties_{};
+ LinkLayerController controller_{address_, properties_};
+};
+
+TEST_F(LeClearFilterAcceptListTest, Success) {
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeClearFilterAcceptList(), ErrorCode::SUCCESS);
+}
+
+TEST_F(LeClearFilterAcceptListTest, ScanningActive) {
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::SUCCESS);
+
+ controller_.SetLeScanFilterPolicy(
+ LeScanningFilterPolicy::FILTER_ACCEPT_LIST_ONLY);
+ controller_.SetLeScanEnable(OpCode::LE_SET_SCAN_ENABLE);
+
+ ASSERT_EQ(controller_.LeClearFilterAcceptList(),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeClearFilterAcceptListTest, LegacyAdvertisingActive) {
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::SUCCESS);
+
+ controller_.SetLeAdvertisingParameters(
+ 0x0800, 0x0800, AdvertisingType::ADV_IND, 0, 0, Address::kEmpty, 0x7,
+ AdvertisingFilterPolicy::LISTED_SCAN);
+ ASSERT_EQ(controller_.SetLeAdvertisingEnable(1), ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeClearFilterAcceptList(),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeClearFilterAcceptListTest, ExtendedAdvertisingActive) {
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::SUCCESS);
+
+ EnabledSet enabled_set;
+ enabled_set.advertising_handle_ = 1;
+ enabled_set.duration_ = 0;
+ ASSERT_EQ(controller_.SetLeExtendedAdvertisingParameters(
+ 1, 0, 0, LegacyAdvertisingEventProperties::ADV_IND,
+ OwnAddressType::PUBLIC_DEVICE_ADDRESS,
+ PeerAddressType::PUBLIC_DEVICE_OR_IDENTITY_ADDRESS,
+ Address::kEmpty, AdvertisingFilterPolicy::LISTED_SCAN, 0x70),
+ ErrorCode::SUCCESS);
+ ASSERT_EQ(controller_.SetLeExtendedAdvertisingEnable(Enable::ENABLED,
+ {enabled_set}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeClearFilterAcceptList(),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+} // namespace rootcanal
diff --git a/tools/rootcanal/test/controller/le/le_clear_resolving_list_test.cc b/tools/rootcanal/test/controller/le/le_clear_resolving_list_test.cc
new file mode 100644
index 0000000000..420a611316
--- /dev/null
+++ b/tools/rootcanal/test/controller/le/le_clear_resolving_list_test.cc
@@ -0,0 +1,90 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "model/controller/link_layer_controller.h"
+
+namespace rootcanal {
+
+using namespace bluetooth::hci;
+
+class LeClearResolvingListTest : public ::testing::Test {
+ public:
+ LeClearResolvingListTest() {
+ // Reduce the size of the resolving list to simplify testing.
+ properties_.le_resolving_list_size = 2;
+ }
+
+ ~LeClearResolvingListTest() override = default;
+
+ protected:
+ Address address_{0};
+ ControllerProperties properties_{};
+ LinkLayerController controller_{address_, properties_};
+};
+
+TEST_F(LeClearResolvingListTest, Success) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeClearResolvingList(), ErrorCode::SUCCESS);
+}
+
+TEST_F(LeClearResolvingListTest, ScanningActive) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true), ErrorCode::SUCCESS);
+ controller_.SetLeScanEnable(OpCode::LE_SET_SCAN_ENABLE);
+
+ ASSERT_EQ(controller_.LeClearResolvingList(), ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeClearResolvingListTest, LegacyAdvertisingActive) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true), ErrorCode::SUCCESS);
+ ASSERT_EQ(controller_.SetLeAdvertisingEnable(1), ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeClearResolvingList(), ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeClearResolvingListTest, ExtendedAdvertisingActive) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ EnabledSet enabled_set;
+ enabled_set.advertising_handle_ = 1;
+ enabled_set.duration_ = 0;
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true), ErrorCode::SUCCESS);
+ ASSERT_EQ(controller_.SetLeExtendedAdvertisingEnable(Enable::ENABLED,
+ {enabled_set}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeClearResolvingList(), ErrorCode::COMMAND_DISALLOWED);
+}
+
+} // namespace rootcanal
diff --git a/tools/rootcanal/test/controller/le/le_remove_device_from_filter_accept_list_test.cc b/tools/rootcanal/test/controller/le/le_remove_device_from_filter_accept_list_test.cc
new file mode 100644
index 0000000000..2970ac7965
--- /dev/null
+++ b/tools/rootcanal/test/controller/le/le_remove_device_from_filter_accept_list_test.cc
@@ -0,0 +1,112 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "model/controller/link_layer_controller.h"
+
+namespace rootcanal {
+
+using namespace bluetooth::hci;
+
+class LeRemoveDeviceFromFilterAcceptListTest : public ::testing::Test {
+ public:
+ LeRemoveDeviceFromFilterAcceptListTest() {
+ // Reduce the size of the resolving list to simplify testing.
+ properties_.le_resolving_list_size = 2;
+ }
+
+ ~LeRemoveDeviceFromFilterAcceptListTest() override = default;
+
+ protected:
+ Address address_{0};
+ ControllerProperties properties_{};
+ LinkLayerController controller_{address_, properties_};
+};
+
+TEST_F(LeRemoveDeviceFromFilterAcceptListTest, Success) {
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeRemoveDeviceFromFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::SUCCESS);
+}
+
+TEST_F(LeRemoveDeviceFromFilterAcceptListTest, NotFound) {
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeRemoveDeviceFromFilterAcceptList(
+ FilterAcceptListAddressType::RANDOM, Address{1}),
+ ErrorCode::SUCCESS);
+}
+
+TEST_F(LeRemoveDeviceFromFilterAcceptListTest, ScanningActive) {
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::SUCCESS);
+
+ controller_.SetLeScanFilterPolicy(
+ LeScanningFilterPolicy::FILTER_ACCEPT_LIST_ONLY);
+ controller_.SetLeScanEnable(OpCode::LE_SET_SCAN_ENABLE);
+
+ ASSERT_EQ(controller_.LeRemoveDeviceFromFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeRemoveDeviceFromFilterAcceptListTest, LegacyAdvertisingActive) {
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::SUCCESS);
+
+ controller_.SetLeAdvertisingParameters(
+ 0x0800, 0x0800, AdvertisingType::ADV_IND, 0, 0, Address::kEmpty, 0x7,
+ AdvertisingFilterPolicy::LISTED_SCAN);
+ ASSERT_EQ(controller_.SetLeAdvertisingEnable(1), ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeRemoveDeviceFromFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeRemoveDeviceFromFilterAcceptListTest, ExtendedAdvertisingActive) {
+ ASSERT_EQ(controller_.LeAddDeviceToFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::SUCCESS);
+
+ EnabledSet enabled_set;
+ enabled_set.advertising_handle_ = 1;
+ enabled_set.duration_ = 0;
+ ASSERT_EQ(controller_.SetLeExtendedAdvertisingParameters(
+ 1, 0, 0, LegacyAdvertisingEventProperties::ADV_IND,
+ OwnAddressType::PUBLIC_DEVICE_ADDRESS,
+ PeerAddressType::PUBLIC_DEVICE_OR_IDENTITY_ADDRESS,
+ Address::kEmpty, AdvertisingFilterPolicy::LISTED_SCAN, 0x70),
+ ErrorCode::SUCCESS);
+ ASSERT_EQ(controller_.SetLeExtendedAdvertisingEnable(Enable::ENABLED,
+ {enabled_set}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeRemoveDeviceFromFilterAcceptList(
+ FilterAcceptListAddressType::PUBLIC, Address{1}),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+} // namespace rootcanal
diff --git a/tools/rootcanal/test/controller/le/le_remove_device_from_resolving_list_test.cc b/tools/rootcanal/test/controller/le/le_remove_device_from_resolving_list_test.cc
new file mode 100644
index 0000000000..ce3745b1e1
--- /dev/null
+++ b/tools/rootcanal/test/controller/le/le_remove_device_from_resolving_list_test.cc
@@ -0,0 +1,109 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "model/controller/link_layer_controller.h"
+
+namespace rootcanal {
+
+using namespace bluetooth::hci;
+
+class LeRemoveDeviceFromResolvingListTest : public ::testing::Test {
+ public:
+ LeRemoveDeviceFromResolvingListTest() {
+ // Reduce the size of the resolving list to simplify testing.
+ properties_.le_resolving_list_size = 2;
+ }
+
+ ~LeRemoveDeviceFromResolvingListTest() override = default;
+
+ protected:
+ Address address_{0};
+ ControllerProperties properties_{};
+ LinkLayerController controller_{address_, properties_};
+};
+
+TEST_F(LeRemoveDeviceFromResolvingListTest, Success) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeRemoveDeviceFromResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1}),
+ ErrorCode::SUCCESS);
+}
+
+TEST_F(LeRemoveDeviceFromResolvingListTest, NotFound) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeRemoveDeviceFromResolvingList(
+ AddressType::RANDOM_DEVICE_ADDRESS, Address{1}),
+ ErrorCode::UNKNOWN_CONNECTION);
+}
+
+TEST_F(LeRemoveDeviceFromResolvingListTest, ScanningActive) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true), ErrorCode::SUCCESS);
+ controller_.SetLeScanEnable(OpCode::LE_SET_SCAN_ENABLE);
+
+ ASSERT_EQ(controller_.LeRemoveDeviceFromResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1}),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeRemoveDeviceFromResolvingListTest, LegacyAdvertisingActive) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true), ErrorCode::SUCCESS);
+ ASSERT_EQ(controller_.SetLeAdvertisingEnable(1), ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeRemoveDeviceFromResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1}),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeRemoveDeviceFromResolvingListTest, ExtendedAdvertisingActive) {
+ ASSERT_EQ(controller_.LeAddDeviceToResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1},
+ std::array<uint8_t, 16>{1}, std::array<uint8_t, 16>{1}),
+ ErrorCode::SUCCESS);
+
+ EnabledSet enabled_set;
+ enabled_set.advertising_handle_ = 1;
+ enabled_set.duration_ = 0;
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true), ErrorCode::SUCCESS);
+ ASSERT_EQ(controller_.SetLeExtendedAdvertisingEnable(Enable::ENABLED,
+ {enabled_set}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeRemoveDeviceFromResolvingList(
+ AddressType::PUBLIC_DEVICE_ADDRESS, Address{1}),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+} // namespace rootcanal
diff --git a/tools/rootcanal/test/controller/le/le_set_address_resolution_enable_test.cc b/tools/rootcanal/test/controller/le/le_set_address_resolution_enable_test.cc
new file mode 100644
index 0000000000..19976c4d90
--- /dev/null
+++ b/tools/rootcanal/test/controller/le/le_set_address_resolution_enable_test.cc
@@ -0,0 +1,73 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "model/controller/link_layer_controller.h"
+
+namespace rootcanal {
+
+using namespace bluetooth::hci;
+
+class LeSetAddressResolutionEnableTest : public ::testing::Test {
+ public:
+ LeSetAddressResolutionEnableTest() = default;
+ ~LeSetAddressResolutionEnableTest() override = default;
+
+ protected:
+ Address address_{0};
+ ControllerProperties properties_{};
+ LinkLayerController controller_{address_, properties_};
+};
+
+TEST_F(LeSetAddressResolutionEnableTest, Success) {
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true), ErrorCode::SUCCESS);
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(false),
+ ErrorCode::SUCCESS);
+}
+
+TEST_F(LeSetAddressResolutionEnableTest, ScanningActive) {
+ controller_.SetLeScanEnable(OpCode::LE_SET_SCAN_ENABLE);
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true),
+ ErrorCode::COMMAND_DISALLOWED);
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(false),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeSetAddressResolutionEnableTest, LegacyAdvertisingActive) {
+ ASSERT_EQ(controller_.SetLeAdvertisingEnable(1), ErrorCode::SUCCESS);
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true),
+ ErrorCode::COMMAND_DISALLOWED);
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(false),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+TEST_F(LeSetAddressResolutionEnableTest, ExtendedAdvertisingActive) {
+ EnabledSet enabled_set;
+ enabled_set.advertising_handle_ = 1;
+ enabled_set.duration_ = 0;
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true), ErrorCode::SUCCESS);
+ ASSERT_EQ(controller_.SetLeExtendedAdvertisingEnable(Enable::ENABLED,
+ {enabled_set}),
+ ErrorCode::SUCCESS);
+
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(true),
+ ErrorCode::COMMAND_DISALLOWED);
+ ASSERT_EQ(controller_.LeSetAddressResolutionEnable(false),
+ ErrorCode::COMMAND_DISALLOWED);
+}
+
+} // namespace rootcanal