peripheral_battery_observer.cc revision 5e3f23d412006dc4db4e659864679f29341e113f
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.
4
5#include "chrome/browser/chromeos/power/peripheral_battery_observer.h"
6
7#include <vector>
8
9#include "ash/shell.h"
10#include "base/bind.h"
11#include "base/strings/string16.h"
12#include "base/strings/string_split.h"
13#include "base/strings/stringprintf.h"
14#include "base/strings/utf_string_conversions.h"
15#include "chrome/browser/browser_process.h"
16#include "chrome/browser/notifications/notification.h"
17#include "chrome/browser/notifications/notification_ui_manager.h"
18#include "chrome/browser/profiles/profile_manager.h"
19#include "chromeos/dbus/dbus_thread_manager.h"
20#include "content/public/browser/browser_thread.h"
21#include "device/bluetooth/bluetooth_adapter_factory.h"
22#include "device/bluetooth/bluetooth_device.h"
23#include "grit/ash_strings.h"
24#include "grit/theme_resources.h"
25#include "ui/base/l10n/l10n_util.h"
26#include "ui/base/resource/resource_bundle.h"
27#include "ui/gfx/image/image.h"
28
29namespace chromeos {
30
31namespace {
32
33// When a peripheral device's battery level is <= kLowBatteryLevel, consider
34// it to be in low battery condition.
35const int kLowBatteryLevel = 15;
36
37// Don't show 2 low battery notification within |kNotificationIntervalSec|
38// seconds.
39const int kNotificationIntervalSec = 60;
40
41const char kNotificationOriginUrl[] = "chrome://peripheral-battery";
42
43// HID Bluetooth device's battery sysfs entry path looks like
44// "/sys/class/power_supply/hid-AA:BB:CC:DD:EE:FF-battery".
45// Here the bluetooth address is showed in reverse order and its true
46// address "FF:EE:DD:CC:BB:AA".
47const char kHIDBatteryPathPrefix[] = "/sys/class/power_supply/hid-";
48const char kHIDBatteryPathSuffix[] = "-battery";
49
50bool IsBluetoothHIDBattery(const std::string& path) {
51  return StartsWithASCII(path, kHIDBatteryPathPrefix, false) &&
52      EndsWith(path, kHIDBatteryPathSuffix, false);
53}
54
55std::string ExtractBluetoothAddress(const std::string& path) {
56  int header_size = strlen(kHIDBatteryPathPrefix);
57  int end_size = strlen(kHIDBatteryPathSuffix);
58  int key_len = path.size() - header_size - end_size;
59  if (key_len <= 0)
60    return std::string();
61  std::string reverse_address = path.substr(header_size, key_len);
62  StringToLowerASCII(&reverse_address);
63  std::vector<std::string> result;
64  base::SplitString(reverse_address, ':', &result);
65  std::reverse(result.begin(), result.end());
66  std::string address = JoinString(result, ':');
67  return address;
68}
69
70class PeripheralBatteryNotificationDelegate : public NotificationDelegate {
71 public:
72  explicit PeripheralBatteryNotificationDelegate(const std::string& id)
73      : id_(id) {}
74
75  // Overridden from NotificationDelegate:
76  virtual void Display() OVERRIDE {}
77  virtual void Error() OVERRIDE {}
78  virtual void Close(bool by_user) OVERRIDE {}
79  virtual void Click() OVERRIDE {}
80  virtual std::string id() const OVERRIDE { return id_; }
81  // A NULL return value prevents loading image from URL. It is OK since our
82  // implementation loads image from system resource bundle.
83  virtual content::RenderViewHost* GetRenderViewHost() const OVERRIDE {
84    return NULL;
85  }
86
87 private:
88  virtual ~PeripheralBatteryNotificationDelegate() {}
89
90  const std::string id_;
91
92  DISALLOW_COPY_AND_ASSIGN(PeripheralBatteryNotificationDelegate);
93};
94
95}  // namespace
96
97PeripheralBatteryObserver::PeripheralBatteryObserver()
98    : testing_clock_(NULL),
99      weakptr_factory_(
100          new base::WeakPtrFactory<PeripheralBatteryObserver>(this)) {
101  DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this);
102  device::BluetoothAdapterFactory::GetAdapter(
103      base::Bind(&PeripheralBatteryObserver::InitializeOnBluetoothReady,
104                 weakptr_factory_->GetWeakPtr()));
105}
106
107PeripheralBatteryObserver::~PeripheralBatteryObserver() {
108  if (bluetooth_adapter_.get())
109    bluetooth_adapter_->RemoveObserver(this);
110  DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this);
111}
112
113void PeripheralBatteryObserver::PeripheralBatteryStatusReceived(
114    const std::string& path,
115    const std::string& name,
116    int level) {
117  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
118  std::string address;
119  if (IsBluetoothHIDBattery(path)) {
120    // For HID bluetooth device, device address is used as key to index
121    // BatteryInfo.
122    address = ExtractBluetoothAddress(path);
123  } else {
124    LOG(ERROR) << "Unsupported battery path " << path;
125    return;
126  }
127
128  if (address.empty()) {
129    LOG(ERROR) << "No valid battery address at path " << path;
130    return;
131  }
132
133  if (level < -1 || level > 100) {
134    LOG(ERROR) << "Invalid battery level " << level
135               << " for device " << name << " at path " << path;
136    return;
137  }
138  // If unknown battery level received, cancel any existing notification.
139  if (level == -1) {
140    CancelNotification(address);
141    return;
142  }
143
144  // Post the notification in 2 cases:
145  // 1. It's the first time the battery level is received, and it is
146  //    below kLowBatteryLevel.
147  // 2. The battery level is in record and it drops below kLowBatteryLevel.
148  if (batteries_.find(address) == batteries_.end()) {
149    BatteryInfo battery(name, level, base::TimeTicks());
150    if (level <= kLowBatteryLevel) {
151      if (PostNotification(address, battery))
152        battery.last_notification_timestamp = testing_clock_ ?
153            testing_clock_->NowTicks() : base::TimeTicks::Now();
154    }
155    batteries_[address] = battery;
156  } else {
157    BatteryInfo* battery = &batteries_[address];
158    battery->name = name;
159    int old_level = battery->level;
160    battery->level = level;
161    if (old_level > kLowBatteryLevel && level <= kLowBatteryLevel) {
162      if (PostNotification(address, *battery))
163        battery->last_notification_timestamp = testing_clock_ ?
164            testing_clock_->NowTicks() : base::TimeTicks::Now();
165    }
166  }
167}
168
169void PeripheralBatteryObserver::DeviceChanged(device::BluetoothAdapter* adapter,
170                                              device::BluetoothDevice* device) {
171  if (!device->IsPaired())
172    RemoveBattery(device->GetAddress());
173}
174
175void PeripheralBatteryObserver::DeviceRemoved(device::BluetoothAdapter* adapter,
176                                              device::BluetoothDevice* device) {
177  RemoveBattery(device->GetAddress());
178}
179
180void PeripheralBatteryObserver::InitializeOnBluetoothReady(
181    scoped_refptr<device::BluetoothAdapter> adapter) {
182  bluetooth_adapter_ = adapter;
183  CHECK(bluetooth_adapter_);
184  bluetooth_adapter_->AddObserver(this);
185}
186
187void PeripheralBatteryObserver::RemoveBattery(const std::string& address) {
188  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
189  std::string address_lowercase = address;
190  StringToLowerASCII(&address_lowercase);
191  std::map<std::string, BatteryInfo>::iterator it =
192      batteries_.find(address_lowercase);
193  if (it != batteries_.end()) {
194    batteries_.erase(it);
195    CancelNotification(address_lowercase);
196  }
197}
198
199bool PeripheralBatteryObserver::PostNotification(const std::string& address,
200                                                 const BatteryInfo& battery) {
201  // Only post notification if kNotificationInterval seconds have passed since
202  // last notification showed, avoiding the case where the battery level
203  // oscillates around the threshold level.
204  base::TimeTicks now = testing_clock_ ? testing_clock_->NowTicks() :
205      base::TimeTicks::Now();
206  if (now - battery.last_notification_timestamp <
207      base::TimeDelta::FromSeconds(kNotificationIntervalSec))
208    return false;
209
210  NotificationUIManager* notification_manager =
211      g_browser_process->notification_ui_manager();
212
213  base::string16 string_text = l10n_util::GetStringFUTF16Int(
214      IDS_ASH_LOW_PERIPHERAL_BATTERY_NOTIFICATION_TEXT,
215      battery.level);
216
217  Notification notification(
218      // TODO(mukai): add SYSTEM priority and use here.
219      GURL(kNotificationOriginUrl),
220      ui::ResourceBundle::GetSharedInstance().GetImageNamed(
221          IDR_NOTIFICATION_PERIPHERAL_BATTERY_LOW),
222      UTF8ToUTF16(battery.name),
223      string_text,
224      WebKit::WebTextDirectionDefault,
225      string16(),
226      UTF8ToUTF16(address),
227      new PeripheralBatteryNotificationDelegate(address));
228
229  notification_manager->Add(notification,
230                            ProfileManager::GetDefaultProfileOrOffTheRecord());
231
232  return true;
233}
234
235void PeripheralBatteryObserver::CancelNotification(const std::string& address) {
236  g_browser_process->notification_ui_manager()->CancelById(address);
237}
238
239}  // namespace chromeos
240