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 384