#include "ash/system/power/power_sounds_controller.h"
#include <memory>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/shell.h"
#include "ash/system/power/battery_saver_controller.h"
#include "ash/system/system_notification_controller.h"
#include "ash/system/test_system_sounds_delegate.h"
#include "ash/test/ash_test_base.h"
#include "base/containers/flat_map.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
using power_manager::PowerSupplyProperties;
namespace ash {
namespace {
using ExternalPower = power_manager::PowerSupplyProperties_ExternalPower;
constexpr ExternalPower kAcPower =
power_manager::PowerSupplyProperties_ExternalPower_AC;
constexpr ExternalPower kUsbPower =
power_manager::PowerSupplyProperties_ExternalPower_USB;
constexpr ExternalPower kDisconnectedPower =
power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED;
constexpr int kCriticalPercentage = 5;
constexpr int kLowPowerPercentage = 10;
constexpr int kCriticalMinutes = 5;
constexpr int kLowPowerMinutes = 15;
}
class PowerSoundsControllerTest : public AshTestBase {
public:
explicit PowerSoundsControllerTest(
std::optional<bool> battery_saver_allowed = false)
: battery_saver_allowed_(battery_saver_allowed) {}
PowerSoundsControllerTest(const PowerSoundsControllerTest&) = delete;
PowerSoundsControllerTest& operator=(const PowerSoundsControllerTest&) =
delete;
~PowerSoundsControllerTest() override = default;
void SetUp() override {
scoped_feature_.InitWithFeatures({features::kBatterySaver}, {});
AshTestBase::SetUp();
OverrideIsBatterySaverAllowedForTesting(battery_saver_allowed_);
SetInitialPowerStatus();
}
void TearDown() override {
AshTestBase::TearDown();
OverrideIsBatterySaverAllowedForTesting(std::nullopt);
}
TestSystemSoundsDelegate* GetSystemSoundsDelegate() const {
return static_cast<TestSystemSoundsDelegate*>(
Shell::Get()->system_sounds_delegate());
}
bool VerifySounds(const std::vector<Sound>& expected_sounds) const {
const auto& actual_sounds =
GetSystemSoundsDelegate()->last_played_sound_keys();
if (actual_sounds.size() != expected_sounds.size()) {
return false;
}
for (size_t i = 0; i < expected_sounds.size(); ++i) {
if (expected_sounds[i] != actual_sounds[i]) {
return false;
}
}
return true;
}
bool SetLidState(bool closed) {
chromeos::FakePowerManagerClient::Get()->SetLidState(
closed ? chromeos::PowerManagerClient::LidState::CLOSED
: chromeos::PowerManagerClient::LidState::OPEN,
base::TimeTicks::Now());
return Shell::Get()
->system_notification_controller()
->power_sounds_->lid_state_ ==
chromeos::PowerManagerClient::LidState::CLOSED;
}
void SetPowerStatus(int battery_level,
ExternalPower external_power,
int minutes_to_empty = 180) {
ASSERT_GE(battery_level, 0);
ASSERT_LE(battery_level, 100);
const bool old_ac_charger_connected = is_ac_charger_connected_;
is_ac_charger_connected_ = external_power == kAcPower;
PowerSupplyProperties proto;
proto.set_external_power(external_power);
proto.set_battery_percent(battery_level);
proto.set_battery_time_to_empty_sec(minutes_to_empty * 60);
proto.set_battery_time_to_full_sec(2 * 60 * 60);
proto.set_is_calculating_battery_time(false);
chromeos::FakePowerManagerClient::Get()->UpdatePowerProperties(proto);
if (old_ac_charger_connected != is_ac_charger_connected_) {
if (is_ac_charger_connected_) {
plugged_in_levels_samples_[battery_level]++;
} else {
unplugged_levels_samples_[battery_level]++;
}
}
}
void SetInitialPowerStatus() {
local_state()->SetBoolean(prefs::kChargingSoundsEnabled, true);
local_state()->SetBoolean(prefs::kLowBatterySoundEnabled, true);
is_ac_charger_connected_ = true;
SetPowerStatus(5, kDisconnectedPower);
EXPECT_FALSE(SetLidState(false));
}
protected:
base::HistogramTester histogram_tester_;
base::flat_map</*battery_level=*/int, /*sample_count=*/int>
plugged_in_levels_samples_;
base::flat_map</*battery_level=*/int, /*sample_count=*/int>
unplugged_levels_samples_;
base::test::ScopedFeatureList scoped_feature_;
private:
bool is_ac_charger_connected_;
std::optional<bool> battery_saver_allowed_;
};
class PowerSoundsControllerWithBatterySaverTest
: public PowerSoundsControllerTest,
public testing::WithParamInterface<
features::BatterySaverNotificationBehavior> {
public:
PowerSoundsControllerWithBatterySaverTest()
: PowerSoundsControllerTest(true) {}
};
INSTANTIATE_TEST_SUITE_P(
All,
PowerSoundsControllerWithBatterySaverTest,
testing::Values(features::BatterySaverNotificationBehavior::kBSMAutoEnable,
features::BatterySaverNotificationBehavior::kBSMOptIn));
TEST_P(PowerSoundsControllerWithBatterySaverTest,
PlayLowBatterySoundForBatterySaver) {
const int battery_saver_threshold =
features::kBatterySaverActivationChargePercent.Get();
GetSystemSoundsDelegate()->reset();
SetPowerStatus(battery_saver_threshold + 1, kDisconnectedPower);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
SetPowerStatus(battery_saver_threshold, kDisconnectedPower);
EXPECT_TRUE(VerifySounds({Sound::kNoChargeLowBattery}));
GetSystemSoundsDelegate()->reset();
SetPowerStatus(battery_saver_threshold - 1, kDisconnectedPower);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
SetPowerStatus(kCriticalPercentage, kDisconnectedPower);
EXPECT_TRUE(VerifySounds({Sound::kNoChargeLowBattery}));
}
TEST_F(PowerSoundsControllerTest, PlaySoundsForCharging) {
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
SetPowerStatus(5, kAcPower);
EXPECT_TRUE(VerifySounds({Sound::kChargeLowBattery}));
GetSystemSoundsDelegate()->reset();
SetPowerStatus(50, kDisconnectedPower);
SetPowerStatus(50, kAcPower);
EXPECT_TRUE(VerifySounds({Sound::kChargeMediumBattery}));
GetSystemSoundsDelegate()->reset();
SetPowerStatus(90, kDisconnectedPower);
SetPowerStatus(90, kAcPower);
EXPECT_TRUE(VerifySounds({Sound::kChargeHighBattery}));
GetSystemSoundsDelegate()->reset();
SetPowerStatus(95, kDisconnectedPower);
SetPowerStatus(95, kUsbPower);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
}
TEST_F(PowerSoundsControllerTest, NoChargingSoundPlayedIfToggleButtonDisabled) {
local_state()->SetBoolean(prefs::kChargingSoundsEnabled, false);
ASSERT_FALSE(local_state()->GetBoolean(prefs::kChargingSoundsEnabled));
SetPowerStatus(5, kAcPower);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
}
TEST_F(PowerSoundsControllerTest, PlayLowBatterySoundForPercentage) {
SetPowerStatus(kLowPowerPercentage + 1, kUsbPower);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
SetPowerStatus(kLowPowerPercentage, kUsbPower);
EXPECT_TRUE(VerifySounds({Sound::kNoChargeLowBattery}));
GetSystemSoundsDelegate()->reset();
SetPowerStatus(kLowPowerPercentage - 1, kUsbPower);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
SetPowerStatus(kCriticalPercentage, kUsbPower);
EXPECT_TRUE(VerifySounds({Sound::kNoChargeLowBattery}));
}
TEST_F(PowerSoundsControllerTest, PlayLowBatterySoundForRemainingTime) {
SetPowerStatus(50, kDisconnectedPower, kLowPowerMinutes + 1);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
SetPowerStatus(50, kDisconnectedPower, kLowPowerMinutes);
EXPECT_TRUE(VerifySounds({Sound::kNoChargeLowBattery}));
GetSystemSoundsDelegate()->reset();
SetPowerStatus(50, kDisconnectedPower, kCriticalMinutes + 1);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
SetPowerStatus(50, kDisconnectedPower, kCriticalMinutes);
EXPECT_TRUE(VerifySounds({Sound::kNoChargeLowBattery}));
}
TEST_F(PowerSoundsControllerTest,
NoLowBatterySoundPlayedIfToggleButtonDisabled) {
local_state()->SetBoolean(prefs::kLowBatterySoundEnabled, false);
ASSERT_FALSE(local_state()->GetBoolean(prefs::kLowBatterySoundEnabled));
SetPowerStatus(16, kUsbPower);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
SetPowerStatus(15, kUsbPower);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
}
TEST_F(PowerSoundsControllerTest, PlaySoundsSequentially) {
SetPowerStatus(10, kDisconnectedPower, kLowPowerMinutes + 1);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
SetPowerStatus(10, kDisconnectedPower, kLowPowerMinutes);
EXPECT_TRUE(VerifySounds({Sound::kNoChargeLowBattery}));
GetSystemSoundsDelegate()->reset();
SetPowerStatus(10, kAcPower, kLowPowerMinutes);
EXPECT_TRUE(VerifySounds({Sound::kChargeLowBattery}));
GetSystemSoundsDelegate()->reset();
SetPowerStatus(10, kDisconnectedPower, kLowPowerMinutes + 1);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
SetPowerStatus(10, kAcPower, kLowPowerMinutes);
EXPECT_TRUE(VerifySounds({Sound::kChargeLowBattery}));
GetSystemSoundsDelegate()->reset();
SetPowerStatus(10, kDisconnectedPower, kLowPowerMinutes);
EXPECT_TRUE(VerifySounds({Sound::kNoChargeLowBattery}));
}
TEST_F(PowerSoundsControllerTest,
RecordingBatteryLevelWhenPluggedInOrUnplugged) {
SetPowerStatus(5, kAcPower);
SetPowerStatus(50, kDisconnectedPower);
SetPowerStatus(50, kAcPower);
SetPowerStatus(100, kDisconnectedPower);
SetPowerStatus(100, kAcPower);
SetPowerStatus(100, kDisconnectedPower);
SetPowerStatus(100, kAcPower);
SetPowerStatus(100, kDisconnectedPower);
for (int i = 0; i <= 100; i++) {
histogram_tester_.ExpectBucketCount(
PowerSoundsController::kPluggedInBatteryLevelHistogramName, i,
plugged_in_levels_samples_[i]);
histogram_tester_.ExpectBucketCount(
PowerSoundsController::kUnpluggedBatteryLevelHistogramName, i,
unplugged_levels_samples_[i]);
}
}
TEST_F(PowerSoundsControllerTest, PlaySoundsOnlyIfLidIsOpened) {
EXPECT_TRUE(SetLidState(true));
SetPowerStatus(5, kAcPower);
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
EXPECT_FALSE(SetLidState(false));
EXPECT_TRUE(GetSystemSoundsDelegate()->empty());
SetPowerStatus(10, kDisconnectedPower);
SetPowerStatus(5, kAcPower);
EXPECT_TRUE(VerifySounds({Sound::kChargeLowBattery}));
}
}