blob: 9cb81ea581567fbd06dd38348f00c6b1c715d217 [file] [log] [blame]
// Copyright 2018 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 <memory>
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/observer_list.h"
#include "base/task/post_task.h"
#include "chromeos/dbus/dbus_thread_manager.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 "components/arc/wake_lock/arc_wake_lock_bridge.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/device/public/mojom/constants.mojom.h"
#include "services/device/public/mojom/wake_lock_provider.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
namespace arc {
namespace {
constexpr char kWakeLockReason[] = "ARC";
// Singleton factory for ArcWakeLockBridge.
class ArcWakeLockBridgeFactory
: public internal::ArcBrowserContextKeyedServiceFactoryBase<
ArcWakeLockBridge,
ArcWakeLockBridgeFactory> {
public:
// Factory name used by ArcBrowserContextKeyedServiceFactoryBase.
static constexpr const char* kName = "ArcWakeLockBridgeFactory";
static ArcWakeLockBridgeFactory* GetInstance() {
return base::Singleton<ArcWakeLockBridgeFactory>::get();
}
private:
friend base::DefaultSingletonTraits<ArcWakeLockBridgeFactory>;
ArcWakeLockBridgeFactory() = default;
~ArcWakeLockBridgeFactory() override = default;
};
} // namespace
// WakeLockRequester 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 ArcWakeLockBridge::WakeLockRequester {
public:
WakeLockRequester(device::mojom::WakeLockType type,
service_manager::Connector* connector)
: type_(type), connector_(connector) {}
~WakeLockRequester() = 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() {
DCHECK_GE(wake_lock_count_, 0);
wake_lock_count_++;
if (wake_lock_count_ > 1) {
DVLOG(1) << "Partial wake lock acquire. Count: " << wake_lock_count_;
return;
}
// Initialize |wake_lock_| if this is the first time we're using it.
DVLOG(1) << "Partial wake lock new acquire. Count: " << wake_lock_count_;
if (!wake_lock_) {
device::mojom::WakeLockProviderPtr provider;
connector_->BindInterface(device::mojom::kServiceName,
mojo::MakeRequest(&provider));
provider->GetWakeLockWithoutContext(
type_, device::mojom::WakeLockReason::kOther, kWakeLockReason,
mojo::MakeRequest(&wake_lock_));
}
wake_lock_->RequestWakeLock();
for (auto& observer : observers_)
observer.OnWakeLockAcquire();
}
// Decrements the number of outstanding Android requests. Cancels the device
// service wake lock when the request count hits zero.
void RemoveRequest() {
DCHECK_GE(wake_lock_count_, 0);
if (wake_lock_count_ == 0) {
LOG(WARNING) << "Release without acquire. Count: " << wake_lock_count_;
return;
}
wake_lock_count_--;
if (wake_lock_count_ >= 1) {
DVLOG(1) << "Partial wake release. Count: " << wake_lock_count_;
return;
}
DCHECK(wake_lock_);
DVLOG(1) << "Partial wake lock force release. Count: " << wake_lock_count_;
wake_lock_->CancelWakeLock();
for (auto& observer : observers_)
observer.OnWakeLockRelease();
}
bool IsWakeLockHeld() const { return wake_lock_count_ > 0; }
void AddObserver(WakeLockObserver* observer) {
DCHECK(observer);
observers_.AddObserver(observer);
}
void RemoveObserver(WakeLockObserver* observer) {
DCHECK(observer);
observers_.RemoveObserver(observer);
}
bool HasObservers() const { return observers_.might_have_observers(); }
// 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.
int64_t wake_lock_count_ = 0;
// Lazily initialized in response to first request.
device::mojom::WakeLockPtr wake_lock_;
base::ObserverList<WakeLockObserver>::Unchecked observers_;
DISALLOW_COPY_AND_ASSIGN(WakeLockRequester);
};
// static
BrowserContextKeyedServiceFactory* ArcWakeLockBridge::GetFactory() {
return ArcWakeLockBridgeFactory::GetInstance();
}
// static
ArcWakeLockBridge* ArcWakeLockBridge::GetForBrowserContext(
content::BrowserContext* context) {
return ArcWakeLockBridgeFactory::GetForBrowserContext(context);
}
// static
ArcWakeLockBridge* ArcWakeLockBridge::GetForBrowserContextForTesting(
content::BrowserContext* context) {
return ArcWakeLockBridgeFactory::GetForBrowserContextForTesting(context);
}
constexpr base::TimeDelta ArcWakeLockBridge::kDarkResumeWakeLockCheckTimeout;
constexpr base::TimeDelta ArcWakeLockBridge::kDarkResumeHardTimeout;
ArcWakeLockBridge::ArcWakeLockBridge(content::BrowserContext* context,
ArcBridgeService* bridge_service)
: arc_bridge_service_(bridge_service),
binding_(this),
weak_ptr_factory_(this) {
arc_bridge_service_->wake_lock()->SetHost(this);
arc_bridge_service_->wake_lock()->AddObserver(this);
chromeos::DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(
this);
}
ArcWakeLockBridge::~ArcWakeLockBridge() {
arc_bridge_service_->wake_lock()->RemoveObserver(this);
arc_bridge_service_->wake_lock()->SetHost(nullptr);
// In case some this wasn't cleared while handling a dark resume.
GetWakeLockRequester(device::mojom::WakeLockType::kPreventAppSuspension)
->RemoveObserver(this);
}
void ArcWakeLockBridge::OnConnectionClosed() {
DVLOG(1) << "OnConnectionClosed";
wake_lock_requesters_.clear();
}
void ArcWakeLockBridge::AcquirePartialWakeLock(
AcquirePartialWakeLockCallback callback) {
GetWakeLockRequester(device::mojom::WakeLockType::kPreventAppSuspension)
->AddRequest();
std::move(callback).Run(true);
}
void ArcWakeLockBridge::ReleasePartialWakeLock(
ReleasePartialWakeLockCallback callback) {
GetWakeLockRequester(device::mojom::WakeLockType::kPreventAppSuspension)
->RemoveRequest();
std::move(callback).Run(true);
}
void ArcWakeLockBridge::DarkSuspendImminent() {
DVLOG(1) << __func__;
suspend_readiness_cb_ = chromeos::DBusThreadManager::Get()
->GetPowerManagerClient()
->GetSuspendReadinessCallback(FROM_HERE);
// Post task that will check for any wake locks acquired in dark resume.
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ArcWakeLockBridge::HandleDarkResumeWakeLockCheckTimeout,
dark_resume_weak_ptr_factory_.GetWeakPtr()),
kDarkResumeWakeLockCheckTimeout);
}
void ArcWakeLockBridge::SuspendDone(const base::TimeDelta& sleep_duration) {
DVLOG(1) << __func__;
// Clear any dark resume state when the device resumes.
ClearDarkResumeState();
}
void ArcWakeLockBridge::OnWakeLockRelease() {
// This observer is only registered once dark resume starts.
DCHECK(suspend_readiness_cb_);
DVLOG(1) << __func__;
// At this point the instance has done its work, tell the power daemon to
// re-suspend.
std::move(suspend_readiness_cb_).Run();
ClearDarkResumeState();
}
void ArcWakeLockBridge::FlushWakeLocksForTesting() {
for (const auto& it : wake_lock_requesters_)
it.second->FlushForTesting();
}
bool ArcWakeLockBridge::IsSuspendReadinessStateSetForTesting() const {
return !suspend_readiness_cb_.is_null();
}
bool ArcWakeLockBridge::WakeLockHasObserversForTesting(
device::mojom::WakeLockType type) {
return GetWakeLockRequester(
device::mojom::WakeLockType::kPreventAppSuspension)
->HasObservers();
}
void ArcWakeLockBridge::HandleDarkResumeWakeLockCheckTimeout() {
DVLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(dark_resume_tasks_sequence_checker_);
// Check if any wake locks are held at this point. If not, then it's assumed
// the instance either acquired and released one or had no reason to acquire
// one in the first place. If it wants to after this then too bad, tell the
// power daemon to re-suspend and invalidate any other state associated with
// dark resume.
if (!GetWakeLockRequester(device::mojom::WakeLockType::kPreventAppSuspension)
->IsWakeLockHeld()) {
DVLOG(1) << "Wake lock not held during check";
std::move(suspend_readiness_cb_).Run();
ClearDarkResumeState();
return;
}
DVLOG(1) << "Wake lock held during check";
// If a wake lock is held then register for a wake lock release
// notification. As soon as it's released tell power daemon to re-suspend.
// If the instance takes a long time then tell powerd daemon to re-suspend
// after a hard timeout irrespective of wake locks held.
GetWakeLockRequester(device::mojom::WakeLockType::kPreventAppSuspension)
->AddObserver(this);
// Post task that will tell the power daemon to re-suspend after a dark
// resume irrespective of any state. This is a last resort timeout to ensure
// the device doesn't stay up indefinitely in dark resume.
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ArcWakeLockBridge::HandleDarkResumeHardTimeout,
dark_resume_weak_ptr_factory_.GetWeakPtr()),
kDarkResumeHardTimeout);
}
void ArcWakeLockBridge::HandleDarkResumeHardTimeout() {
DVLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(dark_resume_tasks_sequence_checker_);
// Enough is enough. Tell power daemon it's okay to suspend.
DCHECK(suspend_readiness_cb_);
std::move(suspend_readiness_cb_).Run();
ClearDarkResumeState();
}
void ArcWakeLockBridge::ClearDarkResumeState() {
DVLOG(1) << __func__;
// Invalidate all other state associated with dark resume.
suspend_readiness_cb_.Reset();
GetWakeLockRequester(device::mojom::WakeLockType::kPreventAppSuspension)
->RemoveObserver(this);
dark_resume_weak_ptr_factory_.InvalidateWeakPtrs();
}
ArcWakeLockBridge::WakeLockRequester* ArcWakeLockBridge::GetWakeLockRequester(
device::mojom::WakeLockType type) {
auto it = wake_lock_requesters_.find(type);
if (it != wake_lock_requesters_.end())
return it->second.get();
service_manager::Connector* connector =
connector_for_test_
? connector_for_test_
: content::ServiceManagerConnection::GetForProcess()->GetConnector();
DCHECK(connector);
it = wake_lock_requesters_
.emplace(type, std::make_unique<WakeLockRequester>(type, connector))
.first;
return it->second.get();
}
} // namespace arc