| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // 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_metrics_logger.h" |
| |
| #include "ash/quick_pair/common/device.h" |
| #include "ash/quick_pair/common/fast_pair/fast_pair_feature_usage_metrics_logger.h" |
| #include "ash/quick_pair/common/fast_pair/fast_pair_metrics.h" |
| #include "base/containers/contains.h" |
| #include "device/bluetooth/bluetooth_adapter_factory.h" |
| |
| namespace ash { |
| namespace quick_pair { |
| |
| QuickPairMetricsLogger::QuickPairMetricsLogger( |
| ScannerBroker* scanner_broker, |
| PairerBroker* pairer_broker, |
| UIBroker* ui_broker, |
| RetroactivePairingDetector* retroactive_pairing_detector) |
| : feature_usage_metrics_logger_( |
| std::make_unique<FastPairFeatureUsageMetricsLogger>()) { |
| device::BluetoothAdapterFactory::Get()->GetAdapter(base::BindOnce( |
| &QuickPairMetricsLogger::OnGetAdapter, weak_ptr_factory_.GetWeakPtr())); |
| |
| scanner_broker_observation_.Observe(scanner_broker); |
| retroactive_pairing_detector_observation_.Observe( |
| retroactive_pairing_detector); |
| pairer_broker_observation_.Observe(pairer_broker); |
| ui_broker_observation_.Observe(ui_broker); |
| } |
| |
| QuickPairMetricsLogger::~QuickPairMetricsLogger() = default; |
| |
| void QuickPairMetricsLogger::OnGetAdapter( |
| scoped_refptr<device::BluetoothAdapter> adapter) { |
| adapter_ = adapter; |
| adapter_observation_.Observe(adapter_.get()); |
| } |
| |
| void QuickPairMetricsLogger::DevicePairedChanged( |
| device::BluetoothAdapter* adapter, |
| device::BluetoothDevice* device, |
| bool new_paired_status) { |
| // This event fires whenever a device pairing has changed with the adapter. |
| // If the |new_paired_status| is false, it means a device was unpaired with |
| // the adapter, so we early return since it would not be a device that has |
| // been paired alternatively. If the device that was paired to that fires this |
| // event is a device we just paired to with Fast Pair, then we early return |
| // since it also wouldn't be one that was alternatively pair to. We want to |
| // only continue our check here if we have a newly paired device that was |
| // paired with classic Bluetooth pairing. |
| const std::string& classic_address = device->GetAddress(); |
| if (!new_paired_status || |
| base::Contains(fast_pair_addresses_, classic_address)) { |
| return; |
| } |
| |
| RecordPairingMethod(PairingMethod::kSystemPairingUi); |
| } |
| |
| void QuickPairMetricsLogger::OnDevicePaired(scoped_refptr<Device> device) { |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent::kPairingSucceeded); |
| RecordPairingResult(*device, /*success=*/true); |
| feature_usage_metrics_logger_->RecordUsage(/*success=*/true); |
| |
| base::TimeDelta total_pair_time = |
| base::TimeTicks::Now() - device_pairing_start_timestamps_[device]; |
| AttemptRecordingTotalUxPairTime(*device, total_pair_time); |
| RecordPairingMethod(PairingMethod::kFastPair); |
| |
| // The classic address is assigned to the Device during the |
| // initial Fast Pair pairing protocol during the key exchange, and if it |
| // doesn't exist, then it wasn't properly paired during initial Fast Pair |
| // pairing. We want to save the addresses here in the event that the |
| // Bluetooth adapter pairing event fires, so we can detect when a device |
| // was paired solely via classic bluetooth, instead of Fast Pair. |
| if (device->classic_address()) |
| fast_pair_addresses_.insert(device->classic_address().value()); |
| } |
| |
| void QuickPairMetricsLogger::OnPairFailure(scoped_refptr<Device> device, |
| PairFailure failure) { |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent::kPairingFailed); |
| base::TimeDelta total_pair_time = |
| base::TimeTicks::Now() - device_pairing_start_timestamps_[device]; |
| device_pairing_start_timestamps_.erase(device); |
| AttemptRecordingTotalUxPairTime(*device, total_pair_time); |
| |
| feature_usage_metrics_logger_->RecordUsage(/*success=*/false); |
| RecordPairingFailureReason(*device, failure); |
| RecordPairingResult(*device, /*success=*/false); |
| } |
| |
| void QuickPairMetricsLogger::OnDiscoveryAction(scoped_refptr<Device> device, |
| DiscoveryAction action) { |
| switch (action) { |
| case DiscoveryAction::kPairToDevice: |
| if (base::Contains(discovery_learn_more_devices_, device)) { |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent:: |
| kDiscoveryUiConnectPressedAfterLearnMorePressed); |
| discovery_learn_more_devices_.erase(device); |
| break; |
| } |
| |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent::kDiscoveryUiConnectPressed); |
| device_pairing_start_timestamps_[device] = base::TimeTicks::Now(); |
| break; |
| case DiscoveryAction::kLearnMore: |
| // We need to record whether or not the Discovery UI for this |
| // device has had the Learn More button pressed because since the |
| // Learn More button is not a terminal state, we need to record |
| // if the subsequent terminal states were reached after the user |
| // has learned more about saving their accounts. So we will check |
| // this map when the user dismisses or saves their account in order |
| // to capture whether or not the user elected to learn more beforehand. |
| discovery_learn_more_devices_.insert(device); |
| |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent::kDiscoveryUiLearnMorePressed); |
| break; |
| case DiscoveryAction::kDismissedByUser: |
| if (base::Contains(discovery_learn_more_devices_, device)) { |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent:: |
| kDiscoveryUiDismissedByUserAfterLearnMorePressed); |
| discovery_learn_more_devices_.erase(device); |
| feature_usage_metrics_logger_->RecordUsage(/*success=*/true); |
| break; |
| } |
| |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent::kDiscoveryUiDismissedByUser); |
| feature_usage_metrics_logger_->RecordUsage(/*success=*/true); |
| break; |
| case DiscoveryAction::kDismissed: |
| if (base::Contains(discovery_learn_more_devices_, device)) { |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent:: |
| kDiscoveryUiDismissedAfterLearnMorePressed); |
| discovery_learn_more_devices_.erase(device); |
| feature_usage_metrics_logger_->RecordUsage(/*success=*/true); |
| break; |
| } |
| |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent::kDiscoveryUiDismissed); |
| feature_usage_metrics_logger_->RecordUsage(/*success=*/true); |
| break; |
| } |
| } |
| |
| void QuickPairMetricsLogger::OnPairingFailureAction( |
| scoped_refptr<Device> device, |
| PairingFailedAction action) { |
| switch (action) { |
| case PairingFailedAction::kNavigateToSettings: |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent::kErrorUiSettingsPressed); |
| break; |
| case PairingFailedAction::kDismissedByUser: |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent::kErrorUiDismissedByUser); |
| break; |
| case PairingFailedAction::kDismissed: |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent::kErrorUiDismissed); |
| break; |
| } |
| } |
| |
| void QuickPairMetricsLogger::OnDeviceFound(scoped_refptr<Device> device) { |
| AttemptRecordingFastPairEngagementFlow( |
| *device, FastPairEngagementFlowEvent::kDiscoveryUiShown); |
| } |
| |
| void QuickPairMetricsLogger::OnRetroactivePairFound( |
| scoped_refptr<Device> device) { |
| AttemptRecordingFastPairRetroactiveEngagementFlow( |
| *device, |
| FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiShown); |
| } |
| |
| void QuickPairMetricsLogger::OnAssociateAccountAction( |
| scoped_refptr<Device> device, |
| AssociateAccountAction action) { |
| switch (action) { |
| case AssociateAccountAction::kAssoicateAccount: |
| if (base::Contains(associate_account_learn_more_devices_, device)) { |
| AttemptRecordingFastPairRetroactiveEngagementFlow( |
| *device, FastPairRetroactiveEngagementFlowEvent:: |
| kAssociateAccountSavePressedAfterLearnMorePressed); |
| associate_account_learn_more_devices_.erase(device); |
| break; |
| } |
| |
| AttemptRecordingFastPairRetroactiveEngagementFlow( |
| *device, |
| FastPairRetroactiveEngagementFlowEvent::kAssociateAccountSavePressed); |
| break; |
| case AssociateAccountAction::kLearnMore: |
| // We need to record whether or not the Associate Account UI for this |
| // device has had the Learn More button pressed because since the |
| // Learn More button is not a terminal state, we need to record |
| // if the subsequent terminal states were reached after the user |
| // has learned more about saving their accounts. So we will check |
| // this map when the user dismisses or saves their account in order |
| // to capture whether or not the user elected to learn more beforehand. |
| associate_account_learn_more_devices_.insert(device); |
| |
| AttemptRecordingFastPairRetroactiveEngagementFlow( |
| *device, FastPairRetroactiveEngagementFlowEvent:: |
| kAssociateAccountLearnMorePressed); |
| break; |
| case AssociateAccountAction::kDismissedByUser: |
| if (base::Contains(associate_account_learn_more_devices_, device)) { |
| AttemptRecordingFastPairRetroactiveEngagementFlow( |
| *device, FastPairRetroactiveEngagementFlowEvent:: |
| kAssociateAccountDismissedByUserAfterLearnMorePressed); |
| associate_account_learn_more_devices_.erase(device); |
| break; |
| } |
| |
| AttemptRecordingFastPairRetroactiveEngagementFlow( |
| *device, FastPairRetroactiveEngagementFlowEvent:: |
| kAssociateAccountUiDismissedByUser); |
| break; |
| case AssociateAccountAction::kDismissed: |
| if (base::Contains(associate_account_learn_more_devices_, device)) { |
| AttemptRecordingFastPairRetroactiveEngagementFlow( |
| *device, FastPairRetroactiveEngagementFlowEvent:: |
| kAssociateAccountDismissedAfterLearnMorePressed); |
| associate_account_learn_more_devices_.erase(device); |
| break; |
| } |
| |
| AttemptRecordingFastPairRetroactiveEngagementFlow( |
| *device, |
| FastPairRetroactiveEngagementFlowEvent::kAssociateAccountUiDismissed); |
| break; |
| } |
| } |
| |
| void QuickPairMetricsLogger::OnAccountKeyWrite( |
| scoped_refptr<Device> device, |
| absl::optional<AccountKeyFailure> error) { |
| if (device->protocol == Protocol::kFastPairRetroactive) |
| RecordRetroactivePairingResult(/*success=*/!error.has_value()); |
| |
| if (error) { |
| RecordAccountKeyResult(*device, /*success=*/false); |
| RecordAccountKeyFailureReason(*device, error.value()); |
| return; |
| } |
| |
| RecordAccountKeyResult(*device, /*success=*/true); |
| } |
| |
| void QuickPairMetricsLogger::OnCompanionAppAction(scoped_refptr<Device> device, |
| CompanionAppAction action) {} |
| |
| void QuickPairMetricsLogger::OnDeviceLost(scoped_refptr<Device> device) {} |
| |
| } // namespace quick_pair |
| } // namespace ash |