Upstream version 11.39.266.0
[platform/framework/web/crosswalk.git] / src / content / browser / battery_status / battery_status_manager_mac.cc
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.h"
6
7 #include <CoreFoundation/CoreFoundation.h>
8 #include <IOKit/ps/IOPowerSources.h>
9 #include <IOKit/ps/IOPSKeys.h>
10 #include <vector>
11
12 #include "base/mac/foundation_util.h"
13 #include "base/mac/scoped_cftyperef.h"
14 #include "base/memory/ref_counted.h"
15 #include "base/metrics/histogram.h"
16 #include "base/time/time.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "third_party/WebKit/public/platform/WebBatteryStatus.h"
19
20 namespace content {
21
22 namespace {
23
24 typedef BatteryStatusService::BatteryUpdateCallback BatteryCallback;
25
26 // Returns the value corresponding to |key| in the dictionary |description|.
27 // Returns |default_value| if the dictionary does not contain |key|, the
28 // corresponding value is NULL or it could not be converted to SInt64.
29 SInt64 GetValueAsSInt64(CFDictionaryRef description,
30                         CFStringRef key,
31                         SInt64 default_value) {
32   CFNumberRef number =
33       base::mac::GetValueFromDictionary<CFNumberRef>(description, key);
34   SInt64 value;
35
36   if (number && CFNumberGetValue(number, kCFNumberSInt64Type, &value))
37     return value;
38
39   return default_value;
40 }
41
42 bool GetValueAsBoolean(CFDictionaryRef description,
43                        CFStringRef key,
44                        bool default_value) {
45   CFBooleanRef boolean =
46       base::mac::GetValueFromDictionary<CFBooleanRef>(description, key);
47
48   return boolean ? CFBooleanGetValue(boolean) : default_value;
49 }
50
51 bool CFStringsAreEqual(CFStringRef string1, CFStringRef string2) {
52   if (!string1 || !string2)
53     return false;
54   return CFStringCompare(string1, string2, 0) == kCFCompareEqualTo;
55 }
56
57 void UpdateNumberBatteriesHistogram(int count) {
58   UMA_HISTOGRAM_CUSTOM_COUNTS(
59       "BatteryStatus.NumberBatteriesMac", count, 1, 5, 6);
60 }
61
62 void FetchBatteryStatus(CFDictionaryRef description,
63                         blink::WebBatteryStatus& status) {
64   CFStringRef current_state =
65       base::mac::GetValueFromDictionary<CFStringRef>(description,
66           CFSTR(kIOPSPowerSourceStateKey));
67
68   bool on_battery_power =
69       CFStringsAreEqual(current_state, CFSTR(kIOPSBatteryPowerValue));
70   bool is_charging =
71       GetValueAsBoolean(description, CFSTR(kIOPSIsChargingKey), true);
72   bool is_charged =
73       GetValueAsBoolean(description, CFSTR(kIOPSIsChargedKey), false);
74
75   status.charging = !on_battery_power || is_charging;
76
77   SInt64 current_capacity =
78       GetValueAsSInt64(description, CFSTR(kIOPSCurrentCapacityKey), -1);
79   SInt64 max_capacity =
80       GetValueAsSInt64(description, CFSTR(kIOPSMaxCapacityKey), -1);
81
82   // Set level if it is available and valid. Otherwise leave the default value,
83   // which is 1.
84   if (current_capacity != -1 && max_capacity != -1 &&
85       current_capacity <= max_capacity && max_capacity != 0) {
86     status.level = current_capacity / static_cast<double>(max_capacity);
87   }
88
89   if (is_charging) {
90     SInt64 charging_time =
91         GetValueAsSInt64(description, CFSTR(kIOPSTimeToFullChargeKey), -1);
92
93     // Battery is charging: set the charging time if it's available, otherwise
94     // set to +infinity.
95     status.chargingTime = charging_time != -1
96         ? base::TimeDelta::FromMinutes(charging_time).InSeconds()
97         : std::numeric_limits<double>::infinity();
98   } else {
99     // Battery is not charging.
100     // Set chargingTime to +infinity if the battery is not charged. Otherwise
101     // leave the default value, which is 0.
102     if (!is_charged)
103       status.chargingTime = std::numeric_limits<double>::infinity();
104
105     // Set dischargingTime if it's available and valid, i.e. when on battery
106     // power. Otherwise leave the default value, which is +infinity.
107     if (on_battery_power) {
108       SInt64 discharging_time =
109           GetValueAsSInt64(description, CFSTR(kIOPSTimeToEmptyKey), -1);
110       if (discharging_time != -1) {
111         status.dischargingTime =
112             base::TimeDelta::FromMinutes(discharging_time).InSeconds();
113       }
114     }
115   }
116 }
117
118 std::vector<blink::WebBatteryStatus> GetInternalBatteriesStates() {
119   std::vector<blink::WebBatteryStatus> internal_sources;
120
121   base::ScopedCFTypeRef<CFTypeRef> info(IOPSCopyPowerSourcesInfo());
122   base::ScopedCFTypeRef<CFArrayRef> power_sources_list(
123       IOPSCopyPowerSourcesList(info));
124   CFIndex count = CFArrayGetCount(power_sources_list);
125
126   for (CFIndex i = 0; i < count; ++i) {
127     CFDictionaryRef description = IOPSGetPowerSourceDescription(info,
128         CFArrayGetValueAtIndex(power_sources_list, i));
129
130     if (!description)
131       continue;
132
133     CFStringRef transport_type =
134         base::mac::GetValueFromDictionary<CFStringRef>(description,
135             CFSTR(kIOPSTransportTypeKey));
136
137     bool internal_source =
138         CFStringsAreEqual(transport_type, CFSTR(kIOPSInternalType));
139     bool source_present =
140         GetValueAsBoolean(description, CFSTR(kIOPSIsPresentKey), false);
141
142     if (internal_source && source_present) {
143       blink::WebBatteryStatus status;
144       FetchBatteryStatus(description, status);
145       internal_sources.push_back(status);
146     }
147   }
148
149   return internal_sources;
150 }
151
152 void OnBatteryStatusChanged(const BatteryCallback& callback) {
153   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
154
155   std::vector<blink::WebBatteryStatus> batteries(GetInternalBatteriesStates());
156
157   if (batteries.empty()) {
158     callback.Run(blink::WebBatteryStatus());
159     return;
160   }
161
162   // TODO(timvolodine): implement the case when there are multiple internal
163   // sources, e.g. when multiple batteries are present. Currently this will
164   // fail a DCHECK.
165   DCHECK(batteries.size() == 1);
166   callback.Run(batteries.front());
167 }
168
169 class BatteryStatusObserver
170     : public base::RefCountedThreadSafe<BatteryStatusObserver> {
171  public:
172   explicit BatteryStatusObserver(const BatteryCallback& callback)
173       : callback_(callback) {}
174
175   void Start() {
176     // Need to start on a thread with UI-type message loop for
177     // |notifier_run_loop_| to receive callbacks.
178     if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
179       StartOnUI();
180     } else {
181       BrowserThread::PostTask(
182           BrowserThread::UI,
183           FROM_HERE,
184           base::Bind(&BatteryStatusObserver::StartOnUI, this));
185     }
186   }
187
188   void Stop() {
189     if (BrowserThread::CurrentlyOn(BrowserThread::UI)) {
190       StopOnUI();
191     } else {
192       BrowserThread::PostTask(
193           BrowserThread::UI,
194           FROM_HERE,
195           base::Bind(&BatteryStatusObserver::StopOnUI, this));
196     }
197   }
198
199  private:
200   friend class base::RefCountedThreadSafe<BatteryStatusObserver>;
201   virtual ~BatteryStatusObserver() { DCHECK(!notifier_run_loop_source_); }
202
203   void StartOnUI() {
204     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
205
206     if (notifier_run_loop_source_)
207       return;
208
209     notifier_run_loop_source_.reset(
210         IOPSNotificationCreateRunLoopSource(OnBatteryStatusChangedUI,
211                                             static_cast<void*>(&callback_)));
212     if (!notifier_run_loop_source_) {
213       LOG(ERROR) << "Failed to create battery status notification run loop";
214       // Make sure to execute to callback with the default values.
215       callback_.Run(blink::WebBatteryStatus());
216       return;
217     }
218
219     OnBatteryStatusChangedUI(static_cast<void*>(&callback_));
220     CFRunLoopAddSource(CFRunLoopGetCurrent(), notifier_run_loop_source_,
221                        kCFRunLoopDefaultMode);
222     UpdateNumberBatteriesHistogram(GetInternalBatteriesStates().size());
223   }
224
225   void StopOnUI() {
226     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
227
228     if (!notifier_run_loop_source_)
229       return;
230
231     CFRunLoopRemoveSource(CFRunLoopGetCurrent(), notifier_run_loop_source_,
232                           kCFRunLoopDefaultMode);
233     notifier_run_loop_source_.reset();
234   }
235
236   static void OnBatteryStatusChangedUI(void* callback) {
237     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
238     // Offload fetching of values and callback execution to the IO thread.
239     BrowserThread::PostTask(
240         BrowserThread::IO,
241         FROM_HERE,
242         base::Bind(&OnBatteryStatusChanged,
243                    *static_cast<BatteryCallback*>(callback)));
244   }
245
246   BatteryCallback callback_;
247   base::ScopedCFTypeRef<CFRunLoopSourceRef> notifier_run_loop_source_;
248
249   DISALLOW_COPY_AND_ASSIGN(BatteryStatusObserver);
250 };
251
252 class BatteryStatusManagerMac : public BatteryStatusManager {
253  public:
254   explicit BatteryStatusManagerMac(const BatteryCallback& callback)
255       : notifier_(new BatteryStatusObserver(callback)) {}
256
257   virtual ~BatteryStatusManagerMac() {
258     notifier_->Stop();
259   }
260
261   // BatteryStatusManager:
262   virtual bool StartListeningBatteryChange() OVERRIDE {
263     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
264     notifier_->Start();
265     return true;
266   }
267
268   virtual void StopListeningBatteryChange() OVERRIDE {
269     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
270     notifier_->Stop();
271   }
272
273  private:
274   scoped_refptr<BatteryStatusObserver> notifier_;
275
276   DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerMac);
277 };
278
279 } // end namespace
280
281 // static
282 scoped_ptr<BatteryStatusManager> BatteryStatusManager::Create(
283     const BatteryStatusService::BatteryUpdateCallback& callback) {
284   return scoped_ptr<BatteryStatusManager>(
285       new BatteryStatusManagerMac(callback));
286 }
287
288 }  // namespace content