| // Copyright 2023 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/system/power/battery_saver_controller.h" |
| |
| #include <memory> |
| |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/shell.h" |
| #include "ash/strings/grit/ash_strings.h" |
| #include "ash/system/power/battery_notification.h" |
| #include "ash/system/power/power_status.h" |
| #include "ash/system/system_notification_controller.h" |
| #include "ash/system/toast/toast_manager_impl.h" |
| #include "ash/system/toast/toast_overlay.h" |
| #include "ash/test/ash_test_base.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chromeos/dbus/power/fake_power_manager_client.h" |
| #include "chromeos/dbus/power_manager/power_supply_properties.pb.h" |
| #include "ui/base/l10n/l10n_util.h" |
| #include "ui/message_center/message_center.h" |
| |
| namespace ash { |
| |
| class BatterySaverControllerTest : public AshTestBase { |
| public: |
| // AshTestBase: |
| void SetUp() override { |
| scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>( |
| features::kBatterySaver); |
| chromeos::PowerManagerClient::InitializeFake(); |
| AshTestBase::SetUp(); |
| |
| // Let initial PowerStatus call to |
| // PowerManagerClient::GetBatterySaverModeState to complete. |
| base::RunLoop run_loop; |
| run_loop.RunUntilIdle(); |
| } |
| |
| void TearDown() override { |
| AshTestBase::TearDown(); |
| chromeos::PowerManagerClient::Shutdown(); |
| scoped_feature_list_.reset(); |
| } |
| |
| protected: |
| BatterySaverController* battery_saver_controller() { |
| return Shell::Get()->battery_saver_controller(); |
| } |
| |
| void UpdatePowerStatus(double battery_percent, |
| base::TimeDelta time_to_empty, |
| bool charging, |
| bool is_usb_charger = false) { |
| power_manager::PowerSupplyProperties props; |
| |
| // Set battery percentage |
| props.set_battery_percent(battery_percent); |
| |
| // Determine battery state |
| auto external_power = |
| power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED; |
| auto battery_state = |
| power_manager::PowerSupplyProperties_BatteryState_DISCHARGING; |
| |
| if (is_usb_charger) { |
| external_power = power_manager::PowerSupplyProperties_ExternalPower_USB; |
| } else if (charging) { // Otherwise, treat like AC charger. |
| external_power = power_manager::PowerSupplyProperties_ExternalPower_AC; |
| } |
| |
| if (battery_percent >= 100) { |
| battery_state = power_manager::PowerSupplyProperties_BatteryState_FULL; |
| } else if (charging) { |
| battery_state = |
| power_manager::PowerSupplyProperties_BatteryState_CHARGING; |
| } |
| |
| // Set battery state |
| props.set_external_power(external_power); |
| props.set_battery_state(battery_state); |
| |
| // Set time |
| props.set_is_calculating_battery_time(false); |
| props.set_battery_time_to_empty_sec(time_to_empty.InSecondsF()); |
| |
| // Flush |
| base::RunLoop run_loop; |
| chromeos::FakePowerManagerClient::Get()->UpdatePowerProperties(props); |
| run_loop.RunUntilIdle(); |
| } |
| |
| ToastOverlay* GetCurrentToast() { |
| return Shell::Get()->toast_manager()->GetCurrentOverlayForTesting(); |
| } |
| |
| void DismissToast() { |
| Shell::Get()->toast_manager()->CloseAllToastsWithAnimation(); |
| } |
| |
| bool IsBatterySaverActive() { |
| return PowerStatus::Get()->IsBatterySaverActive(); |
| } |
| |
| double GetActivationPercent() { |
| return features::kBatterySaverActivationChargePercent.Get(); |
| } |
| |
| constexpr static base::TimeDelta eight_hours_ = base::Hours(8); |
| |
| std::unique_ptr<base::test::ScopedFeatureList> scoped_feature_list_ = nullptr; |
| }; |
| |
| class BatterySaverControllerNotificationTest |
| : public BatterySaverControllerTest, |
| public testing::WithParamInterface< |
| features::BatterySaverNotificationBehavior> { |
| protected: |
| message_center::Notification* GetCurrentNotification() { |
| return message_center::MessageCenter::Get()->FindNotificationById( |
| BatteryNotification::kNotificationId); |
| } |
| |
| void DismissNotification() { |
| message_center::MessageCenter::Get()->RemoveNotification( |
| BatteryNotification::kNotificationId, false); |
| EXPECT_EQ(GetCurrentNotification(), nullptr); |
| } |
| |
| void NotificationNotPresent() { |
| EXPECT_EQ(GetCurrentNotification(), nullptr); |
| } |
| |
| void NotificationPresent() { EXPECT_NE(GetCurrentNotification(), nullptr); } |
| |
| void SetExperimentArm(features::BatterySaverNotificationBehavior arm) { |
| scoped_feature_list_.reset(); |
| base::FieldTrialParams parameters; |
| parameters[features::kBatterySaverNotificationBehavior.name] = |
| features::kBatterySaverNotificationBehavior.options[arm].name; |
| scoped_feature_list_ = std::make_unique<base::test::ScopedFeatureList>(); |
| scoped_feature_list_->InitAndEnableFeatureWithParameters( |
| features::kBatterySaver, parameters); |
| base::RunLoop run_loop; |
| run_loop.RunUntilIdle(); |
| } |
| }; |
| |
| // Test that the automatic logic to turn battery saver on and off without |
| // direct user action works. |
| TEST_F(BatterySaverControllerTest, AutoEnableDisable) { |
| // Battery near full and charging, no battery saver. |
| UpdatePowerStatus(80.0, eight_hours_, true); |
| EXPECT_FALSE(IsBatterySaverActive()); |
| |
| // Battery near full and discharging, still no battery saver. |
| UpdatePowerStatus(80.0, eight_hours_, false); |
| EXPECT_FALSE(IsBatterySaverActive()); |
| |
| // Battery discharging but just above the activation %, still no battery |
| // saver. |
| UpdatePowerStatus(GetActivationPercent() + 1, eight_hours_, false); |
| EXPECT_FALSE(IsBatterySaverActive()); |
| |
| // Battery discharging and at activation %, battery saver turns on. |
| UpdatePowerStatus(GetActivationPercent(), eight_hours_, false); |
| EXPECT_TRUE(IsBatterySaverActive()); |
| |
| // Discharge more, battery saver remains on. |
| UpdatePowerStatus(5.0, eight_hours_, false); |
| EXPECT_TRUE(IsBatterySaverActive()); |
| |
| // Start charging, even with a low battery %, battery saver disables. |
| UpdatePowerStatus(5.0, eight_hours_, true); |
| EXPECT_FALSE(IsBatterySaverActive()); |
| } |
| |
| // Test that we detect charging while asleep, as seen by a sudden jump in charge |
| // while discharging. |
| TEST_F(BatterySaverControllerTest, DetectSleepCharging) { |
| // Battery discharging and below activation %, battery saver turns on. |
| UpdatePowerStatus(5.0, eight_hours_, false); |
| EXPECT_TRUE(IsBatterySaverActive()); |
| |
| // Increase charge while still discharging, but stay below activiation %, |
| // battery saver remains on. |
| UpdatePowerStatus(GetActivationPercent(), eight_hours_, false); |
| EXPECT_TRUE(IsBatterySaverActive()); |
| |
| // Increase charge again, but this time above the activation %, battery saver |
| // turns off. |
| UpdatePowerStatus( |
| GetActivationPercent() + |
| BatterySaverController::kBatterySaverSleepChargeThreshold, |
| eight_hours_, false); |
| EXPECT_FALSE(IsBatterySaverActive()); |
| } |
| |
| TEST_F(BatterySaverControllerTest, EnsureThresholdsCrossed) { |
| // Start the test with full battery, discharging. |
| UpdatePowerStatus(100.0, eight_hours_, false); |
| EXPECT_FALSE(IsBatterySaverActive()); |
| |
| // Enable battery saver mode manually. |
| battery_saver_controller()->SetState( |
| true, BatterySaverController::UpdateReason::kSettings); |
| |
| // Discharge to the percent threshold |
| UpdatePowerStatus(GetActivationPercent(), eight_hours_, false); |
| EXPECT_TRUE(IsBatterySaverActive()); |
| |
| // Disable battery saver mode manually. |
| battery_saver_controller()->SetState( |
| false, BatterySaverController::UpdateReason::kSettings); |
| |
| // When we get to percent_threshold-1, it should still be disabled. |
| UpdatePowerStatus(GetActivationPercent() - 1, eight_hours_, false); |
| EXPECT_FALSE(IsBatterySaverActive()); |
| } |
| |
| // Test that Battery Saver remains enabled when charging with a low power USB |
| // charger. |
| TEST_F(BatterySaverControllerTest, USBCharging) { |
| UpdatePowerStatus(GetActivationPercent() - 5, eight_hours_, false); |
| EXPECT_TRUE(IsBatterySaverActive()); |
| |
| // Attache a low-power charger and slowly charge, Battery Saver remains on. |
| for (int i = -5; i <= 5; i++) { |
| UpdatePowerStatus(GetActivationPercent() + i, eight_hours_, true, true); |
| EXPECT_TRUE(IsBatterySaverActive()); |
| } |
| } |
| |
| // Metrics always logged on enable. |
| void ExpectEnabledMetrics(base::HistogramTester& histogram_tester, |
| base::HistogramBase::Count enabled_count) { |
| histogram_tester.ExpectTotalCount("Ash.BatterySaver.BatteryPercent.Enabled", |
| enabled_count); |
| histogram_tester.ExpectTotalCount("Ash.BatterySaver.TimeToEmpty.Enabled", |
| enabled_count); |
| } |
| |
| // Metrics logged on enable when enabled via settings. |
| void ExpectSettingsEnabledMetrics( |
| base::HistogramTester& histogram_tester, |
| base::HistogramBase::Count settings_enabled_count) { |
| histogram_tester.ExpectTotalCount( |
| "Ash.BatterySaver.BatteryPercent.EnabledSettings", |
| settings_enabled_count); |
| histogram_tester.ExpectTotalCount( |
| "Ash.BatterySaver.TimeToEmpty.EnabledSettings", settings_enabled_count); |
| } |
| |
| // Metrics always logged on disable. |
| void ExpectDisabledMetrics(base::HistogramTester& histogram_tester, |
| base::HistogramBase::Count disabled_count) { |
| histogram_tester.ExpectTotalCount("Ash.BatterySaver.BatteryPercent.Disabled", |
| disabled_count); |
| histogram_tester.ExpectTotalCount("Ash.BatterySaver.TimeToEmpty.Disabled", |
| disabled_count); |
| histogram_tester.ExpectTotalCount("Ash.BatterySaver.Duration", |
| disabled_count); |
| } |
| |
| // Metrics logged on disable when enabled via notification. |
| void ExpectNotificationEnabledMetricsOnDisable( |
| base::HistogramTester& histogram_tester, |
| base::HistogramBase::Count notification_enabled_count) { |
| histogram_tester.ExpectTotalCount( |
| "Ash.BatterySaver.Duration.EnabledNotification", |
| notification_enabled_count); |
| } |
| |
| // Metrics logged on disable when enabled via settings. |
| void ExpectSettingsEnabledMetricsOnDisable( |
| base::HistogramTester& histogram_tester, |
| base::HistogramBase::Count settings_enabled_count) { |
| histogram_tester.ExpectTotalCount("Ash.BatterySaver.Duration.EnabledSettings", |
| settings_enabled_count); |
| } |
| |
| // Metrics logged on disable when disabled via charging. |
| void ExpectChargingDisabledMetrics( |
| base::HistogramTester& histogram_tester, |
| base::HistogramBase::Count charging_disabled_count) { |
| histogram_tester.ExpectTotalCount( |
| "Ash.BatterySaver.Duration.DisabledCharging", charging_disabled_count); |
| } |
| |
| // Metrics logged on disable when disabled via notification. |
| void ExpectNotificationDisabledMetrics( |
| base::HistogramTester& histogram_tester, |
| base::HistogramBase::Count notification_disabled_count) { |
| histogram_tester.ExpectTotalCount( |
| "Ash.BatterySaver.Duration.DisabledNotification", |
| notification_disabled_count); |
| } |
| |
| // Metrics logged on disable when disabled via settings. |
| void ExpectSettingsDisabledMetrics( |
| base::HistogramTester& histogram_tester, |
| base::HistogramBase::Count settings_disabled_count) { |
| histogram_tester.ExpectTotalCount( |
| "Ash.BatterySaver.BatteryPercent.DisabledSettings", |
| settings_disabled_count); |
| histogram_tester.ExpectTotalCount( |
| "Ash.BatterySaver.TimeToEmpty.DisabledSettings", settings_disabled_count); |
| histogram_tester.ExpectTotalCount( |
| "Ash.BatterySaver.Duration.DisabledSettings", settings_disabled_count); |
| } |
| |
| TEST_F(BatterySaverControllerTest, Metrics) { |
| base::HistogramTester ht; |
| |
| // TimeToEmpty should have a value. |
| UpdatePowerStatus(80.0, eight_hours_, false); |
| |
| // Enable with settings. |
| ExpectEnabledMetrics(ht, 0); |
| ExpectSettingsEnabledMetrics(ht, 0); |
| battery_saver_controller()->SetState( |
| true, BatterySaverController::UpdateReason::kSettings); |
| ExpectEnabledMetrics(ht, 1); |
| ExpectSettingsEnabledMetrics(ht, 1); |
| |
| // Disable with settings. |
| ExpectDisabledMetrics(ht, 0); |
| ExpectSettingsEnabledMetricsOnDisable(ht, 0); |
| ExpectSettingsDisabledMetrics(ht, 0); |
| battery_saver_controller()->SetState( |
| false, BatterySaverController::UpdateReason::kSettings); |
| ExpectDisabledMetrics(ht, 1); |
| ExpectSettingsEnabledMetricsOnDisable(ht, 1); |
| ExpectSettingsDisabledMetrics(ht, 1); |
| |
| // Enable with notification. |
| ExpectEnabledMetrics(ht, 1); |
| battery_saver_controller()->SetState( |
| true, BatterySaverController::UpdateReason::kThreshold); |
| ExpectEnabledMetrics(ht, 2); |
| |
| // Disable with notification. |
| ExpectDisabledMetrics(ht, 1); |
| ExpectNotificationEnabledMetricsOnDisable(ht, 0); |
| ExpectNotificationDisabledMetrics(ht, 0); |
| battery_saver_controller()->SetState( |
| false, BatterySaverController::UpdateReason::kThreshold); |
| ExpectDisabledMetrics(ht, 2); |
| ExpectNotificationEnabledMetricsOnDisable(ht, 1); |
| ExpectNotificationDisabledMetrics(ht, 1); |
| |
| // Enable again with notifications, just because we need it enabled to test |
| // disabling with charging. |
| ExpectEnabledMetrics(ht, 2); |
| battery_saver_controller()->SetState( |
| true, BatterySaverController::UpdateReason::kThreshold); |
| ExpectEnabledMetrics(ht, 3); |
| |
| // Disable with charging. |
| ExpectDisabledMetrics(ht, 2); |
| ExpectNotificationEnabledMetricsOnDisable(ht, 1); |
| ExpectChargingDisabledMetrics(ht, 0); |
| battery_saver_controller()->SetState( |
| false, BatterySaverController::UpdateReason::kCharging); |
| ExpectDisabledMetrics(ht, 3); |
| ExpectNotificationEnabledMetricsOnDisable(ht, 2); |
| ExpectChargingDisabledMetrics(ht, 1); |
| |
| // Check that we didn't have any spurious metrics logged. |
| ExpectEnabledMetrics(ht, 3); |
| ExpectSettingsEnabledMetrics(ht, 1); |
| ExpectSettingsEnabledMetricsOnDisable(ht, 1); |
| ExpectNotificationDisabledMetrics(ht, 1); |
| ExpectSettingsDisabledMetrics(ht, 1); |
| } |
| |
| TEST_F(BatterySaverControllerTest, ShowDisableToast) { |
| // Ensure there is no `ToastOverlay` being displayed at the start of the test. |
| ToastOverlay* current_toast = GetCurrentToast(); |
| EXPECT_EQ(current_toast, nullptr); |
| |
| // Test Disabled Toast via Button. |
| // Enable battery saver mode. |
| battery_saver_controller()->SetState( |
| true, BatterySaverController::UpdateReason::kThreshold); |
| |
| // There should be no `ToastOverlay` displayed when battery saver is enabled. |
| current_toast = GetCurrentToast(); |
| EXPECT_EQ(current_toast, nullptr); |
| |
| // Disable battery saver mode via button. |
| battery_saver_controller()->SetState( |
| false, BatterySaverController::UpdateReason::kThreshold); |
| |
| // Check to see if a `ToastOverlay` was displayed, and that it's accurate. |
| current_toast = GetCurrentToast(); |
| EXPECT_NE(current_toast, nullptr); |
| EXPECT_EQ( |
| current_toast->GetText(), |
| l10n_util::GetStringUTF16(IDS_ASH_BATTERY_SAVER_DISABLED_TOAST_TEXT)); |
| DismissToast(); |
| |
| // Test Disabled Toast via Charging. |
| // Enable battery saver mode. |
| battery_saver_controller()->SetState( |
| true, BatterySaverController::UpdateReason::kThreshold); |
| |
| // There should be no `ToastOverlay` displayed when battery saver is enabled. |
| current_toast = GetCurrentToast(); |
| EXPECT_EQ(current_toast, nullptr); |
| |
| // Disable battery saver mode via Charging. |
| battery_saver_controller()->SetState( |
| false, BatterySaverController::UpdateReason::kCharging); |
| |
| // Check to see if a `ToastOverlay` was displayed, and that it's accurate. |
| current_toast = GetCurrentToast(); |
| EXPECT_NE(current_toast, nullptr); |
| EXPECT_EQ( |
| current_toast->GetText(), |
| l10n_util::GetStringUTF16(IDS_ASH_BATTERY_SAVER_DISABLED_TOAST_TEXT)); |
| DismissToast(); |
| |
| // Test Disabled Toast via Settings. |
| // Reenable to test that toast doesn't appear when toggled via Settings. |
| battery_saver_controller()->SetState( |
| true, BatterySaverController::UpdateReason::kSettings); |
| |
| // Disable battery saver mode via Settings toggle. |
| battery_saver_controller()->SetState( |
| false, BatterySaverController::UpdateReason::kSettings); |
| |
| // Check there is still no toast since we disabled via Settings. |
| current_toast = GetCurrentToast(); |
| EXPECT_EQ(current_toast, nullptr); |
| } |
| |
| TEST_F(BatterySaverControllerTest, Allowed) { |
| // Battery Saver is allowed by default. |
| EXPECT_TRUE(IsBatterySaverAllowed()); |
| |
| // If pref is managed and false, then Battery Saver is not allowed. |
| local_state()->SetManagedPref(prefs::kPowerBatterySaver, base::Value(false)); |
| EXPECT_FALSE(IsBatterySaverAllowed()); |
| |
| local_state()->RemoveManagedPref(prefs::kPowerBatterySaver); |
| |
| // If the experiment is off, Battery Saver is not allowed. |
| scoped_feature_list_.reset(); |
| EXPECT_FALSE(IsBatterySaverAllowed()); |
| } |
| |
| TEST_F(BatterySaverControllerNotificationTest, |
| ShowEnableToastAtCriticalPercentageForAutoEnable) { |
| SetExperimentArm(features::kBSMAutoEnable); |
| const int critical_percentage = Shell::Get() |
| ->system_notification_controller() |
| ->power_notification_controller() |
| ->GetCriticalPowerPercentage(); |
| |
| // Ensure there is no `ToastOverlay` being displayed at the start of the test. |
| ToastOverlay* current_toast = GetCurrentToast(); |
| EXPECT_EQ(current_toast, nullptr); |
| |
| // Set the battery to a critical percentage. |
| UpdatePowerStatus(critical_percentage, eight_hours_, /*charging=*/false); |
| |
| // The enabled toast should be displayed. |
| current_toast = GetCurrentToast(); |
| EXPECT_NE(current_toast, nullptr); |
| EXPECT_EQ( |
| current_toast->GetText(), |
| l10n_util::GetStringUTF16(IDS_ASH_BATTERY_SAVER_ENABLED_TOAST_TEXT)); |
| DismissToast(); |
| |
| // Plug In AC Charger. |
| UpdatePowerStatus(critical_percentage, eight_hours_, /*charging=*/true); |
| |
| // Toast should be 'Disabled' text. |
| current_toast = GetCurrentToast(); |
| EXPECT_NE(current_toast, nullptr); |
| EXPECT_EQ( |
| current_toast->GetText(), |
| l10n_util::GetStringUTF16(IDS_ASH_BATTERY_SAVER_DISABLED_TOAST_TEXT)); |
| DismissToast(); |
| } |
| |
| TEST_P(BatterySaverControllerNotificationTest, NotificationStateUpdatesOnUSB) { |
| SetExperimentArm(features::kBatterySaverNotificationBehavior.Get()); |
| DismissNotification(); |
| |
| // Start test with 100% battery, eight hours time remaining, not charging. |
| UpdatePowerStatus(100, eight_hours_, /*charging=*/false); |
| NotificationNotPresent(); |
| DismissNotification(); |
| |
| // Battery read jumps (not smoothly) from above threshold to below threshold. |
| UpdatePowerStatus(GetActivationPercent() - 1, eight_hours_, |
| /*charging=*/false); |
| NotificationPresent(); |
| DismissNotification(); |
| EXPECT_TRUE(IsBatterySaverActive()); |
| |
| // Simulate USB Plug-In, which detects as AC first, then USB. |
| UpdatePowerStatus(GetActivationPercent() - 1, eight_hours_, |
| /*charging=*/true); |
| EXPECT_FALSE(IsBatterySaverActive()); |
| UpdatePowerStatus(GetActivationPercent() - 1, eight_hours_, |
| /*charging=*/false, /*is_usb_charger=*/true); |
| EXPECT_EQ(IsBatterySaverActive(), |
| features::kBatterySaverNotificationBehavior.Get() == |
| features::kBSMAutoEnable); |
| NotificationPresent(); |
| |
| // Disable Battery Saver via Settings toggle. |
| // This should cause an update in notification state, but not in notification. |
| battery_saver_controller()->SetState( |
| false, BatterySaverController::UpdateReason::kSettings); |
| |
| // Unplug USB Charger. |
| UpdatePowerStatus(GetActivationPercent() - 1, eight_hours_, |
| /*charging=*/false, /*is_usb_charger=*/false); |
| |
| // If notification state was updated, then we should show a generic low power |
| // notification. |
| EXPECT_EQ(GetCurrentNotification()->title(), |
| l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_LOW_BATTERY_TITLE)); |
| EXPECT_EQ(GetCurrentNotification()->buttons().size(), static_cast<ulong>(0)); |
| } |
| |
| TEST_P(BatterySaverControllerNotificationTest, |
| ReenableOnUnplugBelowThresholds) { |
| SetExperimentArm(GetParam()); |
| |
| // Start the test at percent threshold with no charging. |
| UpdatePowerStatus(GetActivationPercent(), eight_hours_, false); |
| switch (features::kBatterySaverNotificationBehavior.Get()) { |
| case features::kBSMAutoEnable: |
| EXPECT_TRUE(IsBatterySaverActive()); |
| break; |
| case features::kBSMOptIn: |
| EXPECT_FALSE(IsBatterySaverActive()); |
| break; |
| default: |
| FAIL(); |
| } |
| |
| // Plug in the charger at percent threshold. |
| UpdatePowerStatus(GetActivationPercent(), eight_hours_, true); |
| EXPECT_FALSE(IsBatterySaverActive()); |
| |
| // Unplug the charger, and expect BSM to stay on if we are in an auto-enable |
| // state. |
| UpdatePowerStatus(GetActivationPercent(), eight_hours_, false); |
| switch (features::kBatterySaverNotificationBehavior.Get()) { |
| case features::kBSMAutoEnable: |
| EXPECT_TRUE(IsBatterySaverActive()); |
| break; |
| case features::kBSMOptIn: |
| EXPECT_FALSE(IsBatterySaverActive()); |
| break; |
| default: |
| FAIL(); |
| } |
| } |
| |
| TEST_P(BatterySaverControllerNotificationTest, |
| TestNotificationThresholdsForExperimentArms) { |
| SetExperimentArm(GetParam()); |
| |
| auto NotificationNotPresent = [&]() { |
| EXPECT_EQ(GetCurrentNotification(), nullptr); |
| }; |
| auto NotificationPresent = [&]() { |
| EXPECT_NE(GetCurrentNotification(), nullptr); |
| }; |
| |
| // Start test with 100% battery, eight hours time remaining, not charging. |
| UpdatePowerStatus(100, eight_hours_, true); |
| DismissNotification(); |
| |
| // Battery discharging but just above the activation %, battery saver should |
| // be disabled. |
| UpdatePowerStatus(GetActivationPercent() + 1, eight_hours_, false); |
| EXPECT_FALSE(IsBatterySaverActive()); |
| NotificationNotPresent(); |
| |
| // Battery read jumps (not smoothly) from above threshold to below threshold. |
| UpdatePowerStatus(GetActivationPercent() - 1, eight_hours_, false); |
| switch (features::kBatterySaverNotificationBehavior.Get()) { |
| case features::kBSMAutoEnable: |
| EXPECT_TRUE(IsBatterySaverActive()); |
| break; |
| case features::kBSMOptIn: |
| EXPECT_FALSE(IsBatterySaverActive()); |
| break; |
| default: |
| FAIL(); |
| } |
| |
| // Notification should appear regardless of battery saver state (so user can |
| // opt-in/out). |
| NotificationPresent(); |
| DismissNotification(); |
| |
| // Check to make sure that the notification doesn't reappear after it's been |
| // dismissed. |
| UpdatePowerStatus(GetActivationPercent() - 2, eight_hours_, false); |
| NotificationNotPresent(); |
| } |
| |
| TEST_P(BatterySaverControllerNotificationTest, |
| NotificationReappearsAfterChargeThenDischarge) { |
| SetExperimentArm(GetParam()); |
| |
| // Start test with 100% battery, eight hours time remaining, not charging. |
| UpdatePowerStatus(100, eight_hours_, true); |
| DismissNotification(); |
| |
| // Battery read jumps (not smoothly) from above threshold to below threshold. |
| UpdatePowerStatus(GetActivationPercent() - 1, eight_hours_, false); |
| |
| // Notification for threshold should appear. |
| NotificationPresent(); |
| DismissNotification(); |
| |
| // Charging, battery goes above threshold. |
| UpdatePowerStatus(GetActivationPercent() + 1, eight_hours_, true); |
| NotificationNotPresent(); |
| |
| // Discharging, battery goes below threshold. |
| UpdatePowerStatus(GetActivationPercent() - 1, eight_hours_, false); |
| |
| // Notification should reappear. |
| NotificationPresent(); |
| DismissNotification(); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| BatterySaverControllerNotificationTest, |
| testing::Values(features::BatterySaverNotificationBehavior::kBSMAutoEnable, |
| features::BatterySaverNotificationBehavior::kBSMOptIn)); |
| |
| class BatterySaverControllerInitTest : public testing::Test { |
| public: |
| void SetUp() override { |
| BatterySaverController::RegisterLocalStatePrefs(local_state_.registry()); |
| |
| chromeos::PowerManagerClient::InitializeFake(); |
| power_manager_client_ = chromeos::FakePowerManagerClient::Get(); |
| |
| PowerStatus::Initialize(); |
| } |
| |
| void TearDown() override { |
| PowerStatus::Shutdown(); |
| power_manager_client_ = nullptr; |
| chromeos::PowerManagerClient::Shutdown(); |
| } |
| |
| protected: |
| void UpdatePowerPropertiesToDischarging(int battery_percent) { |
| power_manager::PowerSupplyProperties proto; |
| proto.set_battery_state( |
| power_manager::PowerSupplyProperties_BatteryState_DISCHARGING); |
| proto.set_external_power( |
| power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED); |
| proto.set_battery_percent(battery_percent); |
| proto.set_is_calculating_battery_time(false); |
| proto.set_battery_time_to_empty_sec(3600.0); |
| power_manager_client_->UpdatePowerProperties(proto); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| TestingPrefServiceSimple local_state_; |
| raw_ptr<chromeos::FakePowerManagerClient> power_manager_client_; |
| }; |
| |
| // Test that Battery Saver active state is preserved across restarts. |
| TEST_F(BatterySaverControllerInitTest, RestoreState) { |
| EXPECT_FALSE(power_manager_client_->battery_saver_mode_enabled()); |
| |
| // Disabled by default. |
| { |
| local_state_.ClearPref(prefs::kPowerBatterySaver); |
| std::unique_ptr<BatterySaverController> controller = |
| std::make_unique<BatterySaverController>(&local_state_); |
| EXPECT_FALSE(power_manager_client_->battery_saver_mode_enabled()); |
| } |
| |
| // Restore disabled. |
| { |
| local_state_.SetBoolean(prefs::kPowerBatterySaver, false); |
| std::unique_ptr<BatterySaverController> controller = |
| std::make_unique<BatterySaverController>(&local_state_); |
| EXPECT_FALSE(power_manager_client_->battery_saver_mode_enabled()); |
| } |
| |
| // Restore enabled. |
| { |
| local_state_.SetBoolean(prefs::kPowerBatterySaver, true); |
| std::unique_ptr<BatterySaverController> controller = |
| std::make_unique<BatterySaverController>(&local_state_); |
| EXPECT_TRUE(power_manager_client_->battery_saver_mode_enabled()); |
| } |
| } |
| |
| // Test that Battery Saver is disabled when charging when shut down. |
| TEST_F(BatterySaverControllerInitTest, DisableAfterChargingWhenOff) { |
| base::HistogramTester ht; |
| const int battery_percent = 80; |
| local_state_.SetBoolean(prefs::kPowerBatterySaver, true); |
| local_state_.SetInteger(prefs::kPowerBatterySaverPercent, battery_percent); |
| |
| // Set battery_percent above saved pref value to trigger charge detection. |
| UpdatePowerPropertiesToDischarging( |
| battery_percent + |
| BatterySaverController::kBatterySaverSleepChargeThreshold); |
| |
| // Initialize BatterySaverController and check that battery saver is OFF. |
| std::unique_ptr<BatterySaverController> controller = |
| std::make_unique<BatterySaverController>(&local_state_); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_FALSE(power_manager_client_->battery_saver_mode_enabled()); |
| EXPECT_FALSE(PowerStatus::Get()->IsBatterySaverActive()); |
| |
| ExpectDisabledMetrics(ht, 1); |
| ExpectChargingDisabledMetrics(ht, 1); |
| } |
| |
| // Test that Battery Saver remains on after charging when shut down if the |
| // battery is low enough that it would be re-enabled. |
| TEST_F(BatterySaverControllerInitTest, EnableAfterChargingWhenOffAtLowBattery) { |
| const int battery_percent = 5; |
| local_state_.SetBoolean(prefs::kPowerBatterySaver, true); |
| local_state_.SetInteger(prefs::kPowerBatterySaverPercent, battery_percent); |
| |
| // Set battery_percent above saved pref value to trigger charge detection. |
| UpdatePowerPropertiesToDischarging( |
| battery_percent + |
| BatterySaverController::kBatterySaverSleepChargeThreshold); |
| |
| // Initialize BatterySaverController and check that battery saver is ON. |
| std::unique_ptr<BatterySaverController> controller = |
| std::make_unique<BatterySaverController>(&local_state_); |
| base::RunLoop().RunUntilIdle(); |
| |
| EXPECT_TRUE(power_manager_client_->battery_saver_mode_enabled()); |
| EXPECT_TRUE(PowerStatus::Get()->IsBatterySaverActive()); |
| } |
| |
| } // namespace ash |