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