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 241