1 // Copyright (c) 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.
5 #include "chrome/browser/chromeos/power/peripheral_battery_observer.h"
10 #include "ash/strings/grit/ash_strings.h"
11 #include "base/bind.h"
12 #include "base/strings/string16.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/notifications/notification.h"
19 #include "chrome/browser/notifications/notification_ui_manager.h"
20 #include "chrome/browser/profiles/profile_manager.h"
21 #include "chrome/grit/theme_resources.h"
22 #include "chromeos/dbus/dbus_thread_manager.h"
23 #include "content/public/browser/browser_thread.h"
24 #include "device/bluetooth/bluetooth_adapter_factory.h"
25 #include "device/bluetooth/bluetooth_device.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "ui/gfx/image/image.h"
34 // When a peripheral device's battery level is <= kLowBatteryLevel, consider
35 // it to be in low battery condition.
36 const int kLowBatteryLevel
= 15;
38 // Don't show 2 low battery notification within |kNotificationIntervalSec|
40 const int kNotificationIntervalSec
= 60;
42 const char kNotificationOriginUrl
[] = "chrome://peripheral-battery";
44 // HID Bluetooth device's battery sysfs entry path looks like
45 // "/sys/class/power_supply/hid-AA:BB:CC:DD:EE:FF-battery".
46 // Here the bluetooth address is showed in reverse order and its true
47 // address "FF:EE:DD:CC:BB:AA".
48 const char kHIDBatteryPathPrefix
[] = "/sys/class/power_supply/hid-";
49 const char kHIDBatteryPathSuffix
[] = "-battery";
51 bool IsBluetoothHIDBattery(const std::string
& path
) {
52 return base::StartsWith(path
, kHIDBatteryPathPrefix
,
53 base::CompareCase::INSENSITIVE_ASCII
) &&
54 base::EndsWith(path
, kHIDBatteryPathSuffix
,
55 base::CompareCase::INSENSITIVE_ASCII
);
58 std::string
ExtractBluetoothAddress(const std::string
& path
) {
59 int header_size
= strlen(kHIDBatteryPathPrefix
);
60 int end_size
= strlen(kHIDBatteryPathSuffix
);
61 int key_len
= path
.size() - header_size
- end_size
;
64 std::string reverse_address
=
65 base::ToLowerASCII(path
.substr(header_size
, key_len
));
66 std::vector
<std::string
> result
= base::SplitString(
67 reverse_address
, ":", base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
);
68 std::reverse(result
.begin(), result
.end());
69 std::string address
= base::JoinString(result
, ":");
73 class PeripheralBatteryNotificationDelegate
: public NotificationDelegate
{
75 explicit PeripheralBatteryNotificationDelegate(const std::string
& id
)
78 // Overridden from NotificationDelegate:
79 std::string
id() const override
{ return id_
; }
82 ~PeripheralBatteryNotificationDelegate() override
{}
84 const std::string id_
;
86 DISALLOW_COPY_AND_ASSIGN(PeripheralBatteryNotificationDelegate
);
91 PeripheralBatteryObserver::PeripheralBatteryObserver()
92 : testing_clock_(NULL
),
93 notification_profile_(NULL
),
95 new base::WeakPtrFactory
<PeripheralBatteryObserver
>(this)) {
96 DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this);
97 device::BluetoothAdapterFactory::GetAdapter(
98 base::Bind(&PeripheralBatteryObserver::InitializeOnBluetoothReady
,
99 weakptr_factory_
->GetWeakPtr()));
102 PeripheralBatteryObserver::~PeripheralBatteryObserver() {
103 if (bluetooth_adapter_
.get())
104 bluetooth_adapter_
->RemoveObserver(this);
105 DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this);
108 void PeripheralBatteryObserver::PeripheralBatteryStatusReceived(
109 const std::string
& path
,
110 const std::string
& name
,
112 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
114 if (IsBluetoothHIDBattery(path
)) {
115 // For HID bluetooth device, device address is used as key to index
117 address
= ExtractBluetoothAddress(path
);
119 LOG(ERROR
) << "Unsupported battery path " << path
;
123 if (address
.empty()) {
124 LOG(ERROR
) << "No valid battery address at path " << path
;
128 if (level
< -1 || level
> 100) {
129 LOG(ERROR
) << "Invalid battery level " << level
130 << " for device " << name
<< " at path " << path
;
133 // If unknown battery level received, cancel any existing notification.
135 CancelNotification(address
);
139 // Post the notification in 2 cases:
140 // 1. It's the first time the battery level is received, and it is
141 // below kLowBatteryLevel.
142 // 2. The battery level is in record and it drops below kLowBatteryLevel.
143 if (batteries_
.find(address
) == batteries_
.end()) {
144 BatteryInfo
battery(name
, level
, base::TimeTicks());
145 if (level
<= kLowBatteryLevel
) {
146 if (PostNotification(address
, battery
))
147 battery
.last_notification_timestamp
= testing_clock_
?
148 testing_clock_
->NowTicks() : base::TimeTicks::Now();
150 batteries_
[address
] = battery
;
152 BatteryInfo
* battery
= &batteries_
[address
];
153 battery
->name
= name
;
154 int old_level
= battery
->level
;
155 battery
->level
= level
;
156 if (old_level
> kLowBatteryLevel
&& level
<= kLowBatteryLevel
) {
157 if (PostNotification(address
, *battery
))
158 battery
->last_notification_timestamp
= testing_clock_
?
159 testing_clock_
->NowTicks() : base::TimeTicks::Now();
164 void PeripheralBatteryObserver::DeviceChanged(device::BluetoothAdapter
* adapter
,
165 device::BluetoothDevice
* device
) {
166 if (!device
->IsPaired())
167 RemoveBattery(device
->GetAddress());
170 void PeripheralBatteryObserver::DeviceRemoved(device::BluetoothAdapter
* adapter
,
171 device::BluetoothDevice
* device
) {
172 RemoveBattery(device
->GetAddress());
175 void PeripheralBatteryObserver::InitializeOnBluetoothReady(
176 scoped_refptr
<device::BluetoothAdapter
> adapter
) {
177 bluetooth_adapter_
= adapter
;
178 CHECK(bluetooth_adapter_
.get());
179 bluetooth_adapter_
->AddObserver(this);
182 void PeripheralBatteryObserver::RemoveBattery(const std::string
& address
) {
183 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
184 std::string address_lowercase
= base::ToLowerASCII(address
);
185 std::map
<std::string
, BatteryInfo
>::iterator it
=
186 batteries_
.find(address_lowercase
);
187 if (it
!= batteries_
.end()) {
188 batteries_
.erase(it
);
189 CancelNotification(address_lowercase
);
193 bool PeripheralBatteryObserver::PostNotification(const std::string
& address
,
194 const BatteryInfo
& battery
) {
195 // Only post notification if kNotificationInterval seconds have passed since
196 // last notification showed, avoiding the case where the battery level
197 // oscillates around the threshold level.
198 base::TimeTicks now
= testing_clock_
? testing_clock_
->NowTicks() :
199 base::TimeTicks::Now();
200 if (now
- battery
.last_notification_timestamp
<
201 base::TimeDelta::FromSeconds(kNotificationIntervalSec
))
204 NotificationUIManager
* notification_manager
=
205 g_browser_process
->notification_ui_manager();
207 base::string16 string_text
= l10n_util::GetStringFUTF16Int(
208 IDS_ASH_LOW_PERIPHERAL_BATTERY_NOTIFICATION_TEXT
,
211 Notification
notification(
212 message_center::NOTIFICATION_TYPE_SIMPLE
, base::UTF8ToUTF16(battery
.name
),
213 string_text
, ui::ResourceBundle::GetSharedInstance().GetImageNamed(
214 IDR_NOTIFICATION_PERIPHERAL_BATTERY_LOW
),
215 message_center::NotifierId(GURL(kNotificationOriginUrl
)),
216 base::string16(), GURL(kNotificationOriginUrl
), address
,
217 message_center::RichNotificationData(),
218 new PeripheralBatteryNotificationDelegate(address
));
220 notification
.set_priority(message_center::SYSTEM_PRIORITY
);
222 notification_profile_
= ProfileManager::GetPrimaryUserProfile();
223 notification_manager
->Add(notification
, notification_profile_
);
228 void PeripheralBatteryObserver::CancelNotification(const std::string
& address
) {
229 // If last_used_profile_ is NULL then no notification has been posted yet.
230 if (notification_profile_
) {
231 g_browser_process
->notification_ui_manager()->CancelById(
232 address
, NotificationUIManager::GetProfileID(notification_profile_
));
236 } // namespace chromeos