// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "device/battery/battery_status_manager.h" #include #include #include #include #include "base/mac/foundation_util.h" #include "base/mac/scoped_cftyperef.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" #include "base/time/time.h" namespace device { namespace { typedef BatteryStatusService::BatteryUpdateCallback BatteryCallback; // Returns the value corresponding to |key| in the dictionary |description|. // Returns |default_value| if the dictionary does not contain |key|, the // corresponding value is NULL or it could not be converted to SInt64. SInt64 GetValueAsSInt64(CFDictionaryRef description, CFStringRef key, SInt64 default_value) { CFNumberRef number = base::mac::GetValueFromDictionary(description, key); SInt64 value; if (number && CFNumberGetValue(number, kCFNumberSInt64Type, &value)) return value; return default_value; } bool GetValueAsBoolean(CFDictionaryRef description, CFStringRef key, bool default_value) { CFBooleanRef boolean = base::mac::GetValueFromDictionary(description, key); return boolean ? CFBooleanGetValue(boolean) : default_value; } bool CFStringsAreEqual(CFStringRef string1, CFStringRef string2) { if (!string1 || !string2) return false; return CFStringCompare(string1, string2, 0) == kCFCompareEqualTo; } void UpdateNumberBatteriesHistogram(int count) { UMA_HISTOGRAM_CUSTOM_COUNTS( "BatteryStatus.NumberBatteriesMac", count, 1, 5, 6); } void FetchBatteryStatus(CFDictionaryRef description, BatteryStatus* status) { CFStringRef current_state = base::mac::GetValueFromDictionary(description, CFSTR(kIOPSPowerSourceStateKey)); bool on_battery_power = CFStringsAreEqual(current_state, CFSTR(kIOPSBatteryPowerValue)); bool is_charging = GetValueAsBoolean(description, CFSTR(kIOPSIsChargingKey), true); bool is_charged = GetValueAsBoolean(description, CFSTR(kIOPSIsChargedKey), false); status->charging = !on_battery_power || is_charging; SInt64 current_capacity = GetValueAsSInt64(description, CFSTR(kIOPSCurrentCapacityKey), -1); SInt64 max_capacity = GetValueAsSInt64(description, CFSTR(kIOPSMaxCapacityKey), -1); // Set level if it is available and valid. Otherwise leave the default value, // which is 1. if (current_capacity != -1 && max_capacity != -1 && current_capacity <= max_capacity && max_capacity != 0) { status->level = current_capacity / static_cast(max_capacity); } if (is_charging) { SInt64 charging_time = GetValueAsSInt64(description, CFSTR(kIOPSTimeToFullChargeKey), -1); // Battery is charging: set the charging time if it's available, otherwise // set to +infinity. status->charging_time = charging_time != -1 ? base::TimeDelta::FromMinutes(charging_time).InSeconds() : std::numeric_limits::infinity(); } else { // Battery is not charging. // Set chargingTime to +infinity if the battery is not charged. Otherwise // leave the default value, which is 0. if (!is_charged) status->charging_time = std::numeric_limits::infinity(); // Set dischargingTime if it's available and valid, i.e. when on battery // power. Otherwise leave the default value, which is +infinity. if (on_battery_power) { SInt64 discharging_time = GetValueAsSInt64(description, CFSTR(kIOPSTimeToEmptyKey), -1); if (discharging_time != -1) { status->discharging_time = base::TimeDelta::FromMinutes(discharging_time).InSeconds(); } } } } std::vector GetInternalBatteriesStates() { std::vector internal_sources; base::ScopedCFTypeRef info(IOPSCopyPowerSourcesInfo()); base::ScopedCFTypeRef power_sources_list( IOPSCopyPowerSourcesList(info)); CFIndex count = CFArrayGetCount(power_sources_list); for (CFIndex i = 0; i < count; ++i) { CFDictionaryRef description = IOPSGetPowerSourceDescription(info, CFArrayGetValueAtIndex(power_sources_list, i)); if (!description) continue; CFStringRef transport_type = base::mac::GetValueFromDictionary(description, CFSTR(kIOPSTransportTypeKey)); bool internal_source = CFStringsAreEqual(transport_type, CFSTR(kIOPSInternalType)); bool source_present = GetValueAsBoolean(description, CFSTR(kIOPSIsPresentKey), false); if (internal_source && source_present) { BatteryStatus status; FetchBatteryStatus(description, &status); internal_sources.push_back(status); } } return internal_sources; } void OnBatteryStatusChanged(const BatteryCallback& callback) { std::vector batteries(GetInternalBatteriesStates()); if (batteries.empty()) { callback.Run(BatteryStatus()); return; } // TODO(timvolodine): implement the case when there are multiple internal // sources, e.g. when multiple batteries are present. Currently this will // fail a DCHECK. DCHECK(batteries.size() == 1); callback.Run(batteries.front()); } class BatteryStatusObserver { public: explicit BatteryStatusObserver(const BatteryCallback& callback) : callback_(callback) {} ~BatteryStatusObserver() { DCHECK(!notifier_run_loop_source_); } void Start() { if (notifier_run_loop_source_) return; notifier_run_loop_source_.reset( IOPSNotificationCreateRunLoopSource(CallOnBatteryStatusChanged, static_cast(&callback_))); if (!notifier_run_loop_source_) { LOG(ERROR) << "Failed to create battery status notification run loop"; // Make sure to execute to callback with the default values. callback_.Run(BatteryStatus()); return; } CallOnBatteryStatusChanged(static_cast(&callback_)); CFRunLoopAddSource(CFRunLoopGetCurrent(), notifier_run_loop_source_, kCFRunLoopDefaultMode); UpdateNumberBatteriesHistogram(GetInternalBatteriesStates().size()); } void Stop() { if (!notifier_run_loop_source_) return; CFRunLoopRemoveSource(CFRunLoopGetCurrent(), notifier_run_loop_source_, kCFRunLoopDefaultMode); notifier_run_loop_source_.reset(); } private: static void CallOnBatteryStatusChanged(void* callback) { OnBatteryStatusChanged(*static_cast(callback)); } BatteryCallback callback_; base::ScopedCFTypeRef notifier_run_loop_source_; DISALLOW_COPY_AND_ASSIGN(BatteryStatusObserver); }; class BatteryStatusManagerMac : public BatteryStatusManager { public: explicit BatteryStatusManagerMac(const BatteryCallback& callback) : notifier_(new BatteryStatusObserver(callback)) {} ~BatteryStatusManagerMac() override { notifier_->Stop(); } // BatteryStatusManager: bool StartListeningBatteryChange() override { notifier_->Start(); return true; } void StopListeningBatteryChange() override { notifier_->Stop(); } private: scoped_ptr notifier_; DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerMac); }; } // end namespace // static scoped_ptr BatteryStatusManager::Create( const BatteryStatusService::BatteryUpdateCallback& callback) { return scoped_ptr( new BatteryStatusManagerMac(callback)); } } // namespace device