OLD | NEW |
(Empty) | |
| 1 // Copyright 2014 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 "content/browser/battery_status/battery_status_manager_linux.h" |
| 6 |
| 7 #include "base/macros.h" |
| 8 #include "base/metrics/histogram.h" |
| 9 #include "base/threading/thread.h" |
| 10 #include "base/values.h" |
| 11 #include "content/browser/battery_status/battery_status_manager.h" |
| 12 #include "content/public/browser/browser_thread.h" |
| 13 #include "dbus/bus.h" |
| 14 #include "dbus/message.h" |
| 15 #include "dbus/object_path.h" |
| 16 #include "dbus/object_proxy.h" |
| 17 #include "dbus/property.h" |
| 18 #include "dbus/values_util.h" |
| 19 |
| 20 namespace content { |
| 21 |
| 22 namespace { |
| 23 |
| 24 const char kUPowerServiceName[] = "org.freedesktop.UPower"; |
| 25 const char kUPowerDeviceName[] = "org.freedesktop.UPower.Device"; |
| 26 const char kUPowerPath[] = "/org/freedesktop/UPower"; |
| 27 const char kUPowerDeviceSignalChanged[] = "Changed"; |
| 28 const char kUPowerEnumerateDevices[] = "EnumerateDevices"; |
| 29 const char kBatteryNotifierThreadName[] = "BatteryStatusNotifier"; |
| 30 |
| 31 // UPowerDeviceType reflects the possible UPower.Device.Type values, |
| 32 // see upower.freedesktop.org/docs/Device.html#Device:Type. |
| 33 enum UPowerDeviceType { |
| 34 UPOWER_DEVICE_TYPE_UNKNOWN = 0, |
| 35 UPOWER_DEVICE_TYPE_LINE_POWER = 1, |
| 36 UPOWER_DEVICE_TYPE_BATTERY = 2, |
| 37 UPOWER_DEVICE_TYPE_UPS = 3, |
| 38 UPOWER_DEVICE_TYPE_MONITOR = 4, |
| 39 UPOWER_DEVICE_TYPE_MOUSE = 5, |
| 40 UPOWER_DEVICE_TYPE_KEYBOARD = 6, |
| 41 UPOWER_DEVICE_TYPE_PDA = 7, |
| 42 UPOWER_DEVICE_TYPE_PHONE = 8, |
| 43 }; |
| 44 |
| 45 typedef std::vector<dbus::ObjectPath> PathsVector; |
| 46 |
| 47 double GetPropertyAsDouble(const base::DictionaryValue& dictionary, |
| 48 const std::string& property_name, |
| 49 double default_value) { |
| 50 double value = default_value; |
| 51 return dictionary.GetDouble(property_name, &value) ? value : default_value; |
| 52 } |
| 53 |
| 54 bool GetPropertyAsBoolean(const base::DictionaryValue& dictionary, |
| 55 const std::string& property_name, |
| 56 bool default_value) { |
| 57 bool value = default_value; |
| 58 return dictionary.GetBoolean(property_name, &value) ? value : default_value; |
| 59 } |
| 60 |
| 61 scoped_ptr<base::DictionaryValue> GetPropertiesAsDictionary( |
| 62 dbus::ObjectProxy* proxy) { |
| 63 dbus::MethodCall method_call(dbus::kPropertiesInterface, |
| 64 dbus::kPropertiesGetAll); |
| 65 dbus::MessageWriter builder(&method_call); |
| 66 builder.AppendString(kUPowerDeviceName); |
| 67 |
| 68 scoped_ptr<dbus::Response> response( |
| 69 proxy->CallMethodAndBlock(&method_call, |
| 70 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); |
| 71 if (response) { |
| 72 dbus::MessageReader reader(response.get()); |
| 73 scoped_ptr<base::Value> value(dbus::PopDataAsValue(&reader)); |
| 74 base::DictionaryValue* dictionary_value = NULL; |
| 75 if (value && value->GetAsDictionary(&dictionary_value)) { |
| 76 ignore_result(value.release()); |
| 77 return scoped_ptr<base::DictionaryValue>(dictionary_value); |
| 78 } |
| 79 } |
| 80 return scoped_ptr<base::DictionaryValue>(); |
| 81 } |
| 82 |
| 83 scoped_ptr<PathsVector> GetPowerSourcesPaths(dbus::ObjectProxy* proxy) { |
| 84 scoped_ptr<PathsVector> paths(new PathsVector()); |
| 85 if (!proxy) |
| 86 return paths.Pass(); |
| 87 |
| 88 dbus::MethodCall method_call(kUPowerServiceName, kUPowerEnumerateDevices); |
| 89 scoped_ptr<dbus::Response> response( |
| 90 proxy->CallMethodAndBlock(&method_call, |
| 91 dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)); |
| 92 |
| 93 if (response) { |
| 94 dbus::MessageReader reader(response.get()); |
| 95 reader.PopArrayOfObjectPaths(paths.get()); |
| 96 } |
| 97 return paths.Pass();; |
| 98 } |
| 99 |
| 100 void UpdateNumberBatteriesHistogram(int count) { |
| 101 UMA_HISTOGRAM_CUSTOM_COUNTS( |
| 102 "BatteryStatus.NumberBatteriesLinux", count, 1, 5, 6); |
| 103 } |
| 104 |
| 105 // Class that represents a dedicated thread which communicates with DBus to |
| 106 // obtain battery information and receives battery change notifications. |
| 107 class BatteryStatusNotificationThread : public base::Thread { |
| 108 public: |
| 109 BatteryStatusNotificationThread( |
| 110 const BatteryStatusService::BatteryUpdateCallback& callback) |
| 111 : base::Thread(kBatteryNotifierThreadName), |
| 112 callback_(callback), |
| 113 battery_proxy_(NULL) {} |
| 114 |
| 115 virtual ~BatteryStatusNotificationThread() { |
| 116 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| 117 |
| 118 // Make sure to shutdown the dbus connection if it is still open in the very |
| 119 // end. It needs to happen on the BatteryStatusNotificationThread. |
| 120 message_loop()->PostTask( |
| 121 FROM_HERE, |
| 122 base::Bind(&BatteryStatusNotificationThread::ShutdownDBusConnection, |
| 123 base::Unretained(this))); |
| 124 |
| 125 // Drain the message queue of the BatteryStatusNotificationThread and stop. |
| 126 Stop(); |
| 127 } |
| 128 |
| 129 void StartListening() { |
| 130 DCHECK(OnWatcherThread()); |
| 131 |
| 132 if (system_bus_.get()) |
| 133 return; |
| 134 |
| 135 InitDBus(); |
| 136 dbus::ObjectProxy* power_proxy = |
| 137 system_bus_->GetObjectProxy(kUPowerServiceName, |
| 138 dbus::ObjectPath(kUPowerPath)); |
| 139 scoped_ptr<PathsVector> device_paths = GetPowerSourcesPaths(power_proxy); |
| 140 int num_batteries = 0; |
| 141 |
| 142 for (size_t i = 0; i < device_paths->size(); ++i) { |
| 143 const dbus::ObjectPath& device_path = device_paths->at(i); |
| 144 dbus::ObjectProxy* device_proxy = system_bus_->GetObjectProxy( |
| 145 kUPowerServiceName, device_path); |
| 146 scoped_ptr<base::DictionaryValue> dictionary = |
| 147 GetPropertiesAsDictionary(device_proxy); |
| 148 |
| 149 if (!dictionary) |
| 150 continue; |
| 151 |
| 152 bool is_present = GetPropertyAsBoolean(*dictionary, "IsPresent", false); |
| 153 uint32 type = static_cast<uint32>( |
| 154 GetPropertyAsDouble(*dictionary, "Type", UPOWER_DEVICE_TYPE_UNKNOWN)); |
| 155 |
| 156 if (!is_present || type != UPOWER_DEVICE_TYPE_BATTERY) { |
| 157 system_bus_->RemoveObjectProxy(kUPowerServiceName, |
| 158 device_path, |
| 159 base::Bind(&base::DoNothing)); |
| 160 continue; |
| 161 } |
| 162 |
| 163 if (battery_proxy_) { |
| 164 // TODO(timvolodine): add support for multiple batteries. Currently we |
| 165 // only collect information from the first battery we encounter |
| 166 // (crbug.com/400780). |
| 167 LOG(WARNING) << "multiple batteries found, " |
| 168 << "using status data of the first battery only."; |
| 169 } else { |
| 170 battery_proxy_ = device_proxy; |
| 171 } |
| 172 num_batteries++; |
| 173 } |
| 174 |
| 175 UpdateNumberBatteriesHistogram(num_batteries); |
| 176 |
| 177 if (!battery_proxy_) { |
| 178 callback_.Run(blink::WebBatteryStatus()); |
| 179 return; |
| 180 } |
| 181 |
| 182 battery_proxy_->ConnectToSignal( |
| 183 kUPowerDeviceName, |
| 184 kUPowerDeviceSignalChanged, |
| 185 base::Bind(&BatteryStatusNotificationThread::BatteryChanged, |
| 186 base::Unretained(this)), |
| 187 base::Bind(&BatteryStatusNotificationThread::OnSignalConnected, |
| 188 base::Unretained(this))); |
| 189 } |
| 190 |
| 191 void StopListening() { |
| 192 DCHECK(OnWatcherThread()); |
| 193 ShutdownDBusConnection(); |
| 194 } |
| 195 |
| 196 private: |
| 197 bool OnWatcherThread() { |
| 198 return task_runner()->BelongsToCurrentThread(); |
| 199 } |
| 200 |
| 201 void InitDBus() { |
| 202 DCHECK(OnWatcherThread()); |
| 203 |
| 204 dbus::Bus::Options options; |
| 205 options.bus_type = dbus::Bus::SYSTEM; |
| 206 options.connection_type = dbus::Bus::PRIVATE; |
| 207 system_bus_ = new dbus::Bus(options); |
| 208 } |
| 209 |
| 210 void ShutdownDBusConnection() { |
| 211 DCHECK(OnWatcherThread()); |
| 212 |
| 213 if (!system_bus_.get()) |
| 214 return; |
| 215 |
| 216 // Shutdown DBus connection later because there may be pending tasks on |
| 217 // this thread. |
| 218 message_loop()->PostTask(FROM_HERE, |
| 219 base::Bind(&dbus::Bus::ShutdownAndBlock, |
| 220 system_bus_)); |
| 221 system_bus_ = NULL; |
| 222 battery_proxy_ = NULL; |
| 223 } |
| 224 |
| 225 void OnSignalConnected(const std::string& interface_name, |
| 226 const std::string& signal_name, |
| 227 bool success) { |
| 228 DCHECK(OnWatcherThread()); |
| 229 |
| 230 if (interface_name != kUPowerDeviceName || |
| 231 signal_name != kUPowerDeviceSignalChanged) { |
| 232 return; |
| 233 } |
| 234 |
| 235 if (!system_bus_.get()) |
| 236 return; |
| 237 |
| 238 if (success) { |
| 239 BatteryChanged(NULL); |
| 240 } else { |
| 241 // Failed to register for "Changed" signal, execute callback with the |
| 242 // default values. |
| 243 callback_.Run(blink::WebBatteryStatus()); |
| 244 } |
| 245 } |
| 246 |
| 247 void BatteryChanged(dbus::Signal* signal /* unsused */) { |
| 248 DCHECK(OnWatcherThread()); |
| 249 |
| 250 if (!system_bus_.get()) |
| 251 return; |
| 252 |
| 253 scoped_ptr<base::DictionaryValue> dictionary = |
| 254 GetPropertiesAsDictionary(battery_proxy_); |
| 255 if (dictionary) |
| 256 callback_.Run(ComputeWebBatteryStatus(*dictionary)); |
| 257 else |
| 258 callback_.Run(blink::WebBatteryStatus()); |
| 259 } |
| 260 |
| 261 BatteryStatusService::BatteryUpdateCallback callback_; |
| 262 scoped_refptr<dbus::Bus> system_bus_; |
| 263 dbus::ObjectProxy* battery_proxy_; // owned by the bus |
| 264 |
| 265 DISALLOW_COPY_AND_ASSIGN(BatteryStatusNotificationThread); |
| 266 }; |
| 267 |
| 268 // Runs on IO thread and creates a notification thread and delegates Start/Stop |
| 269 // calls to it. |
| 270 class BatteryStatusManagerLinux : public BatteryStatusManager { |
| 271 public: |
| 272 explicit BatteryStatusManagerLinux( |
| 273 const BatteryStatusService::BatteryUpdateCallback& callback) |
| 274 : callback_(callback) {} |
| 275 |
| 276 virtual ~BatteryStatusManagerLinux() {} |
| 277 |
| 278 private: |
| 279 // BatteryStatusManager: |
| 280 virtual bool StartListeningBatteryChange() override { |
| 281 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 282 |
| 283 if (!StartNotifierThreadIfNecessary()) |
| 284 return false; |
| 285 |
| 286 notifier_thread_->message_loop()->PostTask( |
| 287 FROM_HERE, |
| 288 base::Bind(&BatteryStatusNotificationThread::StartListening, |
| 289 base::Unretained(notifier_thread_.get()))); |
| 290 return true; |
| 291 } |
| 292 |
| 293 virtual void StopListeningBatteryChange() override { |
| 294 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); |
| 295 |
| 296 if (!notifier_thread_) |
| 297 return; |
| 298 |
| 299 notifier_thread_->message_loop()->PostTask( |
| 300 FROM_HERE, |
| 301 base::Bind(&BatteryStatusNotificationThread::StopListening, |
| 302 base::Unretained(notifier_thread_.get()))); |
| 303 } |
| 304 |
| 305 // Starts the notifier thread if not already started and returns true on |
| 306 // success. |
| 307 bool StartNotifierThreadIfNecessary() { |
| 308 if (notifier_thread_) |
| 309 return true; |
| 310 |
| 311 base::Thread::Options thread_options(base::MessageLoop::TYPE_IO, 0); |
| 312 notifier_thread_.reset(new BatteryStatusNotificationThread(callback_)); |
| 313 if (!notifier_thread_->StartWithOptions(thread_options)) { |
| 314 notifier_thread_.reset(); |
| 315 LOG(ERROR) << "Could not start the " << kBatteryNotifierThreadName |
| 316 << " thread"; |
| 317 return false; |
| 318 } |
| 319 return true; |
| 320 } |
| 321 |
| 322 BatteryStatusService::BatteryUpdateCallback callback_; |
| 323 scoped_ptr<BatteryStatusNotificationThread> notifier_thread_; |
| 324 |
| 325 DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerLinux); |
| 326 }; |
| 327 |
| 328 } // namespace |
| 329 |
| 330 blink::WebBatteryStatus ComputeWebBatteryStatus( |
| 331 const base::DictionaryValue& dictionary) { |
| 332 blink::WebBatteryStatus status; |
| 333 if (!dictionary.HasKey("State")) |
| 334 return status; |
| 335 |
| 336 uint32 state = static_cast<uint32>( |
| 337 GetPropertyAsDouble(dictionary, "State", UPOWER_DEVICE_STATE_UNKNOWN)); |
| 338 status.charging = state != UPOWER_DEVICE_STATE_DISCHARGING && |
| 339 state != UPOWER_DEVICE_STATE_EMPTY; |
| 340 double percentage = GetPropertyAsDouble(dictionary, "Percentage", 100); |
| 341 // Convert percentage to a value between 0 and 1 with 2 digits of precision. |
| 342 // This is to bring it in line with other platforms like Mac and Android where |
| 343 // we report level with 1% granularity. It also serves the purpose of reducing |
| 344 // the possibility of fingerprinting and triggers less level change events on |
| 345 // the blink side. |
| 346 // TODO(timvolodine): consider moving this rounding to the blink side. |
| 347 status.level = round(percentage) / 100.f; |
| 348 |
| 349 switch (state) { |
| 350 case UPOWER_DEVICE_STATE_CHARGING : { |
| 351 double time_to_full = GetPropertyAsDouble(dictionary, "TimeToFull", 0); |
| 352 status.chargingTime = |
| 353 (time_to_full > 0) ? time_to_full |
| 354 : std::numeric_limits<double>::infinity(); |
| 355 break; |
| 356 } |
| 357 case UPOWER_DEVICE_STATE_DISCHARGING : { |
| 358 double time_to_empty = GetPropertyAsDouble(dictionary, "TimeToEmpty", 0); |
| 359 // Set dischargingTime if it's available. Otherwise leave the default |
| 360 // value which is +infinity. |
| 361 if (time_to_empty > 0) |
| 362 status.dischargingTime = time_to_empty; |
| 363 status.chargingTime = std::numeric_limits<double>::infinity(); |
| 364 break; |
| 365 } |
| 366 case UPOWER_DEVICE_STATE_FULL : { |
| 367 break; |
| 368 } |
| 369 default: { |
| 370 status.chargingTime = std::numeric_limits<double>::infinity(); |
| 371 } |
| 372 } |
| 373 return status; |
| 374 } |
| 375 |
| 376 // static |
| 377 scoped_ptr<BatteryStatusManager> BatteryStatusManager::Create( |
| 378 const BatteryStatusService::BatteryUpdateCallback& callback) { |
| 379 return scoped_ptr<BatteryStatusManager>( |
| 380 new BatteryStatusManagerLinux(callback)); |
| 381 } |
| 382 |
| 383 } // namespace content |
OLD | NEW |