123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548 |
- // Copyright 2018 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_notification_controller.h"
- #include <map>
- #include <memory>
- #include <string>
- #include "ash/test/ash_test_base.h"
- #include "base/logging.h"
- #include "base/strings/utf_string_conversions.h"
- #include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
- #include "ui/message_center/fake_message_center.h"
- using message_center::Notification;
- using power_manager::PowerSupplyProperties;
- namespace {
- class MockMessageCenter : public message_center::FakeMessageCenter {
- public:
- MockMessageCenter() : add_count_(0), remove_count_(0), update_count_(0) {}
- MockMessageCenter(const MockMessageCenter&) = delete;
- MockMessageCenter& operator=(const MockMessageCenter&) = delete;
- ~MockMessageCenter() override = default;
- int add_count() const { return add_count_; }
- int remove_count() const { return remove_count_; }
- int update_count() const { return update_count_; }
- // message_center::FakeMessageCenter overrides:
- void AddNotification(std::unique_ptr<Notification> notification) override {
- add_count_++;
- notifications_.insert(
- std::make_pair(notification->id(), std::move(notification)));
- }
- void RemoveNotification(const std::string& id, bool by_user) override {
- Notification* notification = FindVisibleNotificationById(id);
- if (notification && notification->delegate())
- notification->delegate()->Close(by_user);
- remove_count_++;
- notifications_.erase(id);
- }
- void UpdateNotification(
- const std::string& id,
- std::unique_ptr<Notification> new_notification) override {
- update_count_++;
- Notification* notification = FindVisibleNotificationById(id);
- if (notification)
- notifications_.erase(id);
- notifications_.insert(
- std::make_pair(new_notification->id(), std::move(new_notification)));
- }
- Notification* FindVisibleNotificationById(const std::string& id) override {
- auto it = notifications_.find(id);
- return it == notifications_.end() ? NULL : it->second.get();
- }
- private:
- int add_count_;
- int remove_count_;
- int update_count_;
- std::map<std::string, std::unique_ptr<Notification>> notifications_;
- };
- } // namespace
- namespace ash {
- class PowerNotificationControllerTest : public AshTestBase {
- public:
- PowerNotificationControllerTest() = default;
- PowerNotificationControllerTest(const PowerNotificationControllerTest&) =
- delete;
- PowerNotificationControllerTest& operator=(
- const PowerNotificationControllerTest&) = delete;
- ~PowerNotificationControllerTest() override = default;
- MockMessageCenter* message_center() { return message_center_.get(); }
- PowerNotificationController* controller() { return controller_.get(); }
- // AshTestBase:
- void SetUp() override {
- AshTestBase::SetUp();
- message_center_ = std::make_unique<MockMessageCenter>();
- controller_ =
- std::make_unique<PowerNotificationController>(message_center_.get());
- }
- void TearDown() override {
- controller_.reset();
- message_center_.reset();
- AshTestBase::TearDown();
- }
- PowerNotificationController::NotificationState notification_state() const {
- return controller_->notification_state_;
- }
- bool MaybeShowUsbChargerNotification(const PowerSupplyProperties& proto) {
- PowerStatus::Get()->SetProtoForTesting(proto);
- return controller_->MaybeShowUsbChargerNotification();
- }
- void MaybeShowDualRoleNotification(const PowerSupplyProperties& proto) {
- PowerStatus::Get()->SetProtoForTesting(proto);
- controller_->MaybeShowDualRoleNotification();
- }
- void UpdateNotificationState(
- const PowerSupplyProperties& proto,
- PowerNotificationController::NotificationState expected_state,
- bool expected_add,
- bool expected_remove) {
- int prev_add = message_center_->add_count();
- int prev_remove = message_center_->remove_count();
- PowerStatus::Get()->SetProtoForTesting(proto);
- controller_->OnPowerStatusChanged();
- EXPECT_EQ(expected_state, notification_state());
- EXPECT_EQ(expected_add, message_center_->add_count() == prev_add + 1);
- EXPECT_EQ(expected_remove,
- message_center_->remove_count() == prev_remove + 1);
- }
- void SetUsbChargerWasConnected(bool connected) {
- controller_->usb_charger_was_connected_ = connected;
- }
- void SetBatteryWasFull(bool full) { controller_->battery_was_full_ = full; }
- // Returns a discharging PowerSupplyProperties more appropriate for testing.
- static PowerSupplyProperties DefaultPowerSupplyProperties() {
- PowerSupplyProperties proto;
- proto.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED);
- proto.set_battery_state(
- power_manager::PowerSupplyProperties_BatteryState_DISCHARGING);
- proto.set_battery_percent(50.0);
- proto.set_battery_time_to_empty_sec(3 * 60 * 60);
- proto.set_battery_time_to_full_sec(2 * 60 * 60);
- proto.set_is_calculating_battery_time(false);
- return proto;
- }
- private:
- std::unique_ptr<MockMessageCenter> message_center_;
- std::unique_ptr<PowerNotificationController> controller_;
- };
- TEST_F(PowerNotificationControllerTest, MaybeShowUsbChargerNotification) {
- PowerSupplyProperties discharging = DefaultPowerSupplyProperties();
- EXPECT_FALSE(MaybeShowUsbChargerNotification(discharging));
- EXPECT_EQ(0, message_center()->add_count());
- EXPECT_EQ(0, message_center()->remove_count());
- // Notification shows when connecting a USB charger.
- PowerSupplyProperties usb_connected = DefaultPowerSupplyProperties();
- usb_connected.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_USB);
- EXPECT_TRUE(MaybeShowUsbChargerNotification(usb_connected));
- EXPECT_EQ(1, message_center()->add_count());
- EXPECT_EQ(0, message_center()->remove_count());
- SetUsbChargerWasConnected(true);
- // Change in charge does not trigger the notification again.
- PowerSupplyProperties more_charge = DefaultPowerSupplyProperties();
- more_charge.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_USB);
- more_charge.set_battery_time_to_full_sec(60 * 60);
- more_charge.set_battery_percent(75.0);
- EXPECT_FALSE(MaybeShowUsbChargerNotification(more_charge));
- EXPECT_EQ(1, message_center()->add_count());
- EXPECT_EQ(0, message_center()->remove_count());
- // Disconnecting a USB charger with the notification showing should close
- // the notification.
- EXPECT_TRUE(MaybeShowUsbChargerNotification(discharging));
- EXPECT_EQ(1, message_center()->add_count());
- EXPECT_EQ(1, message_center()->remove_count());
- SetUsbChargerWasConnected(false);
- // Notification shows when connecting a USB charger again.
- EXPECT_TRUE(MaybeShowUsbChargerNotification(usb_connected));
- EXPECT_EQ(2, message_center()->add_count());
- EXPECT_EQ(1, message_center()->remove_count());
- SetUsbChargerWasConnected(true);
- // Notification hides when external power switches to AC.
- PowerSupplyProperties ac_charger = DefaultPowerSupplyProperties();
- ac_charger.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_AC);
- EXPECT_TRUE(MaybeShowUsbChargerNotification(ac_charger));
- EXPECT_EQ(2, message_center()->add_count());
- EXPECT_EQ(2, message_center()->remove_count());
- SetUsbChargerWasConnected(false);
- // Notification shows when external power switches back to USB.
- EXPECT_TRUE(MaybeShowUsbChargerNotification(usb_connected));
- EXPECT_EQ(3, message_center()->add_count());
- EXPECT_EQ(2, message_center()->remove_count());
- SetUsbChargerWasConnected(true);
- // Notification does not re-appear after being manually dismissed if
- // power supply flickers between AC and USB charger.
- message_center()->RemoveNotification(
- PowerNotificationController::kUsbNotificationId, true);
- EXPECT_EQ(3, message_center()->remove_count());
- EXPECT_TRUE(MaybeShowUsbChargerNotification(ac_charger));
- SetUsbChargerWasConnected(false);
- EXPECT_FALSE(MaybeShowUsbChargerNotification(usb_connected));
- EXPECT_EQ(3, message_center()->add_count());
- SetUsbChargerWasConnected(true);
- // Notification appears again after being manually dismissed if the charger
- // is removed, and then a USB charger is attached.
- MaybeShowUsbChargerNotification(discharging);
- EXPECT_EQ(3, message_center()->add_count());
- SetUsbChargerWasConnected(false);
- MaybeShowUsbChargerNotification(usb_connected);
- EXPECT_EQ(4, message_center()->add_count());
- SetUsbChargerWasConnected(true);
- }
- TEST_F(PowerNotificationControllerTest,
- AvoidUsbChargerNotificationWhenBatteryFull) {
- PowerSupplyProperties full_proto;
- full_proto.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_USB);
- full_proto.set_battery_state(
- power_manager::PowerSupplyProperties_BatteryState_FULL);
- full_proto.set_battery_percent(100.0);
- full_proto.set_is_calculating_battery_time(false);
- PowerSupplyProperties not_full_proto;
- not_full_proto.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_USB);
- not_full_proto.set_battery_state(
- power_manager::PowerSupplyProperties_BatteryState_CHARGING);
- full_proto.set_battery_percent(90.0);
- full_proto.set_is_calculating_battery_time(false);
- // When the battery is reported as full, a notification shouldn't be displayed
- // for a low-power charger: http://b/64913617
- SetUsbChargerWasConnected(false);
- SetBatteryWasFull(false);
- EXPECT_FALSE(MaybeShowUsbChargerNotification(full_proto));
- EXPECT_EQ(0, message_center()->add_count());
- EXPECT_EQ(0, message_center()->remove_count());
- // The notification should be displayed if the battery isn't full, though.
- SetUsbChargerWasConnected(false);
- SetBatteryWasFull(false);
- EXPECT_TRUE(MaybeShowUsbChargerNotification(not_full_proto));
- EXPECT_EQ(1, message_center()->add_count());
- EXPECT_EQ(0, message_center()->remove_count());
- // It should be dismissed if the battery becomes full again while the charger
- // is still connected.
- SetUsbChargerWasConnected(true);
- SetBatteryWasFull(false);
- EXPECT_TRUE(MaybeShowUsbChargerNotification(full_proto));
- EXPECT_EQ(1, message_center()->add_count());
- EXPECT_EQ(1, message_center()->remove_count());
- }
- TEST_F(PowerNotificationControllerTest,
- MaybeShowUsbChargerNotification_NoBattery) {
- // Notification does not show when powered by AC (including high-power
- // USB PD.
- PowerSupplyProperties ac_connected = DefaultPowerSupplyProperties();
- ac_connected.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_AC);
- ac_connected.set_battery_state(
- power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT);
- ac_connected.set_preferred_minimum_external_power(60.0);
- EXPECT_FALSE(MaybeShowUsbChargerNotification(ac_connected));
- EXPECT_EQ(0, message_center()->add_count());
- EXPECT_EQ(0, message_center()->remove_count());
- // Notification shows when powered by low-power USB.
- PowerSupplyProperties usb_connected = DefaultPowerSupplyProperties();
- usb_connected.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_USB);
- usb_connected.set_battery_state(
- power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT);
- usb_connected.set_preferred_minimum_external_power(60.0);
- EXPECT_TRUE(MaybeShowUsbChargerNotification(usb_connected));
- EXPECT_EQ(1, message_center()->add_count());
- EXPECT_EQ(0, message_center()->remove_count());
- auto* notification =
- message_center()->FindVisibleNotificationById("usb-charger");
- ASSERT_TRUE(notification);
- EXPECT_TRUE(notification->never_timeout());
- EXPECT_FALSE(notification->pinned());
- EXPECT_NE(std::string::npos, notification->message().find(u"60W"))
- << notification->message();
- }
- TEST_F(PowerNotificationControllerTest, MaybeShowDualRoleNotification) {
- PowerSupplyProperties discharging = DefaultPowerSupplyProperties();
- discharging.set_supports_dual_role_devices(true);
- MaybeShowDualRoleNotification(discharging);
- EXPECT_EQ(0, message_center()->add_count());
- EXPECT_EQ(0, message_center()->update_count());
- EXPECT_EQ(0, message_center()->remove_count());
- // Notification shows when connecting a dual-role device.
- PowerSupplyProperties dual_role = DefaultPowerSupplyProperties();
- dual_role.set_supports_dual_role_devices(true);
- power_manager::PowerSupplyProperties_PowerSource* source =
- dual_role.add_available_external_power_source();
- source->set_id("dual-role1");
- source->set_active_by_default(false);
- MaybeShowDualRoleNotification(dual_role);
- EXPECT_EQ(1, message_center()->add_count());
- EXPECT_EQ(0, message_center()->update_count());
- EXPECT_EQ(0, message_center()->remove_count());
- // Connecting another dual-role device updates the notification to be plural.
- source = dual_role.add_available_external_power_source();
- source->set_id("dual-role2");
- source->set_active_by_default(false);
- MaybeShowDualRoleNotification(dual_role);
- EXPECT_EQ(1, message_center()->add_count());
- EXPECT_EQ(1, message_center()->update_count());
- EXPECT_EQ(0, message_center()->remove_count());
- // Connecting a 3rd dual-role device doesn't affect the notification.
- source = dual_role.add_available_external_power_source();
- source->set_id("dual-role3");
- source->set_active_by_default(false);
- MaybeShowDualRoleNotification(dual_role);
- EXPECT_EQ(1, message_center()->add_count());
- EXPECT_EQ(1, message_center()->update_count());
- EXPECT_EQ(0, message_center()->remove_count());
- // Connecting a legacy USB device removes the notification.
- PowerSupplyProperties legacy(dual_role);
- power_manager::PowerSupplyProperties_PowerSource* legacy_source =
- legacy.add_available_external_power_source();
- legacy_source->set_id("legacy");
- legacy_source->set_active_by_default(true);
- legacy.set_external_power_source_id("legacy");
- legacy.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_USB);
- MaybeShowDualRoleNotification(legacy);
- EXPECT_EQ(1, message_center()->add_count());
- EXPECT_EQ(1, message_center()->update_count());
- EXPECT_EQ(1, message_center()->remove_count());
- // Removing the legacy USB device adds the notification again.
- MaybeShowDualRoleNotification(dual_role);
- EXPECT_EQ(2, message_center()->add_count());
- EXPECT_EQ(1, message_center()->update_count());
- EXPECT_EQ(1, message_center()->remove_count());
- // Charging from the device updates the notification.
- dual_role.set_external_power_source_id("dual-role1");
- dual_role.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_USB);
- MaybeShowDualRoleNotification(dual_role);
- EXPECT_EQ(2, message_center()->add_count());
- EXPECT_EQ(2, message_center()->update_count());
- EXPECT_EQ(1, message_center()->remove_count());
- // Adding a device as a sink doesn't change the notification, because the
- // notification exposes the source.
- source = dual_role.add_available_external_power_source();
- source->set_active_by_default(false);
- MaybeShowDualRoleNotification(dual_role);
- EXPECT_EQ(2, message_center()->add_count());
- EXPECT_EQ(2, message_center()->update_count());
- EXPECT_EQ(1, message_center()->remove_count());
- // Changing the source to a sink changes the notification.
- dual_role.set_external_power_source_id("");
- dual_role.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED);
- MaybeShowDualRoleNotification(dual_role);
- EXPECT_EQ(2, message_center()->add_count());
- EXPECT_EQ(3, message_center()->update_count());
- EXPECT_EQ(1, message_center()->remove_count());
- // An unrelated change has no effect.
- dual_role.set_battery_time_to_empty_sec(2 * 60 * 60);
- MaybeShowDualRoleNotification(dual_role);
- EXPECT_EQ(2, message_center()->add_count());
- EXPECT_EQ(3, message_center()->update_count());
- EXPECT_EQ(1, message_center()->remove_count());
- // Removing devices hides the notification.
- MaybeShowDualRoleNotification(discharging);
- EXPECT_EQ(2, message_center()->add_count());
- EXPECT_EQ(3, message_center()->update_count());
- EXPECT_EQ(2, message_center()->remove_count());
- }
- TEST_F(PowerNotificationControllerTest, UpdateNotificationState) {
- // No notifications when no battery present.
- PowerSupplyProperties no_battery = DefaultPowerSupplyProperties();
- no_battery.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_AC);
- no_battery.set_battery_state(
- power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT);
- {
- SCOPED_TRACE("No notifications when no battery present");
- UpdateNotificationState(no_battery,
- PowerNotificationController::NOTIFICATION_NONE,
- false, false);
- }
- // No notification when calculating remaining battery time.
- PowerSupplyProperties calculating = DefaultPowerSupplyProperties();
- calculating.set_is_calculating_battery_time(true);
- {
- SCOPED_TRACE("No notification when calculating remaining battery time");
- UpdateNotificationState(calculating,
- PowerNotificationController::NOTIFICATION_NONE,
- false, false);
- }
- // No notification when charging.
- PowerSupplyProperties charging = DefaultPowerSupplyProperties();
- charging.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_AC);
- charging.set_battery_state(
- power_manager::PowerSupplyProperties_BatteryState_CHARGING);
- {
- SCOPED_TRACE("No notification when charging");
- UpdateNotificationState(
- charging, PowerNotificationController::NOTIFICATION_NONE, false, false);
- }
- // When the rounded minutes-to-empty are above the threshold, no notification
- // should be shown.
- PowerSupplyProperties low = DefaultPowerSupplyProperties();
- low.set_battery_time_to_empty_sec(
- PowerNotificationController::kLowPowerMinutes * 60 + 30);
- {
- SCOPED_TRACE("No notification when time to empty above threshold");
- UpdateNotificationState(low, PowerNotificationController::NOTIFICATION_NONE,
- false, false);
- }
- // When the rounded value matches the threshold, the notification should
- // appear.
- low.set_battery_time_to_empty_sec(
- PowerNotificationController::kLowPowerMinutes * 60 + 29);
- {
- SCOPED_TRACE("Notification when time to empty matches threshold");
- UpdateNotificationState(
- low, PowerNotificationController::NOTIFICATION_LOW_POWER, true, false);
- }
- // It should persist at lower values.
- low.set_battery_time_to_empty_sec(
- PowerNotificationController::kLowPowerMinutes * 60 - 20);
- {
- SCOPED_TRACE("Notification persists at lower values");
- UpdateNotificationState(
- low, PowerNotificationController::NOTIFICATION_LOW_POWER, false, false);
- }
- // The critical low battery notification should be shown when the rounded
- // value is at the lower threshold.
- PowerSupplyProperties critical = DefaultPowerSupplyProperties();
- critical.set_battery_time_to_empty_sec(
- PowerNotificationController::kCriticalMinutes * 60 + 29);
- {
- SCOPED_TRACE("Critical notification when time to empty is critical");
- UpdateNotificationState(critical,
- PowerNotificationController::NOTIFICATION_CRITICAL,
- true, true);
- }
- // The notification should be dismissed when the no-warning threshold is
- // reached.
- PowerSupplyProperties safe = DefaultPowerSupplyProperties();
- safe.set_battery_time_to_empty_sec(
- PowerNotificationController::kNoWarningMinutes * 60 - 29);
- {
- SCOPED_TRACE("Notification removed when battery not low");
- UpdateNotificationState(
- safe, PowerNotificationController::NOTIFICATION_NONE, false, true);
- }
- // Test that rounded percentages are used when a USB charger is connected.
- PowerSupplyProperties low_usb = DefaultPowerSupplyProperties();
- low_usb.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_USB);
- low_usb.set_battery_percent(PowerNotificationController::kLowPowerPercentage +
- 0.5);
- {
- SCOPED_TRACE("No notification for rounded battery percent");
- UpdateNotificationState(
- low_usb, PowerNotificationController::NOTIFICATION_NONE, true, false);
- }
- low_usb.set_battery_percent(PowerNotificationController::kLowPowerPercentage +
- 0.49);
- {
- SCOPED_TRACE("Notification for rounded low power percent");
- UpdateNotificationState(low_usb,
- PowerNotificationController::NOTIFICATION_LOW_POWER,
- true, false);
- }
- PowerSupplyProperties critical_usb = DefaultPowerSupplyProperties();
- critical_usb.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_USB);
- critical_usb.set_battery_percent(
- PowerNotificationController::kCriticalPercentage + 0.2);
- {
- SCOPED_TRACE("Notification for rounded critical power percent");
- UpdateNotificationState(critical_usb,
- PowerNotificationController::NOTIFICATION_CRITICAL,
- true, true);
- }
- PowerSupplyProperties safe_usb = DefaultPowerSupplyProperties();
- safe_usb.set_external_power(
- power_manager::PowerSupplyProperties_ExternalPower_USB);
- safe_usb.set_battery_percent(
- PowerNotificationController::kNoWarningPercentage - 0.1);
- {
- SCOPED_TRACE("Notification removed for rounded percent above threshold");
- UpdateNotificationState(
- safe_usb, PowerNotificationController::NOTIFICATION_NONE, false, true);
- }
- }
- // Test that a notification isn't shown if powerd sends a -1 time-to-empty value
- // to indicate that it couldn't produce an estimate: https://crbug.com/930358
- TEST_F(PowerNotificationControllerTest, IgnoreMissingBatteryEstimates) {
- PowerSupplyProperties proto = DefaultPowerSupplyProperties();
- proto.set_battery_time_to_empty_sec(-1);
- UpdateNotificationState(proto, PowerNotificationController::NOTIFICATION_NONE,
- false, false);
- }
- } // namespace ash
|