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