| // Copyright (c) 2013 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/system/power/power_status.h" |
| |
| #include <memory> |
| |
| #include "ash/resources/vector_icons/vector_icons.h" |
| #include "base/run_loop.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "chromeos/dbus/power/power_manager_client.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/cros_system_api/dbus/service_constants.h" |
| #include "ui/gfx/image/image.h" |
| #include "ui/gfx/image/image_unittest_util.h" |
| |
| using power_manager::PowerSupplyProperties; |
| |
| namespace ash { |
| namespace { |
| |
| class TestObserver : public PowerStatus::Observer { |
| public: |
| TestObserver() : power_changed_count_(0) {} |
| ~TestObserver() override = default; |
| |
| int power_changed_count() const { return power_changed_count_; } |
| |
| // PowerStatus::Observer overrides: |
| void OnPowerStatusChanged() override { ++power_changed_count_; } |
| |
| private: |
| int power_changed_count_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestObserver); |
| }; |
| |
| } // namespace |
| |
| class PowerStatusTest : public testing::Test { |
| public: |
| PowerStatusTest() |
| : task_environment_(base::test::TaskEnvironment::MainThreadType::UI), |
| power_status_(NULL) {} |
| ~PowerStatusTest() override = default; |
| |
| void SetUp() override { |
| chromeos::PowerManagerClient::InitializeFake(); |
| PowerStatus::Initialize(); |
| power_status_ = PowerStatus::Get(); |
| test_observer_.reset(new TestObserver); |
| power_status_->AddObserver(test_observer_.get()); |
| } |
| |
| void TearDown() override { |
| power_status_->RemoveObserver(test_observer_.get()); |
| test_observer_.reset(); |
| PowerStatus::Shutdown(); |
| chromeos::PowerManagerClient::Shutdown(); |
| } |
| |
| protected: |
| base::test::TaskEnvironment task_environment_; |
| PowerStatus* power_status_; // Not owned. |
| std::unique_ptr<TestObserver> test_observer_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(PowerStatusTest); |
| }; |
| |
| TEST_F(PowerStatusTest, InitializeAndUpdate) { |
| // Test that the initial power supply state should be acquired after |
| // PowerStatus is instantiated. This depends on |
| // PowerManagerClientStubImpl, which responds to power status update |
| // requests, pretends there is a battery present, and generates some valid |
| // power supply status data. |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(1, test_observer_->power_changed_count()); |
| |
| // Test RequestUpdate, test_obsever_ should be notified for power suuply |
| // status change. |
| power_status_->RequestStatusUpdate(); |
| base::RunLoop().RunUntilIdle(); |
| EXPECT_EQ(2, test_observer_->power_changed_count()); |
| } |
| |
| TEST_F(PowerStatusTest, GetBatteryImageInfo) { |
| 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::BatteryImageInfo info_charging_98 = |
| power_status_->GetBatteryImageInfo(); |
| |
| // 99% should use the same icon as 98%. |
| prop.set_battery_percent(99.0); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_TRUE(info_charging_98.ApproximatelyEqual( |
| power_status_->GetBatteryImageInfo())); |
| |
| // A different icon should be used when the battery is full. |
| prop.set_battery_state(PowerSupplyProperties::FULL); |
| prop.set_battery_percent(100.0); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_FALSE(info_charging_98.ApproximatelyEqual( |
| power_status_->GetBatteryImageInfo())); |
| |
| // A much-lower battery level should use a different icon. |
| prop.set_battery_state(PowerSupplyProperties::CHARGING); |
| prop.set_battery_percent(20.0); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_FALSE(info_charging_98.ApproximatelyEqual( |
| power_status_->GetBatteryImageInfo())); |
| |
| // Ditto for 98%, but on USB instead of AC. |
| prop.set_external_power(PowerSupplyProperties::USB); |
| prop.set_battery_percent(98.0); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_FALSE(info_charging_98.ApproximatelyEqual( |
| power_status_->GetBatteryImageInfo())); |
| } |
| |
| // Tests that the |icon_badge| member of BatteryImageInfo is set correctly |
| // with various power supply property values. |
| TEST_F(PowerStatusTest, BatteryImageInfoIconBadge) { |
| PowerSupplyProperties prop; |
| |
| // A charging battery connected to AC power should have a bolt badge. |
| prop.set_external_power(PowerSupplyProperties::AC); |
| prop.set_battery_state(PowerSupplyProperties::CHARGING); |
| prop.set_battery_percent(98.0); |
| power_status_->SetProtoForTesting(prop); |
| const gfx::VectorIcon* bolt_icon = |
| power_status_->GetBatteryImageInfo().icon_badge; |
| EXPECT_TRUE(bolt_icon); |
| |
| // A discharging battery connected to AC should also have a bolt badge. |
| prop.set_battery_state(PowerSupplyProperties::DISCHARGING); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_EQ(bolt_icon, power_status_->GetBatteryImageInfo().icon_badge); |
| |
| // A charging battery connected to USB power should have an |
| // unreliable badge. |
| prop.set_external_power(PowerSupplyProperties::USB); |
| prop.set_battery_state(PowerSupplyProperties::CHARGING); |
| power_status_->SetProtoForTesting(prop); |
| const gfx::VectorIcon* unreliable_icon = |
| power_status_->GetBatteryImageInfo().icon_badge; |
| EXPECT_NE(unreliable_icon, bolt_icon); |
| EXPECT_TRUE(unreliable_icon); |
| |
| // A discharging battery connected to USB power should also have an |
| // unreliable badge. |
| prop.set_battery_state(PowerSupplyProperties::DISCHARGING); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_EQ(unreliable_icon, power_status_->GetBatteryImageInfo().icon_badge); |
| |
| // Show the right icon when no battery is present. |
| prop.set_external_power(PowerSupplyProperties::DISCONNECTED); |
| prop.set_battery_state(PowerSupplyProperties::NOT_PRESENT); |
| power_status_->SetProtoForTesting(prop); |
| const gfx::VectorIcon* x_icon = |
| power_status_->GetBatteryImageInfo().icon_badge; |
| EXPECT_TRUE(x_icon); |
| EXPECT_NE(bolt_icon, x_icon); |
| EXPECT_NE(unreliable_icon, x_icon); |
| |
| // Do not show a badge when the battery is discharging. |
| prop.set_battery_state(PowerSupplyProperties::DISCHARGING); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_FALSE(power_status_->GetBatteryImageInfo().icon_badge); |
| |
| // Show the right icon for a discharging battery when it falls below |
| // a charge level of PowerStatus::kCriticalBatteryChargePercentage. |
| prop.set_battery_percent(PowerStatus::kCriticalBatteryChargePercentage); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_FALSE(power_status_->GetBatteryImageInfo().icon_badge); |
| prop.set_battery_percent(PowerStatus::kCriticalBatteryChargePercentage - 1); |
| power_status_->SetProtoForTesting(prop); |
| const gfx::VectorIcon* alert_icon = |
| power_status_->GetBatteryImageInfo().icon_badge; |
| EXPECT_TRUE(alert_icon); |
| EXPECT_NE(bolt_icon, alert_icon); |
| EXPECT_NE(unreliable_icon, alert_icon); |
| EXPECT_NE(x_icon, alert_icon); |
| } |
| |
| // Tests that the battery image changes appropriately with various power supply |
| // property values. |
| TEST_F(PowerStatusTest, BatteryImageInfoChargeLevel) { |
| PowerSupplyProperties prop; |
| |
| // No charge level is drawn when the battery is not present. |
| prop.set_external_power(PowerSupplyProperties::DISCONNECTED); |
| prop.set_battery_state(PowerSupplyProperties::NOT_PRESENT); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_EQ(0, power_status_->GetBatteryImageInfo().charge_percent); |
| |
| PowerStatus* power_status = power_status_; |
| auto get_battery_image = [&power_status]() { |
| return gfx::Image( |
| power_status->GetBatteryImage(power_status->GetBatteryImageInfo(), 16, |
| SK_ColorYELLOW, SK_ColorGREEN)); |
| }; |
| |
| prop.set_external_power(PowerSupplyProperties::AC); |
| prop.set_battery_state(PowerSupplyProperties::CHARGING); |
| prop.set_battery_percent(0.0); |
| EXPECT_EQ(0, power_status_->GetBatteryImageInfo().charge_percent); |
| gfx::Image empty_image = get_battery_image(); |
| |
| // 16% and 17% look different (assuming a height of 16, i.e. kTrayIconSize). |
| prop.set_battery_percent(16.0); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_EQ(16, power_status_->GetBatteryImageInfo().charge_percent); |
| gfx::Image image_16 = get_battery_image(); |
| EXPECT_FALSE(gfx::test::AreImagesEqual(empty_image, image_16)); |
| prop.set_battery_percent(17.0); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_EQ(17, power_status_->GetBatteryImageInfo().charge_percent); |
| gfx::Image image_17 = get_battery_image(); |
| EXPECT_FALSE(gfx::test::AreImagesEqual(image_16, image_17)); |
| |
| // 99% and 100% look different. |
| prop.set_battery_percent(99.0); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_EQ(99, power_status_->GetBatteryImageInfo().charge_percent); |
| gfx::Image image_99 = get_battery_image(); |
| prop.set_battery_percent(100.0); |
| power_status_->SetProtoForTesting(prop); |
| EXPECT_EQ(100, power_status_->GetBatteryImageInfo().charge_percent); |
| gfx::Image image_100 = get_battery_image(); |
| EXPECT_FALSE(gfx::test::AreImagesEqual(image_99, image_100)); |
| } |
| |
| // Tests that positive time-to-full and time-to-empty estimates are honored. |
| TEST_F(PowerStatusTest, PositiveBatteryTimeEstimates) { |
| constexpr auto kTime = base::TimeDelta::FromSeconds(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); |
| base::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); |
| } |
| |
| // Tests that missing time-to-full and time-to-empty estimates (which powerd |
| // sends when no battery is present) and negative ones (which powerd sends when |
| // the battery current is close to zero) are disregarded: |
| // https://crbug.com/930358 |
| TEST_F(PowerStatusTest, MissingBatteryTimeEstimates) { |
| // No battery. |
| PowerSupplyProperties prop; |
| prop.set_external_power(PowerSupplyProperties::AC); |
| prop.set_battery_state(PowerSupplyProperties::NOT_PRESENT); |
| power_status_->SetProtoForTesting(prop); |
| base::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"; |
| |
| // Battery is charging, but negative estimate provided. |
| 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"; |
| |
| // Battery is discharging, but negative estimate provided. |
| 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"; |
| } |
| |
| } // namespace ash |