#include "ash/system/power/power_status.h"
#include <memory>
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "chromeos/dbus/power/fake_power_manager_client.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "ui/chromeos/styles/cros_tokens_color_mappings.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/views/widget/widget.h"
using power_manager::PowerSupplyProperties;
namespace ash {
namespace {
class TestObserver : public PowerStatus::Observer {
public:
TestObserver() : power_changed_count_(0) {}
TestObserver(const TestObserver&) = delete;
TestObserver& operator=(const TestObserver&) = delete;
~TestObserver() override = default;
int power_changed_count() const { return power_changed_count_; }
void OnPowerStatusChanged() override { ++power_changed_count_; }
private:
int power_changed_count_;
};
}
class PowerStatusTest : public AshTestBase {
public:
PowerStatusTest() = default;
PowerStatusTest(const PowerStatusTest&) = delete;
PowerStatusTest& operator=(const PowerStatusTest&) = delete;
~PowerStatusTest() override = default;
void SetUp() override {
chromeos::PowerManagerClient::InitializeFake();
AshTestBase::SetUp();
power_status_ = PowerStatus::Get();
test_observer_ = std::make_unique<TestObserver>();
power_status_->AddObserver(test_observer_.get());
}
void TearDown() override {
power_status_->RemoveObserver(test_observer_.get());
test_observer_.reset();
AshTestBase::TearDown();
chromeos::PowerManagerClient::Shutdown();
}
protected:
raw_ptr<PowerStatus, DanglingUntriaged> power_status_ =
nullptr;
std::unique_ptr<TestObserver> test_observer_;
};
TEST_F(PowerStatusTest, InitializeAndUpdate) {
base::RunLoop().RunUntilIdle();
EXPECT_EQ(1, test_observer_->power_changed_count());
power_status_->RequestStatusUpdate();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(2, test_observer_->power_changed_count());
}
TEST_F(PowerStatusTest, GenerateBatteryImageInfo) {
PowerSupplyProperties prop;
prop.set_external_power(PowerSupplyProperties::AC);
prop.set_battery_state(PowerSupplyProperties::CHARGING);
prop.set_battery_percent(98.0);
power_status_->SetProtoForTesting(prop);
const PowerStatus* power_status = power_status_;
auto get_battery_info = [&power_status]() {
return power_status->GenerateBatteryImageInfo(SK_ColorWHITE);
};
const PowerStatus::BatteryImageInfo info_charging_98 = get_battery_info();
prop.set_battery_percent(99.0);
power_status_->SetProtoForTesting(prop);
EXPECT_TRUE(info_charging_98.ApproximatelyEqual(get_battery_info()));
prop.set_battery_state(PowerSupplyProperties::FULL);
prop.set_battery_percent(100.0);
power_status_->SetProtoForTesting(prop);
EXPECT_FALSE(info_charging_98.ApproximatelyEqual(get_battery_info()));
prop.set_battery_state(PowerSupplyProperties::CHARGING);
prop.set_battery_percent(20.0);
power_status_->SetProtoForTesting(prop);
EXPECT_FALSE(info_charging_98.ApproximatelyEqual(get_battery_info()));
prop.set_external_power(PowerSupplyProperties::USB);
prop.set_battery_percent(98.0);
power_status_->SetProtoForTesting(prop);
EXPECT_FALSE(info_charging_98.ApproximatelyEqual(get_battery_info()));
}
TEST_F(PowerStatusTest, BatteryImageInfoIconBadge) {
PowerSupplyProperties prop;
prop.set_external_power(PowerSupplyProperties::AC);
prop.set_battery_state(PowerSupplyProperties::CHARGING);
prop.set_battery_percent(98.0);
power_status_->SetProtoForTesting(prop);
const PowerStatus* power_status = power_status_;
const auto get_battery_icon = [&power_status]() {
return power_status->GenerateBatteryImageInfo(SK_ColorWHITE).icon_badge;
};
const gfx::VectorIcon* bolt_icon = get_battery_icon();
EXPECT_TRUE(bolt_icon);
prop.set_battery_state(PowerSupplyProperties::DISCHARGING);
power_status_->SetProtoForTesting(prop);
EXPECT_EQ(bolt_icon, get_battery_icon());
prop.set_external_power(PowerSupplyProperties::USB);
prop.set_battery_state(PowerSupplyProperties::CHARGING);
power_status_->SetProtoForTesting(prop);
const gfx::VectorIcon* unreliable_icon = get_battery_icon();
EXPECT_NE(unreliable_icon, bolt_icon);
EXPECT_TRUE(unreliable_icon);
prop.set_battery_state(PowerSupplyProperties::DISCHARGING);
power_status_->SetProtoForTesting(prop);
EXPECT_EQ(unreliable_icon, get_battery_icon());
prop.set_external_power(PowerSupplyProperties::DISCONNECTED);
prop.set_battery_state(PowerSupplyProperties::NOT_PRESENT);
power_status_->SetProtoForTesting(prop);
const gfx::VectorIcon* x_icon = get_battery_icon();
EXPECT_TRUE(x_icon);
EXPECT_NE(bolt_icon, x_icon);
EXPECT_NE(unreliable_icon, x_icon);
prop.set_battery_state(PowerSupplyProperties::DISCHARGING);
power_status_->SetProtoForTesting(prop);
EXPECT_FALSE(get_battery_icon());
prop.set_battery_percent(PowerStatus::kCriticalBatteryChargePercentage);
power_status_->SetProtoForTesting(prop);
EXPECT_FALSE(get_battery_icon());
prop.set_battery_percent(PowerStatus::kCriticalBatteryChargePercentage - 1);
power_status_->SetProtoForTesting(prop);
const gfx::VectorIcon* alert_icon = get_battery_icon();
EXPECT_TRUE(alert_icon);
EXPECT_NE(bolt_icon, alert_icon);
EXPECT_NE(unreliable_icon, alert_icon);
EXPECT_NE(x_icon, alert_icon);
}
TEST_F(PowerStatusTest, BatteryImageInfoChargeLevel) {
const std::unique_ptr<views::Widget> test_widget =
CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
PowerSupplyProperties prop;
prop.set_external_power(PowerSupplyProperties::DISCONNECTED);
prop.set_battery_state(PowerSupplyProperties::NOT_PRESENT);
power_status_->SetProtoForTesting(prop);
const PowerStatus* power_status = power_status_;
const auto get_battery_charge_percent = [&power_status]() {
return power_status->GenerateBatteryImageInfo(SK_ColorWHITE).charge_percent;
};
EXPECT_EQ(0, get_battery_charge_percent());
auto get_battery_image = [&power_status, &test_widget]() {
const ui::ColorProvider* color_provider = test_widget->GetColorProvider();
PowerStatus::BatteryImageInfo info =
power_status->GenerateBatteryImageInfo(SK_ColorGREEN);
return gfx::Image(power_status->GetBatteryImage(info, 16, color_provider));
};
prop.set_external_power(PowerSupplyProperties::AC);
prop.set_battery_state(PowerSupplyProperties::CHARGING);
prop.set_battery_percent(0.0);
EXPECT_EQ(0, get_battery_charge_percent());
gfx::Image empty_image = get_battery_image();
prop.set_battery_percent(10.0);
power_status_->SetProtoForTesting(prop);
EXPECT_EQ(10.0, get_battery_charge_percent());
gfx::Image image_10 = get_battery_image();
EXPECT_FALSE(gfx::test::AreImagesEqual(empty_image, image_10));
prop.set_battery_percent(20.0);
power_status_->SetProtoForTesting(prop);
EXPECT_EQ(20.0, get_battery_charge_percent());
gfx::Image image_20 = get_battery_image();
EXPECT_FALSE(gfx::test::AreImagesEqual(image_10, image_20));
prop.set_battery_percent(99.0);
power_status_->SetProtoForTesting(prop);
EXPECT_EQ(99, get_battery_charge_percent());
gfx::Image image_99 = get_battery_image();
prop.set_battery_percent(100.0);
power_status_->SetProtoForTesting(prop);
EXPECT_EQ(100, get_battery_charge_percent());
gfx::Image image_100 = get_battery_image();
EXPECT_FALSE(gfx::test::AreImagesEqual(image_99, image_100));
}
TEST_F(PowerStatusTest, PositiveBatteryTimeEstimates) {
constexpr auto kTime = base::Seconds(120);
PowerSupplyProperties prop;
prop.set_external_power(PowerSupplyProperties::AC);
prop.set_battery_state(PowerSupplyProperties::CHARGING);
prop.set_battery_time_to_full_sec(kTime.InSeconds());
power_status_->SetProtoForTesting(prop);
std::optional<base::TimeDelta> time = power_status_->GetBatteryTimeToFull();
ASSERT_TRUE(time);
EXPECT_EQ(kTime, *time);
prop.Clear();
prop.set_external_power(PowerSupplyProperties::DISCONNECTED);
prop.set_battery_state(PowerSupplyProperties::DISCHARGING);
prop.set_battery_time_to_empty_sec(kTime.InSeconds());
power_status_->SetProtoForTesting(prop);
time = power_status_->GetBatteryTimeToEmpty();
ASSERT_TRUE(time);
EXPECT_EQ(kTime, *time);
}
TEST_F(PowerStatusTest, MissingBatteryTimeEstimates) {
PowerSupplyProperties prop;
prop.set_external_power(PowerSupplyProperties::AC);
prop.set_battery_state(PowerSupplyProperties::NOT_PRESENT);
power_status_->SetProtoForTesting(prop);
std::optional<base::TimeDelta> time = power_status_->GetBatteryTimeToFull();
EXPECT_FALSE(time) << *time << " returned despite missing battery";
time = power_status_->GetBatteryTimeToEmpty();
EXPECT_FALSE(time) << *time << " returned despite missing battery";
prop.set_battery_state(PowerSupplyProperties::CHARGING);
prop.set_battery_time_to_full_sec(-1);
power_status_->SetProtoForTesting(prop);
time = power_status_->GetBatteryTimeToFull();
EXPECT_FALSE(time) << *time << " returned despite negative estimate";
prop.Clear();
prop.set_external_power(PowerSupplyProperties::DISCONNECTED);
prop.set_battery_state(PowerSupplyProperties::DISCHARGING);
prop.set_battery_time_to_empty_sec(-1);
power_status_->SetProtoForTesting(prop);
time = power_status_->GetBatteryTimeToEmpty();
EXPECT_FALSE(time) << *time << " returned despite negative estimate";
}
TEST_F(PowerStatusTest, PreferredMinimumExternalPower) {
PowerSupplyProperties prop;
prop.set_external_power(PowerSupplyProperties::USB);
prop.set_battery_state(PowerSupplyProperties::NOT_PRESENT);
prop.set_preferred_minimum_external_power(23.45);
power_status_->SetProtoForTesting(prop);
EXPECT_EQ(23.45, power_status_->GetPreferredMinimumPower());
}
TEST_F(PowerStatusTest, BatteryImageColorResolution) {
PowerSupplyProperties prop;
prop.set_external_power(PowerSupplyProperties::DISCONNECTED);
prop.set_battery_state(PowerSupplyProperties::DISCHARGING);
power_status_->SetProtoForTesting(prop);
const std::unique_ptr<views::Widget> test_widget =
CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET);
const ui::ColorProvider* color_provider =
test_widget->GetRootView()->GetColorProvider();
BatteryColors resolved_colors = PowerStatus::BatteryImageInfo::ResolveColors(
power_status_->GenerateBatteryImageInfo(SK_ColorRED, SK_ColorBLUE),
color_provider);
EXPECT_EQ(resolved_colors.foreground_color, SK_ColorRED);
EXPECT_EQ(resolved_colors.badge_color, SK_ColorBLUE);
prop.set_external_power(PowerSupplyProperties::AC);
prop.set_battery_state(PowerSupplyProperties::CHARGING);
prop.set_battery_percent(51);
power_status_->SetProtoForTesting(prop);
resolved_colors = PowerStatus::BatteryImageInfo::ResolveColors(
power_status_->GenerateBatteryImageInfo(SK_ColorRED), color_provider);
EXPECT_EQ(resolved_colors.badge_color, SK_ColorRED);
prop.set_battery_percent(50);
power_status_->SetProtoForTesting(prop);
resolved_colors = PowerStatus::BatteryImageInfo::ResolveColors(
power_status_->GenerateBatteryImageInfo(SK_ColorRED), color_provider);
EXPECT_EQ(resolved_colors.badge_color, SK_ColorRED);
resolved_colors = PowerStatus::BatteryImageInfo::ResolveColors(
power_status_->GenerateBatteryImageInfo(SK_ColorRED), color_provider);
EXPECT_EQ(resolved_colors.alert_color,
test_widget->GetRootView()->GetColorProvider()->GetColor(
cros_tokens::kColorAlert));
}
}