// Copyright 2021 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifdef UNSAFE_BUFFERS_BUILD // TODO(crbug.com/374320451): Fix and remove. #pragma allow_unsafe_buffers #endif #include "base/power_monitor/battery_level_provider.h" #define INITGUID #include // Must be in front of other Windows header files. #include #include #include #include #include #include #include #include "base/memory/weak_ptr.h" #include "base/metrics/histogram_macros.h" #include "base/numerics/safe_conversions.h" #include "base/task/sequenced_task_runner.h" #include "base/task/task_traits.h" #include "base/task/thread_pool.h" #include "base/threading/scoped_blocking_call.h" #include "base/win/scoped_devinfo.h" #include "base/win/scoped_handle.h" namespace base { namespace { // Returns a handle to the battery interface identified by |interface_data|, or // nullopt if the request failed. |devices| is a device information set that // contains battery devices information, obtained with ::SetupDiGetClassDevs(). base::win::ScopedHandle GetBatteryHandle( HDEVINFO devices, SP_DEVICE_INTERFACE_DATA* interface_data) { // Query size required to hold |interface_detail|. DWORD required_size = 0; ::SetupDiGetDeviceInterfaceDetail(devices, interface_data, nullptr, 0, &required_size, nullptr); DWORD error = ::GetLastError(); if (error != ERROR_INSUFFICIENT_BUFFER) return base::win::ScopedHandle(); // |interface_detail->DevicePath| is variable size. std::vector raw_buf(required_size); auto* interface_detail = reinterpret_cast(raw_buf.data()); interface_detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); BOOL success = ::SetupDiGetDeviceInterfaceDetail( devices, interface_data, interface_detail, required_size, nullptr, nullptr); if (!success) return base::win::ScopedHandle(); base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::MAY_BLOCK); base::win::ScopedHandle battery( ::CreateFile(interface_detail->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); return battery; } // Returns the current tag for `battery` handle, BATTERY_TAG_INVALID if there is // no battery present in this interface or nullopt on retrieval error. // See // https://docs.microsoft.com/en-us/windows/win32/power/ioctl-battery-query-tag std::optional GetBatteryTag(HANDLE battery) { ULONG battery_tag = 0; ULONG wait = 0; DWORD bytes_returned = 0; BOOL success = ::DeviceIoControl( battery, IOCTL_BATTERY_QUERY_TAG, &wait, sizeof(wait), &battery_tag, sizeof(battery_tag), &bytes_returned, nullptr); if (!success) { if (::GetLastError() == ERROR_FILE_NOT_FOUND) { // No battery present in this interface. // // TODO(crbug.com/40756364): Change CHECK to DCHECK in October 2022 after // verifying that there are no crash reports. CHECK_EQ(battery_tag, static_cast(BATTERY_TAG_INVALID)); return battery_tag; } // Retrieval error. return std::nullopt; } return battery_tag; } // Returns BATTERY_INFORMATION structure containing battery information, given // battery handle and tag, or nullopt if the request failed. Battery handle and // tag are obtained with GetBatteryHandle() and GetBatteryTag(), respectively. std::optional GetBatteryInformation(HANDLE battery, ULONG battery_tag) { BATTERY_QUERY_INFORMATION query_information = {}; query_information.BatteryTag = battery_tag; query_information.InformationLevel = BatteryInformation; BATTERY_INFORMATION battery_information = {}; DWORD bytes_returned; BOOL success = ::DeviceIoControl( battery, IOCTL_BATTERY_QUERY_INFORMATION, &query_information, sizeof(query_information), &battery_information, sizeof(battery_information), &bytes_returned, nullptr); if (!success) return std::nullopt; return battery_information; } // Returns the granularity of the battery discharge. std::optional GetBatteryBatteryDischargeGranularity( HANDLE battery, ULONG battery_tag, ULONG current_capacity, ULONG designed_capacity) { BATTERY_QUERY_INFORMATION query_information = {}; query_information.BatteryTag = battery_tag; query_information.InformationLevel = BatteryGranularityInformation; // The battery discharge granularity can change as the level of the battery // gets closer to zero. The documentation for `BatteryGranularityInformation` // says that a maximum of 4 scales is possible. Each scale contains the // granularity (in mWh) and the capacity (in mWh) at which the scale takes // effect. // https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-battery_reporting_scale std::array battery_reporting_scales; DWORD bytes_returned = 0; BOOL success = ::DeviceIoControl( battery, IOCTL_BATTERY_QUERY_INFORMATION, &query_information, sizeof(query_information), &battery_reporting_scales, sizeof(battery_reporting_scales), &bytes_returned, nullptr); if (!success) return std::nullopt; ptrdiff_t nb_elements = base::checked_cast( bytes_returned / sizeof(BATTERY_REPORTING_SCALE)); if (!nb_elements) return std::nullopt; // The granularities are ordered from the highest capacity to the lowest // capacity, or from the most coarse granularity to the most precise // granularity, according to the documentation. // Just in case, the documentation is not trusted for |max_granularity|. All // the values are still compared to find the most coarse granularity. DWORD max_granularity = std::max_element(std::begin(battery_reporting_scales), std::begin(battery_reporting_scales) + nb_elements, [](const auto& lhs, const auto& rhs) { return lhs.Granularity < rhs.Granularity; }) ->Granularity; // Check if the API can be trusted, which would simplify the implementation of // this function. UMA_HISTOGRAM_BOOLEAN( "Power.BatteryDischargeGranularityIsOrdered", max_granularity == battery_reporting_scales[0].Granularity); return max_granularity; } // Returns BATTERY_STATUS structure containing battery state, given battery // handle and tag, or nullopt if the request failed. Battery handle and tag are // obtained with GetBatteryHandle() and GetBatteryTag(), respectively. std::optional GetBatteryStatus(HANDLE battery, ULONG battery_tag) { BATTERY_WAIT_STATUS wait_status = {}; wait_status.BatteryTag = battery_tag; BATTERY_STATUS battery_status; DWORD bytes_returned; BOOL success = ::DeviceIoControl( battery, IOCTL_BATTERY_QUERY_STATUS, &wait_status, sizeof(wait_status), &battery_status, sizeof(battery_status), &bytes_returned, nullptr); if (!success) return std::nullopt; return battery_status; } } // namespace class BatteryLevelProviderWin : public BatteryLevelProvider { public: BatteryLevelProviderWin() = default; ~BatteryLevelProviderWin() override = default; void GetBatteryState( base::OnceCallback&)> callback) override { // This is run on |blocking_task_runner_| since `GetBatteryStateImpl()` has // blocking calls and can take several seconds to complete. blocking_task_runner_->PostTaskAndReplyWithResult( FROM_HERE, base::BindOnce(&BatteryLevelProviderWin::GetBatteryStateImpl), base::BindOnce(&BatteryLevelProviderWin::OnBatteryStateObtained, weak_ptr_factory_.GetWeakPtr(), std::move(callback))); } private: static std::optional GetBatteryStateImpl(); void OnBatteryStateObtained( base::OnceCallback&)> callback, const std::optional& battery_state) { std::move(callback).Run(battery_state); } // TaskRunner used to run blocking `GetBatteryStateImpl()` queries, sequenced // to avoid the performance cost of concurrent calls. scoped_refptr blocking_task_runner_{ base::ThreadPool::CreateSequencedTaskRunner( {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})}; base::WeakPtrFactory weak_ptr_factory_{this}; }; std::unique_ptr BatteryLevelProvider::Create() { return std::make_unique(); } // static std::optional BatteryLevelProviderWin::GetBatteryStateImpl() { // Proactively mark as blocking to fail early, since calls below may also // trigger ScopedBlockingCall. base::ScopedBlockingCall scoped_blocking_call(FROM_HERE, base::BlockingType::MAY_BLOCK); // Battery interfaces are enumerated at every sample to detect when a new // interface is added, and avoid holding dangling handles when a battery is // disconnected. base::win::ScopedDevInfo devices(::SetupDiGetClassDevs( &GUID_DEVICE_BATTERY, 0, 0, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)); if (!devices.is_valid()) { return std::nullopt; } std::vector battery_details_list; // The algorithm to enumerate battery devices is taken from // https://docs.microsoft.com/en-us/windows/win32/power/enumerating-battery-devices // Limit search to 8 batteries max. A system may have several battery slots // and each slot may hold an actual battery. for (DWORD device_index = 0; device_index < 8; ++device_index) { SP_DEVICE_INTERFACE_DATA interface_data = {}; interface_data.cbSize = sizeof(interface_data); BOOL success = ::SetupDiEnumDeviceInterfaces(devices.get(), 0, &GUID_DEVCLASS_BATTERY, device_index, &interface_data); if (!success) { // Enumeration ended normally. if (::GetLastError() == ERROR_NO_MORE_ITEMS) break; // Error. return std::nullopt; } base::win::ScopedHandle battery = GetBatteryHandle(devices.get(), &interface_data); if (!battery.IsValid()) return std::nullopt; std::optional battery_tag = GetBatteryTag(battery.Get()); if (!battery_tag.has_value()) { return std::nullopt; } else if (battery_tag.value() == BATTERY_TAG_INVALID) { // No battery present in this interface. continue; } auto battery_information = GetBatteryInformation(battery.Get(), *battery_tag); if (!battery_information.has_value()) { return std::nullopt; } auto battery_status = GetBatteryStatus(battery.Get(), *battery_tag); if (!battery_status.has_value()) { return std::nullopt; } std::optional battery_discharge_granularity = GetBatteryBatteryDischargeGranularity( battery.Get(), *battery_tag, battery_status->Capacity, battery_information->DesignedCapacity); battery_details_list.push_back(BatteryDetails( {.is_external_power_connected = !!(battery_status->PowerState & BATTERY_POWER_ON_LINE), .current_capacity = battery_status->Capacity, .full_charged_capacity = battery_information->FullChargedCapacity, .charge_unit = ((battery_information->Capabilities & BATTERY_CAPACITY_RELATIVE) ? BatteryLevelUnit::kRelative : BatteryLevelUnit::kMWh), .battery_discharge_granularity = battery_discharge_granularity})); } return MakeBatteryState(battery_details_list); } } // namespace base