Loading system/bta/le_audio/client.cc +50 −44 Original line number Diff line number Diff line Loading @@ -1812,6 +1812,24 @@ public: } } void handleInitialCtpCccRead(LeAudioDevice* leAudioDevice, uint16_t len, uint8_t* value) { if (len != 2) { log::error("Could not read CCC for {}, disconnecting", leAudioDevice->address_); instance->Disconnect(leAudioDevice->address_); return; } uint16_t val = *(uint16_t*)value; if (val == 0) { log::warn("{} forgot CCC values. Re-subscribing", leAudioDevice->address_); RegisterKnownNotifications(leAudioDevice, false, true); return; } log::verbose("{}, ASCS ctp ccc: {:#x}", leAudioDevice->address_, val); connectionReady(leAudioDevice); } /* This is a generic read/notify/indicate handler for gatt. Here messages * are dispatched to correct elements e.g. ASEs, PACs, audio locations etc. */ Loading @@ -1826,11 +1844,15 @@ public: } ase = leAudioDevice->GetAseByValHandle(hdl); LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); if (ase) { groupStateMachine_->ProcessGattNotifEvent(value, len, ase, leAudioDevice, group); return; } /* Initial CCC read to check if remote device properly keeps CCC values */ if (hdl == leAudioDevice->ctp_hdls_.ccc_hdl) { handleInitialCtpCccRead(leAudioDevice, len, value); return; } Loading Loading @@ -2218,6 +2240,32 @@ public: } } void ReadMustHaveAttributesOnReconnect(LeAudioDevice* leAudioDevice) { log::verbose("{}", leAudioDevice->address_); /* Here we read * 1) ASCS Control Point CCC descriptor in order to validate proper * behavior of remote device which should store CCC values for bonded device. * 2) Available Context Types which normally should be notified by the server, * but since it is crucial for proper streaming experiance, and in the same time * it can change very often which, as we observed, might lead to not being sent by * remote devices */ if (!com::android::bluetooth::flags::le_ase_read_multiple_variable()) { BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_, leAudioDevice->audio_avail_hdls_.val_hdl, OnGattReadRspStatic, NULL); BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.ccc_hdl, OnGattReadRspStatic, NULL); } else { tBTA_GATTC_MULTI multi_read = {.num_attr = 2, .handles = {leAudioDevice->audio_avail_hdls_.val_hdl, leAudioDevice->ctp_hdls_.ccc_hdl}}; BtaGattQueue::ReadMultiCharacteristic(leAudioDevice->conn_id_, multi_read, OnGattReadMultiRspStatic, NULL); } } void OnEncryptionComplete(const RawAddress& address, tBTM_STATUS status) { log::info("{} status 0x{:02x}", address, status); LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); Loading Loading @@ -2268,13 +2316,7 @@ public: * assume remote device keeps bonded CCC values. */ RegisterKnownNotifications(leAudioDevice, true, false); /* Make sure remote keeps CCC values as per specification. * We read only ctp_ccc value. If that one is good, we assume * remote keeps CCC values correctly. */ BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.ccc_hdl, OnGattCtpCccReadRspStatic, NULL); ReadMustHaveAttributesOnReconnect(leAudioDevice); } /* If we know services and read is not ongoing, this is reconnection and Loading Loading @@ -4960,42 +5002,6 @@ public: return false; } static void OnGattCtpCccReadRspStatic(tCONN_ID conn_id, tGATT_STATUS status, uint16_t hdl, uint16_t len, uint8_t* value, void* data) { if (!instance) { return; } log::debug("conn_id: 0x{:04x}, status: 0x{:02x}", conn_id, status); LeAudioDevice* leAudioDevice = instance->leAudioDevices_.FindByConnId(conn_id); if (!leAudioDevice) { log::error("LeAudioDevice not found"); return; } if (status == GATT_DATABASE_OUT_OF_SYNC) { log::info("Database out of sync for {}, re-discovering", leAudioDevice->address_); instance->ClearDeviceInformationAndStartSearch(leAudioDevice); return; } if (status != GATT_SUCCESS || len != 2) { log::error("Could not read CCC for {}, disconnecting", leAudioDevice->address_); instance->Disconnect(leAudioDevice->address_); return; } uint16_t val = *(uint16_t*)value; if (val == 0) { log::info("{} forgot CCC values. Re-subscribing", leAudioDevice->address_); instance->RegisterKnownNotifications(leAudioDevice, false, true); } else { instance->connectionReady(leAudioDevice); } } static void OnGattReadRspStatic(tCONN_ID conn_id, tGATT_STATUS status, uint16_t hdl, uint16_t len, uint8_t* value, void* data) { if (!instance) { Loading system/bta/le_audio/le_audio_client_test.cc +107 −1 Original line number Diff line number Diff line Loading @@ -8677,6 +8677,107 @@ TEST_F(UnicastTest, CheckDeviceIsNotAttachedToStreamWhenNotNeeded) { SyncOnMainLoop(); } TEST_F(UnicastTest, ReconnectedDeviceAndAttachedToStreamBecauseOfAvailableContextTypeChange) { com::android::bluetooth::flags::provider_->le_ase_read_multiple_variable(true); uint8_t group_size = 2; int group_id = 2; /* Scenario * 1. Two devices A and B are streaming * 2. Device A Release ASE and removes all available context types * 3. Device B keeps streaming * 4. Device A disconnectes * 5. Device A reconnect * 6. Device A has context available for streaming and should be attached to the stream */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()).WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); // First earbud connects ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud connects ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Expect two iso channel to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); /* Get group and Device A */ ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); ASSERT_NE(group, nullptr); auto device = group->GetFirstDevice(); /* Simulate available context type being cleared */ InjectAvailableContextTypes(device->address_, device->conn_id_, types::AudioContexts(0), types::AudioContexts(0)); /* Simulate ASE releasing and CIS Disconnection */ for (auto& ase : device->ases_) { /* Releasing state */ if (!ase.active) { continue; } std::vector<uint8_t> releasing_state = { ase.id, static_cast<uint8_t>(types::AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING)}; InjectNotificationEvent(device->address_, device->conn_id_, ase.hdls.val_hdl, releasing_state); SyncOnMainLoop(); InjectCisDisconnected(group_id, ase.cis_conn_hdl); SyncOnMainLoop(); } cis_count_out = 1; cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); /* Device A will disconnect, and do not reconnect automatically */ ON_CALL(mock_gatt_interface_, Open(_, device->address_, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault(Return()); /* Disconnect first device */ auto conn_id = device->conn_id_; InjectDisconnectedEvent(conn_id, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); /* For background connect, test needs to Inject Connected Event. * Note that initial available_snk_context_types_ available_src_context_types_ will * be read after reconnection, which should bring device to the stream again. */ InjectConnectedEvent(device->address_, conn_id); SyncOnMainLoop(); /* Check single device is streaming */ cis_count_out = 2; cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); } TEST_F(UnicastTest, ReconnectedDeviceNotAttachedToStreamBecauseOfNotAvailableContext) { uint8_t group_size = 2; int group_id = 2; Loading Loading @@ -8762,7 +8863,12 @@ TEST_F(UnicastTest, ReconnectedDeviceNotAttachedToStreamBecauseOfNotAvailableCon InjectDisconnectedEvent(conn_id, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); /* For background connect, test needs to Inject Connected Event */ /* For background connect, test needs to Inject Connected Event. * Since after reconnect Android reads available context types, make sure * 0 is read */ available_snk_context_types_ = 0; available_src_context_types_ = 0; InjectConnectedEvent(device->address_, conn_id); SyncOnMainLoop(); Loading Loading
system/bta/le_audio/client.cc +50 −44 Original line number Diff line number Diff line Loading @@ -1812,6 +1812,24 @@ public: } } void handleInitialCtpCccRead(LeAudioDevice* leAudioDevice, uint16_t len, uint8_t* value) { if (len != 2) { log::error("Could not read CCC for {}, disconnecting", leAudioDevice->address_); instance->Disconnect(leAudioDevice->address_); return; } uint16_t val = *(uint16_t*)value; if (val == 0) { log::warn("{} forgot CCC values. Re-subscribing", leAudioDevice->address_); RegisterKnownNotifications(leAudioDevice, false, true); return; } log::verbose("{}, ASCS ctp ccc: {:#x}", leAudioDevice->address_, val); connectionReady(leAudioDevice); } /* This is a generic read/notify/indicate handler for gatt. Here messages * are dispatched to correct elements e.g. ASEs, PACs, audio locations etc. */ Loading @@ -1826,11 +1844,15 @@ public: } ase = leAudioDevice->GetAseByValHandle(hdl); LeAudioDeviceGroup* group = aseGroups_.FindById(leAudioDevice->group_id_); if (ase) { groupStateMachine_->ProcessGattNotifEvent(value, len, ase, leAudioDevice, group); return; } /* Initial CCC read to check if remote device properly keeps CCC values */ if (hdl == leAudioDevice->ctp_hdls_.ccc_hdl) { handleInitialCtpCccRead(leAudioDevice, len, value); return; } Loading Loading @@ -2218,6 +2240,32 @@ public: } } void ReadMustHaveAttributesOnReconnect(LeAudioDevice* leAudioDevice) { log::verbose("{}", leAudioDevice->address_); /* Here we read * 1) ASCS Control Point CCC descriptor in order to validate proper * behavior of remote device which should store CCC values for bonded device. * 2) Available Context Types which normally should be notified by the server, * but since it is crucial for proper streaming experiance, and in the same time * it can change very often which, as we observed, might lead to not being sent by * remote devices */ if (!com::android::bluetooth::flags::le_ase_read_multiple_variable()) { BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_, leAudioDevice->audio_avail_hdls_.val_hdl, OnGattReadRspStatic, NULL); BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.ccc_hdl, OnGattReadRspStatic, NULL); } else { tBTA_GATTC_MULTI multi_read = {.num_attr = 2, .handles = {leAudioDevice->audio_avail_hdls_.val_hdl, leAudioDevice->ctp_hdls_.ccc_hdl}}; BtaGattQueue::ReadMultiCharacteristic(leAudioDevice->conn_id_, multi_read, OnGattReadMultiRspStatic, NULL); } } void OnEncryptionComplete(const RawAddress& address, tBTM_STATUS status) { log::info("{} status 0x{:02x}", address, status); LeAudioDevice* leAudioDevice = leAudioDevices_.FindByAddress(address); Loading Loading @@ -2268,13 +2316,7 @@ public: * assume remote device keeps bonded CCC values. */ RegisterKnownNotifications(leAudioDevice, true, false); /* Make sure remote keeps CCC values as per specification. * We read only ctp_ccc value. If that one is good, we assume * remote keeps CCC values correctly. */ BtaGattQueue::ReadCharacteristic(leAudioDevice->conn_id_, leAudioDevice->ctp_hdls_.ccc_hdl, OnGattCtpCccReadRspStatic, NULL); ReadMustHaveAttributesOnReconnect(leAudioDevice); } /* If we know services and read is not ongoing, this is reconnection and Loading Loading @@ -4960,42 +5002,6 @@ public: return false; } static void OnGattCtpCccReadRspStatic(tCONN_ID conn_id, tGATT_STATUS status, uint16_t hdl, uint16_t len, uint8_t* value, void* data) { if (!instance) { return; } log::debug("conn_id: 0x{:04x}, status: 0x{:02x}", conn_id, status); LeAudioDevice* leAudioDevice = instance->leAudioDevices_.FindByConnId(conn_id); if (!leAudioDevice) { log::error("LeAudioDevice not found"); return; } if (status == GATT_DATABASE_OUT_OF_SYNC) { log::info("Database out of sync for {}, re-discovering", leAudioDevice->address_); instance->ClearDeviceInformationAndStartSearch(leAudioDevice); return; } if (status != GATT_SUCCESS || len != 2) { log::error("Could not read CCC for {}, disconnecting", leAudioDevice->address_); instance->Disconnect(leAudioDevice->address_); return; } uint16_t val = *(uint16_t*)value; if (val == 0) { log::info("{} forgot CCC values. Re-subscribing", leAudioDevice->address_); instance->RegisterKnownNotifications(leAudioDevice, false, true); } else { instance->connectionReady(leAudioDevice); } } static void OnGattReadRspStatic(tCONN_ID conn_id, tGATT_STATUS status, uint16_t hdl, uint16_t len, uint8_t* value, void* data) { if (!instance) { Loading
system/bta/le_audio/le_audio_client_test.cc +107 −1 Original line number Diff line number Diff line Loading @@ -8677,6 +8677,107 @@ TEST_F(UnicastTest, CheckDeviceIsNotAttachedToStreamWhenNotNeeded) { SyncOnMainLoop(); } TEST_F(UnicastTest, ReconnectedDeviceAndAttachedToStreamBecauseOfAvailableContextTypeChange) { com::android::bluetooth::flags::provider_->le_ase_read_multiple_variable(true); uint8_t group_size = 2; int group_id = 2; /* Scenario * 1. Two devices A and B are streaming * 2. Device A Release ASE and removes all available context types * 3. Device B keeps streaming * 4. Device A disconnectes * 5. Device A reconnect * 6. Device A has context available for streaming and should be attached to the stream */ // Report working CSIS ON_CALL(mock_csis_client_module_, IsCsisClientRunning()).WillByDefault(Return(true)); ON_CALL(mock_csis_client_module_, GetDesiredSize(group_id)) .WillByDefault(Invoke([&](int group_id) { return group_size; })); const RawAddress test_address0 = GetTestAddress(0); const RawAddress test_address1 = GetTestAddress(1); // First earbud connects ConnectCsisDevice(test_address0, 1 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontLeft, codec_spec_conf::kLeAudioLocationFrontLeft, group_size, group_id, 1 /* rank*/); // Second earbud connects ConnectCsisDevice(test_address1, 2 /*conn_id*/, codec_spec_conf::kLeAudioLocationFrontRight, codec_spec_conf::kLeAudioLocationFrontRight, group_size, group_id, 2 /* rank*/, true /*connect_through_csis*/); // Start streaming EXPECT_CALL(*mock_le_audio_source_hal_client_, Start(_, _, _)).Times(1); EXPECT_CALL(*mock_le_audio_sink_hal_client_, Start(_, _, _)).Times(1); LeAudioClient::Get()->GroupSetActive(group_id); SyncOnMainLoop(); StartStreaming(AUDIO_USAGE_MEDIA, AUDIO_CONTENT_TYPE_MUSIC, group_id); Mock::VerifyAndClearExpectations(&mock_audio_hal_client_callbacks_); Mock::VerifyAndClearExpectations(mock_le_audio_source_hal_client_); SyncOnMainLoop(); // Expect two iso channel to be fed with data uint8_t cis_count_out = 2; uint8_t cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); /* Get group and Device A */ ASSERT_NE(0lu, streaming_groups.count(group_id)); auto group = streaming_groups.at(group_id); ASSERT_NE(group, nullptr); auto device = group->GetFirstDevice(); /* Simulate available context type being cleared */ InjectAvailableContextTypes(device->address_, device->conn_id_, types::AudioContexts(0), types::AudioContexts(0)); /* Simulate ASE releasing and CIS Disconnection */ for (auto& ase : device->ases_) { /* Releasing state */ if (!ase.active) { continue; } std::vector<uint8_t> releasing_state = { ase.id, static_cast<uint8_t>(types::AseState::BTA_LE_AUDIO_ASE_STATE_RELEASING)}; InjectNotificationEvent(device->address_, device->conn_id_, ase.hdls.val_hdl, releasing_state); SyncOnMainLoop(); InjectCisDisconnected(group_id, ase.cis_conn_hdl); SyncOnMainLoop(); } cis_count_out = 1; cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); /* Device A will disconnect, and do not reconnect automatically */ ON_CALL(mock_gatt_interface_, Open(_, device->address_, BTM_BLE_DIRECT_CONNECTION, _)) .WillByDefault(Return()); /* Disconnect first device */ auto conn_id = device->conn_id_; InjectDisconnectedEvent(conn_id, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); /* For background connect, test needs to Inject Connected Event. * Note that initial available_snk_context_types_ available_src_context_types_ will * be read after reconnection, which should bring device to the stream again. */ InjectConnectedEvent(device->address_, conn_id); SyncOnMainLoop(); /* Check single device is streaming */ cis_count_out = 2; cis_count_in = 0; TestAudioDataTransfer(group_id, cis_count_out, cis_count_in, 1920); } TEST_F(UnicastTest, ReconnectedDeviceNotAttachedToStreamBecauseOfNotAvailableContext) { uint8_t group_size = 2; int group_id = 2; Loading Loading @@ -8762,7 +8863,12 @@ TEST_F(UnicastTest, ReconnectedDeviceNotAttachedToStreamBecauseOfNotAvailableCon InjectDisconnectedEvent(conn_id, GATT_CONN_TERMINATE_PEER_USER); SyncOnMainLoop(); /* For background connect, test needs to Inject Connected Event */ /* For background connect, test needs to Inject Connected Event. * Since after reconnect Android reads available context types, make sure * 0 is read */ available_snk_context_types_ = 0; available_src_context_types_ = 0; InjectConnectedEvent(device->address_, conn_id); SyncOnMainLoop(); Loading