| OLD | NEW |
| (Empty) | |
| 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/string16.h" |
| 12 #include "base/stringprintf.h" |
| 13 #include "base/strings/string_split.h" |
| 14 #include "base/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::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 |
| 97 PeripheralBatteryObserver::PeripheralBatteryObserver() |
| 98 : testing_clock_(NULL), |
| 99 weakptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST( |
| 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_); |
| 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 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 |
| 235 void PeripheralBatteryObserver::CancelNotification(const std::string& address) { |
| 236 g_browser_process->notification_ui_manager()->CancelById(address); |
| 237 } |
| 238 |
| 239 } // namespace chromeos |
| OLD | NEW |