// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/components/arc/power/arc_power_bridge.h"
#include <algorithm>
#include <utility>
#include "ash/components/arc/arc_util.h"
#include "ash/components/arc/session/arc_bridge_service.h"
#include "ash/shell.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "chromeos/ash/components/dbus/patchpanel/patchpanel_client.h"
#include "chromeos/ash/components/dbus/vm_concierge/concierge_service.pb.h"
#include "chromeos/dbus/power/power_policy_controller.h"
#include "chromeos/dbus/power_manager/backlight.pb.h"
#include "content/public/browser/device_service.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/device/public/mojom/wake_lock.mojom.h"
#include "services/device/public/mojom/wake_lock_provider.mojom.h"
#include "ui/display/manager/display_configurator.h"
namespace arc {
namespace {
// Delay for notifying Android about screen brightness changes, added in
// order to prevent spammy brightness updates.
constexpr base::TimeDelta kNotifyBrightnessDelay = base::Milliseconds(200);
} // namespace
using mojom::IdleState;
// static
ArcPowerBridgeFactory* ArcPowerBridgeFactory::GetInstance() {
return base::Singleton<ArcPowerBridgeFactory>::get();
}
// WakeLockRequestor requests a wake lock from the device service in response
// to wake lock requests of a given type from Android. A count is kept of
// outstanding Android requests so that only a single actual wake lock is used.
class ArcPowerBridge::WakeLockRequestor {
public:
WakeLockRequestor(device::mojom::WakeLockType type,
device::mojom::WakeLockProvider* provider)
: type_(type), provider_(provider) {}
WakeLockRequestor(const WakeLockRequestor&) = delete;
WakeLockRequestor& operator=(const WakeLockRequestor&) = delete;
~WakeLockRequestor() = default;
// Increments the number of outstanding requests from Android and requests a
// wake lock from the device service if this is the only request.
void AddRequest() {
num_android_requests_++;
if (num_android_requests_ > 1)
return;
// Initialize |wake_lock_| if this is the first time we're using it.
if (!wake_lock_) {
provider_->GetWakeLockWithoutContext(
type_, device::mojom::WakeLockReason::kOther, "ARC",
wake_lock_.BindNewPipeAndPassReceiver());
}
wake_lock_->RequestWakeLock();
}
// Decrements the number of outstanding Android requests. Cancels the device
// service wake lock when the request count hits zero.
void RemoveRequest() {
DCHECK_GT(num_android_requests_, 0);
num_android_requests_--;
if (num_android_requests_ >= 1)
return;
DCHECK(wake_lock_);
wake_lock_->CancelWakeLock();
}
// Runs the message loop until replies have been received for all pending
// requests on |wake_lock_|.
void FlushForTesting() {
if (wake_lock_)
wake_lock_.FlushForTesting();
}
private:
// Type of wake lock to request.
const device::mojom::WakeLockType type_;
// The WakeLockProvider implementation we use to request WakeLocks. Not owned.
const raw_ptr<device::mojom::WakeLockProvider> provider_;
// Number of outstanding Android requests.
int num_android_requests_ = 0;
// Lazily initialized in response to first request.
mojo::Remote<device::mojom::WakeLock> wake_lock_;
};
// static
ArcPowerBridge* ArcPowerBridge::GetForBrowserContext(
content::BrowserContext* context) {
return ArcPowerBridgeFactory::GetForBrowserContext(context);
}
// static
ArcPowerBridge* ArcPowerBridge::GetForBrowserContextForTesting(
content::BrowserContext* context) {
return ArcPowerBridgeFactory::GetForBrowserContextForTesting(context);
}
ArcPowerBridge::ArcPowerBridge(content::BrowserContext* context,
ArcBridgeService* bridge_service)
: arc_bridge_service_(bridge_service) {
arc_bridge_service_->power()->SetHost(this);
arc_bridge_service_->power()->AddObserver(this);
}
ArcPowerBridge::~ArcPowerBridge() {
for (auto& observer : observer_list_) {
observer.OnWillDestroyArcPowerBridge();
}
arc_bridge_service_->power()->RemoveObserver(this);
arc_bridge_service_->power()->SetHost(nullptr);
}
void ArcPowerBridge::DisableAndroidIdleControl() {
android_idle_control_disabled_ = true;
}
void ArcPowerBridge::AddObserver(Observer* observer) {
observer_list_.AddObserver(observer);
}
void ArcPowerBridge::RemoveObserver(Observer* observer) {
observer_list_.RemoveObserver(observer);
}
void ArcPowerBridge::SetUserIdHash(const std::string& user_id_hash) {
user_id_hash_ = user_id_hash;
}
bool ArcPowerBridge::TriggerNotifyBrightnessTimerForTesting() {
if (!notify_brightness_timer_.IsRunning())
return false;
notify_brightness_timer_.FireNow();
return true;
}
void ArcPowerBridge::FlushWakeLocksForTesting() {
for (const auto& it : wake_lock_requestors_)
it.second->FlushForTesting();
}
void ArcPowerBridge::OnConnectionReady() {
// ash::Shell may not exist in tests.
if (ash::Shell::HasInstance()) {
ash::Shell::Get()->display_configurator()->AddObserver(this);
// Whether display is on is the same signal as whether Android is interactive
// or not.
IsDisplayOn(base::BindOnce(
[](base::WeakPtr<ArcPowerBridge> power_bridge,
ArcBridgeService* bridge_service, bool display_on) {
if (!bridge_service) {
return;
}
power_bridge->NotifyAndroidIdleState(
bridge_service,
display_on ? IdleState::ACTIVE : IdleState::INACTIVE);
},
weak_ptr_factory_.GetWeakPtr(), arc_bridge_service_));
}
chromeos::PowerManagerClient::Get()->AddObserver(this);
chromeos::PowerManagerClient::Get()->GetScreenBrightnessPercent(
base::BindOnce(&ArcPowerBridge::OnGetScreenBrightnessPercent,
weak_ptr_factory_.GetWeakPtr()));
}
void ArcPowerBridge::OnConnectionClosed() {
// ash::Shell may not exist in tests.
if (ash::Shell::HasInstance())
ash::Shell::Get()->display_configurator()->RemoveObserver(this);
chromeos::PowerManagerClient::Get()->RemoveObserver(this);
wake_lock_requestors_.clear();
}
void ArcPowerBridge::SuspendImminent(
power_manager::SuspendImminent::Reason reason) {
is_suspending_ = true;
mojom::PowerInstance* power_instance =
ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->power(), Suspend);
if (!power_instance) {
LOG(WARNING) << "ArcPower: ignoring request due to no bridge.";
return;
}
VLOG(1) << "ArcPower: will request android suspend.";
auto token = base::UnguessableToken::Create();
chromeos::PowerManagerClient::Get()->BlockSuspend(token, "ArcPowerBridge");
power_instance->Suspend(base::BindOnce(&ArcPowerBridge::OnAndroidSuspendReady,
weak_ptr_factory_.GetWeakPtr(),
token));
ash::PatchPanelClient::Get()->NotifyAndroidInteractiveState(false);
}
void ArcPowerBridge::OnAndroidSuspendReady(base::UnguessableToken token) {
// For the ARCVM case, we only want to suspend the VM if a suspend is still
// underway ie. if SuspendImminent was observed without a subsequent
// SuspendDone. Otherwise, skip suspending the VM but still call
// UnblockSuspend to fulfill the contract and to align with ARC container's
// behavior.
if (arc::IsArcVmEnabled() && is_suspending_) {
vm_tools::concierge::SuspendVmRequest request;
request.set_name(kArcVmName);
request.set_owner_id(user_id_hash_);
ash::ConciergeClient::Get()->SuspendVm(
request, base::BindOnce(&ArcPowerBridge::OnConciergeSuspendVmResponse,
weak_ptr_factory_.GetWeakPtr(), token));
return;
}
chromeos::PowerManagerClient::Get()->UnblockSuspend(token);
}
void ArcPowerBridge::OnConciergeSuspendVmResponse(
base::UnguessableToken token,
std::optional<vm_tools::concierge::SuspendVmResponse> reply) {
if (!reply.has_value())
LOG(ERROR) << "Failed to suspend arcvm, no reply received.";
else if (!reply.value().success())
LOG(ERROR) << "Failed to suspend arcvm: " << reply.value().failure_reason();
chromeos::PowerManagerClient::Get()->UnblockSuspend(token);
}
void ArcPowerBridge::SuspendDone(base::TimeDelta sleep_duration) {
VLOG(1) << "ArcPower: Host waking up.";
is_suspending_ = false;
if (arc::IsArcVmEnabled()) {
vm_tools::concierge::ResumeVmRequest request;
request.set_name(kArcVmName);
request.set_owner_id(user_id_hash_);
ash::ConciergeClient::Get()->ResumeVm(
request, base::BindOnce(&ArcPowerBridge::OnConciergeResumeVmResponse,
weak_ptr_factory_.GetWeakPtr()));
return;
}
DispatchAndroidResume();
}
void ArcPowerBridge::OnConciergeResumeVmResponse(
std::optional<vm_tools::concierge::ResumeVmResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to resume arcvm, no reply received.";
return;
}
if (!reply.value().success()) {
LOG(ERROR) << "Failed to resume arcvm: " << reply.value().failure_reason();
return;
}
for (auto& observer : observer_list_) {
observer.OnVmResumed();
}
DispatchAndroidResume();
}
void ArcPowerBridge::DispatchAndroidResume() {
if (android_idle_control_disabled_)
return;
mojom::PowerInstance* power_instance =
ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->power(), Resume);
if (!power_instance) {
LOG(WARNING) << "ArcPower: Ignoring ARC resume due to no bridge.";
return;
}
VLOG(1) << "ArcPower: Requesting Android resume.";
power_instance->Resume();
}
void ArcPowerBridge::ScreenBrightnessChanged(
const power_manager::BacklightBrightnessChange& change) {
const base::TimeTicks now = base::TimeTicks::Now();
if (last_brightness_changed_time_.is_null() ||
(now - last_brightness_changed_time_) >= kNotifyBrightnessDelay) {
UpdateAndroidScreenBrightness(change.percent());
notify_brightness_timer_.Stop();
} else {
notify_brightness_timer_.Start(
FROM_HERE, kNotifyBrightnessDelay,
base::BindOnce(&ArcPowerBridge::UpdateAndroidScreenBrightness,
weak_ptr_factory_.GetWeakPtr(), change.percent()));
}
last_brightness_changed_time_ = now;
}
void ArcPowerBridge::PowerChanged(
const power_manager::PowerSupplyProperties& proto) {
// ARCVM doesn't use this message, since it gets the corresponding
// information from crosvm's goldfish battery device.
if (arc::IsArcVmEnabled()) {
return;
}
mojom::PowerInstance* power_instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_bridge_service_->power(), PowerSupplyInfoChanged);
if (!power_instance)
return;
power_instance->PowerSupplyInfoChanged();
}
void ArcPowerBridge::BatterySaverModeStateChanged(
const power_manager::BatterySaverModeState& state) {
mojom::PowerInstance* power_instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_bridge_service_->power(), OnBatterySaverModeStateChanged);
if (!power_instance) {
return;
}
mojom::BatterySaverModeStatePtr mojo_state =
mojom::BatterySaverModeState::New();
mojo_state->active = state.enabled();
power_instance->OnBatterySaverModeStateChanged(std::move(mojo_state));
}
void ArcPowerBridge::OnPowerStateChanged(
chromeos::DisplayPowerState power_state) {
if (android_idle_control_disabled_)
return;
NotifyAndroidIdleState(arc_bridge_service_,
power_state != chromeos::DISPLAY_POWER_ALL_OFF
? IdleState::ACTIVE
: IdleState::INACTIVE);
}
void ArcPowerBridge::NotifyAndroidIdleState(ArcBridgeService* bridge,
IdleState state) {
if (!bridge) {
return;
}
if (state != IdleState::ACTIVE && is_suspending_) {
LOG(WARNING) << "Suspend is in progress, avoiding display disable";
return;
}
auto* power_instance =
ARC_GET_INSTANCE_FOR_METHOD(bridge->power(), SetIdleState);
if (power_instance) {
VLOG(1) << "ArcPower: SetIdleState to " << state;
power_instance->SetIdleState(state);
} else if ((power_instance = ARC_GET_INSTANCE_FOR_METHOD(
bridge->power(), SetInteractiveDeprecated))) {
VLOG(1) << "ArcPower: SetInteractiveDeprecated to "
<< (state == IdleState::ACTIVE);
power_instance->SetInteractiveDeprecated(state == IdleState::ACTIVE);
} else {
LOG(WARNING) << "ArcPower: Avoiding display change due to no bridge.";
return;
}
// Display power state is the same signal as Android Idle state. When
// power state changes, notify Android interactive state change as well.
ash::PatchPanelClient::Get()->NotifyAndroidInteractiveState(
state == IdleState::ACTIVE);
}
void ArcPowerBridge::OnAcquireDisplayWakeLock(mojom::DisplayWakeLockType type) {
switch (type) {
case mojom::DisplayWakeLockType::BRIGHT:
GetWakeLockRequestor(device::mojom::WakeLockType::kPreventDisplaySleep)
->AddRequest();
break;
case mojom::DisplayWakeLockType::DIM:
GetWakeLockRequestor(
device::mojom::WakeLockType::kPreventDisplaySleepAllowDimming)
->AddRequest();
break;
default:
LOG(WARNING) << "Tried to take invalid wake lock type "
<< static_cast<int>(type);
return;
}
}
void ArcPowerBridge::OnReleaseDisplayWakeLock(mojom::DisplayWakeLockType type) {
switch (type) {
case mojom::DisplayWakeLockType::BRIGHT:
GetWakeLockRequestor(device::mojom::WakeLockType::kPreventDisplaySleep)
->RemoveRequest();
break;
case mojom::DisplayWakeLockType::DIM:
GetWakeLockRequestor(
device::mojom::WakeLockType::kPreventDisplaySleepAllowDimming)
->RemoveRequest();
break;
default:
LOG(WARNING) << "Tried to take invalid wake lock type "
<< static_cast<int>(type);
return;
}
}
void ArcPowerBridge::IsDisplayOn(IsDisplayOnCallback callback) {
bool is_display_on = false;
// TODO(mash): Support this functionality without ash::Shell access in Chrome.
if (ash::Shell::HasInstance())
is_display_on = ash::Shell::Get()->display_configurator()->IsDisplayOn();
std::move(callback).Run(is_display_on);
}
void ArcPowerBridge::OnScreenBrightnessUpdateRequest(double percent) {
power_manager::SetBacklightBrightnessRequest request;
request.set_percent(percent);
request.set_transition(
power_manager::SetBacklightBrightnessRequest_Transition_FAST);
request.set_cause(
power_manager::SetBacklightBrightnessRequest_Cause_USER_REQUEST);
chromeos::PowerManagerClient::Get()->SetScreenBrightness(request);
}
ArcPowerBridge::WakeLockRequestor* ArcPowerBridge::GetWakeLockRequestor(
device::mojom::WakeLockType type) {
auto it = wake_lock_requestors_.find(type);
if (it != wake_lock_requestors_.end())
return it->second.get();
if (!wake_lock_provider_) {
content::GetDeviceService().BindWakeLockProvider(
wake_lock_provider_.BindNewPipeAndPassReceiver());
}
it = wake_lock_requestors_
.emplace(type, std::make_unique<WakeLockRequestor>(
type, wake_lock_provider_.get()))
.first;
return it->second.get();
}
void ArcPowerBridge::OnGetScreenBrightnessPercent(
std::optional<double> percent) {
if (!percent.has_value()) {
LOG(ERROR)
<< "PowerManagerClient::GetScreenBrightnessPercent reports an error";
return;
}
UpdateAndroidScreenBrightness(percent.value());
}
void ArcPowerBridge::OnWakefulnessChanged(mojom::WakefulnessMode mode) {
for (auto& observer : observer_list_)
observer.OnWakefulnessChanged(mode);
}
void ArcPowerBridge::OnPreAnr(mojom::AnrType type) {
base::UmaHistogramEnumeration("Arc.Anr.PreNotified", type);
for (auto& observer : observer_list_)
observer.OnPreAnr(type);
}
void ArcPowerBridge::OnAnrRecoveryFailed(::arc::mojom::AnrType type) {
base::UmaHistogramEnumeration("Arc.Anr.RecoveryFailed", type);
}
void ArcPowerBridge::GetBatterySaverModeState(
GetBatterySaverModeStateCallback callback) {
chromeos::PowerManagerClient::Get()->GetBatterySaverModeState(
base::BindOnce(&ArcPowerBridge::OnBatterySaverModeStateReceived,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ArcPowerBridge::OnBatterySaverModeStateReceived(
GetBatterySaverModeStateCallback callback,
std::optional<power_manager::BatterySaverModeState> state) {
mojom::BatterySaverModeStatePtr mojo_state =
mojom::BatterySaverModeState::New();
if (state.has_value()) {
mojo_state->active = state->enabled();
} else {
LOG(ERROR)
<< "PowerManagerClient::GetBatterySaverModeState reports an error";
}
std::move(callback).Run(std::move(mojo_state));
}
void ArcPowerBridge::UpdateAndroidScreenBrightness(double percent) {
mojom::PowerInstance* power_instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_bridge_service_->power(), UpdateScreenBrightnessSettings);
if (!power_instance)
return;
power_instance->UpdateScreenBrightnessSettings(percent);
}
// static
void ArcPowerBridge::EnsureFactoryBuilt() {
ArcPowerBridgeFactory::GetInstance();
}
} // namespace arc