blob: 268d86f3e99ca7b98f1a963b72b8ba36e9b25084 [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_manager/backlight.pb.h"
#include "chromeos/dbus/power_policy_controller.h"
#include "components/arc/arc_bridge_service.h"
#include "components/arc/arc_browser_context_keyed_service_factory_base.h"
#include "components/arc/arc_service_manager.h"
#include "content/public/common/service_manager_connection.h"
#include "services/device/public/mojom/constants.mojom.h"
#include "services/device/public/mojom/wake_lock.mojom.h"
#include "services/device/public/mojom/wake_lock_provider.mojom.h"
#include "services/service_manager/public/cpp/connector.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,
service_manager::Connector* connector)
: type_(type), connector_(connector) {}
~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_) {
device::mojom::WakeLockProviderPtr provider;
connector_->BindInterface(device::mojom::kServiceName,
mojo::MakeRequest(&provider));
provider->GetWakeLockWithoutContext(
type_, device::mojom::WakeLockReason::kOther, "ARC",
mojo::MakeRequest(&wake_lock_));
}
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.
device::mojom::WakeLockType type_;
// Used to get services. Not owned.
service_manager::Connector* const connector_ = nullptr;
// Number of outstanding Android requests.
int num_android_requests_ = 0;
// Lazily initialized in response to first request.
device::mojom::WakeLockPtr 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), weak_ptr_factory_(this) {
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);
}
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() {
// TODO(mash): Support this functionality without ash::Shell access in Chrome.
if (ash::Shell::HasInstance())
ash::Shell::Get()->display_configurator()->AddObserver(this);
chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(
this);
chromeos::DBusThreadManager::Get()
->GetPowerManagerClient()
->GetScreenBrightnessPercent(
base::BindOnce(&ArcPowerBridge::OnGetScreenBrightnessPercent,
weak_ptr_factory_.GetWeakPtr()));
}
void ArcPowerBridge::OnConnectionClosed() {
// TODO(mash): Support this functionality without ash::Shell access in Chrome.
if (ash::Shell::HasInstance())
ash::Shell::Get()->display_configurator()->RemoveObserver(this);
chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->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;
power_instance->Suspend(chromeos::DBusThreadManager::Get()
->GetPowerManagerClient()
->GetSuspendReadinessCallback(FROM_HERE));
}
void ArcPowerBridge::SuspendDone(const base::TimeDelta& sleep_duration) {
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::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_GRADUAL);
request.set_cause(
power_manager::SetBacklightBrightnessRequest_Cause_USER_REQUEST);
chromeos::DBusThreadManager::Get()
->GetPowerManagerClient()
->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();
service_manager::Connector* connector =
connector_for_test_
? connector_for_test_
: content::ServiceManagerConnection::GetForProcess()->GetConnector();
DCHECK(connector);
it = wake_lock_requestors_
.emplace(type, std::make_unique<WakeLockRequestor>(type, connector))
.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::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