chromium/ui/display/manager/update_display_configuration_task.cc

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

#include "ui/display/manager/update_display_configuration_task.h"

#include <memory>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "display_configurator.h"
#include "ui/display/manager/configure_displays_task.h"
#include "ui/display/manager/display_layout_manager.h"
#include "ui/display/manager/util/display_manager_util.h"
#include "ui/display/types/display_snapshot.h"
#include "ui/display/types/native_display_delegate.h"

namespace display {

namespace {
// Move all internal panel displays to the front of the display list. Otherwise,
// the list remains in order.
void MoveInternalDisplaysToTheFront(
    std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& displays) {
  DisplayConfigurator::DisplayStateList sorted_displays;

  // First pass for internal panels.
  for (DisplaySnapshot* display : displays) {
    if (display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL)
      sorted_displays.push_back(display);
  }

  // Second pass for the rest.
  for (DisplaySnapshot* display : displays) {
    if (display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL)
      continue;

    sorted_displays.push_back(display);
  }

  displays.swap(sorted_displays);
}

bool ResolveOverrides(
    const DisplayConfigurator::RefreshRateOverrideMap& refresh_rate_overrides,
    std::vector<DisplayConfigureRequest>& requests) {
  for (auto& request : requests) {
    if (!request.mode) {
      continue;
    }

    auto override_it =
        refresh_rate_overrides.find(request.display->display_id());
    if (override_it == refresh_rate_overrides.end()) {
      continue;
    }

    request.mode = std::make_unique<const DisplayMode>(
        request.mode->size(), request.mode->is_interlaced(),
        override_it->second, /*vsync_rate_min=*/std::nullopt);
  }
  return true;
}

}  // namespace

UpdateDisplayConfigurationTask::UpdateDisplayConfigurationTask(
    NativeDisplayDelegate* delegate,
    DisplayLayoutManager* layout_manager,
    MultipleDisplayState new_display_state,
    chromeos::DisplayPowerState new_power_state,
    int power_flags,
    const base::flat_set<int64_t>& new_vrr_state,
    const DisplayConfigurator::RefreshRateOverrideMap& refresh_rate_overrides,
    bool force_configure,
    ConfigurationType configuration_type,
    ResponseCallback callback)
    : delegate_(delegate),
      layout_manager_(layout_manager),
      new_display_state_(new_display_state),
      new_power_state_(new_power_state),
      power_flags_(power_flags),
      new_vrr_state_(new_vrr_state),
      refresh_rate_overrides_(refresh_rate_overrides),
      force_configure_(force_configure),
      configuration_type_(configuration_type),
      callback_(std::move(callback)),
      requesting_displays_(false) {
  delegate_->AddObserver(this);
}

UpdateDisplayConfigurationTask::~UpdateDisplayConfigurationTask() {
  delegate_->RemoveObserver(this);
}

void UpdateDisplayConfigurationTask::Run() {
  requesting_displays_ = true;
  delegate_->GetDisplays(
      base::BindOnce(&UpdateDisplayConfigurationTask::OnDisplaysUpdated,
                     weak_ptr_factory_.GetWeakPtr()));
}

void UpdateDisplayConfigurationTask::OnConfigurationChanged() {}

void UpdateDisplayConfigurationTask::OnDisplaySnapshotsInvalidated() {
  cached_displays_.clear();
  if (!requesting_displays_ && weak_ptr_factory_.HasWeakPtrs()) {
    // This task has already been run and getting the displays request is not in
    // flight. We need to re-run it to get updated displays snapshots.
    weak_ptr_factory_.InvalidateWeakPtrs();
    Run();
  }
}

void UpdateDisplayConfigurationTask::OnDisplaysUpdated(
    const std::vector<raw_ptr<DisplaySnapshot, VectorExperimental>>& displays) {
  cached_displays_ = displays;
  MoveInternalDisplaysToTheFront(cached_displays_);
  requesting_displays_ = false;

  // If the user hasn't requested a display state, update it using the requested
  // power state.
  if (new_display_state_ == MULTIPLE_DISPLAY_STATE_INVALID)
    new_display_state_ = ChooseDisplayState();

  VLOG(1) << "OnDisplaysUpdated: new_display_state="
          << MultipleDisplayStateToString(new_display_state_)
          << " new_power_state=" << DisplayPowerStateToString(new_power_state_)
          << " flags=" << power_flags_
          << " new_vrr_state=" << VrrStateToString(new_vrr_state_)
          << " refresh_rate_overrides="
          << RefreshRateOverrideToString(refresh_rate_overrides_)
          << " force_configure=" << force_configure_
          << " display_count=" << cached_displays_.size();
  if (ShouldConfigure()) {
    EnterState(base::BindOnce(&UpdateDisplayConfigurationTask::OnStateEntered,
                              weak_ptr_factory_.GetWeakPtr()));
  } else {
    // If we don't have to configure then we're sticking with the old
    // configuration. Update it such that it reflects in the reported value.
    new_power_state_ = layout_manager_->GetPowerState();
    FinishConfiguration(true);
  }
}

void UpdateDisplayConfigurationTask::EnterState(
    ConfigureDisplaysTask::ResponseCallback callback) {
  VLOG(2) << "EnterState";
  std::vector<DisplayConfigureRequest> requests;
  if (!layout_manager_->GetDisplayLayout(cached_displays_, new_display_state_,
                                         new_power_state_, new_vrr_state_,
                                         &requests)) {
    std::move(callback).Run(ConfigureDisplaysTask::ERROR);
    return;
  }
  if (!ResolveOverrides(refresh_rate_overrides_, requests)) {
    std::move(callback).Run(ConfigureDisplaysTask::ERROR);
    return;
  }

  if (!requests.empty()) {
    configure_task_ = std::make_unique<ConfigureDisplaysTask>(
        delegate_, requests, std::move(callback), configuration_type_);
    configure_task_->Run();
  } else {
    VLOG(2) << "No displays";
    std::move(callback).Run(ConfigureDisplaysTask::SUCCESS);
  }
}

void UpdateDisplayConfigurationTask::OnStateEntered(
    ConfigureDisplaysTask::Status status) {
  bool success = status != ConfigureDisplaysTask::ERROR;
  if (new_display_state_ == MULTIPLE_DISPLAY_STATE_MULTI_MIRROR &&
      status == ConfigureDisplaysTask::PARTIAL_SUCCESS)
    success = false;

  if (layout_manager_->GetSoftwareMirroringController()) {
    bool enable_software_mirroring = false;
    if (!success && new_display_state_ == MULTIPLE_DISPLAY_STATE_MULTI_MIRROR) {
      if (layout_manager_->GetDisplayState() !=
              MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED ||
          layout_manager_->GetPowerState() != new_power_state_ ||
          force_configure_) {
        new_display_state_ = MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED;
        EnterState(base::BindOnce(
            &UpdateDisplayConfigurationTask::OnEnableSoftwareMirroring,
            weak_ptr_factory_.GetWeakPtr()));
        return;
      }

      success = layout_manager_->GetDisplayState() ==
                MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED;
      enable_software_mirroring = success;
      if (success)
        new_display_state_ = MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED;
    }

    layout_manager_->GetSoftwareMirroringController()->SetSoftwareMirroring(
        enable_software_mirroring);
  }

  FinishConfiguration(success);
}

void UpdateDisplayConfigurationTask::OnEnableSoftwareMirroring(
    ConfigureDisplaysTask::Status status) {
  bool success = status != ConfigureDisplaysTask::ERROR;
  layout_manager_->GetSoftwareMirroringController()->SetSoftwareMirroring(
      success);
  FinishConfiguration(success);
}

void UpdateDisplayConfigurationTask::FinishConfiguration(bool success) {
  base::UmaHistogramBoolean(
      "DisplayManager.UpdateDisplayConfigurationTask.Success", success);

  std::move(callback_).Run(success, cached_displays_,
                           cached_unassociated_displays_, new_display_state_,
                           new_power_state_);
}

bool UpdateDisplayConfigurationTask::ShouldForceDpms() const {
  return new_power_state_ != chromeos::DISPLAY_POWER_ALL_OFF &&
         (layout_manager_->GetPowerState() != new_power_state_ ||
          (power_flags_ & DisplayConfigurator::kSetDisplayPowerForceProbe));
}

bool UpdateDisplayConfigurationTask::ShouldConfigure() const {
  if (force_configure_)
    return true;

  if (cached_displays_.size() == 1 &&
      cached_displays_[0]->type() == DISPLAY_CONNECTION_TYPE_INTERNAL)
    return true;

  if (!(power_flags_ &
        DisplayConfigurator::kSetDisplayPowerOnlyIfSingleInternalDisplay))
    return true;

  if (new_display_state_ != layout_manager_->GetDisplayState())
    return true;

  // Compare refresh rate overrides with current states.
  if (ShouldConfigureRefreshRate()) {
    return true;
  }

  if (ShouldConfigureVrr()) {
    return true;
  }

  return false;
}

MultipleDisplayState UpdateDisplayConfigurationTask::ChooseDisplayState()
    const {
  int num_displays = cached_displays_.size();
  int num_on_displays =
      GetDisplayPower(cached_displays_, new_power_state_, nullptr);

  if (num_displays == 0)
    return MULTIPLE_DISPLAY_STATE_HEADLESS;

  if (num_displays == 1 || num_on_displays == 1) {
    // If only one display is currently turned on, return the "single" state
    // so that its native mode will be used.
    return MULTIPLE_DISPLAY_STATE_SINGLE;
  }

  // Try to use the saved configuration; otherwise, default to extended.
  DisplayConfigurator::StateController* state_controller =
      layout_manager_->GetStateController();

  if (!state_controller)
    return MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED;
  return state_controller->GetStateForDisplayIds(cached_displays_);
}

bool UpdateDisplayConfigurationTask::ShouldConfigureVrr() const {
  for (const DisplaySnapshot* display : cached_displays_) {
    if (!display->IsVrrCapable()) {
      continue;
    }

    if (new_vrr_state_.contains(display->display_id()) !=
        display->IsVrrEnabled()) {
      return true;
    }
  }

  return false;
}

bool UpdateDisplayConfigurationTask::ShouldConfigureRefreshRate() const {
  for (const DisplaySnapshot* display : cached_displays_) {
    // TODO b/334104991: Refresh rate override is only enabled for internal
    // displays.
    if (display->type() != DISPLAY_CONNECTION_TYPE_INTERNAL) {
      continue;
    }

    // No mode means display isn't turned on. Refresh rate override should
    // not affect whether a display is enabled.
    if (!display->current_mode() || !display->native_mode()) {
      continue;
    }

    // Target refresh rate is the native mode's refresh rate, unless an override
    // is specified.
    float target_refresh_rate = display->native_mode()->refresh_rate();
    auto it = refresh_rate_overrides_.find(display->display_id());
    if (it != refresh_rate_overrides_.end()) {
      target_refresh_rate = it->second;
    }

    // If the target refresh rate doesn't match the current refresh rate, then
    // a configuration is needed.
    if (display->current_mode()->refresh_rate() != target_refresh_rate) {
      return true;
    }
  }

  // Checked all displays, and none of them require a refresh rate override.
  return false;
}

}  // namespace display