b7ebea0b5318b3096fc87d6f692c35c58ae20894
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / power / peripheral_battery_observer.cc
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
29 namespace chromeos {
30
31 namespace {
32
33 // When a peripheral device's battery level is <= kLowBatteryLevel, consider
34 // it to be in low battery condition.
35 const int kLowBatteryLevel = 15;
36
37 // Don't show 2 low battery notification within |kNotificationIntervalSec|
38 // seconds.
39 const int kNotificationIntervalSec = 60;
40
41 const 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".
47 const char kHIDBatteryPathPrefix[] = "/sys/class/power_supply/hid-";
48 const char kHIDBatteryPathSuffix[] = "-battery";
49
50 bool IsBluetoothHIDBattery(const std::string& path) {
51   return StartsWithASCII(path, kHIDBatteryPathPrefix, false) &&
52       EndsWith(path, kHIDBatteryPathSuffix, false);
53 }
54
55 std::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
70 class 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::WebContents* GetWebContents() 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
97 PeripheralBatteryObserver::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
107 PeripheralBatteryObserver::~PeripheralBatteryObserver() {
108   if (bluetooth_adapter_.get())
109     bluetooth_adapter_->RemoveObserver(this);
110   DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this);
111 }
112
113 void 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
169 void PeripheralBatteryObserver::DeviceChanged(device::BluetoothAdapter* adapter,
170                                               device::BluetoothDevice* device) {
171   if (!device->IsPaired())
172     RemoveBattery(device->GetAddress());
173 }
174
175 void PeripheralBatteryObserver::DeviceRemoved(device::BluetoothAdapter* adapter,
176                                               device::BluetoothDevice* device) {
177   RemoveBattery(device->GetAddress());
178 }
179
180 void PeripheralBatteryObserver::InitializeOnBluetoothReady(
181     scoped_refptr<device::BluetoothAdapter> adapter) {
182   bluetooth_adapter_ = adapter;
183   CHECK(bluetooth_adapter_.get());
184   bluetooth_adapter_->AddObserver(this);
185 }
186
187 void 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
199 bool 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       base::UTF8ToUTF16(battery.name),
223       string_text,
224       blink::WebTextDirectionDefault,
225       base::string16(),
226       base::UTF8ToUTF16(address),
227       new PeripheralBatteryNotificationDelegate(address));
228
229   notification_manager->Add(
230       notification,
231       ProfileManager::GetPrimaryUserProfile());
232
233   return true;
234 }
235
236 void PeripheralBatteryObserver::CancelNotification(const std::string& address) {
237   g_browser_process->notification_ui_manager()->CancelById(address);
238 }
239
240 }  // namespace chromeos