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_win.h"
6
7#include "base/memory/ref_counted.h"
8#include "base/metrics/histogram.h"
9#include "base/strings/string16.h"
10#include "base/win/message_window.h"
11#include "base/win/windows_version.h"
12#include "content/browser/battery_status/battery_status_manager.h"
13#include "content/public/browser/browser_thread.h"
14
15namespace content {
16
17namespace {
18
19typedef BatteryStatusService::BatteryUpdateCallback BatteryCallback;
20
21const wchar_t kWindowClassName[] = L"BatteryStatusMessageWindow";
22
23// This enum is used for histogram. Don't change the order of the existing
24// values.
25enum NumberBatteriesType {
26  UNKNOWN_BATTERIES = 0,
27  NO_BATTERY = 1,
28  ONE_OR_MORE_BATTERIES = 2,
29  BATTERY_TYPES_COUNT = 3,
30};
31
32void UpdateNumberBatteriesHistogram(NumberBatteriesType count) {
33  UMA_HISTOGRAM_ENUMERATION("BatteryStatus.NumberBatteriesWin",
34                            count,
35                            BATTERY_TYPES_COUNT);
36}
37
38void UpdateNumberBatteriesHistogram() {
39  SYSTEM_POWER_STATUS win_status;
40  if (!GetSystemPowerStatus(&win_status)) {
41    UpdateNumberBatteriesHistogram(UNKNOWN_BATTERIES);
42    return;
43  }
44
45  if (win_status.BatteryFlag == 255)
46    UpdateNumberBatteriesHistogram(UNKNOWN_BATTERIES);
47  else if (win_status.BatteryFlag == 128)
48    UpdateNumberBatteriesHistogram(NO_BATTERY);
49  else
50    UpdateNumberBatteriesHistogram(ONE_OR_MORE_BATTERIES);
51}
52
53// Message-only window for handling battery changes on Windows.
54class BatteryStatusObserver
55    : public base::RefCountedThreadSafe<BatteryStatusObserver> {
56 public:
57  explicit BatteryStatusObserver(const BatteryCallback& callback)
58      : power_handle_(NULL),
59        battery_change_handle_(NULL),
60        callback_(callback) {
61  }
62
63  virtual ~BatteryStatusObserver() { DCHECK(!window_); }
64
65  void Start() {
66    // Need to start on the UI thread to receive battery status notifications.
67    BrowserThread::PostTask(
68        BrowserThread::UI,
69        FROM_HERE,
70        base::Bind(&BatteryStatusObserver::StartOnUI, this));
71  }
72
73  void Stop() {
74    BrowserThread::PostTask(
75        BrowserThread::UI,
76        FROM_HERE,
77        base::Bind(&BatteryStatusObserver::StopOnUI, this));
78  }
79
80 private:
81  void StartOnUI() {
82    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
83    if (window_)
84      return;
85
86    if (CreateMessageWindow()) {
87      BatteryChanged();
88      // RegisterPowerSettingNotification function work from Windows Vista
89      // onwards. However even without them we will receive notifications,
90      // e.g. when a power source is connected.
91      // TODO(timvolodine) : consider polling for battery changes on windows
92      // versions prior to Vista, see crbug.com/402466.
93      power_handle_ =
94          RegisterNotification(&GUID_ACDC_POWER_SOURCE);
95      battery_change_handle_ =
96          RegisterNotification(&GUID_BATTERY_PERCENTAGE_REMAINING);
97    } else {
98      // Could not create a message window, execute callback with the default
99      // values.
100      callback_.Run(blink::WebBatteryStatus());
101    }
102
103    UpdateNumberBatteriesHistogram();
104  }
105
106  void StopOnUI() {
107    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
108    if (!window_)
109      return;
110
111    if (power_handle_)
112      UnregisterNotification(power_handle_);
113    if (battery_change_handle_)
114      UnregisterNotification(battery_change_handle_);
115    window_.reset();
116  }
117
118  void BatteryChanged() {
119    SYSTEM_POWER_STATUS win_status;
120    if (GetSystemPowerStatus(&win_status))
121      callback_.Run(ComputeWebBatteryStatus(win_status));
122    else
123      callback_.Run(blink::WebBatteryStatus());
124  }
125
126  bool HandleMessage(UINT message,
127                     WPARAM wparam,
128                     LPARAM lparam,
129                     LRESULT* result) {
130    switch(message) {
131      case WM_POWERBROADCAST:
132        if (wparam == PBT_APMPOWERSTATUSCHANGE ||
133            wparam == PBT_POWERSETTINGCHANGE) {
134          BatteryChanged();
135        }
136        *result = NULL;
137        return true;
138      default:
139        return false;
140    }
141  }
142
143  HPOWERNOTIFY RegisterNotification(LPCGUID power_setting) {
144    if (base::win::GetVersion() < base::win::VERSION_VISTA)
145      return NULL;
146
147    return RegisterPowerSettingNotification(window_->hwnd(), power_setting,
148                                     DEVICE_NOTIFY_WINDOW_HANDLE);
149  }
150
151  BOOL UnregisterNotification(HPOWERNOTIFY handle) {
152    if (base::win::GetVersion() < base::win::VERSION_VISTA)
153      return FALSE;
154
155    return UnregisterPowerSettingNotification(handle);
156  }
157
158  bool CreateMessageWindow() {
159    // TODO(timvolodine): consider reusing the message window of PowerMonitor.
160    window_.reset(new base::win::MessageWindow());
161    if (!window_->CreateNamed(base::Bind(&BatteryStatusObserver::HandleMessage,
162                                         base::Unretained(this)),
163                              base::string16(kWindowClassName))) {
164      LOG(ERROR) << "Failed to create message window: " << kWindowClassName;
165      window_.reset();
166      return false;
167    }
168    return true;
169  }
170
171  HPOWERNOTIFY power_handle_;
172  HPOWERNOTIFY battery_change_handle_;
173  BatteryCallback callback_;
174  scoped_ptr<base::win::MessageWindow> window_;
175
176  DISALLOW_COPY_AND_ASSIGN(BatteryStatusObserver);
177};
178
179class BatteryStatusManagerWin : public BatteryStatusManager {
180 public:
181  explicit BatteryStatusManagerWin(const BatteryCallback& callback)
182      : battery_observer_(new BatteryStatusObserver(callback)) {}
183  virtual ~BatteryStatusManagerWin() { battery_observer_->Stop(); }
184
185 public:
186  // BatteryStatusManager:
187  virtual bool StartListeningBatteryChange() OVERRIDE {
188    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
189    battery_observer_->Start();
190    return true;
191  }
192
193  virtual void StopListeningBatteryChange() OVERRIDE {
194    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
195    battery_observer_->Stop();
196  }
197
198 private:
199  scoped_refptr<BatteryStatusObserver> battery_observer_;
200
201  DISALLOW_COPY_AND_ASSIGN(BatteryStatusManagerWin);
202};
203
204}  // namespace
205
206blink::WebBatteryStatus ComputeWebBatteryStatus(
207    const SYSTEM_POWER_STATUS& win_status) {
208  blink::WebBatteryStatus status;
209  status.charging = win_status.ACLineStatus != WIN_AC_LINE_STATUS_OFFLINE;
210
211  // Set level if available. Otherwise keep the default value which is 1.
212  if (win_status.BatteryLifePercent != 255) {
213    // Convert percentage to a value between 0 and 1 with 2 significant digits.
214    status.level = static_cast<double>(win_status.BatteryLifePercent) / 100.;
215  }
216
217  if (!status.charging) {
218    // Set dischargingTime if available otherwise keep the default value,
219    // which is +Infinity.
220    if (win_status.BatteryLifeTime != (DWORD)-1)
221      status.dischargingTime = win_status.BatteryLifeTime;
222    status.chargingTime = std::numeric_limits<double>::infinity();
223  } else {
224    // Set chargingTime to +Infinity if not fully charged, otherwise leave the
225    // default value, which is 0.
226    if (status.level < 1)
227      status.chargingTime = std::numeric_limits<double>::infinity();
228  }
229  return status;
230}
231
232// static
233scoped_ptr<BatteryStatusManager> BatteryStatusManager::Create(
234    const BatteryStatusService::BatteryUpdateCallback& callback) {
235  return scoped_ptr<BatteryStatusManager>(
236      new BatteryStatusManagerWin(callback));
237}
238
239}  // namespace content
240