summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ash/ash_strings.grd3
-rw-r--r--chrome/app/theme/theme_resources.grd3
-rw-r--r--chrome/browser/chromeos/chrome_browser_main_chromeos.cc4
-rw-r--r--chrome/browser/chromeos/chrome_browser_main_chromeos.h2
-rw-r--r--chrome/browser/chromeos/power/peripheral_battery_observer.cc239
-rw-r--r--chrome/browser/chromeos/power/peripheral_battery_observer.h99
-rw-r--r--chrome/browser/chromeos/power/peripheral_battery_observer_browsertest.cc158
-rw-r--r--chrome/chrome_browser_chromeos.gypi2
-rw-r--r--chrome/chrome_tests.gypi1
9 files changed, 511 insertions, 0 deletions
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 8b1cf38..14de4a9 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -457,6 +457,9 @@ Press Shift + Alt to switch.
<message name="IDS_ASH_INTERNAL_DISPLAY_NAME" desc="The name of the internal display which is shown in the display settings.">
Internal Display
</message>
+ <message name="IDS_ASH_LOW_PERIPHERAL_BATTERY_NOTIFICATION_TEXT" desc="The text of the notification when a peripheral device is in low battery condition.">
+ Battery low (<ph name="percentage">$1<ex>56</ex></ph>%)
+ </message>
<message name="IDS_ASH_SCREENSHOT_NOTIFICATION_TITLE_SUCCESS" desc="The title of the notification when a screenshot was taken.">
Screenshot taken
</message>
diff --git a/chrome/app/theme/theme_resources.grd b/chrome/app/theme/theme_resources.grd
index c803e2a..ad0c91c 100644
--- a/chrome/app/theme/theme_resources.grd
+++ b/chrome/app/theme/theme_resources.grd
@@ -510,6 +510,9 @@
<structure type="chrome_scaled_image" name="IDR_NEWTAB_CHROME_WELCOME_PAGE_THUMBNAIL" file="common/ntp_welcome_thumb.png" />
</if>
<structure type="chrome_scaled_image" name="IDR_NEWTAB_WEBSTORE_THUMBNAIL" file="ntp_webstore_thumb.png" />
+ <if expr="pp_ifdef('chromeos')">
+ <structure type="chrome_scaled_image" name="IDR_NOTIFICATION_PERIPHERAL_BATTERY_LOW" file="cros/notification_peripheral_battery_low.png" />
+ </if>
<if expr="is_win">
<structure type="chrome_scaled_image" name="IDR_NOTIFICATION_TRAY_DIM" file="win/notification_tray_dim.png" />
<structure type="chrome_scaled_image" name="IDR_NOTIFICATION_TRAY_LIT" file="win/notification_tray_lit.png" />
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
index d3acb3a..8c6c20f 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
@@ -56,6 +56,7 @@
#include "chrome/browser/chromeos/net/network_portal_detector.h"
#include "chrome/browser/chromeos/power/brightness_observer.h"
#include "chrome/browser/chromeos/power/idle_action_warning_observer.h"
+#include "chrome/browser/chromeos/power/peripheral_battery_observer.h"
#include "chrome/browser/chromeos/power/power_button_observer.h"
#include "chrome/browser/chromeos/power/resume_observer.h"
#include "chrome/browser/chromeos/power/screen_lock_observer.h"
@@ -634,6 +635,8 @@ void ChromeBrowserMainPartsChromeos::PostProfileInit() {
}
chromeos::accessibility::Initialize();
+ peripheral_battery_observer_.reset(new PeripheralBatteryObserver());
+
storage_monitor_->Init();
// Initialize the network portal detector for Chrome OS. The network
@@ -738,6 +741,7 @@ void ChromeBrowserMainPartsChromeos::PostMainMessageLoopRun() {
resume_observer_.reset();
brightness_observer_.reset();
retail_mode_power_save_blocker_.reset();
+ peripheral_battery_observer_.reset();
// The XInput2 event listener needs to be shut down earlier than when
// Singletons are finally destroyed in AtExitManager.
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.h b/chrome/browser/chromeos/chrome_browser_main_chromeos.h
index 8da40ad..3f18070 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.h
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.h
@@ -25,6 +25,7 @@ class BrightnessObserver;
class DisplayConfigurationObserver;
class IdleActionWarningObserver;
class MagnificationManager;
+class PeripheralBatteryObserver;
class PowerButtonObserver;
class ResumeObserver;
class ScreenLockObserver;
@@ -80,6 +81,7 @@ class ChromeBrowserMainPartsChromeos : public ChromeBrowserMainPartsLinux {
scoped_ptr<ResumeObserver> resume_observer_;
scoped_ptr<ScreenLockObserver> screen_lock_observer_;
scoped_ptr<ScreensaverController> screensaver_controller_;
+ scoped_ptr<PeripheralBatteryObserver> peripheral_battery_observer_;
scoped_ptr<PowerButtonObserver> power_button_observer_;
scoped_ptr<content::PowerSaveBlocker> retail_mode_power_save_blocker_;
scoped_ptr<UserActivityNotifier> user_activity_notifier_;
diff --git a/chrome/browser/chromeos/power/peripheral_battery_observer.cc b/chrome/browser/chromeos/power/peripheral_battery_observer.cc
new file mode 100644
index 0000000..f2487bc
--- /dev/null
+++ b/chrome/browser/chromeos/power/peripheral_battery_observer.cc
@@ -0,0 +1,239 @@
+// 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 "chrome/browser/chromeos/power/peripheral_battery_observer.h"
+
+#include <vector>
+
+#include "ash/shell.h"
+#include "base/bind.h"
+#include "base/string16.h"
+#include "base/stringprintf.h"
+#include "base/strings/string_split.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/notifications/notification.h"
+#include "chrome/browser/notifications/notification_ui_manager.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "content/public/browser/browser_thread.h"
+#include "device/bluetooth/bluetooth_adapter_factory.h"
+#include "device/bluetooth/bluetooth_device.h"
+#include "grit/ash_strings.h"
+#include "grit/theme_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image.h"
+
+namespace chromeos {
+
+namespace {
+
+// When a peripheral device's battery level is <= kLowBatteryLevel, consider
+// it to be in low battery condition.
+const int kLowBatteryLevel = 15;
+
+// Don't show 2 low battery notification within |kNotificationIntervalSec|
+// seconds.
+const int kNotificationIntervalSec = 60;
+
+const char kNotificationOriginUrl[] = "chrome://peripheral-battery";
+
+// HID Bluetooth device's battery sysfs entry path looks like
+// "/sys/class/power_supply/hid-AA:BB:CC:DD:EE:FF-battery".
+// Here the bluetooth address is showed in reverse order and its true
+// address "FF:EE:DD:CC:BB:AA".
+const char kHIDBatteryPathPrefix[] = "/sys/class/power_supply/hid-";
+const char kHIDBatteryPathSuffix[] = "-battery";
+
+bool IsBluetoothHIDBattery(const std::string& path) {
+ return StartsWithASCII(path, kHIDBatteryPathPrefix, false) &&
+ EndsWith(path, kHIDBatteryPathSuffix, false);
+}
+
+std::string ExtractBluetoothAddress(const std::string& path) {
+ int header_size = strlen(kHIDBatteryPathPrefix);
+ int end_size = strlen(kHIDBatteryPathSuffix);
+ int key_len = path.size() - header_size - end_size;
+ if (key_len <= 0)
+ return std::string();
+ std::string reverse_address = path.substr(header_size, key_len);
+ StringToLowerASCII(&reverse_address);
+ std::vector<std::string> result;
+ base::SplitString(reverse_address, ':', &result);
+ std::reverse(result.begin(), result.end());
+ std::string address = JoinString(result, ':');
+ return address;
+}
+
+class PeripheralBatteryNotificationDelegate : public NotificationDelegate {
+ public:
+ explicit PeripheralBatteryNotificationDelegate(const std::string& id)
+ : id_(id) {}
+
+ // Overridden from NotificationDelegate:
+ virtual void Display() OVERRIDE {}
+ virtual void Error() OVERRIDE {}
+ virtual void Close(bool by_user) OVERRIDE {}
+ virtual void Click() OVERRIDE {}
+ virtual std::string id() const OVERRIDE { return id_; }
+ // A NULL return value prevents loading image from URL. It is OK since our
+ // implementation loads image from system resource bundle.
+ virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE {
+ return NULL;
+ }
+
+ private:
+ virtual ~PeripheralBatteryNotificationDelegate() {}
+
+ const std::string id_;
+
+ DISALLOW_COPY_AND_ASSIGN(PeripheralBatteryNotificationDelegate);
+};
+
+} // namespace
+
+PeripheralBatteryObserver::PeripheralBatteryObserver()
+ : testing_clock_(NULL),
+ weakptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(
+ new base::WeakPtrFactory<PeripheralBatteryObserver>(this))) {
+ DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this);
+ device::BluetoothAdapterFactory::GetAdapter(
+ base::Bind(&PeripheralBatteryObserver::InitializeOnBluetoothReady,
+ weakptr_factory_->GetWeakPtr()));
+}
+
+PeripheralBatteryObserver::~PeripheralBatteryObserver() {
+ if (bluetooth_adapter_.get())
+ bluetooth_adapter_->RemoveObserver(this);
+ DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this);
+}
+
+void PeripheralBatteryObserver::PeripheralBatteryStatusReceived(
+ const std::string& path,
+ const std::string& name,
+ int level) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ std::string address;
+ if (IsBluetoothHIDBattery(path)) {
+ // For HID bluetooth device, device address is used as key to index
+ // BatteryInfo.
+ address = ExtractBluetoothAddress(path);
+ } else {
+ LOG(ERROR) << "Unsupported battery path " << path;
+ return;
+ }
+
+ if (address.empty()) {
+ LOG(ERROR) << "No valid battery address at path " << path;
+ return;
+ }
+
+ if (level < -1 || level > 100) {
+ LOG(ERROR) << "Invalid battery level " << level
+ << " for device " << name << " at path " << path;
+ return;
+ }
+ // If unknown battery level received, cancel any existing notification.
+ if (level == -1) {
+ CancelNotification(address);
+ return;
+ }
+
+ // Post the notification in 2 cases:
+ // 1. It's the first time the battery level is received, and it is
+ // below kLowBatteryLevel.
+ // 2. The battery level is in record and it drops below kLowBatteryLevel.
+ if (batteries_.find(address) == batteries_.end()) {
+ BatteryInfo battery(name, level, base::TimeTicks());
+ if (level <= kLowBatteryLevel) {
+ if (PostNotification(address, battery))
+ battery.last_notification_timestamp = testing_clock_ ?
+ testing_clock_->NowTicks() : base::TimeTicks::Now();
+ }
+ batteries_[address] = battery;
+ } else {
+ BatteryInfo* battery = &batteries_[address];
+ battery->name = name;
+ int old_level = battery->level;
+ battery->level = level;
+ if (old_level > kLowBatteryLevel && level <= kLowBatteryLevel) {
+ if (PostNotification(address, *battery))
+ battery->last_notification_timestamp = testing_clock_ ?
+ testing_clock_->NowTicks() : base::TimeTicks::Now();
+ }
+ }
+}
+
+void PeripheralBatteryObserver::DeviceChanged(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) {
+ if (!device->IsPaired())
+ RemoveBattery(device->GetAddress());
+}
+
+void PeripheralBatteryObserver::DeviceRemoved(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) {
+ RemoveBattery(device->GetAddress());
+}
+
+void PeripheralBatteryObserver::InitializeOnBluetoothReady(
+ scoped_refptr<device::BluetoothAdapter> adapter) {
+ bluetooth_adapter_ = adapter;
+ CHECK(bluetooth_adapter_);
+ bluetooth_adapter_->AddObserver(this);
+}
+
+void PeripheralBatteryObserver::RemoveBattery(const std::string& address) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ std::string address_lowercase = address;
+ StringToLowerASCII(&address_lowercase);
+ std::map<std::string, BatteryInfo>::iterator it =
+ batteries_.find(address_lowercase);
+ if (it != batteries_.end()) {
+ batteries_.erase(it);
+ CancelNotification(address_lowercase);
+ }
+}
+
+bool PeripheralBatteryObserver::PostNotification(const std::string& address,
+ const BatteryInfo& battery) {
+ // Only post notification if kNotificationInterval seconds have passed since
+ // last notification showed, avoiding the case where the battery level
+ // oscillates around the threshold level.
+ base::TimeTicks now = testing_clock_ ? testing_clock_->NowTicks() :
+ base::TimeTicks::Now();
+ if (now - battery.last_notification_timestamp <
+ base::TimeDelta::FromSeconds(kNotificationIntervalSec))
+ return false;
+
+ NotificationUIManager* notification_manager =
+ g_browser_process->notification_ui_manager();
+
+ base::string16 string_text = l10n_util::GetStringFUTF16Int(
+ IDS_ASH_LOW_PERIPHERAL_BATTERY_NOTIFICATION_TEXT,
+ battery.level);
+
+ Notification notification(
+ // TODO(mukai): add SYSTEM priority and use here.
+ GURL(kNotificationOriginUrl),
+ ui::ResourceBundle::GetSharedInstance().GetImageNamed(
+ IDR_NOTIFICATION_PERIPHERAL_BATTERY_LOW),
+ UTF8ToUTF16(battery.name),
+ string_text,
+ WebKit::WebTextDirectionDefault,
+ string16(),
+ UTF8ToUTF16(address),
+ new PeripheralBatteryNotificationDelegate(address));
+
+ notification_manager->Add(notification,
+ ProfileManager::GetDefaultProfileOrOffTheRecord());
+
+ return true;
+}
+
+void PeripheralBatteryObserver::CancelNotification(const std::string& address) {
+ g_browser_process->notification_ui_manager()->CancelById(address);
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/chromeos/power/peripheral_battery_observer.h b/chrome/browser/chromeos/power/peripheral_battery_observer.h
new file mode 100644
index 0000000..0e958be
--- /dev/null
+++ b/chrome/browser/chromeos/power/peripheral_battery_observer.h
@@ -0,0 +1,99 @@
+// 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.
+
+#ifndef CHROME_BROWSER_CHROMEOS_POWER_PERIPHERAL_BATTERY_OBSERVER_H_
+#define CHROME_BROWSER_CHROMEOS_POWER_PERIPHERAL_BATTERY_OBSERVER_H_
+
+#include <map>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/test/simple_test_tick_clock.h"
+#include "chromeos/dbus/power_manager_client.h"
+#include "device/bluetooth/bluetooth_adapter.h"
+
+namespace chromeos {
+
+class BluetoothDevice;
+class PeripheralBatteryObserverTest;
+
+// This observer listens for peripheral device battery status and shows
+// notifications for low battery conditions.
+class PeripheralBatteryObserver : public PowerManagerClient::Observer,
+ public device::BluetoothAdapter::Observer {
+ public:
+ // This class registers/unregisters itself as an observer in ctor/dtor.
+ PeripheralBatteryObserver();
+ virtual ~PeripheralBatteryObserver();
+
+ void set_testing_clock(base::SimpleTestTickClock* clock) {
+ testing_clock_ = clock;
+ }
+
+ // PowerManagerClient::Observer implementation.
+ virtual void PeripheralBatteryStatusReceived(const std::string& path,
+ const std::string& name,
+ int level) OVERRIDE;
+
+ // device::BluetoothAdapter::Observer implementation.
+ virtual void DeviceChanged(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) OVERRIDE;
+ virtual void DeviceRemoved(device::BluetoothAdapter* adapter,
+ device::BluetoothDevice* device) OVERRIDE;
+
+ private:
+ friend class PeripheralBatteryObserverTest;
+ FRIEND_TEST_ALL_PREFIXES(PeripheralBatteryObserverTest, Basic);
+ FRIEND_TEST_ALL_PREFIXES(PeripheralBatteryObserverTest, InvalidBatteryInfo);
+ FRIEND_TEST_ALL_PREFIXES(PeripheralBatteryObserverTest, DeviceRemove);
+
+ struct BatteryInfo {
+ BatteryInfo() : level(-1) {}
+ BatteryInfo(const std::string& name,
+ int level,
+ base::TimeTicks notification_timestamp)
+ : name(name),
+ level(level),
+ last_notification_timestamp(notification_timestamp) {
+ }
+
+ // Human readable name for the device. It is changeable.
+ std::string name;
+ // Battery level within range [0, 100], and -1 for unknown level.
+ int level;
+ base::TimeTicks last_notification_timestamp;
+ };
+
+ void InitializeOnBluetoothReady(
+ scoped_refptr<device::BluetoothAdapter> adapter);
+
+ void RemoveBattery(const std::string& address);
+
+ // Posts a low battery notification with unique id |address|. Returns true
+ // if the notification is posted, false if not.
+ bool PostNotification(const std::string& address, const BatteryInfo& battery);
+
+ void CancelNotification(const std::string& address);
+
+ // Record of existing battery infomation. For bluetooth HID device, the key
+ // is the address of the bluetooth device.
+ std::map<std::string, BatteryInfo> batteries_;
+
+ // PeripheralBatteryObserver is an observer of |bluetooth_adapter_| for
+ // bluetooth device change/remove events.
+ scoped_refptr<device::BluetoothAdapter> bluetooth_adapter_;
+
+ // Used only for helping test. Not owned and can be NULL.
+ base::SimpleTestTickClock* testing_clock_;
+
+ scoped_ptr<base::WeakPtrFactory<PeripheralBatteryObserver> > weakptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(PeripheralBatteryObserver);
+};
+
+} // namespace chromeos
+
+#endif // CHROME_BROWSER_CHROMEOS_POWER_PERIPHERAL_BATTERY_OBSERVER_H_
diff --git a/chrome/browser/chromeos/power/peripheral_battery_observer_browsertest.cc b/chrome/browser/chromeos/power/peripheral_battery_observer_browsertest.cc
new file mode 100644
index 0000000..533a5bb
--- /dev/null
+++ b/chrome/browser/chromeos/power/peripheral_battery_observer_browsertest.cc
@@ -0,0 +1,158 @@
+// 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 "chrome/browser/chromeos/power/peripheral_battery_observer.h"
+
+#include "base/command_line.h"
+#include "base/message_loop.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/cros/cros_in_process_browser_test.h"
+#include "chrome/browser/notifications/notification_ui_manager.h"
+#include "chromeos/dbus/mock_dbus_thread_manager.h"
+#include "chromeos/dbus/mock_update_engine_client.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_browser_thread.h"
+#include "content/public/test/test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::SaveArg;
+
+namespace {
+
+const char kTestBatteryPath[] = "/sys/class/power_supply/hid-AA:BB:CC-battery";
+const char kTestBatteryAddress[] = "cc:bb:aa";
+const char kTestDeviceName[] = "test device";
+
+} // namespace
+
+namespace chromeos {
+
+class PeripheralBatteryObserverTest : public CrosInProcessBrowserTest {
+ public:
+ PeripheralBatteryObserverTest () {}
+ virtual ~PeripheralBatteryObserverTest () {}
+
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ MockDBusThreadManager* mock_dbus_thread_manager = new MockDBusThreadManager;
+ DBusThreadManager::InitializeForTesting(mock_dbus_thread_manager);
+ CrosInProcessBrowserTest::SetUpInProcessBrowserTestFixture();
+ }
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ observer_.reset(new PeripheralBatteryObserver());
+ }
+
+ virtual void CleanUpOnMainThread() OVERRIDE {
+ observer_.reset();
+ }
+
+ virtual void TearDownInProcessBrowserTestFixture() OVERRIDE {
+ CrosInProcessBrowserTest::TearDownInProcessBrowserTestFixture();
+ DBusThreadManager::Shutdown();
+ }
+
+ protected:
+ scoped_ptr<PeripheralBatteryObserver> observer_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PeripheralBatteryObserverTest);
+};
+
+IN_PROC_BROWSER_TEST_F(PeripheralBatteryObserverTest, Basic) {
+ base::SimpleTestTickClock clock;
+ observer_->set_testing_clock(&clock);
+
+ NotificationUIManager* notification_manager =
+ g_browser_process->notification_ui_manager();
+
+ // Level 50 at time 100, no low-battery notification.
+ clock.Advance(base::TimeDelta::FromSeconds(100));
+ observer_->PeripheralBatteryStatusReceived(kTestBatteryPath,
+ kTestDeviceName, 50);
+ EXPECT_EQ(observer_->batteries_.count(kTestBatteryAddress), 1u);
+
+ const PeripheralBatteryObserver::BatteryInfo& info =
+ observer_->batteries_[kTestBatteryAddress];
+
+ EXPECT_EQ(info.name, kTestDeviceName);
+ EXPECT_EQ(info.level, 50);
+ EXPECT_EQ(info.last_notification_timestamp, base::TimeTicks());
+ EXPECT_FALSE(notification_manager->DoesIdExist(kTestBatteryAddress));
+
+ // Level 5 at time 110, low-battery notification.
+ clock.Advance(base::TimeDelta::FromSeconds(10));
+ observer_->PeripheralBatteryStatusReceived(kTestBatteryPath,
+ kTestDeviceName, 5);
+ EXPECT_EQ(info.level, 5);
+ EXPECT_EQ(info.last_notification_timestamp, clock.NowTicks());
+ EXPECT_TRUE(notification_manager->DoesIdExist(kTestBatteryAddress));
+
+ // Level -1 at time 115, cancel previous notification
+ clock.Advance(base::TimeDelta::FromSeconds(5));
+ observer_->PeripheralBatteryStatusReceived(kTestBatteryPath,
+ kTestDeviceName, -1);
+ EXPECT_EQ(info.level, 5);
+ EXPECT_EQ(info.last_notification_timestamp,
+ clock.NowTicks() - base::TimeDelta::FromSeconds(5));
+ EXPECT_FALSE(notification_manager->DoesIdExist(kTestBatteryAddress));
+
+ // Level 50 at time 120, no low-battery notification.
+ clock.Advance(base::TimeDelta::FromSeconds(5));
+ observer_->PeripheralBatteryStatusReceived(kTestBatteryPath,
+ kTestDeviceName, 50);
+ EXPECT_EQ(info.level, 50);
+ EXPECT_EQ(info.last_notification_timestamp,
+ clock.NowTicks() - base::TimeDelta::FromSeconds(10));
+ EXPECT_FALSE(notification_manager->DoesIdExist(kTestBatteryAddress));
+
+ // Level 5 at time 130, no low-battery notification (throttling).
+ clock.Advance(base::TimeDelta::FromSeconds(10));
+ observer_->PeripheralBatteryStatusReceived(kTestBatteryPath,
+ kTestDeviceName, 5);
+ EXPECT_EQ(info.level, 5);
+ EXPECT_EQ(info.last_notification_timestamp,
+ clock.NowTicks() - base::TimeDelta::FromSeconds(20));
+ EXPECT_FALSE(notification_manager->DoesIdExist(kTestBatteryAddress));
+}
+
+IN_PROC_BROWSER_TEST_F(PeripheralBatteryObserverTest, InvalidBatteryInfo) {
+ observer_->PeripheralBatteryStatusReceived("invalid-path", kTestDeviceName,
+ 10);
+ EXPECT_TRUE(observer_->batteries_.empty());
+
+ observer_->PeripheralBatteryStatusReceived(
+ "/sys/class/power_supply/hid-battery", kTestDeviceName, 10);
+ EXPECT_TRUE(observer_->batteries_.empty());
+
+ observer_->PeripheralBatteryStatusReceived(kTestBatteryPath,
+ kTestDeviceName, -2);
+ EXPECT_TRUE(observer_->batteries_.empty());
+
+ observer_->PeripheralBatteryStatusReceived(kTestBatteryPath,
+ kTestDeviceName, 101);
+ EXPECT_TRUE(observer_->batteries_.empty());
+
+ observer_->PeripheralBatteryStatusReceived(kTestBatteryPath,
+ kTestDeviceName, -1);
+ EXPECT_TRUE(observer_->batteries_.empty());
+}
+
+IN_PROC_BROWSER_TEST_F(PeripheralBatteryObserverTest, DeviceRemove) {
+ NotificationUIManager* notification_manager =
+ g_browser_process->notification_ui_manager();
+
+ observer_->PeripheralBatteryStatusReceived(kTestBatteryPath,
+ kTestDeviceName, 5);
+ EXPECT_EQ(observer_->batteries_.count(kTestBatteryAddress), 1u);
+ EXPECT_TRUE(notification_manager->DoesIdExist(kTestBatteryAddress));
+
+ observer_->RemoveBattery(kTestBatteryAddress);
+ EXPECT_FALSE(notification_manager->DoesIdExist(kTestBatteryAddress));
+}
+
+} // namespace chromeos
diff --git a/chrome/chrome_browser_chromeos.gypi b/chrome/chrome_browser_chromeos.gypi
index 084cb0b..a239e25 100644
--- a/chrome/chrome_browser_chromeos.gypi
+++ b/chrome/chrome_browser_chromeos.gypi
@@ -605,6 +605,8 @@
'browser/chromeos/power/idle_action_warning_dialog_view.h',
'browser/chromeos/power/idle_action_warning_observer.cc',
'browser/chromeos/power/idle_action_warning_observer.h',
+ 'browser/chromeos/power/peripheral_battery_observer.cc',
+ 'browser/chromeos/power/peripheral_battery_observer.h',
'browser/chromeos/power/power_button_observer.cc',
'browser/chromeos/power/power_button_observer.h',
'browser/chromeos/power/resume_observer.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 100c17b..7e13ab6 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1211,6 +1211,7 @@
'browser/chromeos/policy/device_status_collector_browsertest.cc',
'browser/chromeos/policy/policy_cert_verifier_browsertest.cc',
'browser/chromeos/policy/power_policy_browsertest.cc',
+ 'browser/chromeos/power/peripheral_battery_observer_browsertest.cc',
'browser/chromeos/screensaver/screensaver_controller_browsertest.cc',
'browser/chromeos/system/tray_accessibility_browsertest.cc',
'browser/chromeos/ui/idle_logout_dialog_view_browsertest.cc',