chromium/chromecast/ui/display_settings/screen_power_controller_aura.cc

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

#include "chromecast/ui/display_settings/screen_power_controller_aura.h"

#include "base/check.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"

namespace chromecast {

namespace {

constexpr base::TimeDelta kScreenOnOffDuration = base::Milliseconds(200);
// These delays are needed to ensure there are no visible artifacts due to the
// backlight turning on prior to the LCD fully initializing or vice-versa.
// TODO(b/161140301): Make this configurable for different products
// TODO(b/161268188): Remove these if the delays can be handled by the kernel
constexpr base::TimeDelta kDisplayPowerOnDelay = base::Milliseconds(35);
constexpr base::TimeDelta kDisplayPowerOffDelay = base::Milliseconds(85);

}  // namespace

std::unique_ptr<ScreenPowerController> ScreenPowerController::Create(
    Delegate* delegate) {
  return std::make_unique<ScreenPowerControllerAura>(delegate);
}

ScreenPowerControllerAura::ScreenPowerControllerAura(
    ScreenPowerController::Delegate* delegate)
    : screen_on_(true),
      screen_power_on_(true),
      allow_screen_power_off_(false),
      pending_task_(PendingTask::kNone),
      delegate_(delegate),
      weak_factory_(this) {
  DCHECK(delegate_);
}

ScreenPowerControllerAura::~ScreenPowerControllerAura() = default;

void ScreenPowerControllerAura::SetScreenOn() {
  if (pending_task_ != PendingTask::kNone) {
    pending_task_ = PendingTask::kOn;
    return;
  }

  if (!screen_on_) {
    pending_task_ = PendingTask::kOn;
    TriggerPendingTask();
  }
}

void ScreenPowerControllerAura::SetScreenOff() {
  if (pending_task_ != PendingTask::kNone) {
    if (!allow_screen_power_off_) {
      pending_task_ = PendingTask::kBrightnessOff;
    } else {
      pending_task_ = PendingTask::kPowerOff;
    }
    return;
  }

  if (!allow_screen_power_off_ && (screen_on_ || !screen_power_on_)) {
    pending_task_ = PendingTask::kBrightnessOff;
    TriggerPendingTask();
  } else if (allow_screen_power_off_ && screen_power_on_) {
    pending_task_ = PendingTask::kPowerOff;
    TriggerPendingTask();
  }
}

void ScreenPowerControllerAura::SetAllowScreenPowerOff(bool allow_power_off) {
  LOG(INFO) << "SetAllowScreenPowerOff set to "
            << (allow_power_off ? "true" : "false");
  allow_screen_power_off_ = allow_power_off;
  switch (pending_task_) {
    case PendingTask::kOn:
      return;
    case PendingTask::kBrightnessOff:
    case PendingTask::kPowerOff:
      if (!allow_screen_power_off_) {
        pending_task_ = PendingTask::kBrightnessOff;
      } else {
        pending_task_ = PendingTask::kPowerOff;
      }
      return;
    case PendingTask::kNone:
      if (!screen_on_) {
        // Set screen power off if screen is already off.
        SetScreenOff();
      }
      return;
  }
}

bool ScreenPowerControllerAura::IsScreenOn() const {
  return screen_on_;
}

void ScreenPowerControllerAura::TriggerPendingTask() {
  switch (pending_task_) {
    case PendingTask::kOn:
      if (screen_on_) {
        NOTREACHED_IN_MIGRATION();
      } else if (screen_power_on_) {
        SetScreenBrightnessOn(true);
        pending_task_ = PendingTask::kNone;
      } else {
        SetScreenPowerOn();
      }
      return;
    case PendingTask::kBrightnessOff:
      if (screen_on_) {
        SetScreenBrightnessOn(false);
        pending_task_ = PendingTask::kNone;
      } else if (screen_power_on_) {
        pending_task_ = PendingTask::kNone;
      } else {
        SetScreenPowerOn();
      }
      return;
    case PendingTask::kPowerOff:
      if (screen_on_) {
        SetScreenBrightnessOn(false);
        SetScreenPowerOff();
      } else if (screen_power_on_) {
        SetScreenPowerOff();
      } else {
        NOTREACHED_IN_MIGRATION();
      }
      return;
    case PendingTask::kNone:
      NOTREACHED_IN_MIGRATION();
  }
}

void ScreenPowerControllerAura::SetScreenBrightnessOn(bool brightness_on) {
  delegate_->SetScreenBrightnessOn(brightness_on, kScreenOnOffDuration);
  screen_on_ = brightness_on;
}

void ScreenPowerControllerAura::SetScreenPowerOn() {
  delegate_->SetScreenPowerOn(
      base::BindOnce(&ScreenPowerControllerAura::OnScreenPoweredOn,
                     weak_factory_.GetWeakPtr()));
}

void ScreenPowerControllerAura::SetScreenPowerOff() {
  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&ScreenPowerControllerAura::OnDisplayOffTimeoutCompleted,
                     weak_factory_.GetWeakPtr()),
      kDisplayPowerOffDelay + kScreenOnOffDuration);
}

void ScreenPowerControllerAura::OnScreenPoweredOn(bool succeeded) {
  if (!succeeded) {
    // Fatal since the user has no other way of turning the screen on if this
    // failed.
    LOG(FATAL) << "Failed to power on the screen";
  }
  LOG(INFO) << "Screen is powered on";
  screen_power_on_ = true;

  switch (pending_task_) {
    case PendingTask::kOn:
    case PendingTask::kBrightnessOff:
      // TODO(b/161268188): This can be simplified and the delays removed if
      // backlight timing is handled by the kernel
      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(
              &ScreenPowerControllerAura::OnDisplayOnTimeoutCompleted,
              weak_factory_.GetWeakPtr()),
          kDisplayPowerOnDelay);
      return;
    case PendingTask::kPowerOff:
      TriggerPendingTask();
      return;
    case PendingTask::kNone:
      NOTREACHED_IN_MIGRATION();
      return;
  }
}

void ScreenPowerControllerAura::OnScreenPoweredOff(bool succeeded) {
  if (!succeeded) {
    LOG(ERROR) << "Failed to power off the screen";
    return;
  }
  LOG(INFO) << "Screen is powered off";
  screen_power_on_ = false;

  switch (pending_task_) {
    case PendingTask::kOn:
    case PendingTask::kBrightnessOff:
      TriggerPendingTask();
      return;
    case PendingTask::kPowerOff:
      pending_task_ = PendingTask::kNone;
      return;
    case PendingTask::kNone:
      NOTREACHED_IN_MIGRATION();
      return;
  }
}

void ScreenPowerControllerAura::OnDisplayOnTimeoutCompleted() {
  switch (pending_task_) {
    case PendingTask::kBrightnessOff:
      pending_task_ = PendingTask::kNone;
      return;
    case PendingTask::kOn:
    case PendingTask::kPowerOff:
      TriggerPendingTask();
      return;
    case PendingTask::kNone:
      NOTREACHED_IN_MIGRATION();
      return;
  }
}

void ScreenPowerControllerAura::OnDisplayOffTimeoutCompleted() {
  switch (pending_task_) {
    case PendingTask::kOn:
      TriggerPendingTask();
      return;
    case PendingTask::kBrightnessOff:
      pending_task_ = PendingTask::kNone;
      return;
    case PendingTask::kPowerOff:
      delegate_->SetScreenPowerOff(
          base::BindOnce(&ScreenPowerControllerAura::OnScreenPoweredOff,
                         weak_factory_.GetWeakPtr()));
      return;
    case PendingTask::kNone:
      NOTREACHED_IN_MIGRATION();
      return;
  }
}

}  // namespace chromecast