[go: nahoru, domu]

blob: 9b72bea6156a016dd84c05625f5e991572d2e97d [file] [log] [blame]
// Copyright 2020 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 "ash/system/power/peripheral_battery_listener.h"
#include <string>
#include <vector>
#include "ash/power/hid_battery_util.h"
#include "ash/public/cpp/notification_utils.h"
#include "ash/resources/vector_icons/vector_icons.h"
#include "ash/shell.h"
#include "ash/strings/grit/ash_strings.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/strings/string16.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "device/bluetooth/bluetooth_adapter_factory.h"
#include "device/bluetooth/bluetooth_device.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/devices/touchscreen_device.h"
#include "ui/gfx/image/image.h"
#include "ui/message_center/message_center.h"
#include "ui/message_center/public/cpp/notification.h"
namespace ash {
namespace {
constexpr char kBluetoothDeviceIdPrefix[] = "battery_bluetooth-";
// Currently we expect at most one peripheral charger to exist, and
// it will always be the stylus charger.
constexpr char kStylusChargerFilename[] = "/PCHG0";
constexpr char kStylusChargerID[] = "PCHG0";
// Checks if the device is an external stylus.
bool IsStylusDevice(const std::string& path, const std::string& model_name) {
std::string identifier = ExtractHIDBatteryIdentifier(path);
for (const ui::TouchscreenDevice& device :
ui::DeviceDataManager::GetInstance()->GetTouchscreenDevices()) {
if (device.has_stylus &&
(device.name == model_name ||
device.name.find(model_name) != std::string::npos) &&
device.sys_path.value().find(identifier) != std::string::npos) {
return true;
}
}
return false;
}
// Checks if device is the internal charger for an external stylus.
bool IsPeripheralCharger(const std::string& path) {
return base::EndsWith(path, kStylusChargerFilename);
}
std::string GetMapKeyForBluetoothAddress(const std::string& bluetooth_address) {
return kBluetoothDeviceIdPrefix + base::ToLowerASCII(bluetooth_address);
}
// Returns the corresponding map key for a HID device.
std::string GetBatteryMapKey(const std::string& path) {
// Check if the HID path corresponds to a Bluetooth device.
const std::string bluetooth_address =
ExtractBluetoothAddressFromHIDBatteryPath(path);
if (IsPeripheralCharger(path))
return kStylusChargerID;
else if (!bluetooth_address.empty())
return GetMapKeyForBluetoothAddress(bluetooth_address);
else
return path;
}
std::string GetBatteryMapKey(device::BluetoothDevice* device) {
return GetMapKeyForBluetoothAddress(device->GetAddress());
}
PeripheralBatteryListener::BatteryInfo::ChargeStatus
ConvertPowerManagerChargeStatus(
power_manager::PeripheralBatteryStatus_ChargeStatus incoming) {
switch (incoming) {
case power_manager::
PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_UNKNOWN:
return PeripheralBatteryListener::BatteryInfo::ChargeStatus::kUnknown;
case power_manager::
PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_DISCHARGING:
return PeripheralBatteryListener::BatteryInfo::ChargeStatus::kDischarging;
case power_manager::
PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_CHARGING:
return PeripheralBatteryListener::BatteryInfo::ChargeStatus::kCharging;
case power_manager::
PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_NOT_CHARGING:
return PeripheralBatteryListener::BatteryInfo::ChargeStatus::kNotCharging;
case power_manager::PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_FULL:
return PeripheralBatteryListener::BatteryInfo::ChargeStatus::kFull;
case power_manager::
PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_ERROR:
return PeripheralBatteryListener::BatteryInfo::ChargeStatus::kError;
}
}
} // namespace
PeripheralBatteryListener::BatteryInfo::BatteryInfo() = default;
PeripheralBatteryListener::BatteryInfo::BatteryInfo(
const std::string& key,
const std::u16string& name,
base::Optional<uint8_t> level,
base::TimeTicks last_update_timestamp,
PeripheralType type,
ChargeStatus charge_status,
const std::string& bluetooth_address)
: key(key),
name(name),
level(level),
last_update_timestamp(last_update_timestamp),
type(type),
charge_status(charge_status),
bluetooth_address(bluetooth_address) {}
PeripheralBatteryListener::BatteryInfo::~BatteryInfo() = default;
PeripheralBatteryListener::BatteryInfo::BatteryInfo(const BatteryInfo& info) =
default;
PeripheralBatteryListener::PeripheralBatteryListener() {
chromeos::PowerManagerClient::Get()->AddObserver(this);
device::BluetoothAdapterFactory::Get()->GetAdapter(
base::BindOnce(&PeripheralBatteryListener::InitializeOnBluetoothReady,
weak_factory_.GetWeakPtr()));
}
PeripheralBatteryListener::~PeripheralBatteryListener() {
if (bluetooth_adapter_)
bluetooth_adapter_->RemoveObserver(this);
chromeos::PowerManagerClient::Get()->RemoveObserver(this);
}
// Observing chromeos::PowerManagerClient
void PeripheralBatteryListener::PeripheralBatteryStatusReceived(
const std::string& path,
const std::string& name,
int level,
power_manager::PeripheralBatteryStatus_ChargeStatus pmc_charge_status,
bool active_update) {
// Note that zero levels are seen during boot on hid devices; a
// power_supply node may be created without a real charge level, and
// we must let it through to allow the BatteryInfo to be created as
// soon as we are aware of it.
if (level < -1 || level > 100) {
LOG(ERROR) << "Invalid battery level " << level << " for device " << name
<< " at path " << path;
return;
}
if (!IsHIDBattery(path) && !IsPeripheralCharger(path)) {
LOG(ERROR) << "Unsupported battery path " << path;
return;
}
if (!ui::DeviceDataManager::HasInstance() ||
!ui::DeviceDataManager::GetInstance()->AreDeviceListsComplete()) {
LOG(ERROR) << "Discarding peripheral battery notification before devices "
"are enumerated";
return;
}
BatteryInfo::PeripheralType type;
if (IsPeripheralCharger(path))
type = BatteryInfo::PeripheralType::kStylusViaCharger;
else if (IsStylusDevice(path, name))
type = BatteryInfo::PeripheralType::kStylusViaScreen;
else
type = BatteryInfo::PeripheralType::kOther;
std::string map_key = GetBatteryMapKey(path);
base::Optional<uint8_t> opt_level;
// 0-level charge events can come through when devices are created,
// usually on boot; for the stylus, convert the level to 'not present',
// as they are not informative.
if (level == -1 ||
(level == 0 && type == BatteryInfo::PeripheralType::kStylusViaScreen)) {
opt_level = base::nullopt;
} else {
opt_level = level;
}
PeripheralBatteryListener::BatteryInfo battery{
map_key,
base::ASCIIToUTF16(name),
opt_level,
base::TimeTicks::Now(),
type,
ConvertPowerManagerChargeStatus(pmc_charge_status),
ExtractBluetoothAddressFromHIDBatteryPath(path)};
UpdateBattery(battery, active_update);
}
// Observing device::BluetoothAdapter
void PeripheralBatteryListener::DeviceBatteryChanged(
device::BluetoothAdapter* adapter,
device::BluetoothDevice* device,
base::Optional<uint8_t> new_battery_percentage) {
if (new_battery_percentage)
DCHECK_LE(new_battery_percentage.value(), 100);
BatteryInfo battery{GetBatteryMapKey(device),
device->GetNameForDisplay(),
new_battery_percentage,
base::TimeTicks::Now(),
BatteryInfo::PeripheralType::kOther,
BatteryInfo::ChargeStatus::kUnknown,
device->GetAddress()};
// Bluetooth does not communicate charge state, do not fill in. Updates
// will generally pull from the remote device, so consider them active.
UpdateBattery(battery, /*active_update=*/true);
}
// Observing device::BluetoothAdapter
void PeripheralBatteryListener::DeviceConnectedStateChanged(
device::BluetoothAdapter* adapter,
device::BluetoothDevice* device,
bool is_now_connected) {
if (!is_now_connected)
RemoveBluetoothBattery(device->GetAddress());
}
// Observing device::BluetoothAdapter
void PeripheralBatteryListener::DeviceRemoved(device::BluetoothAdapter* adapter,
device::BluetoothDevice* device) {
RemoveBluetoothBattery(device->GetAddress());
}
void PeripheralBatteryListener::InitializeOnBluetoothReady(
scoped_refptr<device::BluetoothAdapter> adapter) {
bluetooth_adapter_ = adapter;
CHECK(bluetooth_adapter_);
bluetooth_adapter_->AddObserver(this);
}
void PeripheralBatteryListener::RemoveBluetoothBattery(
const std::string& bluetooth_address) {
auto it = batteries_.find(kBluetoothDeviceIdPrefix +
base::ToLowerASCII(bluetooth_address));
if (it != batteries_.end()) {
NotifyRemovingBattery(it->second);
batteries_.erase(it);
}
}
void PeripheralBatteryListener::UpdateBattery(const BatteryInfo& battery_info,
bool active_update) {
const std::string& map_key = battery_info.key;
auto it = batteries_.find(map_key);
if (it == batteries_.end()) {
batteries_[map_key] = battery_info;
NotifyAddingBattery(batteries_[map_key]);
} else {
BatteryInfo& existing_battery_info = it->second;
// Only some fields should ever change.
DCHECK(existing_battery_info.bluetooth_address == battery_info.bluetooth_address);
DCHECK(existing_battery_info.type == battery_info.type);
existing_battery_info.name = battery_info.name;
existing_battery_info.level = battery_info.level;
existing_battery_info.last_update_timestamp =
battery_info.last_update_timestamp;
existing_battery_info.charge_status = battery_info.charge_status;
}
BatteryInfo& info = batteries_[map_key];
if (active_update) {
info.last_active_update_timestamp = info.last_update_timestamp;
}
NotifyUpdatedBatteryLevel(info);
}
void PeripheralBatteryListener::NotifyAddingBattery(
const BatteryInfo& battery) {
for (auto& obs : observers_)
obs.OnAddingBattery(battery);
}
void PeripheralBatteryListener::NotifyRemovingBattery(
const BatteryInfo& battery) {
for (auto& obs : observers_)
obs.OnRemovingBattery(battery);
}
void PeripheralBatteryListener::NotifyUpdatedBatteryLevel(
const BatteryInfo& battery) {
for (auto& obs : observers_)
obs.OnUpdatedBatteryLevel(battery);
}
void PeripheralBatteryListener::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
// As possible latecomer, introduce observer to batteries that already exist.
for (auto it : batteries_) {
observer->OnAddingBattery(it.second);
observer->OnUpdatedBatteryLevel(it.second);
}
}
void PeripheralBatteryListener::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
bool PeripheralBatteryListener::HasObserver(const Observer* observer) const {
return observers_.HasObserver(observer);
}
} // namespace ash