| // Copyright 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/peripheral_battery_notifier.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "ash/public/cpp/test/test_system_tray_client.h" |
| #include "ash/shell.h" |
| #include "ash/system/power/peripheral_battery_listener.h" |
| #include "ash/system/power/peripheral_battery_tests.h" |
| #include "ash/test/ash_test_base.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "ui/events/devices/device_data_manager.h" |
| #include "ui/events/devices/device_data_manager_test_api.h" |
| #include "ui/events/devices/touchscreen_device.h" |
| #include "ui/message_center/message_center.h" |
| #include "ui/message_center/public/cpp/notification.h" |
| |
| using BI = ash::PeripheralBatteryListener::BatteryInfo; |
| |
| namespace { |
| |
| const std::u16string& NotificationMessagePrefix() { |
| static const std::u16string prefix(base::ASCIIToUTF16("Battery low (")); |
| return prefix; |
| } |
| |
| const std::u16string& NotificationMessageSuffix() { |
| static const std::u16string suffix(base::ASCIIToUTF16("%)")); |
| return suffix; |
| } |
| |
| } // namespace |
| |
| namespace ash { |
| |
| class PeripheralBatteryNotifierTest : public AshTestBase { |
| public: |
| PeripheralBatteryNotifierTest() |
| : AshTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {} |
| PeripheralBatteryNotifierTest(const PeripheralBatteryNotifierTest&) = delete; |
| PeripheralBatteryNotifierTest& operator=( |
| const PeripheralBatteryNotifierTest&) = delete; |
| ~PeripheralBatteryNotifierTest() override = default; |
| |
| void SetUp() override { |
| ui::DeviceDataManager::CreateInstance(); |
| |
| AshTestBase::SetUp(); |
| |
| // Simulate the complete listing of input devices, required by the listener. |
| ui::DeviceDataManagerTestApi().OnDeviceListsComplete(); |
| |
| message_center_ = message_center::MessageCenter::Get(); |
| system_tray_client_ = GetSystemTrayClient(); |
| |
| battery_listener_ = std::make_unique<PeripheralBatteryListener>(); |
| battery_notifier_ = |
| std::make_unique<PeripheralBatteryNotifier>(battery_listener_.get()); |
| // No notifications should have been posted yet. |
| ASSERT_EQ(0u, message_center_->NotificationCount()); |
| } |
| |
| void TearDown() override { |
| battery_notifier_.reset(); |
| battery_listener_.reset(); |
| AshTestBase::TearDown(); |
| |
| ui::DeviceDataManager::DeleteInstance(); |
| } |
| |
| // Extracts the battery percentage from the message of a notification. |
| uint8_t ExtractBatteryPercentage(message_center::Notification* notification) { |
| const std::u16string& message = notification->message(); |
| EXPECT_TRUE(base::StartsWith(message, NotificationMessagePrefix(), |
| base::CompareCase::SENSITIVE)); |
| EXPECT_TRUE(base::EndsWith(message, NotificationMessageSuffix(), |
| base::CompareCase::SENSITIVE)); |
| |
| int prefix_size = NotificationMessagePrefix().size(); |
| int suffix_size = NotificationMessageSuffix().size(); |
| int key_len = message.size() - prefix_size - suffix_size; |
| EXPECT_GT(key_len, 0); |
| |
| int battery_percentage; |
| EXPECT_TRUE(base::StringToInt(message.substr(prefix_size, key_len), |
| &battery_percentage)); |
| EXPECT_GE(battery_percentage, 0); |
| EXPECT_LE(battery_percentage, 100); |
| return battery_percentage; |
| } |
| |
| base::TimeTicks GetTestingClock() { return base::TimeTicks::Now(); } |
| |
| void ClockAdvance(base::TimeDelta delta) { |
| task_environment()->AdvanceClock(delta); |
| } |
| |
| void UpdateBatteryLevel(bool add_first, |
| const std::string key, |
| const std::string name, |
| base::Optional<uint8_t> level, |
| BI::PeripheralType type, |
| const std::string btaddr) { |
| BI info(key, base::ASCIIToUTF16(name), level, GetTestingClock(), type, |
| BI::ChargeStatus::kUnknown, btaddr); |
| if (add_first) |
| battery_notifier_->OnAddingBattery(info); |
| battery_notifier_->OnUpdatedBatteryLevel(info); |
| } |
| |
| void RemoveBattery(const std::string key, |
| const std::string name, |
| base::Optional<uint8_t> level, |
| BI::PeripheralType type, |
| const std::string btaddr) { |
| BI info(key, base::ASCIIToUTF16(name), level, GetTestingClock(), type, |
| BI::ChargeStatus::kUnknown, btaddr); |
| battery_notifier_->OnRemovingBattery(info); |
| } |
| |
| protected: |
| message_center::MessageCenter* message_center_; |
| TestSystemTrayClient* system_tray_client_; |
| std::unique_ptr<PeripheralBatteryNotifier> battery_notifier_; |
| std::unique_ptr<PeripheralBatteryListener> battery_listener_; |
| |
| void set_complete_devices(bool complete_devices) { |
| complete_devices_ = complete_devices; |
| } |
| |
| // SetUp() doesn't complete devices if this is set to false. |
| bool complete_devices_ = true; |
| }; |
| |
| TEST_F(PeripheralBatteryNotifierTest, Basic) { |
| |
| // Level 50 at time 100, no low-battery notification. |
| ClockAdvance(base::TimeDelta::FromSeconds(100)); |
| UpdateBatteryLevel(true, kTestBatteryId, kTestDeviceName, 50, |
| BI::PeripheralType::kOther, kTestBatteryAddress); |
| EXPECT_EQ(1u, |
| battery_notifier_->battery_notifications_.count(kTestBatteryId)); |
| |
| const PeripheralBatteryNotifier::NotificationInfo& info = |
| battery_notifier_->battery_notifications_[kTestBatteryId]; |
| |
| EXPECT_EQ(base::nullopt, info.level); |
| EXPECT_EQ(GetTestingClock(), info.last_notification_timestamp); |
| EXPECT_FALSE( |
| message_center_->FindVisibleNotificationById(kTestBatteryNotificationId)); |
| |
| // Level 5 at time 110, low-battery notification. |
| ClockAdvance(base::TimeDelta::FromSeconds(10)); |
| UpdateBatteryLevel(false, kTestBatteryId, kTestDeviceName, 5, |
| BI::PeripheralType::kOther, kTestBatteryAddress); |
| EXPECT_EQ(5, info.level); |
| |
| EXPECT_EQ(GetTestingClock(), info.last_notification_timestamp); |
| EXPECT_TRUE( |
| message_center_->FindVisibleNotificationById(kTestBatteryNotificationId)); |
| |
| // Verify that the low-battery notification for stylus does not show up. |
| EXPECT_FALSE(message_center_->FindVisibleNotificationById( |
| PeripheralBatteryNotifier::kStylusNotificationId)); |
| |
| // Level -1 at time 115, cancel previous notification. |
| ClockAdvance(base::TimeDelta::FromSeconds(5)); |
| UpdateBatteryLevel(false, kTestBatteryId, kTestDeviceName, base::nullopt, |
| BI::PeripheralType::kOther, kTestBatteryAddress); |
| EXPECT_EQ(base::nullopt, info.level); |
| EXPECT_EQ(GetTestingClock() - base::TimeDelta::FromSeconds(5), |
| info.last_notification_timestamp); |
| EXPECT_FALSE( |
| message_center_->FindVisibleNotificationById(kTestBatteryNotificationId)); |
| |
| // Level 50 at time 120, no low-battery notification. |
| ClockAdvance(base::TimeDelta::FromSeconds(5)); |
| UpdateBatteryLevel(false, kTestBatteryId, kTestDeviceName, 50, |
| BI::PeripheralType::kOther, kTestBatteryAddress); |
| EXPECT_EQ(base::nullopt, info.level); |
| EXPECT_EQ(GetTestingClock() - base::TimeDelta::FromSeconds(10), |
| info.last_notification_timestamp); |
| EXPECT_FALSE( |
| message_center_->FindVisibleNotificationById(kTestBatteryNotificationId)); |
| |
| // Level 5 at time 130, no low-battery notification (throttling). |
| ClockAdvance(base::TimeDelta::FromSeconds(10)); |
| UpdateBatteryLevel(false, kTestBatteryId, kTestDeviceName, 5, |
| BI::PeripheralType::kOther, kTestBatteryAddress); |
| EXPECT_EQ(5, info.level); |
| EXPECT_EQ(GetTestingClock() - base::TimeDelta::FromSeconds(20), |
| info.last_notification_timestamp); |
| EXPECT_FALSE( |
| message_center_->FindVisibleNotificationById(kTestBatteryNotificationId)); |
| } |
| |
| TEST_F(PeripheralBatteryNotifierTest, EarlyNotification) { |
| // Level 15 at time 10, low-battery notification. |
| ClockAdvance(base::TimeDelta::FromSeconds(10)); |
| UpdateBatteryLevel(true, kTestBatteryId, kTestDeviceName, 15, |
| BI::PeripheralType::kOther, kTestBatteryAddress); |
| EXPECT_EQ(1u, |
| battery_notifier_->battery_notifications_.count(kTestBatteryId)); |
| |
| const PeripheralBatteryNotifier::NotificationInfo& info = |
| battery_notifier_->battery_notifications_[kTestBatteryId]; |
| |
| EXPECT_EQ(15, info.level); |
| EXPECT_EQ(GetTestingClock(), info.last_notification_timestamp); |
| EXPECT_TRUE( |
| message_center_->FindVisibleNotificationById(kTestBatteryNotificationId)); |
| } |
| |
| TEST_F(PeripheralBatteryNotifierTest, StylusNotification) { |
| const std::string kTestStylusBatteryPath = |
| "/sys/class/power_supply/hid-AAAA:BBBB:CCCC.DDDD-battery"; |
| const std::string kTestStylusBatteryId = |
| "???hxxxxid-AAAA:BBBB:CCCC.DDDD-battery"; |
| const std::string kTestStylusName = "test_stylus"; |
| |
| // Add an external stylus to our test device manager. |
| ui::TouchscreenDevice stylus( |
| /*id=*/0, ui::INPUT_DEVICE_USB, kTestStylusName, gfx::Size(), |
| /*touch_points=*/1, |
| /*has_stylus=*/true); |
| stylus.sys_path = base::FilePath(kTestStylusBatteryPath); |
| |
| ui::DeviceDataManagerTestApi().SetTouchscreenDevices({stylus}); |
| |
| // Verify that when the battery level is 50, no stylus low battery |
| // notification is shown. |
| UpdateBatteryLevel(true, kTestStylusBatteryId, kTestStylusName, 50, |
| BI::PeripheralType::kStylusViaScreen, ""); |
| EXPECT_FALSE(message_center_->FindVisibleNotificationById( |
| PeripheralBatteryNotifier::kStylusNotificationId)); |
| |
| // Verify that when the battery level is 5, a stylus low battery notification |
| // is shown. Also check that a non stylus device low battery notification will |
| // not show up. |
| UpdateBatteryLevel(false, kTestStylusBatteryId, kTestStylusName, 5, |
| BI::PeripheralType::kStylusViaScreen, ""); |
| EXPECT_TRUE(message_center_->FindVisibleNotificationById( |
| PeripheralBatteryNotifier::kStylusNotificationId)); |
| EXPECT_FALSE( |
| message_center_->FindVisibleNotificationById(kTestBatteryAddress)); |
| |
| // Verify that when the battery level is -1, the previous stylus low battery |
| // notification is cancelled. |
| UpdateBatteryLevel(false, kTestStylusBatteryId, kTestStylusName, |
| base::nullopt, BI::PeripheralType::kStylusViaScreen, ""); |
| EXPECT_FALSE(message_center_->FindVisibleNotificationById( |
| PeripheralBatteryNotifier::kStylusNotificationId)); |
| } |
| |
| TEST_F(PeripheralBatteryNotifierTest, |
| Bluetooth_CreatesANotificationForEachDevice) { |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, 5, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| UpdateBatteryLevel(true, kBluetoothDeviceId2, kBluetoothDeviceName2, 0, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress2); |
| |
| // Verify 2 notifications were posted with the correct values. |
| EXPECT_EQ(2u, message_center_->NotificationCount()); |
| message_center::Notification* notification_1 = |
| message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId1); |
| message_center::Notification* notification_2 = |
| message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId2); |
| |
| EXPECT_TRUE(notification_1); |
| EXPECT_EQ(base::ASCIIToUTF16(kBluetoothDeviceName1), notification_1->title()); |
| EXPECT_EQ(5, ExtractBatteryPercentage(notification_1)); |
| EXPECT_TRUE(notification_2); |
| EXPECT_EQ(base::ASCIIToUTF16(kBluetoothDeviceName2), notification_2->title()); |
| EXPECT_EQ(0, ExtractBatteryPercentage(notification_2)); |
| } |
| |
| TEST_F(PeripheralBatteryNotifierTest, |
| Bluetooth_RemovesNotificationForDisconnectedDevices) { |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, 5, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| UpdateBatteryLevel(true, kBluetoothDeviceId2, kBluetoothDeviceName2, 0, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress2); |
| |
| // Verify 2 notifications were posted. |
| EXPECT_EQ(2u, message_center_->NotificationCount()); |
| |
| // Verify only the notification for device 1 gets removed. |
| RemoveBattery(kBluetoothDeviceId1, kBluetoothDeviceName1, 5, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| EXPECT_EQ(1u, message_center_->NotificationCount()); |
| EXPECT_TRUE(message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId2)); |
| |
| // Remove the second notification. |
| RemoveBattery(kBluetoothDeviceId2, kBluetoothDeviceName2, 0, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress2); |
| EXPECT_EQ(0u, message_center_->NotificationCount()); |
| } |
| |
| TEST_F(PeripheralBatteryNotifierTest, |
| Bluetooth_CancelNotificationForInvalidBatteryLevel) { |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, 1, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| EXPECT_TRUE(message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId1)); |
| |
| // The notification should get canceled. |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, |
| base::nullopt, BI::PeripheralType::kOther, |
| kBluetoothDeviceAddress1); |
| EXPECT_FALSE(message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId1)); |
| } |
| |
| // Don't post a notification if the battery level drops again under the |
| // threshold before kNotificationInterval is completed. |
| TEST_F(PeripheralBatteryNotifierTest, |
| DontShowSecondNotificationWithinASmallTimeInterval) { |
| ClockAdvance(base::TimeDelta::FromSeconds(100)); |
| |
| // Post a notification. |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, 1, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| EXPECT_TRUE(message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId1)); |
| |
| // Cancel the notification. |
| ClockAdvance(base::TimeDelta::FromSeconds(1)); |
| UpdateBatteryLevel(false, kBluetoothDeviceId1, kBluetoothDeviceName1, |
| base::nullopt, BI::PeripheralType::kOther, |
| kBluetoothDeviceAddress1); |
| EXPECT_FALSE(message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId1)); |
| |
| // The battery level falls below the threshold after a short time period. No |
| // notification should get posted. |
| ClockAdvance(base::TimeDelta::FromSeconds(1)); |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, 1, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| EXPECT_FALSE(message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId1)); |
| } |
| |
| // Post a notification if the battery is under threshold, then unknown level and |
| // then is again under the threshold after kNotificationInterval is completed. |
| TEST_F(PeripheralBatteryNotifierTest, |
| PostNotificationIfBatteryGoesFromUnknownLevelToBelowThreshold) { |
| ClockAdvance(base::TimeDelta::FromSeconds(100)); |
| |
| // Post a notification. |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, 1, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| EXPECT_TRUE(message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId1)); |
| |
| // Cancel the notification. |
| ClockAdvance(base::TimeDelta::FromSeconds(1)); |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, |
| base::nullopt, BI::PeripheralType::kOther, |
| kBluetoothDeviceAddress1); |
| EXPECT_FALSE(message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId1)); |
| |
| // Post notification if we are out of the kNotificationInterval. |
| ClockAdvance(base::TimeDelta::FromSeconds(100)); |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, 1, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| EXPECT_TRUE(message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId1)); |
| } |
| |
| // Don't Post another notification if the battery level keeps low and the user |
| // dismissed the previous notification. |
| TEST_F(PeripheralBatteryNotifierTest, |
| DontRepostNotificationIfUserDismissedPreviousOne) { |
| ClockAdvance(base::TimeDelta::FromSeconds(100)); |
| |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, 5, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| EXPECT_EQ(1u, message_center_->NotificationCount()); |
| |
| // Simulate the user clears the notification. |
| message_center_->RemoveAllNotifications( |
| /*by_user=*/true, message_center::MessageCenter::RemoveType::ALL); |
| |
| // The battery level remains low, but shouldn't post a notification. |
| ClockAdvance(base::TimeDelta::FromSeconds(100)); |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, 5, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| EXPECT_EQ(0u, message_center_->NotificationCount()); |
| } |
| |
| // If there is an existing notification and the battery level remains low, |
| // update its content. |
| TEST_F(PeripheralBatteryNotifierTest, UpdateNotificationIfVisible) { |
| ClockAdvance(base::TimeDelta::FromSeconds(100)); |
| |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, 5, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| EXPECT_EQ(1u, message_center_->NotificationCount()); |
| |
| // The battery level remains low, should update the notification. |
| ClockAdvance(base::TimeDelta::FromSeconds(100)); |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, 3, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| message_center::Notification* notification = |
| message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId1); |
| EXPECT_TRUE(notification); |
| EXPECT_EQ(base::ASCIIToUTF16(kBluetoothDeviceName1), notification->title()); |
| EXPECT_EQ(3, ExtractBatteryPercentage(notification)); |
| } |
| |
| TEST_F(PeripheralBatteryNotifierTest, OpenBluetoothSettingsUi) { |
| ClockAdvance(base::TimeDelta::FromSeconds(100)); |
| |
| UpdateBatteryLevel(true, kBluetoothDeviceId1, kBluetoothDeviceName1, 5, |
| BI::PeripheralType::kOther, kBluetoothDeviceAddress1); |
| EXPECT_EQ(1u, message_center_->NotificationCount()); |
| |
| message_center::Notification* notification = |
| message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId1); |
| EXPECT_TRUE(notification); |
| |
| message_center_->ClickOnNotification(notification->id()); |
| EXPECT_EQ(1, system_tray_client_->show_bluetooth_settings_count()); |
| EXPECT_FALSE(message_center_->FindVisibleNotificationById( |
| kBluetoothDeviceNotificationId1)); |
| } |
| |
| } // namespace ash |