| // 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/metrics/stylus_metrics_recorder.h" |
| |
| #include <memory> |
| #include <set> |
| #include <string> |
| |
| #include "ash/system/power/peripheral_battery_listener.h" |
| #include "ash/system/power/peripheral_battery_tests.h" |
| #include "ash/test/ash_test_base.h" |
| #include "base/containers/contains.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| |
| using BatteryInfo = ash::PeripheralBatteryListener::BatteryInfo; |
| |
| namespace ash { |
| namespace { |
| |
| const char kHistogramGarageSessionMetric[] = |
| "ChromeOS.FeatureUsage.StylusDetachedFromGarageSession"; |
| const char kHistogramGarageSessionUsetimeMetric[] = |
| "ChromeOS.FeatureUsage.StylusDetachedFromGarageSession.Usetime"; |
| const char kHistogramDockSessionMetric[] = |
| "ChromeOS.FeatureUsage.StylusDetachedFromDockSession"; |
| const char kHistogramDockSessionUsetimeMetric[] = |
| "ChromeOS.FeatureUsage.StylusDetachedFromDockSession.Usetime"; |
| const char kHistogramGarageOrDockSessionMetric[] = |
| "ChromeOS.FeatureUsage.StylusDetachedFromGarageOrDockSession"; |
| const char kHistogramGarageOrDockSessionUsetimeMetric[] = |
| "ChromeOS.FeatureUsage.StylusDetachedFromGarageOrDockSession.Usetime"; |
| |
| enum class StylusChargingStyle { kDock, kGarage }; |
| |
| std::string BatteryKey(StylusChargingStyle style) { |
| return (style == StylusChargingStyle::kGarage) ? "garaged-stylus-charger" |
| : "docked-stylus-charger"; |
| } |
| |
| // Test fixture for the StylusMetricsRecorder class. |
| class StylusMetricsRecorderTest : public AshTestBase { |
| public: |
| StylusMetricsRecorderTest() |
| : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| StylusMetricsRecorderTest(const StylusMetricsRecorderTest&) = delete; |
| StylusMetricsRecorderTest& operator=(const StylusMetricsRecorderTest&) = |
| delete; |
| ~StylusMetricsRecorderTest() override = default; |
| |
| // AshTestBase: |
| |
| void SetUp() override { |
| AshTestBase::SetUp(); |
| stylus_metrics_recorder_ = std::make_unique<ash::StylusMetricsRecorder>(); |
| histogram_tester_ = std::make_unique<base::HistogramTester>(); |
| } |
| |
| void TearDown() override { |
| stylus_metrics_recorder_.reset(); |
| AshTestBase::TearDown(); |
| } |
| |
| base::TimeTicks NowTicks() { return task_environment()->NowTicks(); } |
| |
| void AdvanceClock(base::TimeDelta delta) { |
| task_environment()->AdvanceClock(delta); |
| } |
| |
| BatteryInfo ConstructBatteryInfo( |
| StylusChargingStyle style, |
| BatteryInfo::ChargeStatus charge_status, |
| bool battery_report_eligible = true, |
| BatteryInfo::PeripheralType type = |
| BatteryInfo::PeripheralType::kStylusViaCharger) { |
| const std::string key = BatteryKey(style); |
| const int level = 50; |
| const std::u16string name = base::ASCIIToUTF16("name:" + key); |
| const std::string btaddr = ""; |
| |
| return BatteryInfo(key, name, level, battery_report_eligible, NowTicks(), |
| type, charge_status, btaddr); |
| } |
| |
| void SetChargerState(StylusChargingStyle style, |
| BatteryInfo::ChargeStatus charge_status, |
| bool battery_report_eligible = true, |
| BatteryInfo::PeripheralType type = |
| BatteryInfo::PeripheralType::kStylusViaCharger) { |
| const BatteryInfo info = ConstructBatteryInfo( |
| style, charge_status, battery_report_eligible, type); |
| if (!base::Contains(known_batteries_, info.key)) { |
| stylus_metrics_recorder_->OnAddingBattery(info); |
| known_batteries_.insert(info.key); |
| } |
| stylus_metrics_recorder_->OnUpdatedBatteryLevel(info); |
| } |
| |
| void RemoveCharger(StylusChargingStyle style, |
| BatteryInfo::ChargeStatus charge_status, |
| bool battery_report_eligible = true, |
| BatteryInfo::PeripheralType type = |
| BatteryInfo::PeripheralType::kStylusViaCharger) { |
| const BatteryInfo info = ConstructBatteryInfo( |
| style, charge_status, battery_report_eligible, type); |
| if (base::Contains(known_batteries_, info.key)) { |
| stylus_metrics_recorder_->OnRemovingBattery(info); |
| known_batteries_.erase(info.key); |
| } |
| } |
| |
| protected: |
| // The test target. |
| std::unique_ptr<StylusMetricsRecorder> stylus_metrics_recorder_; |
| |
| // Used to verify recorded data. |
| std::unique_ptr<base::HistogramTester> histogram_tester_; |
| |
| // Track whether batteries are known or need to be added. |
| std::set<std::string> known_batteries_; |
| }; |
| |
| } // namespace |
| |
| // Verifies that histogram is not recorded when no events are received. |
| TEST_F(StylusMetricsRecorderTest, Baseline) { |
| histogram_tester_->ExpectTotalCount(kHistogramGarageSessionMetric, 0); |
| histogram_tester_->ExpectTotalCount(kHistogramDockSessionMetric, 0); |
| histogram_tester_->ExpectTotalCount( |
| kHistogramGarageOrDockSessionUsetimeMetric, 0); |
| histogram_tester_->ExpectTotalCount(kHistogramGarageSessionUsetimeMetric, 0); |
| histogram_tester_->ExpectTotalCount(kHistogramDockSessionUsetimeMetric, 0); |
| histogram_tester_->ExpectTotalCount( |
| kHistogramGarageOrDockSessionUsetimeMetric, 0); |
| } |
| |
| TEST_F(StylusMetricsRecorderTest, BaselineStayInGarage) { |
| const base::TimeDelta kTimeSpentCharging = base::Minutes(5); |
| |
| SetChargerState(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kCharging); |
| AdvanceClock(kTimeSpentCharging); |
| // By removing the battery, we force the stylus_metrics_recorder to close out |
| // the session. |
| RemoveCharger(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kCharging); |
| |
| histogram_tester_->ExpectTotalCount(kHistogramGarageSessionMetric, 0); |
| histogram_tester_->ExpectTotalCount(kHistogramDockSessionMetric, 0); |
| histogram_tester_->ExpectTotalCount(kHistogramGarageOrDockSessionMetric, 0); |
| |
| histogram_tester_->ExpectTotalCount(kHistogramGarageSessionUsetimeMetric, 0); |
| histogram_tester_->ExpectTotalCount(kHistogramDockSessionUsetimeMetric, 0); |
| histogram_tester_->ExpectTotalCount( |
| kHistogramGarageOrDockSessionUsetimeMetric, 0); |
| } |
| |
| TEST_F(StylusMetricsRecorderTest, BaselineStayInDock) { |
| const base::TimeDelta kTimeSpentCharging = base::Minutes(5); |
| |
| SetChargerState(StylusChargingStyle::kDock, |
| BatteryInfo::ChargeStatus::kCharging); |
| AdvanceClock(kTimeSpentCharging); |
| // By removing the battery, we force the stylus_metrics_recorder to close out |
| // the session. |
| RemoveCharger(StylusChargingStyle::kDock, |
| BatteryInfo::ChargeStatus::kCharging); |
| |
| histogram_tester_->ExpectTotalCount(kHistogramGarageSessionMetric, 0); |
| histogram_tester_->ExpectTotalCount(kHistogramDockSessionMetric, 0); |
| histogram_tester_->ExpectTotalCount(kHistogramGarageOrDockSessionMetric, 0); |
| |
| histogram_tester_->ExpectTotalCount(kHistogramGarageSessionUsetimeMetric, 0); |
| histogram_tester_->ExpectTotalCount(kHistogramDockSessionUsetimeMetric, 0); |
| histogram_tester_->ExpectTotalCount( |
| kHistogramGarageOrDockSessionUsetimeMetric, 0); |
| } |
| |
| TEST_F(StylusMetricsRecorderTest, RemovedFromGarage) { |
| const base::TimeDelta kTimeSpentInUse = base::Minutes(5); |
| const base::TimeDelta kTimeSpentCharging = base::Minutes(1); |
| |
| SetChargerState(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kDischarging); |
| AdvanceClock(kTimeSpentInUse); |
| // By removing the battery, we force the stylus_metrics_recorder to close out |
| // the session. |
| SetChargerState(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kCharging); |
| // Step time further when we're back on charge, to make sure this time is not |
| // counted |
| AdvanceClock(kTimeSpentCharging); |
| RemoveCharger(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kCharging); |
| |
| histogram_tester_->ExpectTotalCount(kHistogramDockSessionMetric, 0); |
| |
| histogram_tester_->ExpectBucketCount( |
| kHistogramGarageSessionMetric, |
| static_cast<int>( |
| feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess), |
| 1); |
| histogram_tester_->ExpectBucketCount( |
| kHistogramGarageSessionMetric, |
| static_cast<int>(feature_usage::FeatureUsageMetrics::Event::kEnabled), 1); |
| histogram_tester_->ExpectBucketCount( |
| kHistogramGarageSessionMetric, |
| static_cast<int>(feature_usage::FeatureUsageMetrics::Event::kEligible), |
| 1); |
| |
| histogram_tester_->ExpectBucketCount( |
| kHistogramGarageOrDockSessionMetric, |
| static_cast<int>( |
| feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess), |
| 1); |
| histogram_tester_->ExpectBucketCount( |
| kHistogramGarageOrDockSessionMetric, |
| static_cast<int>(feature_usage::FeatureUsageMetrics::Event::kEnabled), 1); |
| histogram_tester_->ExpectBucketCount( |
| kHistogramGarageOrDockSessionMetric, |
| static_cast<int>(feature_usage::FeatureUsageMetrics::Event::kEligible), |
| 1); |
| |
| histogram_tester_->ExpectTimeBucketCount(kHistogramGarageSessionUsetimeMetric, |
| kTimeSpentInUse, 1); |
| histogram_tester_->ExpectTimeBucketCount( |
| kHistogramGarageOrDockSessionUsetimeMetric, kTimeSpentInUse, 1); |
| } |
| |
| TEST_F(StylusMetricsRecorderTest, RemovedFromDock) { |
| const base::TimeDelta kTimeSpentInUse = base::Minutes(5); |
| const base::TimeDelta kTimeSpentCharging = base::Minutes(1); |
| |
| SetChargerState(StylusChargingStyle::kDock, |
| BatteryInfo::ChargeStatus::kDischarging); |
| AdvanceClock(kTimeSpentInUse); |
| // By removing the battery, we force the stylus_metrics_recorder to close out |
| // the session. |
| SetChargerState(StylusChargingStyle::kDock, |
| BatteryInfo::ChargeStatus::kCharging); |
| // Step time further when we're back on charge, to make sure this time is not |
| // counted |
| AdvanceClock(kTimeSpentCharging); |
| RemoveCharger(StylusChargingStyle::kDock, |
| BatteryInfo::ChargeStatus::kCharging); |
| |
| histogram_tester_->ExpectTotalCount(kHistogramGarageSessionMetric, 0); |
| |
| histogram_tester_->ExpectBucketCount( |
| kHistogramDockSessionMetric, |
| static_cast<int>( |
| feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess), |
| 1); |
| histogram_tester_->ExpectBucketCount( |
| kHistogramDockSessionMetric, |
| static_cast<int>(feature_usage::FeatureUsageMetrics::Event::kEnabled), 1); |
| histogram_tester_->ExpectBucketCount( |
| kHistogramDockSessionMetric, |
| static_cast<int>(feature_usage::FeatureUsageMetrics::Event::kEligible), |
| 1); |
| |
| histogram_tester_->ExpectBucketCount( |
| kHistogramGarageOrDockSessionMetric, |
| static_cast<int>( |
| feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess), |
| 1); |
| histogram_tester_->ExpectBucketCount( |
| kHistogramGarageOrDockSessionMetric, |
| static_cast<int>(feature_usage::FeatureUsageMetrics::Event::kEnabled), 1); |
| histogram_tester_->ExpectBucketCount( |
| kHistogramGarageOrDockSessionMetric, |
| static_cast<int>(feature_usage::FeatureUsageMetrics::Event::kEligible), |
| 1); |
| |
| histogram_tester_->ExpectTimeBucketCount(kHistogramDockSessionUsetimeMetric, |
| kTimeSpentInUse, 1); |
| histogram_tester_->ExpectTimeBucketCount( |
| kHistogramGarageOrDockSessionUsetimeMetric, kTimeSpentInUse, 1); |
| } |
| |
| TEST_F(StylusMetricsRecorderTest, ShutdownWhileStylusRemoved) { |
| const base::TimeDelta kTimeSpentInUse = base::Minutes(5); |
| |
| SetChargerState(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kDischarging); |
| AdvanceClock(kTimeSpentInUse); |
| // By removing the battery, we force the stylus_metrics_recorder to close out |
| // the session. |
| SetChargerState(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kCharging); |
| // Just destroy the recorder, without replacing the stylus; we should still |
| // see the time recorded |
| stylus_metrics_recorder_.reset(); |
| |
| histogram_tester_->ExpectBucketCount( |
| kHistogramGarageSessionMetric, |
| static_cast<int>( |
| feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess), |
| 1); |
| |
| histogram_tester_->ExpectTimeBucketCount(kHistogramGarageSessionUsetimeMetric, |
| kTimeSpentInUse, 1); |
| } |
| |
| TEST_F(StylusMetricsRecorderTest, StylusUsageOverMultipleDays) { |
| const base::TimeDelta kTimeSpentInUse = base::Hours(48); |
| |
| SetChargerState(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kDischarging); |
| AdvanceClock(kTimeSpentInUse); |
| RemoveCharger(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kCharging); |
| |
| histogram_tester_->ExpectBucketCount( |
| kHistogramGarageSessionMetric, |
| static_cast<int>( |
| feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess), |
| 1); |
| |
| histogram_tester_->ExpectTimeBucketCount(kHistogramGarageSessionUsetimeMetric, |
| kTimeSpentInUse, 1); |
| } |
| |
| TEST_F(StylusMetricsRecorderTest, StylusChargeSequencing) { |
| const base::TimeDelta kTimeSpentTrickleCharging = base::Minutes(1); |
| const base::TimeDelta kTimeSpentCharging = base::Minutes(60); |
| const base::TimeDelta kTimeSpentFull = base::Minutes(5); |
| const base::TimeDelta kTimeSpentDischarging = base::Minutes(60); |
| const int kCycles = 2; |
| |
| // Initial state, stylus is garage, charging, not in use |
| SetChargerState(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kCharging); |
| |
| for (int cycle = 0; cycle < kCycles; cycle++) { |
| SetChargerState(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kCharging); |
| AdvanceClock(kTimeSpentTrickleCharging); |
| SetChargerState(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kCharging); |
| AdvanceClock(kTimeSpentCharging); |
| SetChargerState(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kFull); |
| AdvanceClock(kTimeSpentFull); |
| // Stylus is removed from garage when it starts discharging |
| SetChargerState(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kDischarging); |
| AdvanceClock(kTimeSpentDischarging); |
| } |
| |
| // Final state, same as initial |
| SetChargerState(StylusChargingStyle::kGarage, |
| BatteryInfo::ChargeStatus::kCharging); |
| |
| histogram_tester_->ExpectBucketCount( |
| kHistogramGarageSessionMetric, |
| static_cast<int>( |
| feature_usage::FeatureUsageMetrics::Event::kUsedWithSuccess), |
| kCycles); |
| |
| histogram_tester_->ExpectTimeBucketCount(kHistogramGarageSessionUsetimeMetric, |
| kTimeSpentDischarging, kCycles); |
| } |
| |
| } // namespace ash |