You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

686 lines
23 KiB

/*
* Copyright 2019 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.
*/
#define LOG_TAG "bt_shim_btm"
#include <algorithm>
#include <chrono>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <mutex>
#include "bta/include/bta_api.h"
#include "main/shim/btm.h"
#include "main/shim/controller.h"
#include "main/shim/entry.h"
#include "main/shim/helpers.h"
#include "main/shim/shim.h"
#include "stack/btm/btm_dev.h"
#include "stack/btm/btm_int_types.h"
#include "types/bt_transport.h"
#include "types/raw_address.h"
#include "gd/hci/le_advertising_manager.h"
#include "gd/hci/le_scanning_manager.h"
#include "gd/neighbor/connectability.h"
#include "gd/neighbor/discoverability.h"
#include "gd/neighbor/inquiry.h"
#include "gd/neighbor/name.h"
#include "gd/neighbor/page.h"
#include "gd/security/security_module.h"
extern tBTM_CB btm_cb;
static constexpr size_t kRemoteDeviceNameLength = 248;
static constexpr bool kActiveScanning = true;
static constexpr bool kPassiveScanning = false;
using BtmRemoteDeviceName = tBTM_REMOTE_DEV_NAME;
extern void btm_process_cancel_complete(uint8_t status, uint8_t mode);
extern void btm_process_inq_complete(uint8_t status, uint8_t result_type);
extern void btm_ble_process_adv_addr(RawAddress& raw_address,
tBLE_ADDR_TYPE* address_type);
extern void btm_ble_process_adv_pkt_cont(
uint16_t event_type, uint8_t address_type, const RawAddress& raw_address,
uint8_t primary_phy, uint8_t secondary_phy, uint8_t advertising_sid,
int8_t tx_power, int8_t rssi, uint16_t periodic_adv_int, uint8_t data_len,
uint8_t* data);
extern void btm_api_process_inquiry_result(const RawAddress& raw_address,
uint8_t page_scan_rep_mode,
DEV_CLASS device_class,
uint16_t clock_offset);
extern void btm_api_process_inquiry_result_with_rssi(RawAddress raw_address,
uint8_t page_scan_rep_mode,
DEV_CLASS device_class,
uint16_t clock_offset,
int8_t rssi);
extern void btm_api_process_extended_inquiry_result(
RawAddress raw_address, uint8_t page_scan_rep_mode, DEV_CLASS device_class,
uint16_t clock_offset, int8_t rssi, const uint8_t* eir_data,
size_t eir_len);
namespace bluetooth {
namespace shim {
bool Btm::ReadRemoteName::Start(RawAddress raw_address) {
std::unique_lock<std::mutex> lock(mutex_);
if (in_progress_) {
return false;
}
raw_address_ = raw_address;
in_progress_ = true;
return true;
}
void Btm::ReadRemoteName::Stop() {
std::unique_lock<std::mutex> lock(mutex_);
raw_address_ = RawAddress::kEmpty;
in_progress_ = false;
}
bool Btm::ReadRemoteName::IsInProgress() const { return in_progress_; }
std::string Btm::ReadRemoteName::AddressString() const {
return raw_address_.ToString();
}
void Btm::ScanningCallbacks::OnScannerRegistered(
const bluetooth::hci::Uuid app_uuid, bluetooth::hci::ScannerId scanner_id,
ScanningStatus status){};
void Btm::ScanningCallbacks::OnScanResult(
uint16_t event_type, uint8_t address_type, bluetooth::hci::Address address,
uint8_t primary_phy, uint8_t secondary_phy, uint8_t advertising_sid,
int8_t tx_power, int8_t rssi, uint16_t periodic_advertising_interval,
std::vector<uint8_t> advertising_data) {
tBLE_ADDR_TYPE ble_address_type = static_cast<tBLE_ADDR_TYPE>(address_type);
uint16_t extended_event_type = 0;
RawAddress raw_address;
RawAddress::FromString(address.ToString(), raw_address);
if (ble_address_type != BLE_ADDR_ANONYMOUS) {
btm_ble_process_adv_addr(raw_address, &ble_address_type);
}
btm_ble_process_adv_addr(raw_address, &ble_address_type);
btm_ble_process_adv_pkt_cont(extended_event_type, ble_address_type,
raw_address, primary_phy, secondary_phy,
advertising_sid, tx_power, rssi,
periodic_advertising_interval,
advertising_data.size(), &advertising_data[0]);
}
void Btm::ScanningCallbacks::OnTrackAdvFoundLost(
bluetooth::hci::AdvertisingFilterOnFoundOnLostInfo on_found_on_lost_info){};
void Btm::ScanningCallbacks::OnBatchScanReports(int client_if, int status,
int report_format,
int num_records,
std::vector<uint8_t> data){};
void Btm::ScanningCallbacks::OnBatchScanThresholdCrossed(int client_if){};
void Btm::ScanningCallbacks::OnTimeout(){};
void Btm::ScanningCallbacks::OnFilterEnable(bluetooth::hci::Enable enable,
uint8_t status){};
void Btm::ScanningCallbacks::OnFilterParamSetup(
uint8_t available_spaces, bluetooth::hci::ApcfAction action,
uint8_t status){};
void Btm::ScanningCallbacks::OnFilterConfigCallback(
bluetooth::hci::ApcfFilterType filter_type, uint8_t available_spaces,
bluetooth::hci::ApcfAction action, uint8_t status){};
Btm::Btm(os::Handler* handler, neighbor::InquiryModule* inquiry)
: scanning_timer_(handler), observing_timer_(handler) {
ASSERT(handler != nullptr);
ASSERT(inquiry != nullptr);
bluetooth::neighbor::InquiryCallbacks inquiry_callbacks = {
.result = std::bind(&Btm::OnInquiryResult, this, std::placeholders::_1),
.result_with_rssi =
std::bind(&Btm::OnInquiryResultWithRssi, this, std::placeholders::_1),
.extended_result =
std::bind(&Btm::OnExtendedInquiryResult, this, std::placeholders::_1),
.complete =
std::bind(&Btm::OnInquiryComplete, this, std::placeholders::_1)};
inquiry->RegisterCallbacks(std::move(inquiry_callbacks));
}
void Btm::OnInquiryResult(bluetooth::hci::InquiryResultView view) {
for (auto& response : view.GetInquiryResults()) {
btm_api_process_inquiry_result(
ToRawAddress(response.bd_addr_),
static_cast<uint8_t>(response.page_scan_repetition_mode_),
response.class_of_device_.data(), response.clock_offset_);
}
}
void Btm::OnInquiryResultWithRssi(
bluetooth::hci::InquiryResultWithRssiView view) {
for (auto& response : view.GetInquiryResults()) {
btm_api_process_inquiry_result_with_rssi(
ToRawAddress(response.address_),
static_cast<uint8_t>(response.page_scan_repetition_mode_),
response.class_of_device_.data(), response.clock_offset_,
response.rssi_);
}
}
void Btm::OnExtendedInquiryResult(
bluetooth::hci::ExtendedInquiryResultView view) {
constexpr size_t kMaxExtendedInquiryResponse = 240;
uint8_t gap_data_buffer[kMaxExtendedInquiryResponse];
uint8_t* data = nullptr;
size_t data_len = 0;
if (!view.GetExtendedInquiryResponse().empty()) {
bzero(gap_data_buffer, sizeof(gap_data_buffer));
uint8_t* p = gap_data_buffer;
for (auto gap_data : view.GetExtendedInquiryResponse()) {
*p++ = gap_data.data_.size() + sizeof(gap_data.data_type_);
*p++ = static_cast<uint8_t>(gap_data.data_type_);
p = (uint8_t*)memcpy(p, &gap_data.data_[0], gap_data.data_.size()) +
gap_data.data_.size();
}
data = gap_data_buffer;
data_len = p - data;
}
btm_api_process_extended_inquiry_result(
ToRawAddress(view.GetAddress()),
static_cast<uint8_t>(view.GetPageScanRepetitionMode()),
view.GetClassOfDevice().data(), view.GetClockOffset(), view.GetRssi(),
data, data_len);
}
void Btm::OnInquiryComplete(bluetooth::hci::ErrorCode status) {
limited_inquiry_active_ = false;
general_inquiry_active_ = false;
legacy_inquiry_complete_callback_((static_cast<uint16_t>(status) == 0)
? (BTM_SUCCESS)
: (BTM_ERR_PROCESSING),
active_inquiry_mode_);
active_inquiry_mode_ = kInquiryModeOff;
}
void Btm::SetStandardInquiryResultMode() {
GetInquiry()->SetStandardInquiryResultMode();
}
void Btm::SetInquiryWithRssiResultMode() {
GetInquiry()->SetInquiryWithRssiResultMode();
}
void Btm::SetExtendedInquiryResultMode() {
GetInquiry()->SetExtendedInquiryResultMode();
}
void Btm::SetInterlacedInquiryScan() { GetInquiry()->SetInterlacedScan(); }
void Btm::SetStandardInquiryScan() { GetInquiry()->SetStandardScan(); }
bool Btm::IsInterlacedScanSupported() const {
return controller_get_interface()->supports_interlaced_inquiry_scan();
}
/**
* One shot inquiry
*/
bool Btm::StartInquiry(
uint8_t mode, uint8_t duration, uint8_t max_responses,
LegacyInquiryCompleteCallback legacy_inquiry_complete_callback) {
switch (mode) {
case kInquiryModeOff:
LOG_INFO("%s Stopping inquiry mode", __func__);
if (limited_inquiry_active_ || general_inquiry_active_) {
GetInquiry()->StopInquiry();
limited_inquiry_active_ = false;
general_inquiry_active_ = false;
}
active_inquiry_mode_ = kInquiryModeOff;
break;
case kLimitedInquiryMode:
case kGeneralInquiryMode: {
if (mode == kLimitedInquiryMode) {
LOG_INFO(
"%s Starting limited inquiry mode duration:%hhd max responses:%hhd",
__func__, duration, max_responses);
limited_inquiry_active_ = true;
GetInquiry()->StartLimitedInquiry(duration, max_responses);
active_inquiry_mode_ = kLimitedInquiryMode;
} else {
LOG_INFO(
"%s Starting general inquiry mode duration:%hhd max responses:%hhd",
__func__, duration, max_responses);
general_inquiry_active_ = true;
GetInquiry()->StartGeneralInquiry(duration, max_responses);
legacy_inquiry_complete_callback_ = legacy_inquiry_complete_callback;
}
} break;
default:
LOG_WARN("%s Unknown inquiry mode:%d", __func__, mode);
return false;
}
return true;
}
void Btm::CancelInquiry() {
LOG_INFO("%s", __func__);
if (limited_inquiry_active_ || general_inquiry_active_) {
GetInquiry()->StopInquiry();
limited_inquiry_active_ = false;
general_inquiry_active_ = false;
}
}
bool Btm::IsInquiryActive() const {
return IsGeneralInquiryActive() || IsLimitedInquiryActive();
}
bool Btm::IsGeneralInquiryActive() const { return general_inquiry_active_; }
bool Btm::IsLimitedInquiryActive() const { return limited_inquiry_active_; }
/**
* Periodic
*/
bool Btm::StartPeriodicInquiry(uint8_t mode, uint8_t duration,
uint8_t max_responses, uint16_t max_delay,
uint16_t min_delay,
tBTM_INQ_RESULTS_CB* p_results_cb) {
switch (mode) {
case kInquiryModeOff:
limited_periodic_inquiry_active_ = false;
general_periodic_inquiry_active_ = false;
GetInquiry()->StopPeriodicInquiry();
break;
case kLimitedInquiryMode:
case kGeneralInquiryMode: {
if (mode == kLimitedInquiryMode) {
LOG_INFO("%s Starting limited periodic inquiry mode", __func__);
limited_periodic_inquiry_active_ = true;
GetInquiry()->StartLimitedPeriodicInquiry(duration, max_responses,
max_delay, min_delay);
} else {
LOG_INFO("%s Starting general periodic inquiry mode", __func__);
general_periodic_inquiry_active_ = true;
GetInquiry()->StartGeneralPeriodicInquiry(duration, max_responses,
max_delay, min_delay);
}
} break;
default:
LOG_WARN("%s Unknown inquiry mode:%d", __func__, mode);
return false;
}
return true;
}
bool Btm::IsGeneralPeriodicInquiryActive() const {
return general_periodic_inquiry_active_;
}
bool Btm::IsLimitedPeriodicInquiryActive() const {
return limited_periodic_inquiry_active_;
}
/**
* Discoverability
*/
bluetooth::neighbor::ScanParameters params_{
.interval = 0,
.window = 0,
};
void Btm::SetClassicGeneralDiscoverability(uint16_t window, uint16_t interval) {
params_.window = window;
params_.interval = interval;
GetInquiry()->SetScanActivity(params_);
GetDiscoverability()->StartGeneralDiscoverability();
}
void Btm::SetClassicLimitedDiscoverability(uint16_t window, uint16_t interval) {
params_.window = window;
params_.interval = interval;
GetInquiry()->SetScanActivity(params_);
GetDiscoverability()->StartLimitedDiscoverability();
}
void Btm::SetClassicDiscoverabilityOff() {
GetDiscoverability()->StopDiscoverability();
}
DiscoverabilityState Btm::GetClassicDiscoverabilityState() const {
DiscoverabilityState state{.mode = BTM_NON_DISCOVERABLE,
.interval = params_.interval,
.window = params_.window};
if (GetDiscoverability()->IsGeneralDiscoverabilityEnabled()) {
state.mode = BTM_GENERAL_DISCOVERABLE;
} else if (GetDiscoverability()->IsLimitedDiscoverabilityEnabled()) {
state.mode = BTM_LIMITED_DISCOVERABLE;
}
return state;
}
void Btm::SetLeGeneralDiscoverability() {
LOG_WARN("UNIMPLEMENTED %s", __func__);
}
void Btm::SetLeLimitedDiscoverability() {
LOG_WARN("UNIMPLEMENTED %s", __func__);
}
void Btm::SetLeDiscoverabilityOff() { LOG_WARN("UNIMPLEMENTED %s", __func__); }
DiscoverabilityState Btm::GetLeDiscoverabilityState() const {
DiscoverabilityState state{
.mode = kDiscoverableModeOff,
.interval = 0,
.window = 0,
};
LOG_WARN("UNIMPLEMENTED %s", __func__);
return state;
}
/**
* Connectability
*/
void Btm::SetClassicConnectibleOn() {
GetConnectability()->StartConnectability();
}
void Btm::SetClassicConnectibleOff() {
GetConnectability()->StopConnectability();
}
ConnectabilityState Btm::GetClassicConnectabilityState() const {
ConnectabilityState state{.interval = params_.interval,
.window = params_.window};
if (GetConnectability()->IsConnectable()) {
state.mode = BTM_CONNECTABLE;
} else {
state.mode = BTM_NON_CONNECTABLE;
}
return state;
}
void Btm::SetInterlacedPageScan() { GetPage()->SetInterlacedScan(); }
void Btm::SetStandardPageScan() { GetPage()->SetStandardScan(); }
void Btm::SetLeConnectibleOn() { LOG_WARN("UNIMPLEMENTED %s", __func__); }
void Btm::SetLeConnectibleOff() { LOG_WARN("UNIMPLEMENTED %s", __func__); }
ConnectabilityState Btm::GetLeConnectabilityState() const {
ConnectabilityState state{
.mode = kConnectibleModeOff,
.interval = 0,
.window = 0,
};
LOG_WARN("UNIMPLEMENTED %s", __func__);
return state;
}
bool Btm::UseLeLink(const RawAddress& raw_address) const {
if (GetAclManager()->HACK_GetHandle(ToGdAddress(raw_address)) != 0xFFFF) {
return false;
}
if (GetAclManager()->HACK_GetLeHandle(ToGdAddress(raw_address)) != 0xFFFF) {
return true;
}
// TODO(hsz): use correct transport by using storage records. For now assume
// LE for GATT and HID.
return true;
}
BtmStatus Btm::ReadClassicRemoteDeviceName(const RawAddress& raw_address,
tBTM_CMPL_CB* callback) {
if (!CheckClassicAclLink(raw_address)) {
return BTM_UNKNOWN_ADDR;
}
if (!classic_read_remote_name_.Start(raw_address)) {
LOG_INFO("%s Read remote name is currently busy address:%s", __func__,
raw_address.ToString().c_str());
return BTM_BUSY;
}
LOG_INFO("%s Start read name from address:%s", __func__,
raw_address.ToString().c_str());
GetName()->ReadRemoteNameRequest(
ToGdAddress(raw_address), hci::PageScanRepetitionMode::R1,
0 /* clock_offset */, hci::ClockOffsetValid::INVALID,
base::Bind(
[](tBTM_CMPL_CB* callback, ReadRemoteName* classic_read_remote_name,
hci::ErrorCode status, hci::Address address,
std::array<uint8_t, kRemoteDeviceNameLength> remote_name) {
RawAddress raw_address = ToRawAddress(address);
BtmRemoteDeviceName name{
.status = (static_cast<uint8_t>(status) == 0)
? (BTM_SUCCESS)
: (BTM_BAD_VALUE_RET),
.bd_addr = raw_address,
.length = kRemoteDeviceNameLength,
};
std::copy(remote_name.begin(), remote_name.end(),
name.remote_bd_name);
LOG_INFO("%s Finish read name from address:%s name:%s", __func__,
address.ToString().c_str(), name.remote_bd_name);
callback(&name);
classic_read_remote_name->Stop();
},
callback, &classic_read_remote_name_),
GetGdShimHandler());
return BTM_CMD_STARTED;
}
BtmStatus Btm::ReadLeRemoteDeviceName(const RawAddress& raw_address,
tBTM_CMPL_CB* callback) {
if (!CheckLeAclLink(raw_address)) {
return BTM_UNKNOWN_ADDR;
}
if (!le_read_remote_name_.Start(raw_address)) {
return BTM_BUSY;
}
LOG_INFO("UNIMPLEMENTED %s need access to GATT module", __func__);
return BTM_UNKNOWN_ADDR;
}
BtmStatus Btm::CancelAllReadRemoteDeviceName() {
if (classic_read_remote_name_.IsInProgress() ||
le_read_remote_name_.IsInProgress()) {
if (classic_read_remote_name_.IsInProgress()) {
hci::Address address;
hci::Address::FromString(classic_read_remote_name_.AddressString(),
address);
GetName()->CancelRemoteNameRequest(
address,
common::BindOnce(
[](ReadRemoteName* classic_read_remote_name,
hci::ErrorCode status,
hci::Address address) { classic_read_remote_name->Stop(); },
&classic_read_remote_name_),
GetGdShimHandler());
}
if (le_read_remote_name_.IsInProgress()) {
LOG_INFO("UNIMPLEMENTED %s need access to GATT module", __func__);
}
return BTM_UNKNOWN_ADDR;
}
LOG_WARN("%s Cancelling classic remote device name without one in progress",
__func__);
return BTM_WRONG_MODE;
}
void Btm::StartAdvertising() {
if (advertiser_id_ == hci::LeAdvertisingManager::kInvalidId) {
LOG_WARN("%s Already advertising; please stop prior to starting again",
__func__);
return;
}
hci::ExtendedAdvertisingConfig config = {};
advertiser_id_ = GetAdvertising()->ExtendedCreateAdvertiser(
0x00, config,
common::Bind([](hci::Address, hci::AddressType) { /*OnScan*/ }),
common::Bind([](hci::ErrorCode, uint8_t, uint8_t) { /*OnTerminated*/ }),
0, 0, GetGdShimHandler());
if (advertiser_id_ == hci::LeAdvertisingManager::kInvalidId) {
LOG_WARN("%s Unable to start advertising", __func__);
return;
}
LOG_INFO("%s Started advertising", __func__);
}
void Btm::StopAdvertising() {
if (advertiser_id_ == hci::LeAdvertisingManager::kInvalidId) {
LOG_WARN("%s No active advertising", __func__);
return;
}
GetAdvertising()->RemoveAdvertiser(advertiser_id_);
advertiser_id_ = hci::LeAdvertisingManager::kInvalidId;
LOG_INFO("%s Stopped advertising", __func__);
}
void Btm::StartConnectability() { StartAdvertising(); }
void Btm::StopConnectability() { StopAdvertising(); }
void Btm::StartActiveScanning() { StartScanning(kActiveScanning); }
void Btm::StopActiveScanning() { GetScanning()->Scan(false); }
void Btm::SetScanningTimer(uint64_t duration_ms,
common::OnceCallback<void()> callback) {
scanning_timer_.Schedule(std::move(callback),
std::chrono::milliseconds(duration_ms));
}
void Btm::CancelScanningTimer() { scanning_timer_.Cancel(); }
void Btm::StartObserving() { StartScanning(kPassiveScanning); }
void Btm::StopObserving() { StopActiveScanning(); }
void Btm::SetObservingTimer(uint64_t duration_ms,
common::OnceCallback<void()> callback) {
observing_timer_.Schedule(std::move(callback),
std::chrono::milliseconds(duration_ms));
}
void Btm::CancelObservingTimer() { observing_timer_.Cancel(); }
void Btm::StartScanning(bool use_active_scanning) {
GetScanning()->RegisterScanningCallback(&scanning_callbacks_);
GetScanning()->Scan(true);
}
size_t Btm::GetNumberOfAdvertisingInstances() const {
return GetAdvertising()->GetNumberOfAdvertisingInstances();
}
tBTM_STATUS Btm::CreateBond(const RawAddress& bd_addr, tBLE_ADDR_TYPE addr_type,
tBT_TRANSPORT transport, int device_type) {
if (transport == BT_TRANSPORT_UNKNOWN) {
if (device_type & BT_DEVICE_TYPE_BLE) {
transport = BT_TRANSPORT_LE;
} else if (device_type & BT_DEVICE_TYPE_BREDR) {
transport = BT_TRANSPORT_BR_EDR;
}
LOG_INFO("%s guessing transport as %02x ", __func__, transport);
}
auto security_manager = GetSecurityModule()->GetSecurityManager();
switch (transport) {
case BT_TRANSPORT_BR_EDR:
security_manager->CreateBond(ToAddressWithType(bd_addr, BLE_ADDR_PUBLIC));
break;
case BT_TRANSPORT_LE:
security_manager->CreateBondLe(ToAddressWithType(bd_addr, addr_type));
break;
default:
return BTM_ILLEGAL_VALUE;
}
return BTM_CMD_STARTED;
}
bool Btm::CancelBond(const RawAddress& bd_addr) {
auto security_manager = GetSecurityModule()->GetSecurityManager();
security_manager->CancelBond(ToAddressWithType(bd_addr, BLE_ADDR_PUBLIC));
return true;
}
bool Btm::RemoveBond(const RawAddress& bd_addr) {
// TODO(cmanton) Check if acl is connected
auto security_manager = GetSecurityModule()->GetSecurityManager();
security_manager->RemoveBond(ToAddressWithType(bd_addr, BLE_ADDR_PUBLIC));
return true;
}
uint16_t Btm::GetAclHandle(const RawAddress& remote_bda,
tBT_TRANSPORT transport) {
auto acl_manager = GetAclManager();
if (transport == BT_TRANSPORT_BR_EDR) {
return acl_manager->HACK_GetHandle(ToGdAddress(remote_bda));
} else {
return acl_manager->HACK_GetLeHandle(ToGdAddress(remote_bda));
}
}
hci::AddressWithType Btm::GetAddressAndType(const RawAddress& bd_addr) {
tBTM_SEC_DEV_REC* p_dev_rec = btm_find_dev(bd_addr);
if (p_dev_rec != NULL && p_dev_rec->device_type & BT_DEVICE_TYPE_BLE) {
if (!p_dev_rec->ble.identity_address_with_type.bda.IsEmpty()) {
return ToAddressWithType(p_dev_rec->ble.identity_address_with_type.bda,
p_dev_rec->ble.identity_address_with_type.type);
} else {
return ToAddressWithType(p_dev_rec->ble.pseudo_addr,
p_dev_rec->ble.ble_addr_type);
}
}
LOG(ERROR) << "Unknown bd_addr. Use public address";
return ToAddressWithType(bd_addr, BLE_ADDR_PUBLIC);
}
void Btm::Register_HACK_SetScoDisconnectCallback(
HACK_ScoDisconnectCallback callback) {
GetAclManager()->HACK_SetScoDisconnectCallback(callback);
}
} // namespace shim
} // namespace bluetooth