| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "ash/system/chromeos/power/tray_power.h" | |
| 6 | |
| 7 #include <map> | |
| 8 #include <memory> | |
| 9 #include <string> | |
| 10 | |
| 11 #include "ash/ash_switches.h" | |
| 12 #include "ash/test/ash_test_base.h" | |
| 13 #include "chromeos/dbus/power_manager/power_supply_properties.pb.h" | |
| 14 #include "ui/message_center/fake_message_center.h" | |
| 15 | |
| 16 using message_center::Notification; | |
| 17 using power_manager::PowerSupplyProperties; | |
| 18 | |
| 19 namespace { | |
| 20 | |
| 21 class MockMessageCenter : public message_center::FakeMessageCenter { | |
| 22 public: | |
| 23 MockMessageCenter() : add_count_(0), remove_count_(0), update_count_(0) {} | |
| 24 ~MockMessageCenter() override {} | |
| 25 | |
| 26 int add_count() const { return add_count_; } | |
| 27 int remove_count() const { return remove_count_; } | |
| 28 int update_count() const { return update_count_; } | |
| 29 | |
| 30 // message_center::FakeMessageCenter overrides: | |
| 31 void AddNotification(std::unique_ptr<Notification> notification) override { | |
| 32 add_count_++; | |
| 33 notifications_.insert( | |
| 34 std::make_pair(notification->id(), std::move(notification))); | |
| 35 } | |
| 36 void RemoveNotification(const std::string& id, bool by_user) override { | |
| 37 Notification* notification = FindVisibleNotificationById(id); | |
| 38 if (notification && notification->delegate()) | |
| 39 notification->delegate()->Close(by_user); | |
| 40 remove_count_++; | |
| 41 notifications_.erase(id); | |
| 42 } | |
| 43 void UpdateNotification( | |
| 44 const std::string& id, | |
| 45 std::unique_ptr<Notification> new_notification) override { | |
| 46 update_count_++; | |
| 47 Notification* notification = FindVisibleNotificationById(id); | |
| 48 if (notification) | |
| 49 notifications_.erase(id); | |
| 50 notifications_.insert( | |
| 51 std::make_pair(new_notification->id(), std::move(new_notification))); | |
| 52 } | |
| 53 | |
| 54 Notification* FindVisibleNotificationById(const std::string& id) override { | |
| 55 auto it = notifications_.find(id); | |
| 56 return it == notifications_.end() ? NULL : it->second.get(); | |
| 57 } | |
| 58 | |
| 59 private: | |
| 60 int add_count_; | |
| 61 int remove_count_; | |
| 62 int update_count_; | |
| 63 std::map<std::string, std::unique_ptr<Notification>> notifications_; | |
| 64 | |
| 65 DISALLOW_COPY_AND_ASSIGN(MockMessageCenter); | |
| 66 }; | |
| 67 | |
| 68 } // namespace | |
| 69 | |
| 70 namespace ash { | |
| 71 | |
| 72 class TrayPowerTest : public test::AshTestBase { | |
| 73 public: | |
| 74 TrayPowerTest() {} | |
| 75 ~TrayPowerTest() override {} | |
| 76 | |
| 77 MockMessageCenter* message_center() { return message_center_.get(); } | |
| 78 TrayPower* tray_power() { return tray_power_.get(); } | |
| 79 | |
| 80 // test::AshTestBase::SetUp() overrides: | |
| 81 void SetUp() override { | |
| 82 test::AshTestBase::SetUp(); | |
| 83 message_center_.reset(new MockMessageCenter()); | |
| 84 tray_power_.reset(new TrayPower(NULL, message_center_.get())); | |
| 85 } | |
| 86 | |
| 87 void TearDown() override { | |
| 88 tray_power_.reset(); | |
| 89 message_center_.reset(); | |
| 90 test::AshTestBase::TearDown(); | |
| 91 } | |
| 92 | |
| 93 TrayPower::NotificationState notification_state() const { | |
| 94 return tray_power_->notification_state_; | |
| 95 } | |
| 96 | |
| 97 bool MaybeShowUsbChargerNotification(const PowerSupplyProperties& proto) { | |
| 98 PowerStatus::Get()->SetProtoForTesting(proto); | |
| 99 return tray_power_->MaybeShowUsbChargerNotification(); | |
| 100 } | |
| 101 | |
| 102 void MaybeShowDualRoleNotification(const PowerSupplyProperties& proto) { | |
| 103 PowerStatus::Get()->SetProtoForTesting(proto); | |
| 104 tray_power_->MaybeShowDualRoleNotification(); | |
| 105 } | |
| 106 | |
| 107 void UpdateNotificationState(const PowerSupplyProperties& proto, | |
| 108 TrayPower::NotificationState expected_state, | |
| 109 bool expected_add, | |
| 110 bool expected_remove) { | |
| 111 int prev_add = message_center_->add_count(); | |
| 112 int prev_remove = message_center_->remove_count(); | |
| 113 PowerStatus::Get()->SetProtoForTesting(proto); | |
| 114 tray_power_->OnPowerStatusChanged(); | |
| 115 EXPECT_EQ(expected_state, notification_state()); | |
| 116 EXPECT_EQ(expected_add, message_center_->add_count() == prev_add + 1); | |
| 117 EXPECT_EQ(expected_remove, | |
| 118 message_center_->remove_count() == prev_remove + 1); | |
| 119 } | |
| 120 | |
| 121 void SetUsbChargerConnected(bool connected) { | |
| 122 tray_power_->usb_charger_was_connected_ = connected; | |
| 123 } | |
| 124 | |
| 125 // Returns a discharging PowerSupplyProperties more appropriate for testing. | |
| 126 static PowerSupplyProperties DefaultPowerSupplyProperties() { | |
| 127 PowerSupplyProperties proto; | |
| 128 proto.set_external_power( | |
| 129 power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED); | |
| 130 proto.set_battery_state( | |
| 131 power_manager::PowerSupplyProperties_BatteryState_DISCHARGING); | |
| 132 proto.set_battery_percent(50.0); | |
| 133 proto.set_battery_time_to_empty_sec(3 * 60 * 60); | |
| 134 proto.set_battery_time_to_full_sec(2 * 60 * 60); | |
| 135 proto.set_is_calculating_battery_time(false); | |
| 136 return proto; | |
| 137 } | |
| 138 | |
| 139 private: | |
| 140 std::unique_ptr<MockMessageCenter> message_center_; | |
| 141 std::unique_ptr<TrayPower> tray_power_; | |
| 142 | |
| 143 DISALLOW_COPY_AND_ASSIGN(TrayPowerTest); | |
| 144 }; | |
| 145 | |
| 146 TEST_F(TrayPowerTest, MaybeShowUsbChargerNotification) { | |
| 147 PowerSupplyProperties discharging = DefaultPowerSupplyProperties(); | |
| 148 EXPECT_FALSE(MaybeShowUsbChargerNotification(discharging)); | |
| 149 EXPECT_EQ(0, message_center()->add_count()); | |
| 150 EXPECT_EQ(0, message_center()->remove_count()); | |
| 151 | |
| 152 // Notification shows when connecting a USB charger. | |
| 153 PowerSupplyProperties usb_connected = DefaultPowerSupplyProperties(); | |
| 154 usb_connected.set_external_power( | |
| 155 power_manager::PowerSupplyProperties_ExternalPower_USB); | |
| 156 EXPECT_TRUE(MaybeShowUsbChargerNotification(usb_connected)); | |
| 157 EXPECT_EQ(1, message_center()->add_count()); | |
| 158 EXPECT_EQ(0, message_center()->remove_count()); | |
| 159 SetUsbChargerConnected(true); | |
| 160 | |
| 161 // Change in charge does not trigger the notification again. | |
| 162 PowerSupplyProperties more_charge = DefaultPowerSupplyProperties(); | |
| 163 more_charge.set_external_power( | |
| 164 power_manager::PowerSupplyProperties_ExternalPower_USB); | |
| 165 more_charge.set_battery_time_to_full_sec(60 * 60); | |
| 166 more_charge.set_battery_percent(75.0); | |
| 167 EXPECT_FALSE(MaybeShowUsbChargerNotification(more_charge)); | |
| 168 EXPECT_EQ(1, message_center()->add_count()); | |
| 169 EXPECT_EQ(0, message_center()->remove_count()); | |
| 170 | |
| 171 // Disconnecting a USB charger with the notification showing should close | |
| 172 // the notification. | |
| 173 EXPECT_TRUE(MaybeShowUsbChargerNotification(discharging)); | |
| 174 EXPECT_EQ(1, message_center()->add_count()); | |
| 175 EXPECT_EQ(1, message_center()->remove_count()); | |
| 176 SetUsbChargerConnected(false); | |
| 177 | |
| 178 // Notification shows when connecting a USB charger again. | |
| 179 EXPECT_TRUE(MaybeShowUsbChargerNotification(usb_connected)); | |
| 180 EXPECT_EQ(2, message_center()->add_count()); | |
| 181 EXPECT_EQ(1, message_center()->remove_count()); | |
| 182 SetUsbChargerConnected(true); | |
| 183 | |
| 184 // Notification hides when external power switches to AC. | |
| 185 PowerSupplyProperties ac_charger = DefaultPowerSupplyProperties(); | |
| 186 ac_charger.set_external_power( | |
| 187 power_manager::PowerSupplyProperties_ExternalPower_AC); | |
| 188 EXPECT_TRUE(MaybeShowUsbChargerNotification(ac_charger)); | |
| 189 EXPECT_EQ(2, message_center()->add_count()); | |
| 190 EXPECT_EQ(2, message_center()->remove_count()); | |
| 191 SetUsbChargerConnected(false); | |
| 192 | |
| 193 // Notification shows when external power switches back to USB. | |
| 194 EXPECT_TRUE(MaybeShowUsbChargerNotification(usb_connected)); | |
| 195 EXPECT_EQ(3, message_center()->add_count()); | |
| 196 EXPECT_EQ(2, message_center()->remove_count()); | |
| 197 SetUsbChargerConnected(true); | |
| 198 | |
| 199 // Notification does not re-appear after being manually dismissed if | |
| 200 // power supply flickers between AC and USB charger. | |
| 201 message_center()->RemoveNotification(TrayPower::kUsbNotificationId, true); | |
| 202 EXPECT_EQ(3, message_center()->remove_count()); | |
| 203 EXPECT_TRUE(MaybeShowUsbChargerNotification(ac_charger)); | |
| 204 SetUsbChargerConnected(false); | |
| 205 EXPECT_FALSE(MaybeShowUsbChargerNotification(usb_connected)); | |
| 206 EXPECT_EQ(3, message_center()->add_count()); | |
| 207 SetUsbChargerConnected(true); | |
| 208 | |
| 209 // Notification appears again after being manually dismissed if the charger | |
| 210 // is removed, and then a USB charger is attached. | |
| 211 MaybeShowUsbChargerNotification(discharging); | |
| 212 EXPECT_EQ(3, message_center()->add_count()); | |
| 213 SetUsbChargerConnected(false); | |
| 214 MaybeShowUsbChargerNotification(usb_connected); | |
| 215 EXPECT_EQ(4, message_center()->add_count()); | |
| 216 SetUsbChargerConnected(true); | |
| 217 } | |
| 218 | |
| 219 TEST_F(TrayPowerTest, MaybeShowDualRoleNotification) { | |
| 220 PowerSupplyProperties discharging = DefaultPowerSupplyProperties(); | |
| 221 discharging.set_supports_dual_role_devices(true); | |
| 222 MaybeShowDualRoleNotification(discharging); | |
| 223 EXPECT_EQ(0, message_center()->add_count()); | |
| 224 EXPECT_EQ(0, message_center()->update_count()); | |
| 225 EXPECT_EQ(0, message_center()->remove_count()); | |
| 226 | |
| 227 // Notification shows when connecting a dual-role device. | |
| 228 PowerSupplyProperties dual_role = DefaultPowerSupplyProperties(); | |
| 229 dual_role.set_supports_dual_role_devices(true); | |
| 230 power_manager::PowerSupplyProperties_PowerSource* source = | |
| 231 dual_role.add_available_external_power_source(); | |
| 232 source->set_id("dual-role1"); | |
| 233 source->set_active_by_default(false); | |
| 234 MaybeShowDualRoleNotification(dual_role); | |
| 235 EXPECT_EQ(1, message_center()->add_count()); | |
| 236 EXPECT_EQ(0, message_center()->update_count()); | |
| 237 EXPECT_EQ(0, message_center()->remove_count()); | |
| 238 | |
| 239 // Connecting another dual-role device updates the notification to be plural. | |
| 240 source = dual_role.add_available_external_power_source(); | |
| 241 source->set_id("dual-role2"); | |
| 242 source->set_active_by_default(false); | |
| 243 MaybeShowDualRoleNotification(dual_role); | |
| 244 EXPECT_EQ(1, message_center()->add_count()); | |
| 245 EXPECT_EQ(1, message_center()->update_count()); | |
| 246 EXPECT_EQ(0, message_center()->remove_count()); | |
| 247 | |
| 248 // Connecting a 3rd dual-role device doesn't affect the notification. | |
| 249 source = dual_role.add_available_external_power_source(); | |
| 250 source->set_id("dual-role3"); | |
| 251 source->set_active_by_default(false); | |
| 252 MaybeShowDualRoleNotification(dual_role); | |
| 253 EXPECT_EQ(1, message_center()->add_count()); | |
| 254 EXPECT_EQ(1, message_center()->update_count()); | |
| 255 EXPECT_EQ(0, message_center()->remove_count()); | |
| 256 | |
| 257 // Connecting a legacy USB device removes the notification. | |
| 258 PowerSupplyProperties legacy(dual_role); | |
| 259 power_manager::PowerSupplyProperties_PowerSource* legacy_source = | |
| 260 legacy.add_available_external_power_source(); | |
| 261 legacy_source->set_id("legacy"); | |
| 262 legacy_source->set_active_by_default(true); | |
| 263 legacy.set_external_power_source_id("legacy"); | |
| 264 legacy.set_external_power( | |
| 265 power_manager::PowerSupplyProperties_ExternalPower_USB); | |
| 266 MaybeShowDualRoleNotification(legacy); | |
| 267 EXPECT_EQ(1, message_center()->add_count()); | |
| 268 EXPECT_EQ(1, message_center()->update_count()); | |
| 269 EXPECT_EQ(1, message_center()->remove_count()); | |
| 270 | |
| 271 // Removing the legacy USB device adds the notification again. | |
| 272 MaybeShowDualRoleNotification(dual_role); | |
| 273 EXPECT_EQ(2, message_center()->add_count()); | |
| 274 EXPECT_EQ(1, message_center()->update_count()); | |
| 275 EXPECT_EQ(1, message_center()->remove_count()); | |
| 276 | |
| 277 // Charging from the device updates the notification. | |
| 278 dual_role.set_external_power_source_id("dual-role1"); | |
| 279 dual_role.set_external_power( | |
| 280 power_manager::PowerSupplyProperties_ExternalPower_USB); | |
| 281 MaybeShowDualRoleNotification(dual_role); | |
| 282 EXPECT_EQ(2, message_center()->add_count()); | |
| 283 EXPECT_EQ(2, message_center()->update_count()); | |
| 284 EXPECT_EQ(1, message_center()->remove_count()); | |
| 285 | |
| 286 // Adding a device as a sink doesn't change the notification, because the | |
| 287 // notification exposes the source. | |
| 288 source = dual_role.add_available_external_power_source(); | |
| 289 source->set_active_by_default(false); | |
| 290 MaybeShowDualRoleNotification(dual_role); | |
| 291 EXPECT_EQ(2, message_center()->add_count()); | |
| 292 EXPECT_EQ(2, message_center()->update_count()); | |
| 293 EXPECT_EQ(1, message_center()->remove_count()); | |
| 294 | |
| 295 // Changing the source to a sink changes the notification. | |
| 296 dual_role.set_external_power_source_id(""); | |
| 297 dual_role.set_external_power( | |
| 298 power_manager::PowerSupplyProperties_ExternalPower_DISCONNECTED); | |
| 299 MaybeShowDualRoleNotification(dual_role); | |
| 300 EXPECT_EQ(2, message_center()->add_count()); | |
| 301 EXPECT_EQ(3, message_center()->update_count()); | |
| 302 EXPECT_EQ(1, message_center()->remove_count()); | |
| 303 | |
| 304 // An unrelated change has no effect. | |
| 305 dual_role.set_battery_time_to_empty_sec(2 * 60 * 60); | |
| 306 MaybeShowDualRoleNotification(dual_role); | |
| 307 EXPECT_EQ(2, message_center()->add_count()); | |
| 308 EXPECT_EQ(3, message_center()->update_count()); | |
| 309 EXPECT_EQ(1, message_center()->remove_count()); | |
| 310 | |
| 311 // Removing devices hides the notification. | |
| 312 MaybeShowDualRoleNotification(discharging); | |
| 313 EXPECT_EQ(2, message_center()->add_count()); | |
| 314 EXPECT_EQ(3, message_center()->update_count()); | |
| 315 EXPECT_EQ(2, message_center()->remove_count()); | |
| 316 } | |
| 317 | |
| 318 TEST_F(TrayPowerTest, UpdateNotificationState) { | |
| 319 // No notifications when no battery present. | |
| 320 PowerSupplyProperties no_battery = DefaultPowerSupplyProperties(); | |
| 321 no_battery.set_external_power( | |
| 322 power_manager::PowerSupplyProperties_ExternalPower_AC); | |
| 323 no_battery.set_battery_state( | |
| 324 power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT); | |
| 325 { | |
| 326 SCOPED_TRACE("No notifications when no battery present"); | |
| 327 UpdateNotificationState(no_battery, TrayPower::NOTIFICATION_NONE, false, | |
| 328 false); | |
| 329 } | |
| 330 | |
| 331 // No notification when calculating remaining battery time. | |
| 332 PowerSupplyProperties calculating = DefaultPowerSupplyProperties(); | |
| 333 calculating.set_is_calculating_battery_time(true); | |
| 334 { | |
| 335 SCOPED_TRACE("No notification when calculating remaining battery time"); | |
| 336 UpdateNotificationState(calculating, TrayPower::NOTIFICATION_NONE, false, | |
| 337 false); | |
| 338 } | |
| 339 | |
| 340 // No notification when charging. | |
| 341 PowerSupplyProperties charging = DefaultPowerSupplyProperties(); | |
| 342 charging.set_external_power( | |
| 343 power_manager::PowerSupplyProperties_ExternalPower_AC); | |
| 344 charging.set_battery_state( | |
| 345 power_manager::PowerSupplyProperties_BatteryState_CHARGING); | |
| 346 { | |
| 347 SCOPED_TRACE("No notification when charging"); | |
| 348 UpdateNotificationState(charging, TrayPower::NOTIFICATION_NONE, false, | |
| 349 false); | |
| 350 } | |
| 351 | |
| 352 // When the rounded minutes-to-empty are above the threshold, no notification | |
| 353 // should be shown. | |
| 354 PowerSupplyProperties low = DefaultPowerSupplyProperties(); | |
| 355 low.set_battery_time_to_empty_sec(TrayPower::kLowPowerMinutes * 60 + 30); | |
| 356 { | |
| 357 SCOPED_TRACE("No notification when time to empty above threshold"); | |
| 358 UpdateNotificationState(low, TrayPower::NOTIFICATION_NONE, false, false); | |
| 359 } | |
| 360 | |
| 361 // When the rounded value matches the threshold, the notification should | |
| 362 // appear. | |
| 363 low.set_battery_time_to_empty_sec(TrayPower::kLowPowerMinutes * 60 + 29); | |
| 364 { | |
| 365 SCOPED_TRACE("Notification when time to empty matches threshold"); | |
| 366 UpdateNotificationState(low, TrayPower::NOTIFICATION_LOW_POWER, true, | |
| 367 false); | |
| 368 } | |
| 369 | |
| 370 // It should persist at lower values. | |
| 371 low.set_battery_time_to_empty_sec(TrayPower::kLowPowerMinutes * 60 - 20); | |
| 372 { | |
| 373 SCOPED_TRACE("Notification persists at lower values"); | |
| 374 UpdateNotificationState(low, TrayPower::NOTIFICATION_LOW_POWER, false, | |
| 375 false); | |
| 376 } | |
| 377 | |
| 378 // The critical low battery notification should be shown when the rounded | |
| 379 // value is at the lower threshold. | |
| 380 PowerSupplyProperties critical = DefaultPowerSupplyProperties(); | |
| 381 critical.set_battery_time_to_empty_sec(TrayPower::kCriticalMinutes * 60 + 29); | |
| 382 { | |
| 383 SCOPED_TRACE("Critical notification when time to empty is critical"); | |
| 384 UpdateNotificationState(critical, TrayPower::NOTIFICATION_CRITICAL, true, | |
| 385 true); | |
| 386 } | |
| 387 | |
| 388 // The notification should be dismissed when the no-warning threshold is | |
| 389 // reached. | |
| 390 PowerSupplyProperties safe = DefaultPowerSupplyProperties(); | |
| 391 safe.set_battery_time_to_empty_sec(TrayPower::kNoWarningMinutes * 60 - 29); | |
| 392 { | |
| 393 SCOPED_TRACE("Notification removed when battery not low"); | |
| 394 UpdateNotificationState(safe, TrayPower::NOTIFICATION_NONE, false, true); | |
| 395 } | |
| 396 | |
| 397 // Test that rounded percentages are used when a USB charger is connected. | |
| 398 PowerSupplyProperties low_usb = DefaultPowerSupplyProperties(); | |
| 399 low_usb.set_external_power( | |
| 400 power_manager::PowerSupplyProperties_ExternalPower_USB); | |
| 401 low_usb.set_battery_percent(TrayPower::kLowPowerPercentage + 0.5); | |
| 402 { | |
| 403 SCOPED_TRACE("No notification for rounded battery percent"); | |
| 404 UpdateNotificationState(low_usb, TrayPower::NOTIFICATION_NONE, true, false); | |
| 405 } | |
| 406 | |
| 407 low_usb.set_battery_percent(TrayPower::kLowPowerPercentage + 0.49); | |
| 408 { | |
| 409 SCOPED_TRACE("Notification for rounded low power percent"); | |
| 410 UpdateNotificationState(low_usb, TrayPower::NOTIFICATION_LOW_POWER, true, | |
| 411 false); | |
| 412 } | |
| 413 | |
| 414 PowerSupplyProperties critical_usb = DefaultPowerSupplyProperties(); | |
| 415 critical_usb.set_external_power( | |
| 416 power_manager::PowerSupplyProperties_ExternalPower_USB); | |
| 417 critical_usb.set_battery_percent(TrayPower::kCriticalPercentage + 0.2); | |
| 418 { | |
| 419 SCOPED_TRACE("Notification for rounded critical power percent"); | |
| 420 UpdateNotificationState(critical_usb, TrayPower::NOTIFICATION_CRITICAL, | |
| 421 true, true); | |
| 422 } | |
| 423 | |
| 424 PowerSupplyProperties safe_usb = DefaultPowerSupplyProperties(); | |
| 425 safe_usb.set_external_power( | |
| 426 power_manager::PowerSupplyProperties_ExternalPower_USB); | |
| 427 safe_usb.set_battery_percent(TrayPower::kNoWarningPercentage - 0.1); | |
| 428 { | |
| 429 SCOPED_TRACE("Notification removed for rounded percent above threshold"); | |
| 430 UpdateNotificationState(safe_usb, TrayPower::NOTIFICATION_NONE, false, | |
| 431 true); | |
| 432 } | |
| 433 } | |
| 434 | |
| 435 } // namespace ash | |
| OLD | NEW |