chromium/chromeos/ash/services/libassistant/power_manager_provider_impl.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/services/libassistant/power_manager_provider_impl.h"

#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "chromeos/ash/services/libassistant/public/mojom/platform_delegate.mojom.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/device/public/mojom/wake_lock_provider.mojom.h"

namespace ash::libassistant {

namespace {

// Tag used to identify Assistant timers.
constexpr char kTag[] = "Assistant";

// Used with wake lock APIs.
constexpr char kWakeLockReason[] = "Assistant";

// Copied from Chrome's //base/time/time_now_posix.cc.
// Returns count of |clk_id| in the form of a time delta. Returns an empty time
// delta if |clk_id| isn't present on the system.
base::TimeDelta ClockNow(clockid_t clk_id) {
  struct timespec ts;
  if (clock_gettime(clk_id, &ts) != 0) {
    NOTREACHED_IN_MIGRATION() << "clock_gettime(" << clk_id << ") failed.";
    return base::TimeDelta();
  }
  return base::TimeDelta::FromTimeSpec(ts);
}

}  // namespace

PowerManagerProviderImpl::PowerManagerProviderImpl()
    : main_thread_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
      weak_factory_(this) {}

void PowerManagerProviderImpl::Initialize(mojom::PlatformDelegate* delegate) {
  platform_delegate_ = delegate;
}

PowerManagerProviderImpl::~PowerManagerProviderImpl() = default;

PowerManagerProviderImpl::AlarmId PowerManagerProviderImpl::AddWakeAlarm(
    uint64_t relative_time_ms,
    uint64_t max_delay_ms,
    assistant_client::Callback0 callback) {
  const AlarmId id = next_id_++;
  DVLOG(1) << __func__ << "Add alarm ID " << id << " for "
           << base::Time::Now() + base::Milliseconds(relative_time_ms);

  main_thread_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(
          &PowerManagerProviderImpl::AddWakeAlarmOnMainThread,
          weak_factory_.GetWeakPtr(), id,
          GetCurrentBootTime() + base::Milliseconds(relative_time_ms),
          std::move(callback)));
  return id;
}

void PowerManagerProviderImpl::ExpireWakeAlarmNow(AlarmId id) {
  DVLOG(1) << __func__;
  main_thread_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&PowerManagerProviderImpl::OnTimerFiredOnMainThread,
                     weak_factory_.GetWeakPtr(), id));
}

void PowerManagerProviderImpl::AcquireWakeLock() {
  DVLOG(1) << __func__;
  main_thread_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&PowerManagerProviderImpl::AcquireWakeLockOnMainThread,
                     weak_factory_.GetWeakPtr()));
}

void PowerManagerProviderImpl::ReleaseWakeLock() {
  DVLOG(1) << __func__;
  main_thread_task_runner_->PostTask(
      FROM_HERE,
      base::BindOnce(&PowerManagerProviderImpl::ReleaseWakeLockOnMainThread,
                     weak_factory_.GetWeakPtr()));
}

base::TimeTicks PowerManagerProviderImpl::GetCurrentBootTime() {
  if (tick_clock_)
    return tick_clock_->NowTicks();
  return base::TimeTicks() + ClockNow(CLOCK_BOOTTIME);
}

void PowerManagerProviderImpl::AddWakeAlarmOnMainThread(
    AlarmId id,
    base::TimeTicks absolute_expiration_time,
    assistant_client::Callback0 callback) {
  DVLOG(1) << __func__;
  DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());

  auto timer =
      std::make_unique<chromeos::NativeTimer>(kTag + base::NumberToString(id));
  // Once the timer is created successfully, start the timer and store
  // associated data. The stored |callback| will be called in
  // |OnTimerFiredOnMainThread|.
  DVLOG(1) << "Starting timer with ID " << id << " for "
           << absolute_expiration_time;
  timer->Start(
      absolute_expiration_time,
      base::BindOnce(&PowerManagerProviderImpl::OnTimerFiredOnMainThread,
                     weak_factory_.GetWeakPtr(), id),
      base::BindOnce(&PowerManagerProviderImpl::OnStartTimerCallback,
                     weak_factory_.GetWeakPtr(), id));
  timers_[id] = std::make_pair(std::move(callback), std::move(timer));
}

void PowerManagerProviderImpl::AcquireWakeLockOnMainThread() {
  DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
  DCHECK_GE(wake_lock_count_, 0);

  wake_lock_count_++;
  if (wake_lock_count_ > 1) {
    DVLOG(1) << "Wake lock acquire. Count: " << wake_lock_count_;
    return;
  }

  // Initialize |wake_lock_| if this is the first time we're using it. Assistant
  // can acquire a wake lock even when it has nothing to show on the display,
  // this shouldn't wake the display up. Hence, the wake lock acquired is of
  // type kPreventAppSuspension.
  if (!wake_lock_) {
    mojo::Remote<device::mojom::WakeLockProvider> provider;
    platform_delegate_->BindWakeLockProvider(
        provider.BindNewPipeAndPassReceiver());
    provider->GetWakeLockWithoutContext(
        device::mojom::WakeLockType::kPreventAppSuspension,
        device::mojom::WakeLockReason::kOther, kWakeLockReason,
        wake_lock_.BindNewPipeAndPassReceiver());
  }

  DVLOG(1) << "Wake lock new acquire";
  // This would violate |GetWakeLockWithoutContext|'s API contract.
  DCHECK(wake_lock_);
  wake_lock_->RequestWakeLock();
}

void PowerManagerProviderImpl::ReleaseWakeLockOnMainThread() {
  DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());
  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) << "Wake lock release. Count: " << wake_lock_count_;
    return;
  }

  DCHECK(wake_lock_);
  DVLOG(1) << "Wake lock force release";
  wake_lock_->CancelWakeLock();
}

void PowerManagerProviderImpl::OnStartTimerCallback(AlarmId id, bool result) {
  DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());

  if (!result) {
    // TODO(crbug.com/40608625): Notify Assistant of error so that it can do
    // something meaningful in the UI.
    LOG(ERROR) << "Failed to start timer on alarm ID " << id;
    // Remove any metadata and resources associated with timers mapped to |id|.
    DCHECK_GT(timers_.erase(id), 0UL);
  }
}

void PowerManagerProviderImpl::OnTimerFiredOnMainThread(AlarmId id) {
  DVLOG(1) << __func__ << " ID " << id;
  DCHECK(main_thread_task_runner_->RunsTasksInCurrentSequence());

  auto it = timers_.find(id);
  if (it == timers_.end()) {
    LOG(ERROR) << "Alarm with id " << id << " not found";
    return;
  }

  // Stop tracking the timer once it has fired.
  CallbackAndTimer& callback_and_timer = it->second;
  callback_and_timer.first();
  timers_.erase(id);
}

}  // namespace ash::libassistant