blob: c7d9636554eea845d3c24a1959c734444fa5e92b [file] [log] [blame]
// Copyright 2015 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 "components/arc/power/arc_power_bridge.h"
#include <algorithm>
#include <utility>
#include "ash/shell.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/power/power_policy_controller.h"
#include "chromeos/dbus/power_manager/backlight.pb.h"
#include "components/arc/arc_browser_context_keyed_service_factory_base.h"
#include "components/arc/arc_service_manager.h"
#include "components/arc/arc_util.h"
#include "components/arc/session/arc_bridge_service.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::TimeDelta::FromMilliseconds(200);
// Singleton factory for ArcPowerBridge.
class ArcPowerBridgeFactory
: public internal::ArcBrowserContextKeyedServiceFactoryBase<
ArcPowerBridge,
ArcPowerBridgeFactory> {
public:
// Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
static constexpr const char* kName = "ArcPowerBridgeFactory";
static ArcPowerBridgeFactory* GetInstance() {
return base::Singleton<ArcPowerBridgeFactory>::get();
}
private:
friend base::DefaultSingletonTraits<ArcPowerBridgeFactory>;
ArcPowerBridgeFactory() = default;
~ArcPowerBridgeFactory() override = default;
};
} // namespace
// 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() = 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.
device::mojom::WakeLockProvider* const 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_;
DISALLOW_COPY_AND_ASSIGN(WakeLockRequestor);
};
// static
ArcPowerBridge* ArcPowerBridge::GetForBrowserContext(
content::BrowserContext* context) {
return ArcPowerBridgeFactory::GetForBrowserContext(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() {
arc_bridge_service_->power()->RemoveObserver(this);
arc_bridge_service_->power()->SetHost(nullptr);
}
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);
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) {
mojom::PowerInstance* power_instance =
ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->power(), Suspend);
if (!power_instance)
return;
auto token = base::UnguessableToken::Create();
chromeos::PowerManagerClient::Get()->BlockSuspend(token, "ArcPowerBridge");
power_instance->Suspend(base::BindOnce(&ArcPowerBridge::OnAndroidSuspendReady,
weak_ptr_factory_.GetWeakPtr(),
token));
}
void ArcPowerBridge::OnAndroidSuspendReady(base::UnguessableToken token) {
if (arc::IsArcVmEnabled()) {
vm_tools::concierge::SuspendVmRequest request;
request.set_name(kArcVmName);
request.set_owner_id(user_id_hash_);
chromeos::DBusThreadManager::Get()->GetConciergeClient()->SuspendVm(
request, base::BindOnce(&ArcPowerBridge::OnConciergeSuspendVmResponse,
weak_ptr_factory_.GetWeakPtr(), token));
return;
}
chromeos::PowerManagerClient::Get()->UnblockSuspend(token);
}
void ArcPowerBridge::OnConciergeSuspendVmResponse(
base::UnguessableToken token,
base::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) {
if (arc::IsArcVmEnabled()) {
vm_tools::concierge::ResumeVmRequest request;
request.set_name(kArcVmName);
request.set_owner_id(user_id_hash_);
chromeos::DBusThreadManager::Get()->GetConciergeClient()->ResumeVm(
request, base::BindOnce(&ArcPowerBridge::OnConciergeResumeVmResponse,
weak_ptr_factory_.GetWeakPtr()));
return;
}
DispatchAndroidResume();
}
void ArcPowerBridge::OnConciergeResumeVmResponse(
base::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;
}
DispatchAndroidResume();
}
void ArcPowerBridge::DispatchAndroidResume() {
mojom::PowerInstance* power_instance =
ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->power(), Resume);
if (!power_instance)
return;
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) {
mojom::PowerInstance* power_instance = ARC_GET_INSTANCE_FOR_METHOD(
arc_bridge_service_->power(), PowerSupplyInfoChanged);
if (!power_instance)
return;
power_instance->PowerSupplyInfoChanged();
}
void ArcPowerBridge::OnPowerStateChanged(
chromeos::DisplayPowerState power_state) {
mojom::PowerInstance* power_instance =
ARC_GET_INSTANCE_FOR_METHOD(arc_bridge_service_->power(), SetInteractive);
if (!power_instance)
return;
bool enabled = (power_state != chromeos::DISPLAY_POWER_ALL_OFF);
power_instance->SetInteractive(enabled);
}
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(
base::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::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);
}
} // namespace arc