/* * 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. */ #include #include #include #include "hci/acl_manager.h" #include "hci/controller.h" #include "hci/hci_layer.h" #include "hci/hci_packets.h" #include "hci/le_scanning_interface.h" #include "hci/le_scanning_manager.h" #include "hci/vendor_specific_event_manager.h" #include "module.h" #include "os/handler.h" #include "os/log.h" namespace bluetooth { namespace hci { constexpr uint16_t kLeScanWindowMin = 0x0004; constexpr uint16_t kLeScanWindowMax = 0x4000; constexpr uint16_t kDefaultLeExtendedScanWindow = 4800; constexpr uint16_t kLeExtendedScanWindowMax = 0xFFFF; constexpr uint16_t kLeScanIntervalMin = 0x0004; constexpr uint16_t kLeScanIntervalMax = 0x4000; constexpr uint16_t kDefaultLeExtendedScanInterval = 4800; constexpr uint16_t kLeExtendedScanIntervalMax = 0xFFFF; constexpr uint8_t kScannableBit = 1; constexpr uint8_t kDirectedBit = 2; constexpr uint8_t kScanResponseBit = 3; constexpr uint8_t kLegacyBit = 4; constexpr uint8_t kDataStatusBits = 5; const ModuleFactory LeScanningManager::Factory = ModuleFactory([]() { return new LeScanningManager(); }); enum class ScanApiType { LEGACY = 1, ANDROID_HCI = 2, EXTENDED = 3, }; struct Scanner { Uuid app_uuid; bool in_use; }; class AdvertisingCache { public: const std::vector& Set(const AddressWithType& address_with_type, std::vector data) { auto it = Find(address_with_type); if (it != items.end()) { it->data = std::move(data); return it->data; } if (items.size() > cache_max) { items.pop_back(); } items.emplace_front(address_with_type, std::move(data)); return items.front().data; } bool Exist(const AddressWithType& address_with_type) { auto it = Find(address_with_type); if (it == items.end()) { return false; } return true; } const std::vector& Append(const AddressWithType& address_with_type, std::vector data) { auto it = Find(address_with_type); if (it != items.end()) { it->data.insert(it->data.end(), data.begin(), data.end()); return it->data; } if (items.size() > cache_max) { items.pop_back(); } items.emplace_front(address_with_type, std::move(data)); return items.front().data; } /* Clear data for device |addr_type, addr| */ void Clear(AddressWithType address_with_type) { auto it = Find(address_with_type); if (it != items.end()) { items.erase(it); } } void ClearAll() { items.clear(); } struct Item { AddressWithType address_with_type; std::vector data; Item(const AddressWithType& address_with_type, std::vector data) : address_with_type(address_with_type), data(data) {} }; std::list::iterator Find(const AddressWithType& address_with_type) { for (auto it = items.begin(); it != items.end(); it++) { if (it->address_with_type == address_with_type) { return it; } } return items.end(); } /* we keep maximum 7 devices in the cache */ const size_t cache_max = 1000; std::list items; }; class NullScanningCallback : public ScanningCallback { void OnScannerRegistered(const bluetooth::hci::Uuid app_uuid, ScannerId scanner_id, ScanningStatus status) { LOG_INFO("OnScannerRegistered in NullScanningCallback"); } void OnScanResult( uint16_t event_type, uint8_t address_type, 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 advertising_data) { LOG_INFO("OnScanResult in NullScanningCallback"); } void OnTrackAdvFoundLost(AdvertisingFilterOnFoundOnLostInfo on_found_on_lost_info) { LOG_INFO("OnTrackAdvFoundLost in NullScanningCallback"); } void OnBatchScanReports(int client_if, int status, int report_format, int num_records, std::vector data) { LOG_INFO("OnBatchScanReports in NullScanningCallback"); } void OnBatchScanThresholdCrossed(int client_if) { LOG_INFO("OnBatchScanThresholdCrossed in NullScanningCallback"); } void OnTimeout() { LOG_INFO("OnTimeout in NullScanningCallback"); } void OnFilterEnable(Enable enable, uint8_t status) { LOG_INFO("OnFilterEnable in NullScanningCallback"); } void OnFilterParamSetup(uint8_t available_spaces, ApcfAction action, uint8_t status) { LOG_INFO("OnFilterParamSetup in NullScanningCallback"); } void OnFilterConfigCallback( ApcfFilterType filter_type, uint8_t available_spaces, ApcfAction action, uint8_t status) { LOG_INFO("OnFilterConfigCallback in NullScanningCallback"); } }; enum class BatchScanState { ERROR_STATE = 0, ENABLE_CALLED = 1, ENABLED_STATE = 2, DISABLE_CALLED = 3, DISABLED_STATE = 4, }; #define BTM_BLE_BATCH_SCAN_MODE_DISABLE 0 #define BTM_BLE_BATCH_SCAN_MODE_PASS 1 #define BTM_BLE_BATCH_SCAN_MODE_ACTI 2 #define BTM_BLE_BATCH_SCAN_MODE_PASS_ACTI 3 struct BatchScanConfig { BatchScanState current_state; BatchScanMode scan_mode; uint32_t scan_interval; uint32_t scan_window; BatchScanDiscardRule discard_rule; ScannerId ref_value; }; struct LeScanningManager::impl : public bluetooth::hci::LeAddressManagerCallback { impl(Module* module) : module_(module), le_scanning_interface_(nullptr) {} ~impl() { if (address_manager_registered_) { le_address_manager_->Unregister(this); } } void start( os::Handler* handler, hci::HciLayer* hci_layer, hci::Controller* controller, hci::AclManager* acl_manager, hci::VendorSpecificEventManager* vendor_specific_event_manager) { module_handler_ = handler; hci_layer_ = hci_layer; controller_ = controller; vendor_specific_event_manager_ = vendor_specific_event_manager; le_address_manager_ = acl_manager->GetLeAddressManager(); le_scanning_interface_ = hci_layer_->GetLeScanningInterface( module_handler_->BindOn(this, &LeScanningManager::impl::handle_scan_results)); if (controller_->IsSupported(OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS)) { api_type_ = ScanApiType::EXTENDED; interval_ms_ = kDefaultLeExtendedScanInterval; window_ms_ = kDefaultLeExtendedScanWindow; } else if (controller_->IsSupported(OpCode::LE_EXTENDED_SCAN_PARAMS)) { api_type_ = ScanApiType::ANDROID_HCI; } else { api_type_ = ScanApiType::LEGACY; } is_filter_support_ = controller_->IsSupported(OpCode::LE_ADV_FILTER); is_batch_scan_support_ = controller->IsSupported(OpCode::LE_BATCH_SCAN); if (is_batch_scan_support_) { vendor_specific_event_manager_->RegisterEventHandler( VseSubeventCode::BLE_THRESHOLD, handler->BindOn(this, &LeScanningManager::impl::on_storage_threshold_breach)); vendor_specific_event_manager_->RegisterEventHandler( VseSubeventCode::BLE_TRACKING, handler->BindOn(this, &LeScanningManager::impl::on_advertisement_tracking)); } scanners_ = std::vector(kMaxAppNum + 1); for (size_t i = 0; i < scanners_.size(); i++) { scanners_[i].app_uuid = Uuid::kEmpty; scanners_[i].in_use = false; } batch_scan_config_.current_state = BatchScanState::DISABLED_STATE; batch_scan_config_.ref_value = kInvalidScannerId; configure_scan(); } void stop() { for (auto subevent_code : LeScanningEvents) { hci_layer_->UnregisterLeEventHandler(subevent_code); } if (is_batch_scan_support_) { // TODO implete vse module // hci_layer_->UnregisterVesEventHandler(VseSubeventCode::BLE_THRESHOLD); // hci_layer_->UnregisterVesEventHandler(VseSubeventCode::BLE_TRACKING); } batch_scan_config_.current_state = BatchScanState::DISABLED_STATE; batch_scan_config_.ref_value = kInvalidScannerId; scanning_callbacks_ = &null_scanning_callback_; } void handle_scan_results(LeMetaEventView event) { switch (event.GetSubeventCode()) { case hci::SubeventCode::ADVERTISING_REPORT: handle_advertising_report(LeAdvertisingReportView::Create(event)); break; case hci::SubeventCode::DIRECTED_ADVERTISING_REPORT: handle_directed_advertising_report(LeDirectedAdvertisingReportView::Create(event)); break; case hci::SubeventCode::EXTENDED_ADVERTISING_REPORT: handle_extended_advertising_report(LeExtendedAdvertisingReportView::Create(event)); break; case hci::SubeventCode::SCAN_TIMEOUT: scanning_callbacks_->OnTimeout(); break; default: LOG_ALWAYS_FATAL("Unknown advertising subevent %s", hci::SubeventCodeText(event.GetSubeventCode()).c_str()); } } struct ExtendedEventTypeOptions { bool connectable{false}; bool scannable{false}; bool directed{false}; bool scan_response{false}; bool legacy{false}; bool continuing{false}; bool truncated{false}; }; void transform_to_extended_event_type(uint16_t* extended_event_type, ExtendedEventTypeOptions o) { ASSERT(extended_event_type != nullptr); *extended_event_type = (o.connectable ? 0x0001 << 0 : 0) | (o.scannable ? 0x0001 << 1 : 0) | (o.directed ? 0x0001 << 2 : 0) | (o.scan_response ? 0x0001 << 3 : 0) | (o.legacy ? 0x0001 << 4 : 0) | (o.continuing ? 0x0001 << 5 : 0) | (o.truncated ? 0x0001 << 6 : 0); } void handle_advertising_report(LeAdvertisingReportView event_view) { if (!event_view.IsValid()) { LOG_INFO("Dropping invalid advertising event"); return; } std::vector reports = event_view.GetAdvertisingReports(); if (reports.empty()) { LOG_INFO("Zero results in advertising event"); return; } for (LeAdvertisingReport report : reports) { uint16_t extended_event_type = 0; switch (report.event_type_) { case hci::AdvertisingEventType::ADV_IND: transform_to_extended_event_type( &extended_event_type, {.connectable = true, .scannable = true, .legacy = true}); break; case hci::AdvertisingEventType::ADV_DIRECT_IND: transform_to_extended_event_type( &extended_event_type, {.connectable = true, .directed = true, .legacy = true}); break; case hci::AdvertisingEventType::ADV_SCAN_IND: transform_to_extended_event_type(&extended_event_type, {.scannable = true, .legacy = true}); break; case hci::AdvertisingEventType::ADV_NONCONN_IND: transform_to_extended_event_type(&extended_event_type, {.legacy = true}); break; case hci::AdvertisingEventType::SCAN_RESPONSE: transform_to_extended_event_type( &extended_event_type, {.connectable = true, .scannable = true, .scan_response = true, .legacy = true}); break; default: LOG_WARN("Unsupported event type:%d", (uint16_t)report.event_type_); return; } std::vector advertising_data = {}; for (auto gap_data : report.advertising_data_) { advertising_data.push_back((uint8_t)gap_data.size() - 1); advertising_data.push_back((uint8_t)gap_data.data_type_); advertising_data.insert(advertising_data.end(), gap_data.data_.begin(), gap_data.data_.end()); } process_advertising_package_content( extended_event_type, (uint8_t)report.address_type_, report.address_, (uint8_t)PrimaryPhyType::LE_1M, (uint8_t)SecondaryPhyType::NO_PACKETS, kAdvertisingDataInfoNotPresent, kTxPowerInformationNotPresent, report.rssi_, kNotPeriodicAdvertisement, advertising_data); } } void handle_directed_advertising_report(LeDirectedAdvertisingReportView event_view) { if (!event_view.IsValid()) { LOG_INFO("Dropping invalid advertising event"); return; } std::vector reports = event_view.GetAdvertisingReports(); if (reports.empty()) { LOG_INFO("Zero results in advertising event"); return; } uint16_t extended_event_type = 0; transform_to_extended_event_type(&extended_event_type, {.connectable = true, .directed = true, .legacy = true}); // TODO: parse report } void handle_extended_advertising_report(LeExtendedAdvertisingReportView event_view) { if (!event_view.IsValid()) { LOG_INFO("Dropping invalid advertising event"); return; } std::vector reports = event_view.GetAdvertisingReports(); if (reports.empty()) { LOG_INFO("Zero results in advertising event"); return; } for (LeExtendedAdvertisingReport report : reports) { uint16_t event_type = report.connectable_ | (report.scannable_ << kScannableBit) | (report.directed_ << kDirectedBit) | (report.scan_response_ << kScanResponseBit) | (report.legacy_ << kLegacyBit) | ((uint16_t)report.data_status_ << kDataStatusBits); process_advertising_package_content( event_type, (uint8_t)report.address_type_, report.address_, (uint8_t)report.primary_phy_, (uint8_t)report.secondary_phy_, report.advertising_sid_, report.tx_power_, report.rssi_, report.periodic_advertising_interval_, report.advertising_data_); } } void process_advertising_package_content( uint16_t event_type, uint8_t address_type, 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 advertising_data) { bool is_scannable = event_type & (1 << kScannableBit); bool is_scan_response = event_type & (1 << kScanResponseBit); bool is_legacy = event_type & (1 << kLegacyBit); if (address_type == (uint8_t)DirectAdvertisingAddressType::NO_ADDRESS) { scanning_callbacks_->OnScanResult( event_type, address_type, address, primary_phy, secondary_phy, advertising_sid, tx_power, rssi, periodic_advertising_interval, advertising_data); return; } else if (address == Address::kEmpty) { LOG_WARN("Receive non-anonymous advertising report with empty address, skip!"); return; } AddressWithType address_with_type(address, (AddressType)address_type); if (is_legacy && is_scan_response && !advertising_cache_.Exist(address_with_type)) { return; } bool is_start = is_legacy && is_scannable && !is_scan_response; std::vector const& adv_data = is_start ? advertising_cache_.Set(address_with_type, advertising_data) : advertising_cache_.Append(address_with_type, advertising_data); uint8_t data_status = event_type >> kDataStatusBits; if (data_status == (uint8_t)DataStatus::CONTINUING) { // Waiting for whole data return; } if (is_scannable && !is_scan_response) { // Waiting for scan response return; } scanning_callbacks_->OnScanResult( event_type, address_type, address, primary_phy, secondary_phy, advertising_sid, tx_power, rssi, periodic_advertising_interval, adv_data); advertising_cache_.Clear(address_with_type); } void configure_scan() { std::vector parameter_vector; PhyScanParameters phy_scan_parameters; phy_scan_parameters.le_scan_window_ = window_ms_; phy_scan_parameters.le_scan_interval_ = interval_ms_; phy_scan_parameters.le_scan_type_ = le_scan_type_; parameter_vector.push_back(phy_scan_parameters); uint8_t phys_in_use = 1; // The Host shall not issue set scan parameter command when scanning is enabled stop_scan(); if (le_address_manager_->GetAddressPolicy() != LeAddressManager::USE_PUBLIC_ADDRESS) { own_address_type_ = OwnAddressType::RANDOM_DEVICE_ADDRESS; } switch (api_type_) { case ScanApiType::EXTENDED: le_scanning_interface_->EnqueueCommand(hci::LeSetExtendedScanParametersBuilder::Create( own_address_type_, filter_policy_, phys_in_use, parameter_vector), module_handler_->BindOnce(impl::check_status)); break; case ScanApiType::ANDROID_HCI: le_scanning_interface_->EnqueueCommand( hci::LeExtendedScanParamsBuilder::Create(LeScanType::ACTIVE, interval_ms_, window_ms_, own_address_type_, filter_policy_), module_handler_->BindOnce(impl::check_status)); break; case ScanApiType::LEGACY: le_scanning_interface_->EnqueueCommand( hci::LeSetScanParametersBuilder::Create(LeScanType::ACTIVE, interval_ms_, window_ms_, own_address_type_, filter_policy_), module_handler_->BindOnce(impl::check_status)); break; } } void register_scanner(const Uuid app_uuid) { for (uint8_t i = 1; i <= kMaxAppNum; i++) { if (scanners_[i].in_use && scanners_[i].app_uuid == app_uuid) { LOG_ERROR("Application already registered %s", app_uuid.ToString().c_str()); scanning_callbacks_->OnScannerRegistered(app_uuid, 0x00, ScanningCallback::ScanningStatus::INTERNAL_ERROR); return; } } // valid value of scanner id : 1 ~ kMaxAppNum for (uint8_t i = 1; i <= kMaxAppNum; i++) { if (!scanners_[i].in_use) { scanners_[i].app_uuid = app_uuid; scanners_[i].in_use = true; scanning_callbacks_->OnScannerRegistered(app_uuid, i, ScanningCallback::ScanningStatus::SUCCESS); return; } } LOG_ERROR("Unable to register scanner, max client reached:%d", kMaxAppNum); scanning_callbacks_->OnScannerRegistered(app_uuid, 0x00, ScanningCallback::ScanningStatus::NO_RESOURCES); } void unregister_scanner(ScannerId scanner_id) { if (scanner_id <= 0 || scanner_id > kMaxAppNum) { LOG_WARN("Invalid scanner id"); return; } if (scanners_[scanner_id].in_use) { scanners_[scanner_id].in_use = false; scanners_[scanner_id].app_uuid = Uuid::kEmpty; } else { LOG_WARN("Unregister scanner with unused scanner id"); } } void scan(bool start) { if (start) { configure_scan(); start_scan(); } else { if (address_manager_registered_) { le_address_manager_->Unregister(this); address_manager_registered_ = false; } stop_scan(); } } void start_scan() { // If we receive start_scan during paused, set scan_on_resume_ to true if (paused_) { scan_on_resume_ = true; return; } is_scanning_ = true; if (!address_manager_registered_) { le_address_manager_->Register(this); address_manager_registered_ = true; } switch (api_type_) { case ScanApiType::EXTENDED: le_scanning_interface_->EnqueueCommand( hci::LeSetExtendedScanEnableBuilder::Create( Enable::ENABLED, FilterDuplicates::DISABLED /* filter duplicates */, 0, 0), module_handler_->BindOnce(impl::check_status)); break; case ScanApiType::ANDROID_HCI: case ScanApiType::LEGACY: le_scanning_interface_->EnqueueCommand( hci::LeSetScanEnableBuilder::Create(Enable::ENABLED, Enable::DISABLED /* filter duplicates */), module_handler_->BindOnce(impl::check_status)); break; } } void stop_scan() { is_scanning_ = false; switch (api_type_) { case ScanApiType::EXTENDED: le_scanning_interface_->EnqueueCommand( hci::LeSetExtendedScanEnableBuilder::Create( Enable::DISABLED, FilterDuplicates::DISABLED /* filter duplicates */, 0, 0), module_handler_->BindOnce(impl::check_status)); break; case ScanApiType::ANDROID_HCI: case ScanApiType::LEGACY: le_scanning_interface_->EnqueueCommand( hci::LeSetScanEnableBuilder::Create(Enable::DISABLED, Enable::DISABLED /* filter duplicates */), module_handler_->BindOnce(impl::check_status)); break; } } void set_scan_parameters(LeScanType scan_type, uint16_t scan_interval, uint16_t scan_window) { uint32_t max_scan_interval = kLeScanIntervalMax; uint32_t max_scan_window = kLeScanWindowMax; if (api_type_ == ScanApiType::EXTENDED) { max_scan_interval = kLeExtendedScanIntervalMax; max_scan_window = kLeExtendedScanWindowMax; } if (scan_type != LeScanType::ACTIVE && scan_type != LeScanType::PASSIVE) { LOG_ERROR("Invalid scan type"); return; } if (scan_interval > max_scan_interval || scan_interval < kLeScanIntervalMin) { LOG_ERROR("Invalid scan_interval %d", scan_interval); return; } if (scan_window > max_scan_window || scan_window < kLeScanWindowMin) { LOG_ERROR("Invalid scan_window %d", scan_window); return; } le_scan_type_ = scan_type; interval_ms_ = scan_interval; window_ms_ = scan_window; } void scan_filter_enable(bool enable) { if (!is_filter_support_) { LOG_WARN("Advertising filter is not supported"); return; } Enable apcf_enable = enable ? Enable::ENABLED : Enable::DISABLED; le_scanning_interface_->EnqueueCommand( LeAdvFilterEnableBuilder::Create(apcf_enable), module_handler_->BindOnceOn(this, &impl::on_advertising_filter_complete)); } void scan_filter_parameter_setup( ApcfAction action, uint8_t filter_index, AdvertisingFilterParameter advertising_filter_parameter) { if (!is_filter_support_) { LOG_WARN("Advertising filter is not supported"); return; } switch (action) { case ApcfAction::ADD: le_scanning_interface_->EnqueueCommand( LeAdvFilterAddFilteringParametersBuilder::Create( filter_index, advertising_filter_parameter.feature_selection, advertising_filter_parameter.list_logic_type, advertising_filter_parameter.filter_logic_type, advertising_filter_parameter.rssi_high_thresh, advertising_filter_parameter.delivery_mode, advertising_filter_parameter.onfound_timeout, advertising_filter_parameter.onfound_timeout_cnt, advertising_filter_parameter.rssi_low_thres, advertising_filter_parameter.onlost_timeout, advertising_filter_parameter.num_of_tracking_entries), module_handler_->BindOnceOn(this, &impl::on_advertising_filter_complete)); break; case ApcfAction::DELETE: le_scanning_interface_->EnqueueCommand( LeAdvFilterDeleteFilteringParametersBuilder::Create(filter_index), module_handler_->BindOnceOn(this, &impl::on_advertising_filter_complete)); break; case ApcfAction::CLEAR: le_scanning_interface_->EnqueueCommand( LeAdvFilterClearFilteringParametersBuilder::Create(), module_handler_->BindOnceOn(this, &impl::on_advertising_filter_complete)); break; default: LOG_ERROR("Unknown action type: %d", (uint16_t)action); break; } } void scan_filter_add(uint8_t filter_index, std::vector filters) { if (!is_filter_support_) { LOG_WARN("Advertising filter is not supported"); return; } ApcfAction apcf_action = ApcfAction::ADD; for (auto filter : filters) { /* If data is passed, both mask and data have to be the same length */ if (filter.data.size() != filter.data_mask.size() && filter.data.size() != 0 && filter.data_mask.size() != 0) { LOG_ERROR("data and data_mask are of different size"); continue; } switch (filter.filter_type) { case ApcfFilterType::BROADCASTER_ADDRESS: { update_address_filter(apcf_action, filter_index, filter.address, filter.application_address_type); break; } case ApcfFilterType::SERVICE_UUID: case ApcfFilterType::SERVICE_SOLICITATION_UUID: { update_uuid_filter(apcf_action, filter_index, filter.filter_type, filter.uuid, filter.uuid_mask); break; } case ApcfFilterType::LOCAL_NAME: { update_local_name_filter(apcf_action, filter_index, filter.name); break; } case ApcfFilterType::MANUFACTURER_DATA: { update_manufacturer_data_filter( apcf_action, filter_index, filter.company, filter.company_mask, filter.data, filter.data_mask); break; } case ApcfFilterType::SERVICE_DATA: { update_service_data_filter(apcf_action, filter_index, filter.data, filter.data_mask); break; } default: LOG_ERROR("Unknown filter type: %d", (uint16_t)filter.filter_type); break; } } } void update_address_filter( ApcfAction action, uint8_t filter_index, Address address, ApcfApplicationAddressType address_type) { if (action != ApcfAction::CLEAR) { le_scanning_interface_->EnqueueCommand( LeAdvFilterBroadcasterAddressBuilder::Create(action, filter_index, address, address_type), module_handler_->BindOnceOn(this, &impl::on_advertising_filter_complete)); } else { le_scanning_interface_->EnqueueCommand( LeAdvFilterClearBroadcasterAddressBuilder::Create(filter_index), module_handler_->BindOnceOn(this, &impl::on_advertising_filter_complete)); } } void update_uuid_filter( ApcfAction action, uint8_t filter_index, ApcfFilterType filter_type, Uuid uuid, Uuid uuid_mask) { std::vector combined_data = {}; if (action != ApcfAction::CLEAR) { uint8_t uuid_len = uuid.GetShortestRepresentationSize(); if (uuid_len == Uuid::kNumBytes16) { uint16_t data = uuid.As16Bit(); combined_data.push_back((uint8_t)data); combined_data.push_back((uint8_t)(data >> 8)); } else if (uuid_len == Uuid::kNumBytes32) { uint16_t data = uuid.As32Bit(); combined_data.push_back((uint8_t)data); combined_data.push_back((uint8_t)(data >> 8)); combined_data.push_back((uint8_t)(data >> 16)); combined_data.push_back((uint8_t)(data >> 24)); } else if (uuid_len == Uuid::kNumBytes128) { auto data = uuid.To128BitLE(); combined_data.insert(combined_data.end(), data.begin(), data.end()); } else { LOG_ERROR("illegal UUID length: %d", (uint16_t)uuid_len); return; } if (!uuid_mask.IsEmpty()) { if (uuid_len == Uuid::kNumBytes16) { uint16_t data = uuid_mask.As16Bit(); combined_data.push_back((uint8_t)data); combined_data.push_back((uint8_t)(data >> 8)); } else if (uuid_len == Uuid::kNumBytes32) { uint16_t data = uuid_mask.As32Bit(); combined_data.push_back((uint8_t)data); combined_data.push_back((uint8_t)(data >> 8)); combined_data.push_back((uint8_t)(data >> 16)); combined_data.push_back((uint8_t)(data >> 24)); } else if (uuid_len == Uuid::kNumBytes128) { auto data = uuid_mask.To128BitLE(); combined_data.insert(combined_data.end(), data.begin(), data.end()); } } else { std::vector data(uuid_len, 0xFF); combined_data.insert(combined_data.end(), data.begin(), data.end()); } } if (filter_type == ApcfFilterType::SERVICE_UUID) { le_scanning_interface_->EnqueueCommand( LeAdvFilterServiceUuidBuilder::Create(action, filter_index, combined_data), module_handler_->BindOnceOn(this, &impl::on_advertising_filter_complete)); } else { le_scanning_interface_->EnqueueCommand( LeAdvFilterSolicitationUuidBuilder::Create(action, filter_index, combined_data), module_handler_->BindOnceOn(this, &impl::on_advertising_filter_complete)); } } void update_local_name_filter(ApcfAction action, uint8_t filter_index, std::vector name) { le_scanning_interface_->EnqueueCommand( LeAdvFilterLocalNameBuilder::Create(action, filter_index, name), module_handler_->BindOnceOn(this, &impl::on_advertising_filter_complete)); } void update_manufacturer_data_filter( ApcfAction action, uint8_t filter_index, uint16_t company_id, uint16_t company_id_mask, std::vector data, std::vector data_mask) { if (data.size() != data_mask.size()) { LOG_ERROR("manufacturer data mask should have the same length as manufacturer data"); return; } std::vector combined_data = {}; if (action != ApcfAction::CLEAR) { combined_data.push_back((uint8_t)company_id); combined_data.push_back((uint8_t)(company_id >> 8)); if (data.size() != 0) { combined_data.insert(combined_data.end(), data.begin(), data.end()); } if (company_id_mask != 0) { combined_data.push_back((uint8_t)company_id_mask); combined_data.push_back((uint8_t)(company_id_mask >> 8)); } else { combined_data.push_back(0xFF); combined_data.push_back(0xFF); } if (data_mask.size() != 0) { combined_data.insert(combined_data.end(), data_mask.begin(), data_mask.end()); } } le_scanning_interface_->EnqueueCommand( LeAdvFilterManufacturerDataBuilder::Create(action, filter_index, combined_data), module_handler_->BindOnceOn(this, &impl::on_advertising_filter_complete)); } void update_service_data_filter( ApcfAction action, uint8_t filter_index, std::vector data, std::vector data_mask) { if (data.size() != data_mask.size()) { LOG_ERROR("service data mask should have the same length as service data"); return; } std::vector combined_data = {}; if (action != ApcfAction::CLEAR && data.size() != 0) { combined_data.insert(combined_data.end(), data.begin(), data.end()); combined_data.insert(combined_data.end(), data_mask.begin(), data_mask.end()); } le_scanning_interface_->EnqueueCommand( LeAdvFilterServiceDataBuilder::Create(action, filter_index, combined_data), module_handler_->BindOnceOn(this, &impl::on_advertising_filter_complete)); } void batch_scan_set_storage_parameter( uint8_t batch_scan_full_max, uint8_t batch_scan_truncated_max, uint8_t batch_scan_notify_threshold, ScannerId scanner_id) { if (!is_batch_scan_support_) { LOG_WARN("Batch scan is not supported"); return; } // scanner id for OnBatchScanThresholdCrossed batch_scan_config_.ref_value = scanner_id; if (batch_scan_config_.current_state == BatchScanState::ERROR_STATE || batch_scan_config_.current_state == BatchScanState::DISABLED_STATE || batch_scan_config_.current_state == BatchScanState::DISABLE_CALLED) { batch_scan_config_.current_state = BatchScanState::ENABLE_CALLED; le_scanning_interface_->EnqueueCommand( LeBatchScanEnableBuilder::Create(Enable::ENABLED), module_handler_->BindOnceOn(this, &impl::on_batch_scan_enable_complete)); } le_scanning_interface_->EnqueueCommand( LeBatchScanSetStorageParametersBuilder::Create( batch_scan_full_max, batch_scan_truncated_max, batch_scan_notify_threshold), module_handler_->BindOnceOn(this, &impl::on_batch_scan_complete)); } void batch_scan_enable( BatchScanMode scan_mode, uint32_t duty_cycle_scan_window_slots, uint32_t duty_cycle_scan_interval_slots, BatchScanDiscardRule batch_scan_discard_rule) { if (!is_batch_scan_support_) { LOG_WARN("Batch scan is not supported"); return; } if (batch_scan_config_.current_state == BatchScanState::ERROR_STATE || batch_scan_config_.current_state == BatchScanState::DISABLED_STATE || batch_scan_config_.current_state == BatchScanState::DISABLE_CALLED) { batch_scan_config_.current_state = BatchScanState::ENABLE_CALLED; le_scanning_interface_->EnqueueCommand( LeBatchScanEnableBuilder::Create(Enable::ENABLED), module_handler_->BindOnceOn(this, &impl::on_batch_scan_enable_complete)); } batch_scan_config_.scan_mode = scan_mode; batch_scan_config_.scan_interval = duty_cycle_scan_interval_slots; batch_scan_config_.scan_window = duty_cycle_scan_window_slots; batch_scan_config_.discard_rule = batch_scan_discard_rule; /* This command starts batch scanning, if enabled */ batch_scan_set_scan_parameter( scan_mode, duty_cycle_scan_window_slots, duty_cycle_scan_interval_slots, batch_scan_discard_rule); } void batch_scan_disable() { if (!is_batch_scan_support_) { LOG_WARN("Batch scan is not supported"); return; } batch_scan_config_.current_state = BatchScanState::DISABLE_CALLED; batch_scan_set_scan_parameter( BatchScanMode::DISABLE, batch_scan_config_.scan_window, batch_scan_config_.scan_interval, batch_scan_config_.discard_rule); } void batch_scan_set_scan_parameter( BatchScanMode scan_mode, uint32_t duty_cycle_scan_window_slots, uint32_t duty_cycle_scan_interval_slots, BatchScanDiscardRule batch_scan_discard_rule) { if (!is_batch_scan_support_) { LOG_WARN("Batch scan is not supported"); return; } AdvertisingAddressType own_address_type = AdvertisingAddressType::PUBLIC_ADDRESS; if (own_address_type_ == OwnAddressType::RANDOM_DEVICE_ADDRESS || own_address_type_ == OwnAddressType::RESOLVABLE_OR_RANDOM_ADDRESS) { own_address_type = AdvertisingAddressType::RANDOM_ADDRESS; } uint8_t truncated_mode_enabled = 0x00; uint8_t full_mode_enabled = 0x00; if (scan_mode == BatchScanMode::TRUNCATED || scan_mode == BatchScanMode::TRUNCATED_AND_FULL) { truncated_mode_enabled = 0x01; } if (scan_mode == BatchScanMode::FULL || scan_mode == BatchScanMode::TRUNCATED_AND_FULL) { full_mode_enabled = 0x01; } if (scan_mode == BatchScanMode::DISABLE) { le_scanning_interface_->EnqueueCommand( LeBatchScanSetScanParametersBuilder::Create( truncated_mode_enabled, full_mode_enabled, duty_cycle_scan_window_slots, duty_cycle_scan_interval_slots, own_address_type, batch_scan_discard_rule), module_handler_->BindOnceOn(this, &impl::on_batch_scan_disable_complete)); } else { le_scanning_interface_->EnqueueCommand( LeBatchScanSetScanParametersBuilder::Create( truncated_mode_enabled, full_mode_enabled, duty_cycle_scan_window_slots, duty_cycle_scan_interval_slots, own_address_type, batch_scan_discard_rule), module_handler_->BindOnceOn(this, &impl::on_batch_scan_complete)); } } void batch_scan_read_results(ScannerId scanner_id, uint16_t total_num_of_records, BatchScanMode scan_mode) { if (!is_batch_scan_support_) { LOG_WARN("Batch scan is not supported"); int status = static_cast(ErrorCode::UNSUPORTED_FEATURE_OR_PARAMETER_VALUE); scanning_callbacks_->OnBatchScanReports(scanner_id, status, 0, 0, {}); return; } if (scan_mode != BatchScanMode::FULL && scan_mode != BatchScanMode::TRUNCATED) { LOG_WARN("Invalid scan mode %d", (uint16_t)scan_mode); int status = static_cast(ErrorCode::INVALID_HCI_COMMAND_PARAMETERS); scanning_callbacks_->OnBatchScanReports(scanner_id, status, 0, 0, {}); return; } if (batch_scan_result_cache_.find(scanner_id) == batch_scan_result_cache_.end()) { std::vector empty_data = {}; batch_scan_result_cache_.emplace(scanner_id, empty_data); } le_scanning_interface_->EnqueueCommand( LeBatchScanReadResultParametersBuilder::Create(static_cast(scan_mode)), module_handler_->BindOnceOn(this, &impl::on_batch_scan_read_result_complete, scanner_id, total_num_of_records)); } void track_advertiser(ScannerId scanner_id) { if (!is_batch_scan_support_) { LOG_WARN("Batch scan is not supported"); AdvertisingFilterOnFoundOnLostInfo on_found_on_lost_info = {}; on_found_on_lost_info.scanner_id = scanner_id; on_found_on_lost_info.advertiser_info_present = AdvtInfoPresent::NO_ADVT_INFO_PRESENT; scanning_callbacks_->OnTrackAdvFoundLost(on_found_on_lost_info); return; } tracker_id = scanner_id; } void register_scanning_callback(ScanningCallback* scanning_callbacks) { scanning_callbacks_ = scanning_callbacks; } void on_advertising_filter_complete(CommandCompleteView view) { ASSERT(view.IsValid()); auto status_view = LeAdvFilterCompleteView::Create(view); ASSERT(status_view.IsValid()); if (status_view.GetStatus() != ErrorCode::SUCCESS) { LOG_INFO( "Got a Command complete %s, status %s", OpCodeText(view.GetCommandOpCode()).c_str(), ErrorCodeText(status_view.GetStatus()).c_str()); } ApcfOpcode apcf_opcode = status_view.GetApcfOpcode(); switch (apcf_opcode) { case ApcfOpcode::ENABLE: { auto complete_view = LeAdvFilterEnableCompleteView::Create(status_view); ASSERT(complete_view.IsValid()); scanning_callbacks_->OnFilterEnable(complete_view.GetApcfEnable(), (uint8_t)complete_view.GetStatus()); } break; case ApcfOpcode::SET_FILTERING_PARAMETERS: { auto complete_view = LeAdvFilterSetFilteringParametersCompleteView::Create(status_view); ASSERT(complete_view.IsValid()); scanning_callbacks_->OnFilterParamSetup( complete_view.GetApcfAvailableSpaces(), complete_view.GetApcfAction(), (uint8_t)complete_view.GetStatus()); } break; case ApcfOpcode::BROADCASTER_ADDRESS: { auto complete_view = LeAdvFilterBroadcasterAddressCompleteView::Create(status_view); ASSERT(complete_view.IsValid()); scanning_callbacks_->OnFilterConfigCallback( ApcfFilterType::BROADCASTER_ADDRESS, complete_view.GetApcfAvailableSpaces(), complete_view.GetApcfAction(), (uint8_t)complete_view.GetStatus()); } break; case ApcfOpcode::SERVICE_UUID: { auto complete_view = LeAdvFilterServiceUuidCompleteView::Create(status_view); ASSERT(complete_view.IsValid()); scanning_callbacks_->OnFilterConfigCallback( ApcfFilterType::SERVICE_UUID, complete_view.GetApcfAvailableSpaces(), complete_view.GetApcfAction(), (uint8_t)complete_view.GetStatus()); } break; case ApcfOpcode::SERVICE_SOLICITATION_UUID: { auto complete_view = LeAdvFilterSolicitationUuidCompleteView::Create(status_view); ASSERT(complete_view.IsValid()); scanning_callbacks_->OnFilterConfigCallback( ApcfFilterType::SERVICE_SOLICITATION_UUID, complete_view.GetApcfAvailableSpaces(), complete_view.GetApcfAction(), (uint8_t)complete_view.GetStatus()); } break; case ApcfOpcode::LOCAL_NAME: { auto complete_view = LeAdvFilterLocalNameCompleteView::Create(status_view); ASSERT(complete_view.IsValid()); scanning_callbacks_->OnFilterConfigCallback( ApcfFilterType::LOCAL_NAME, complete_view.GetApcfAvailableSpaces(), complete_view.GetApcfAction(), (uint8_t)complete_view.GetStatus()); } break; case ApcfOpcode::MANUFACTURER_DATA: { auto complete_view = LeAdvFilterManufacturerDataCompleteView::Create(status_view); ASSERT(complete_view.IsValid()); scanning_callbacks_->OnFilterConfigCallback( ApcfFilterType::MANUFACTURER_DATA, complete_view.GetApcfAvailableSpaces(), complete_view.GetApcfAction(), (uint8_t)complete_view.GetStatus()); } break; case ApcfOpcode::SERVICE_DATA: { auto complete_view = LeAdvFilterServiceDataCompleteView::Create(status_view); ASSERT(complete_view.IsValid()); scanning_callbacks_->OnFilterConfigCallback( ApcfFilterType::SERVICE_DATA, complete_view.GetApcfAvailableSpaces(), complete_view.GetApcfAction(), (uint8_t)complete_view.GetStatus()); } break; default: LOG_WARN("Unexpected event type %s", OpCodeText(view.GetCommandOpCode()).c_str()); } } void on_batch_scan_complete(CommandCompleteView view) { ASSERT(view.IsValid()); auto status_view = LeBatchScanCompleteView::Create(view); ASSERT(status_view.IsValid()); if (status_view.GetStatus() != ErrorCode::SUCCESS) { LOG_INFO( "Got a Command complete %s, status %s, batch_scan_opcode %s", OpCodeText(view.GetCommandOpCode()).c_str(), ErrorCodeText(status_view.GetStatus()).c_str(), BatchScanOpcodeText(status_view.GetBatchScanOpcode()).c_str()); } } void on_batch_scan_enable_complete(CommandCompleteView view) { ASSERT(view.IsValid()); auto status_view = LeBatchScanCompleteView::Create(view); ASSERT(status_view.IsValid()); auto complete_view = LeBatchScanEnableCompleteView::Create(status_view); ASSERT(complete_view.IsValid()); if (status_view.GetStatus() != ErrorCode::SUCCESS) { LOG_INFO("Got batch scan enable complete, status %s", ErrorCodeText(status_view.GetStatus()).c_str()); batch_scan_config_.current_state = BatchScanState::ERROR_STATE; } else { batch_scan_config_.current_state = BatchScanState::ENABLED_STATE; } } void on_batch_scan_disable_complete(CommandCompleteView view) { ASSERT(view.IsValid()); auto status_view = LeBatchScanCompleteView::Create(view); ASSERT(status_view.IsValid()); auto complete_view = LeBatchScanSetScanParametersCompleteView::Create(status_view); ASSERT(complete_view.IsValid()); ASSERT(status_view.GetStatus() == ErrorCode::SUCCESS); batch_scan_config_.current_state = BatchScanState::DISABLED_STATE; } void on_batch_scan_read_result_complete( ScannerId scanner_id, uint16_t total_num_of_records, CommandCompleteView view) { ASSERT(view.IsValid()); auto status_view = LeBatchScanCompleteView::Create(view); ASSERT(status_view.IsValid()); auto complete_view = LeBatchScanReadResultParametersCompleteRawView::Create(status_view); ASSERT(complete_view.IsValid()); if (complete_view.GetStatus() != ErrorCode::SUCCESS) { LOG_INFO("Got batch scan read result complete, status %s", ErrorCodeText(status_view.GetStatus()).c_str()); } uint8_t num_of_records = complete_view.GetNumOfRecords(); auto report_format = complete_view.GetBatchScanDataRead(); if (num_of_records == 0) { scanning_callbacks_->OnBatchScanReports( scanner_id, 0x00, (int)report_format, total_num_of_records, batch_scan_result_cache_[scanner_id]); batch_scan_result_cache_.erase(scanner_id); } else { auto raw_data = complete_view.GetRawData(); batch_scan_result_cache_[scanner_id].insert( batch_scan_result_cache_[scanner_id].end(), raw_data.begin(), raw_data.end()); total_num_of_records += num_of_records; batch_scan_read_results(scanner_id, total_num_of_records, static_cast(report_format)); } } void on_storage_threshold_breach(VendorSpecificEventView event) { if (batch_scan_config_.ref_value == kInvalidScannerId) { LOG_WARN("storage threshold was not set !!"); return; } scanning_callbacks_->OnBatchScanThresholdCrossed(static_cast(batch_scan_config_.ref_value)); } void on_advertisement_tracking(VendorSpecificEventView event) { if (tracker_id == kInvalidScannerId) { LOG_WARN("Advertisement track is not register"); return; } auto view = LEAdvertisementTrackingEventView::Create(event); ASSERT(view.IsValid()); AdvertisingFilterOnFoundOnLostInfo on_found_on_lost_info = {}; on_found_on_lost_info.scanner_id = tracker_id; on_found_on_lost_info.filter_index = view.GetApcfFilterIndex(); on_found_on_lost_info.advertiser_state = view.GetAdvertiserState(); on_found_on_lost_info.advertiser_address = view.GetAdvertiserAddress(); on_found_on_lost_info.advertiser_address_type = view.GetAdvertiserAddressType(); on_found_on_lost_info.advertiser_info_present = view.GetAdvtInfoPresent(); /* Extract the adv info details */ if (on_found_on_lost_info.advertiser_info_present == AdvtInfoPresent::ADVT_INFO_PRESENT) { auto info_view = LEAdvertisementTrackingWithInfoEventView::Create(view); ASSERT(info_view.IsValid()); on_found_on_lost_info.tx_power = info_view.GetTxPower(); on_found_on_lost_info.rssi = info_view.GetRssi(); on_found_on_lost_info.time_stamp = info_view.GetTimestamp(); auto adv_data = info_view.GetAdvPacket(); on_found_on_lost_info.adv_packet.reserve(adv_data.size()); on_found_on_lost_info.adv_packet.insert(on_found_on_lost_info.adv_packet.end(), adv_data.begin(), adv_data.end()); auto scan_rsp_data = info_view.GetScanResponse(); on_found_on_lost_info.scan_response.reserve(scan_rsp_data.size()); on_found_on_lost_info.scan_response.insert( on_found_on_lost_info.scan_response.end(), scan_rsp_data.begin(), scan_rsp_data.end()); } scanning_callbacks_->OnTrackAdvFoundLost(on_found_on_lost_info); } void OnPause() override { paused_ = true; scan_on_resume_ = is_scanning_; stop_scan(); ack_pause(); } void ack_pause() { le_address_manager_->AckPause(this); } void OnResume() override { paused_ = false; if (scan_on_resume_ == true) { start_scan(); } le_address_manager_->AckResume(this); } ScanApiType api_type_; Module* module_; os::Handler* module_handler_; hci::HciLayer* hci_layer_; hci::Controller* controller_; hci::VendorSpecificEventManager* vendor_specific_event_manager_; hci::LeScanningInterface* le_scanning_interface_; hci::LeAddressManager* le_address_manager_; bool address_manager_registered_ = false; NullScanningCallback null_scanning_callback_; ScanningCallback* scanning_callbacks_ = &null_scanning_callback_; std::vector scanners_; bool is_scanning_ = false; bool scan_on_resume_ = false; bool paused_ = false; AdvertisingCache advertising_cache_; bool is_filter_support_ = false; bool is_batch_scan_support_ = false; LeScanType le_scan_type_ = LeScanType::ACTIVE; uint32_t interval_ms_{1000}; uint16_t window_ms_{1000}; OwnAddressType own_address_type_{OwnAddressType::PUBLIC_DEVICE_ADDRESS}; LeScanningFilterPolicy filter_policy_{LeScanningFilterPolicy::ACCEPT_ALL}; BatchScanConfig batch_scan_config_; std::map> batch_scan_result_cache_; ScannerId tracker_id = kInvalidScannerId; static void check_status(CommandCompleteView view) { switch (view.GetCommandOpCode()) { case (OpCode::LE_SET_SCAN_ENABLE): { auto status_view = LeSetScanEnableCompleteView::Create(view); ASSERT(status_view.IsValid()); ASSERT(status_view.GetStatus() == ErrorCode::SUCCESS); } break; case (OpCode::LE_SET_EXTENDED_SCAN_ENABLE): { auto status_view = LeSetExtendedScanEnableCompleteView::Create(view); ASSERT(status_view.IsValid()); ASSERT(status_view.GetStatus() == ErrorCode::SUCCESS); } break; case (OpCode::LE_SET_SCAN_PARAMETERS): { auto status_view = LeSetScanParametersCompleteView::Create(view); ASSERT(status_view.IsValid()); ASSERT(status_view.GetStatus() == ErrorCode::SUCCESS); } break; case (OpCode::LE_EXTENDED_SCAN_PARAMS): { auto status_view = LeExtendedScanParamsCompleteView::Create(view); ASSERT(status_view.IsValid()); ASSERT(status_view.GetStatus() == ErrorCode::SUCCESS); } break; case (OpCode::LE_SET_EXTENDED_SCAN_PARAMETERS): { auto status_view = LeSetExtendedScanParametersCompleteView::Create(view); ASSERT(status_view.IsValid()); ASSERT(status_view.GetStatus() == ErrorCode::SUCCESS); } break; default: LOG_ALWAYS_FATAL("Unhandled event %s", OpCodeText(view.GetCommandOpCode()).c_str()); } } }; LeScanningManager::LeScanningManager() { pimpl_ = std::make_unique(this); } void LeScanningManager::ListDependencies(ModuleList* list) { list->add(); list->add(); list->add(); list->add(); } void LeScanningManager::Start() { pimpl_->start( GetHandler(), GetDependency(), GetDependency(), GetDependency(), GetDependency()); } void LeScanningManager::Stop() { pimpl_->stop(); pimpl_.reset(); } std::string LeScanningManager::ToString() const { return "Le Scanning Manager"; } void LeScanningManager::RegisterScanner(Uuid app_uuid) { CallOn(pimpl_.get(), &impl::register_scanner, app_uuid); } void LeScanningManager::Unregister(ScannerId scanner_id) { CallOn(pimpl_.get(), &impl::unregister_scanner, scanner_id); } void LeScanningManager::Scan(bool start) { CallOn(pimpl_.get(), &impl::scan, start); } void LeScanningManager::SetScanParameters(LeScanType scan_type, uint16_t scan_interval, uint16_t scan_window) { CallOn(pimpl_.get(), &impl::set_scan_parameters, scan_type, scan_interval, scan_window); } void LeScanningManager::ScanFilterEnable(bool enable) { CallOn(pimpl_.get(), &impl::scan_filter_enable, enable); } void LeScanningManager::ScanFilterParameterSetup( ApcfAction action, uint8_t filter_index, AdvertisingFilterParameter advertising_filter_parameter) { CallOn(pimpl_.get(), &impl::scan_filter_parameter_setup, action, filter_index, advertising_filter_parameter); } void LeScanningManager::ScanFilterAdd( uint8_t filter_index, std::vector filters) { CallOn(pimpl_.get(), &impl::scan_filter_add, filter_index, filters); } void LeScanningManager::BatchScanConifgStorage( uint8_t batch_scan_full_max, uint8_t batch_scan_truncated_max, uint8_t batch_scan_notify_threshold, ScannerId scanner_id) { CallOn( pimpl_.get(), &impl::batch_scan_set_storage_parameter, batch_scan_full_max, batch_scan_truncated_max, batch_scan_notify_threshold, scanner_id); } void LeScanningManager::BatchScanEnable( BatchScanMode scan_mode, uint32_t duty_cycle_scan_window_slots, uint32_t duty_cycle_scan_interval_slots, BatchScanDiscardRule batch_scan_discard_rule) { CallOn( pimpl_.get(), &impl::batch_scan_enable, scan_mode, duty_cycle_scan_window_slots, duty_cycle_scan_interval_slots, batch_scan_discard_rule); } void LeScanningManager::BatchScanDisable() { CallOn(pimpl_.get(), &impl::batch_scan_disable); } void LeScanningManager::BatchScanReadReport(ScannerId scanner_id, BatchScanMode scan_mode) { CallOn(pimpl_.get(), &impl::batch_scan_read_results, scanner_id, 0, scan_mode); } void LeScanningManager::TrackAdvertiser(ScannerId scanner_id) { CallOn(pimpl_.get(), &impl::track_advertiser, scanner_id); } void LeScanningManager::RegisterScanningCallback(ScanningCallback* scanning_callback) { CallOn(pimpl_.get(), &impl::register_scanning_callback, scanning_callback); } } // namespace hci } // namespace bluetooth