// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/quick_pair/keyed_service/quick_pair_mediator.h"
#include <cstdint>
#include <memory>
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/bluetooth_config_service.h"
#include "ash/quick_pair/common/device.h"
#include "ash/quick_pair/companion_app/companion_app_broker_impl.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_gatt_service_client_lookup_impl.h"
#include "ash/quick_pair/fast_pair_handshake/fast_pair_handshake_lookup.h"
#include "ash/quick_pair/feature_status_tracker/fast_pair_pref_enabled_provider.h"
#include "ash/quick_pair/feature_status_tracker/quick_pair_feature_status_tracker.h"
#include "ash/quick_pair/feature_status_tracker/quick_pair_feature_status_tracker_impl.h"
#include "ash/quick_pair/keyed_service/battery_update_message_handler.h"
#include "ash/quick_pair/keyed_service/fast_pair_bluetooth_config_delegate.h"
#include "ash/quick_pair/keyed_service/quick_pair_metrics_logger.h"
#include "ash/quick_pair/message_stream/message_stream_lookup.h"
#include "ash/quick_pair/message_stream/message_stream_lookup_impl.h"
#include "ash/quick_pair/pairing/pairer_broker_impl.h"
#include "ash/quick_pair/pairing/retroactive_pairing_detector_impl.h"
#include "ash/quick_pair/repository/fast_pair/device_address_map.h"
#include "ash/quick_pair/repository/fast_pair/device_image_store.h"
#include "ash/quick_pair/repository/fast_pair/pending_write_store.h"
#include "ash/quick_pair/repository/fast_pair/saved_device_registry.h"
#include "ash/quick_pair/repository/fast_pair_repository_impl.h"
#include "ash/quick_pair/scanning/scanner_broker_impl.h"
#include "ash/quick_pair/ui/actions.h"
#include "ash/quick_pair/ui/ui_broker_impl.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/ash/services/bluetooth_config/fast_pair_delegate.h"
#include "chromeos/ash/services/quick_pair/quick_pair_process.h"
#include "chromeos/ash/services/quick_pair/quick_pair_process_manager_impl.h"
#include "components/cross_device/logging/logging.h"
#include "components/prefs/pref_registry_simple.h"
namespace ash {
namespace quick_pair {
namespace {
constexpr base::TimeDelta kDismissedDiscoveryNotificationBanTime =
base::Seconds(2);
constexpr base::TimeDelta kShortBanDiscoveryNotificationBanTime =
base::Minutes(5);
} // namespace
std::unique_ptr<Mediator> Mediator::FactoryImpl::BuildInstance() {
auto process_manager = std::make_unique<QuickPairProcessManagerImpl>();
auto pairer_broker = std::make_unique<PairerBrokerImpl>();
auto message_stream_lookup = std::make_unique<MessageStreamLookupImpl>();
return std::make_unique<Mediator>(
std::make_unique<FeatureStatusTrackerImpl>(),
std::make_unique<ScannerBrokerImpl>(process_manager.get()),
std::make_unique<RetroactivePairingDetectorImpl>(
pairer_broker.get(), message_stream_lookup.get()),
std::move(message_stream_lookup), std::move(pairer_broker),
std::make_unique<UIBrokerImpl>(),
std::make_unique<CompanionAppBrokerImpl>(),
std::make_unique<FastPairRepositoryImpl>(), std::move(process_manager));
}
Mediator::Mediator(
std::unique_ptr<FeatureStatusTracker> feature_status_tracker,
std::unique_ptr<ScannerBroker> scanner_broker,
std::unique_ptr<RetroactivePairingDetector> retroactive_pairing_detector,
std::unique_ptr<MessageStreamLookup> message_stream_lookup,
std::unique_ptr<PairerBroker> pairer_broker,
std::unique_ptr<UIBroker> ui_broker,
std::unique_ptr<CompanionAppBroker> companion_app_broker,
std::unique_ptr<FastPairRepository> fast_pair_repository,
std::unique_ptr<QuickPairProcessManager> process_manager)
: feature_status_tracker_(std::move(feature_status_tracker)),
scanner_broker_(std::move(scanner_broker)),
message_stream_lookup_(std::move(message_stream_lookup)),
pairer_broker_(std::move(pairer_broker)),
retroactive_pairing_detector_(std::move(retroactive_pairing_detector)),
ui_broker_(std::move(ui_broker)),
companion_app_broker_(std::move(companion_app_broker)),
fast_pair_repository_(std::move(fast_pair_repository)),
process_manager_(std::move(process_manager)),
fast_pair_bluetooth_config_delegate_(
std::make_unique<FastPairBluetoothConfigDelegate>(
this /* delegate */)) {
metrics_logger_ = std::make_unique<QuickPairMetricsLogger>(
scanner_broker_.get(), pairer_broker_.get(), ui_broker_.get(),
retroactive_pairing_detector_.get());
battery_update_message_handler_ =
std::make_unique<BatteryUpdateMessageHandler>(
message_stream_lookup_.get());
feature_status_tracker_observation_.Observe(feature_status_tracker_.get());
companion_app_broker_observation_.Observe(companion_app_broker_.get());
scanner_broker_observation_.Observe(scanner_broker_.get());
retroactive_pairing_detector_observation_.Observe(
retroactive_pairing_detector_.get());
pairer_broker_observation_.Observe(pairer_broker_.get());
ui_broker_observation_.Observe(ui_broker_.get());
// If we already have a discovery session via the Settings pairing dialog,
// don't start Fast Pair scanning.
SetFastPairState(feature_status_tracker_->IsFastPairEnabled() &&
!has_at_least_one_discovery_session_);
quick_pair_process::SetProcessManager(process_manager_.get());
// Asynchronously bind to CrosBluetoothConfig so that we don't attempt to
// bind to it before it has initialized.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&Mediator::BindToCrosBluetoothConfig,
weak_ptr_factory_.GetWeakPtr()));
}
Mediator::~Mediator() {
// The metrics logger must be deleted first because it depends on other
// members.
metrics_logger_.reset();
}
// static
void Mediator::RegisterProfilePrefs(PrefRegistrySimple* registry) {
FastPairPrefEnabledProvider::RegisterProfilePrefs(registry);
SavedDeviceRegistry::RegisterProfilePrefs(registry);
PendingWriteStore::RegisterProfilePrefs(registry);
}
// static
void Mediator::RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
DeviceAddressMap::RegisterLocalStatePrefs(registry);
DeviceImageStore::RegisterLocalStatePrefs(registry);
}
void Mediator::BindToCrosBluetoothConfig() {
GetBluetoothConfigService(
remote_cros_bluetooth_config_.BindNewPipeAndPassReceiver());
remote_cros_bluetooth_config_->ObserveDiscoverySessionStatusChanges(
cros_discovery_session_observer_receiver_.BindNewPipeAndPassRemote());
}
bluetooth_config::FastPairDelegate* Mediator::GetFastPairDelegate() {
return fast_pair_bluetooth_config_delegate_.get();
}
void Mediator::OnFastPairEnabledChanged(bool is_enabled) {
// If we already have a discovery session via the Settings pairing dialog,
// don't start Fast Pair scanning.
SetFastPairState(is_enabled && !has_at_least_one_discovery_session_);
// Dismiss all in-progress handshakes which will interfere with discovering
// devices later.
// TODO(b/229663296): We cancel pairing mid-pair to prevent a crash, but we
// shouldn't cancel pairing if pairer_broker_->IsPairing() is true.
if (!is_enabled) {
CancelPairing();
}
}
bool Mediator::IsDeviceCurrentlyShowingNotification(
scoped_refptr<Device> device) {
// BLE addresses could have rotated, causing this check to return false for
// the same device. Fast Pair considers a device different if they have
// different BLE addresses. Similarly, the this check will fail if it is the
// same physical device under different scenarios: for example, if a device
// is found via the initial scenario and via the subsequent scenario, Fast
// Pair does not consider them the same device.
return device_currently_showing_notification_ &&
device_currently_showing_notification_->metadata_id() ==
device->metadata_id() &&
device_currently_showing_notification_->ble_address() ==
device->ble_address() &&
device_currently_showing_notification_->protocol() ==
device->protocol();
}
bool Mediator::IsDeviceBlockedForDiscoveryNotifications(
scoped_refptr<Device> device) {
auto it = discovery_notification_block_list_.find(
std::make_pair(device->metadata_id(), device->protocol()));
if (it == discovery_notification_block_list_.end()) {
return false;
}
DiscoveryNotificationDismissalState notification_state = it->second.first;
// We can reference |ban_expire_time|'s value' directly since we check for
// `kLongBan` beforehand, and |ban_expire_time| is expected to have a value in
// all cases except `kLongBan`.
std::optional<base::Time> ban_expire_time = it->second.second;
return (notification_state == DiscoveryNotificationDismissalState::kLongBan ||
base::Time::Now() < ban_expire_time.value());
}
void Mediator::OnDeviceFound(scoped_refptr<Device> device) {
CD_LOG(VERBOSE, Feature::FP) << __func__ << ": " << device;
if (IsDeviceCurrentlyShowingNotification(device)) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": Extending notification for re-discovered device="
<< device_currently_showing_notification_;
ui_broker_->ExtendNotification();
return;
} else if (device_currently_showing_notification_) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__
<< ": Already showing a notification for a different device="
<< device_currently_showing_notification_;
return;
}
// Because we expect advertisements to be emitted 100ms for discoverable
// advertisements and 250ms for not discoverable advertisements according to
// the Fast Pair spec
// (https://developers.google.com/nearby/fast-pair/specifications/service/provider#advertising_interval_when_discoverable),
// this means we expect the Mediator’s `OnDeviceFound` event to be triggered
// frequently for the same device.
if (IsDeviceBlockedForDiscoveryNotifications(device)) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__
<< ": device is currently blocked for discovery notifications";
return;
}
// Get the device name and add it to the device object, the device will only
// have a name in the cache if this is a subsequent pairing scenario.
if (device->protocol() == Protocol::kFastPairSubsequent &&
device->account_key().has_value()) {
device->set_display_name(
fast_pair_repository_->GetDeviceDisplayNameFromCache(
device->account_key().value()));
}
// On discovery, download and decode device images. TODO (b/244472452):
// remove logic that is executed for every advertisement even if no
// notification is shown.
device_currently_showing_notification_ = device;
ui_broker_->ShowDiscovery(device);
fast_pair_repository_->FetchDeviceImages(device);
// Don't modify the delegate's list when flag is disabled.
if (!features::IsFastPairDevicesBluetoothSettingsEnabled() ||
device->protocol() != Protocol::kFastPairSubsequent) {
return;
}
// Add device to Subsequent Pairable devices list, AKA Account Linked
// devices for bluetooth.
fast_pair_bluetooth_config_delegate_->AddFastPairDevice(device);
}
void Mediator::OnDeviceLost(scoped_refptr<Device> device) {
CD_LOG(VERBOSE, Feature::FP) << __func__ << ": " << device;
// Don't modify the delegate's list when flag is disabled.
if (!features::IsFastPairDevicesBluetoothSettingsEnabled() ||
device->protocol() != Protocol::kFastPairSubsequent) {
return;
}
// Remove device from Subsequent Pairable devices list, AKA Account Linked
// devices for bluetooth.
fast_pair_bluetooth_config_delegate_->RemoveFastPairDevice(device);
}
void Mediator::OnRetroactivePairFound(scoped_refptr<Device> device) {
CD_LOG(VERBOSE, Feature::FP) << __func__ << ": " << device;
// SFUL metrics will cause a crash if Fast Pair is disabled when we
// retroactive pair, so prevent a notification from popping up.
// TODO(b/247148054): Look into moving this elsewhere.
if (!feature_status_tracker_->IsFastPairEnabled()) {
return;
}
// Although at this point in the flow, we have not yet showed a notification
// a notification will immediately follow after account key writing, so we
// still want to block Fast Pair pairings until it is complete.
device_currently_showing_notification_ = device;
// If a device can retroactively pair, it has a fast pair version higher than
// V1.
device->set_version(DeviceFastPairVersion::kHigherThanV1);
pairer_broker_->PairDevice(device);
// Try saving mac address to model ID mapping one more time.
// TODO(b/235117226): we aren't really fetching device images here,
// since the images are already saved. We just want to save the mapping
// from mac address to model ID, and for Retroactive Pair this is one
// of the first times we have mac address and model ID for a paired device.
fast_pair_repository_->FetchDeviceImages(device);
fast_pair_repository_->PersistDeviceImages(device);
}
void Mediator::SetFastPairState(bool is_enabled) {
CD_LOG(VERBOSE, Feature::FP) << __func__ << ": " << is_enabled;
if (is_enabled) {
scanner_broker_->StartScanning(Protocol::kFastPairInitial);
return;
}
scanner_broker_->StopScanning(Protocol::kFastPairInitial);
ui_broker_->RemoveNotifications();
discovery_notification_block_list_.clear();
device_currently_showing_notification_ = nullptr;
}
void Mediator::CancelPairing() {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": Clearing handshakes and pairiers.";
// |pairer_broker_| and its children objects depend on the handshake
// instance. Shut them down before destroying the handshakes.
pairer_broker_->StopPairing();
FastPairHandshakeLookup::GetInstance()->Clear();
FastPairGattServiceClientLookup::GetInstance()->Clear();
// Don't modify the delegate's list when flag is disabled.
if (!features::IsFastPairDevicesBluetoothSettingsEnabled()) {
return;
}
// Clear Subsequent Pairable devices list, AKA Account Linked
// devices for bluetooth.
fast_pair_bluetooth_config_delegate_->ClearFastPairableDevices();
}
void Mediator::OnDevicePaired(scoped_refptr<Device> device) {
CD_LOG(VERBOSE, Feature::FP) << __func__ << ": Device=" << device;
ui_broker_->RemoveNotifications();
scanner_broker_->OnDevicePaired(device);
if (features::IsFastPairPwaCompanionEnabled()) {
if (!companion_app_broker_->MaybeShowCompanionAppActions(device)) {
device_currently_showing_notification_ = nullptr;
}
} else {
device_currently_showing_notification_ = nullptr;
}
// Try saving mac address to model ID mapping one more time.
// TODO(b/235117226): we aren't really fetching device images here,
// since the images are already saved. We just want to save the mapping
// from mac address to model ID, and for Initial/Subsequent Pair this is one
// of the first times we have mac address and model ID for a paired device.
fast_pair_repository_->FetchDeviceImages(device);
fast_pair_repository_->PersistDeviceImages(device);
// Unban notifications for this device since it was successfully paired.
RemoveFromDiscoveryBlockList(device);
// Don't modify the delegate's list when flag is disabled.
if (!features::IsFastPairDevicesBluetoothSettingsEnabled() ||
device->protocol() != Protocol::kFastPairSubsequent) {
return;
}
// Remove device from Subsequent Pairable devices list, AKA Account Linked
// devices for bluetooth.
fast_pair_bluetooth_config_delegate_->RemoveFastPairDevice(device);
}
void Mediator::OnPairFailure(scoped_refptr<Device> device,
PairFailure failure) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": Device=" << device << ",Failure=" << failure;
ui_broker_->ShowPairingFailed(device);
// Don't modify the delegate's list when flag is disabled.
if (!features::IsFastPairDevicesBluetoothSettingsEnabled() ||
device->protocol() != Protocol::kFastPairSubsequent) {
return;
}
// Update device's pairing state to kError.
fast_pair_bluetooth_config_delegate_->UpdateFastPairableDevicePairingState(
device, bluetooth_config::mojom::FastPairableDevicePairingState::kError);
}
void Mediator::OnAccountKeyWrite(scoped_refptr<Device> device,
std::optional<AccountKeyFailure> error) {
if (error.has_value()) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": Device=" << device << ",Error=" << error.value();
return;
}
CD_LOG(VERBOSE, Feature::FP) << __func__ << ": Device=" << device;
if (device->protocol() == Protocol::kFastPairRetroactive) {
ui_broker_->ShowAssociateAccount(std::move(device));
}
}
void Mediator::UpdateDiscoveryBlockList(scoped_refptr<Device> device) {
auto it = discovery_notification_block_list_.find(
std::make_pair(device->metadata_id(), device->protocol()));
// If this is the first time we are seeing this device, create a new value in
// the block-list.
if (it == discovery_notification_block_list_.end()) {
discovery_notification_block_list_[std::make_pair(device->metadata_id(),
device->protocol())] =
std::make_pair(
DiscoveryNotificationDismissalState::kDismissed,
std::make_optional(base::Time::Now() +
kDismissedDiscoveryNotificationBanTime));
return;
}
// If the device is already in the block-list, update the state and the
// expire timestamp.
DiscoveryNotificationDismissalState dismissal_state = it->second.first;
switch (dismissal_state) {
case DiscoveryNotificationDismissalState::kDismissed:
it->second = std::make_pair(
DiscoveryNotificationDismissalState::kShortBan,
std::make_optional(base::Time::Now() +
kShortBanDiscoveryNotificationBanTime));
return;
case DiscoveryNotificationDismissalState::kShortBan:
// Since `IsDeviceBlockedForDiscoveryNotifications` has an explicit
// check for `kLongBan`, the timestamp is std::nullopt. The `kLongBan`
// does not have an expiration timeout.
it->second = std::make_pair(DiscoveryNotificationDismissalState::kLongBan,
std::nullopt);
return;
case DiscoveryNotificationDismissalState::kLongBan:
// If the device had the state `kLongBan`, it should have never been
// shown again, so we are expected to never get to this state when a
// `kLongBan` was shown, and then dismissed by user.
NOTREACHED();
}
}
void Mediator::RemoveFromDiscoveryBlockList(scoped_refptr<Device> device) {
auto key = std::make_pair(device->metadata_id(), device->protocol());
discovery_notification_block_list_.erase(key);
}
void Mediator::OnDiscoveryAction(scoped_refptr<Device> device,
DiscoveryAction action) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": Device=" << device << ", Action=" << action;
switch (action) {
case DiscoveryAction::kPairToDevice: {
// Skip showing the in-progress UI for Fast Pair v1 because that pairing
// is not handled by us E2E.
if (device->version().value() == DeviceFastPairVersion::kHigherThanV1) {
ui_broker_->ShowPairing(device);
}
pairer_broker_->PairDevice(device);
// Don't modify the delegate's list when flag is disabled.
if (!features::IsFastPairDevicesBluetoothSettingsEnabled() ||
device->protocol() != Protocol::kFastPairSubsequent) {
break;
}
// Update device's pairing state to kPairing.
fast_pair_bluetooth_config_delegate_
->UpdateFastPairableDevicePairingState(
device, bluetooth_config::mojom::FastPairableDevicePairingState::
kPairing);
} break;
case DiscoveryAction::kDismissedByOs:
break;
case DiscoveryAction::kDismissedByUser:
// When the user explicitly dismisses the discovery notification, update
// the device's block-list value accordingly.
UpdateDiscoveryBlockList(device);
[[fallthrough]];
case DiscoveryAction::kDismissedByTimeout:
// When the notification is dismissed by timeout or dismissed by user,
// there will be no more notifications for |device|. We reset
// |device_currently_showing_notification_| to enforce the first come,
// first serve notification strategy to allow other notifications to be
// shown. We do not do this for `kDismissedByOs` because this is triggered
// when a discovery notification is removed to be replaced by the
// connection notification to signify pairing is progress, and thus not
// in a terminal state, and we do not want to permit other notifications
// during this time.
device_currently_showing_notification_ = nullptr;
FastPairHandshakeLookup::GetInstance()->Erase(device);
break;
case DiscoveryAction::kLearnMore:
break;
}
}
void Mediator::OnPairingFailureAction(scoped_refptr<Device> device,
PairingFailedAction action) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": Device=" << device << ", Action=" << action;
device_currently_showing_notification_ = nullptr;
}
void Mediator::OnCompanionAppAction(scoped_refptr<Device> device,
CompanionAppAction action) {
CHECK(features::IsFastPairPwaCompanionEnabled());
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": Device=" << device << ", Action=" << action;
switch (action) {
case CompanionAppAction::kDownloadAndLaunchApp:
ui_broker_->RemoveNotifications();
companion_app_broker_->InstallCompanionApp(device);
device_currently_showing_notification_ = nullptr;
break;
case CompanionAppAction::kLaunchApp:
ui_broker_->RemoveNotifications();
companion_app_broker_->LaunchCompanionApp(device);
device_currently_showing_notification_ = nullptr;
break;
case CompanionAppAction::kDismissedByUser:
[[fallthrough]];
case CompanionAppAction::kDismissed:
device_currently_showing_notification_ = nullptr;
break;
}
}
void Mediator::OnAssociateAccountAction(scoped_refptr<Device> device,
AssociateAccountAction action) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": Device=" << device << ", Action=" << action;
switch (action) {
case AssociateAccountAction::kAssociateAccount:
DCHECK(device->account_key().has_value());
fast_pair_repository_->WriteAccountAssociationToFootprints(
device, device->account_key().value());
ui_broker_->RemoveNotifications();
device_currently_showing_notification_ = nullptr;
break;
case AssociateAccountAction::kDismissedByOs:
break;
case AssociateAccountAction::kDismissedByTimeout:
case AssociateAccountAction::kDismissedByUser:
// Retroactive pairing only has the associate account notification. If the
// user elects to save the device or dismisses it, the lifetime of the
// notification is over and a new one can appear.
device_currently_showing_notification_ = nullptr;
break;
case AssociateAccountAction::kLearnMore:
break;
}
}
void Mediator::ShowInstallCompanionApp(scoped_refptr<Device> device) {
CHECK(features::IsFastPairPwaCompanionEnabled());
ui_broker_->ShowInstallCompanionApp(device);
}
void Mediator::ShowLaunchCompanionApp(scoped_refptr<Device> device) {
CHECK(features::IsFastPairPwaCompanionEnabled());
ui_broker_->ShowLaunchCompanionApp(device);
}
// TODO(b/274973687): Implement this function
void Mediator::OnCompanionAppInstalled(scoped_refptr<Device> device) {}
void Mediator::OnAdapterStateControllerChanged(
bluetooth_config::AdapterStateController* adapter_state_controller) {
// Always reset the observation first to handle the case where the ptr
// became a nullptr (i.e. AdapterStateController was destroyed).
adapter_state_controller_observation_.Reset();
if (adapter_state_controller) {
adapter_state_controller_observation_.Observe(adapter_state_controller);
}
}
void Mediator::OnAdapterStateChanged() {
bluetooth_config::AdapterStateController* adapter_state_controller =
fast_pair_bluetooth_config_delegate_->adapter_state_controller();
DCHECK(adapter_state_controller);
bluetooth_config::mojom::BluetoothSystemState adapter_state =
adapter_state_controller->GetAdapterState();
// The FeatureStatusTracker already observes when Bluetooth is enabled,
// disabled, or unavailable. We observe the Bluetooth Config to additionally
// disable Fast Pair when the adapter is disabling.
if (adapter_state ==
bluetooth_config::mojom::BluetoothSystemState::kDisabling) {
CD_LOG(VERBOSE, Feature::FP)
<< __func__ << ": Adapter disabling, disabling Fast Pair.";
SetFastPairState(false);
// In addition to stopping scanning, we cancel pairing here to prevent a
// crash that occurs mid-pair when Bluetooth is disabling.
CancelPairing();
}
}
// TODO(b/243586447): Investigate why the classic BT pairing dialog being open
// interferes with Fast Pair GATT connections.
//
// The logic here is necessary to prevent Fast Pair connecting notification
// hanging when Fast Pair pairing has starting and the classic BT pairing
// dialog is open.
void Mediator::OnHasAtLeastOneDiscoverySessionChanged(
bool has_at_least_one_discovery_session) {
has_at_least_one_discovery_session_ = has_at_least_one_discovery_session;
CD_LOG(VERBOSE, Feature::FP) << __func__
<< ": Discovery session status changed, we"
" have at least one discovery session: "
<< has_at_least_one_discovery_session_;
// If we have a discovery session via the Settings pairing dialog, stop
// Fast Pair scanning. Else, start/stop scanning according to the feature
// status tracker.
SetFastPairState(!has_at_least_one_discovery_session_ &&
feature_status_tracker_->IsFastPairEnabled());
// If we haven't begun pairing, dismiss all in-progress handshakes which
// will interfere with the discovery session. Note that V1 device Fast Pair
// via the Settings pairing dialog, so we also check for that case here.
if (has_at_least_one_discovery_session_ && !pairer_broker_->IsPairing()) {
CancelPairing();
}
}
} // namespace quick_pair
} // namespace ash