chromium/ui/display/manager/display_configurator_unittest.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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

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

#include <stddef.h>
#include <stdint.h>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/display_features.h"
#include "ui/display/manager/configure_displays_task.h"
#include "ui/display/manager/display_layout_manager.h"
#include "ui/display/manager/test/action_logger_util.h"
#include "ui/display/manager/test/fake_display_snapshot.h"
#include "ui/display/manager/test/test_native_display_delegate.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/util/display_util.h"
#include "ui/gfx/geometry/size.h"
#include "ui/ozone/public/ozone_switches.h"

namespace display::test {

namespace {

constexpr int64_t kDisplayIds[3] = {123, 456, 789};

// Non-zero generic connector IDs.
constexpr uint64_t kEdpConnectorId = 71u;
constexpr uint64_t kSecondConnectorId = kEdpConnectorId + 10u;
constexpr uint64_t kThirdConnectorId = kEdpConnectorId + 20u;

std::unique_ptr<DisplayMode> MakeDisplayMode(int width,
                                             int height,
                                             bool is_interlaced,
                                             float refresh_rate) {
  return std::make_unique<DisplayMode>(gfx::Size{width, height}, is_interlaced,
                                       refresh_rate);
}

enum CallbackResult {
  CALLBACK_FAILURE,
  CALLBACK_SUCCESS,
  CALLBACK_NOT_CALLED,
};

// Expected immediate configurations should be done without any delays.
constexpr base::TimeDelta kNoDelay = base::Milliseconds(0);

// The expected configuration delay when resuming from suspend while in 2+
// display mode.
constexpr base::TimeDelta kLongDelay = base::Milliseconds(
    DisplayConfigurator::kResumeConfigureMultiDisplayDelayMs);

class TestObserver : public DisplayConfigurator::Observer {
 public:
  explicit TestObserver(DisplayConfigurator* configurator)
      : configurator_(configurator) {
    Reset();
    configurator_->AddObserver(this);
  }

  TestObserver(const TestObserver&) = delete;
  TestObserver& operator=(const TestObserver&) = delete;

  ~TestObserver() override { configurator_->RemoveObserver(this); }

  int num_changes() const { return num_changes_; }
  int num_failures() const { return num_failures_; }
  int num_power_state_changes() const { return num_power_state_changes_; }
  const DisplayConfigurator::DisplayStateList& latest_outputs() const {
    return latest_outputs_;
  }
  MultipleDisplayState latest_failed_state() const {
    return latest_failed_state_;
  }
  chromeos::DisplayPowerState latest_power_state() const {
    return latest_power_state_;
  }

  void Reset() {
    num_changes_ = 0;
    num_failures_ = 0;
    num_power_state_changes_ = 0;
    latest_outputs_.clear();
    latest_failed_state_ = MULTIPLE_DISPLAY_STATE_INVALID;
    latest_power_state_ = chromeos::DISPLAY_POWER_ALL_OFF;
  }

  // DisplayConfigurator::Observer overrides:
  void OnDisplayConfigurationChanged(
      const DisplayConfigurator::DisplayStateList& outputs) override {
    num_changes_++;
    latest_outputs_ = outputs;
  }

  void OnDisplayConfigurationChangeFailed(
      const DisplayConfigurator::DisplayStateList& outputs,
      MultipleDisplayState failed_new_state) override {
    num_failures_++;
    latest_failed_state_ = failed_new_state;
  }

  void OnPowerStateChanged(chromeos::DisplayPowerState power_state) override {
    num_power_state_changes_++;
    latest_power_state_ = power_state;
  }

 private:
  raw_ptr<DisplayConfigurator> configurator_;  // Not owned.

  // Number of times that OnDisplayMode*() has been called.
  int num_changes_;
  int num_failures_;
  // Number of times that OnPowerStateChanged() has been called.
  int num_power_state_changes_;

  // Parameters most recently passed to OnDisplayMode*().
  DisplayConfigurator::DisplayStateList latest_outputs_;
  MultipleDisplayState latest_failed_state_;
  // Value most recently passed to OnPowerStateChanged().
  chromeos::DisplayPowerState latest_power_state_;
};

class TestStateController : public DisplayConfigurator::StateController {
 public:
  TestStateController() : state_(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED) {}

  TestStateController(const TestStateController&) = delete;
  TestStateController& operator=(const TestStateController&) = delete;

  ~TestStateController() override = default;

  void set_state(MultipleDisplayState state) { state_ = state; }

  // DisplayConfigurator::StateController overrides:
  MultipleDisplayState GetStateForDisplayIds(
      const DisplayConfigurator::DisplayStateList& outputs) override {
    return state_;
  }
  bool GetSelectedModeForDisplayId(
      int64_t display_id,
      ManagedDisplayMode* out_mode) const override {
    return false;
  }

 private:
  MultipleDisplayState state_;
};

class TestMirroringController
    : public DisplayConfigurator::SoftwareMirroringController {
 public:
  TestMirroringController() : software_mirroring_enabled_(false) {}

  TestMirroringController(const TestMirroringController&) = delete;
  TestMirroringController& operator=(const TestMirroringController&) = delete;

  ~TestMirroringController() override = default;

  void SetSoftwareMirroring(bool enabled) override {
    software_mirroring_enabled_ = enabled;
  }

  bool SoftwareMirroringEnabled() const override {
    return software_mirroring_enabled_;
  }

  bool IsSoftwareMirroringEnforced() const override { return false; }

 private:
  bool software_mirroring_enabled_;
};

// Abstracts waiting for the display configuration to be completed and getting
// the time it took to complete.
class ConfigurationWaiter {
 public:
  explicit ConfigurationWaiter(DisplayConfigurator::TestApi* test_api)
      : test_api_(test_api), callback_result_(CALLBACK_NOT_CALLED) {}

  ConfigurationWaiter(const ConfigurationWaiter&) = delete;
  ConfigurationWaiter& operator=(const ConfigurationWaiter&) = delete;

  ~ConfigurationWaiter() = default;

  DisplayConfigurator::ConfigurationCallback on_configuration_callback() {
    return base::BindOnce(&ConfigurationWaiter::OnConfigured,
                          base::Unretained(this));
  }

  CallbackResult callback_result() const { return callback_result_; }

  void Reset() { callback_result_ = CALLBACK_NOT_CALLED; }

  // Simulates waiting for the next configuration. If an async task is pending,
  // runs it and returns base::TimeDelta(). Otherwise, triggers the
  // configuration timer and returns its delay. If the timer wasn't running,
  // returns base::TimeDelta::Max().
  [[nodiscard]] base::TimeDelta Wait() {
    base::RunLoop().RunUntilIdle();
    if (callback_result_ != CALLBACK_NOT_CALLED)
      return base::TimeDelta();

    const base::TimeDelta delay = test_api_->GetConfigureDelay();
    if (!test_api_->TriggerConfigureTimeout())
      return base::TimeDelta::Max();

    return delay;
  }

 private:
  void OnConfigured(bool status) {
    CHECK_EQ(callback_result_, CALLBACK_NOT_CALLED);
    callback_result_ = status ? CALLBACK_SUCCESS : CALLBACK_FAILURE;
  }

  raw_ptr<DisplayConfigurator::TestApi> test_api_;  // Not owned.

  // The status of the display configuration.
  CallbackResult callback_result_;
};

class DisplayConfiguratorTest : public testing::Test {
 public:
  DisplayConfiguratorTest() = default;

  DisplayConfiguratorTest(const DisplayConfiguratorTest&) = delete;
  DisplayConfiguratorTest& operator=(const DisplayConfiguratorTest&) = delete;

  ~DisplayConfiguratorTest() override = default;

  void SetUp() override {
    log_ = std::make_unique<ActionLogger>();

    // TODO(crbug.com/1161556): |kEnableHardwareMirrorMode| is disabled by
    // default. We enable it here to maintain test coverage for hardware mirror
    // mode until it is permanently removed.
    scoped_feature_list_.InitAndEnableFeature(
        features::kEnableHardwareMirrorMode);

    native_display_delegate_ = new TestNativeDisplayDelegate(log_.get());
    // Force configuring displays to simulate on-device configurator behavior.
    configurator_.SetConfigureDisplays(true);
    configurator_.SetDelegateForTesting(
        std::unique_ptr<NativeDisplayDelegate>(native_display_delegate_));

    configurator_.set_state_controller(&state_controller_);
    configurator_.set_mirroring_controller(&mirroring_controller_);

    outputs_[0] =
        FakeDisplaySnapshot::Builder()
            .SetId(kDisplayIds[0])
            .SetNativeMode(small_mode_.Clone())
            .SetCurrentMode(small_mode_.Clone())
            .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
            .SetBaseConnectorId(kEdpConnectorId)
            .SetIsAspectPreservingScaling(true)
            .SetVariableRefreshRateState(VariableRefreshRateState::kVrrDisabled)
            .Build();

    outputs_[1] = FakeDisplaySnapshot::Builder()
                      .SetId(kDisplayIds[1])
                      .SetNativeMode(big_mode_.Clone())
                      .SetCurrentMode(big_mode_.Clone())
                      .AddMode(small_mode_.Clone())
                      .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                      .SetBaseConnectorId(kSecondConnectorId)
                      .SetIsAspectPreservingScaling(true)
                      .SetVariableRefreshRateState(
                          VariableRefreshRateState::kVrrNotCapable)
                      .Build();

    outputs_[2] = FakeDisplaySnapshot::Builder()
                      .SetId(kDisplayIds[2])
                      .SetNativeMode(small_mode_.Clone())
                      .SetCurrentMode(small_mode_.Clone())
                      .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                      .SetBaseConnectorId(kThirdConnectorId)
                      .SetIsAspectPreservingScaling(true)
                      .SetVariableRefreshRateState(
                          VariableRefreshRateState::kVrrNotCapable)
                      .Build();

    UpdateOutputs(2, false);
  }

  void OnDisplayControlUpdated(bool success) {
    display_control_result_ = success ? CALLBACK_SUCCESS : CALLBACK_FAILURE;
  }

  // Predefined modes that can be used by outputs.
  const DisplayMode small_mode_ = DisplayMode({1366, 768}, false, 60.0f);
  const DisplayMode big_mode_ = DisplayMode({2560, 1600}, false, 60.0f);

 protected:
  // Returns the output at the specified |index| as it currently exists within
  // |native_display_delegate_|.
  const DisplaySnapshot* GetOutput(size_t index) const {
    return native_display_delegate_->GetOutputs()[index];
  }

  // Sets the test-owned output at the specified |index| without sending updates
  // to |native_display_delegate_|. Must be followed by UpdateOutputs to effect
  // changes.
  void SetOutput(size_t index, std::unique_ptr<DisplaySnapshot> output) {
    outputs_[index] = std::move(output);
  }

  // Configures |native_display_delegate_| to return the first |num_outputs|
  // entries from |outputs_|. If |send_events| is true, also sends screen-change
  // and output-change events to |configurator_| and triggers the configure
  // timeout if one was scheduled.
  void UpdateOutputs(size_t num_outputs, bool send_events) {
    ASSERT_LE(num_outputs, std::size(outputs_));
    std::vector<std::unique_ptr<DisplaySnapshot>> outputs;
    for (size_t i = 0; i < num_outputs; ++i) {
      outputs.push_back(outputs_[i]->Clone());
    }
    native_display_delegate_->SetOutputs(std::move(outputs));

    if (send_events) {
      configurator_.OnConfigurationChanged();
      EXPECT_TRUE(test_api_.TriggerConfigureTimeout());
    }
  }

  void Init(bool panel_fitting_enabled) {
    configurator_.Init(nullptr, panel_fitting_enabled);
  }

  enum class DisplayConfig { kOff, kMirror, kStack };

  // |modes| are expected display modes for |outputs_| at respective positions.
  template <typename... Modes>
  void InitWithOutputs(Modes... modes) {
    UpdateOutputs(sizeof...(modes), false);
    EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
    Init(false);

    EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
    configurator_.ForceInitialConfigure();
    std::string actions = GetCrtcActions(DisplayConfig::kStack, modes...);
    EXPECT_EQ(actions.empty()
                  ? kInit
                  : JoinActions(
                        kInit, kTestModesetStr,
                        GetCrtcActions(DisplayConfig::kStack, modes...).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(DisplayConfig::kStack, modes...).c_str(),
                        kModesetOutcomeSuccess, nullptr),
              log_->GetActionsAndClear());
  }

  template <typename... Modes>
  std::string GetCrtcActions(Modes... modes) {
    return GetCrtcActions(DisplayConfig::kStack, modes...);
  }

  template <typename... Modes>
  std::string GetCrtcActions(DisplayConfig config, Modes... modes) {
    return JoinCrtcActions<0>(config, gfx::Point(), modes...);
  }

  CallbackResult PopDisplayControlResult() {
    CallbackResult result = display_control_result_;
    display_control_result_ = CALLBACK_NOT_CALLED;
    return result;
  }

  base::test::SingleThreadTaskEnvironment task_environment_;
  TestStateController state_controller_;
  TestMirroringController mirroring_controller_;
  DisplayConfigurator configurator_;
  TestObserver observer_{&configurator_};
  std::unique_ptr<ActionLogger> log_;
  raw_ptr<TestNativeDisplayDelegate> native_display_delegate_;  // not owned
  DisplayConfigurator::TestApi test_api_{&configurator_};
  ConfigurationWaiter config_waiter_{&test_api_};
  base::test::ScopedFeatureList scoped_feature_list_;

  CallbackResult display_control_result_ = CALLBACK_NOT_CALLED;

 private:
  template <size_t I>
  std::string JoinCrtcActions(DisplayConfig, const gfx::Point&) {
    return {};
  }

  template <size_t I, typename... Modes>
  std::string JoinCrtcActions(DisplayConfig config,
                              gfx::Point origin,
                              const DisplayMode* mode,
                              Modes... modes) {
    static_assert(I < kNumOutputs, "More expected modes than outputs");

    std::string action =
        GetCrtcAction({outputs_[I]->display_id(), origin,
                       config == DisplayConfig::kOff ? nullptr : mode});

    if (mode && config != DisplayConfig::kMirror)
      origin += {0, mode->size().height() + DisplayConfigurator::kVerticalGap};

    std::string rest = JoinCrtcActions<I + 1>(config, origin, modes...);
    return rest.empty() ? action
                        : JoinActions(action.c_str(), rest.c_str(), nullptr);
  }

  static constexpr size_t kNumOutputs = 3;
  // These snapshots are owned by the test. They are cloned whenever updates are
  // sent to |native_display_delegate_|.
  std::unique_ptr<DisplaySnapshot> outputs_[kNumOutputs];
};

}  // namespace

TEST_F(DisplayConfiguratorTest, FindDisplayModeMatchingSize) {
  std::unique_ptr<DisplaySnapshot> output =
      FakeDisplaySnapshot::Builder()
          .SetId(kDisplayIds[0])
          .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))
          .SetNativeMode(MakeDisplayMode(1920, 1200, false, 50.0))
          // Different rates.
          .AddMode(MakeDisplayMode(1920, 1080, false, 30.0))
          .AddMode(MakeDisplayMode(1920, 1080, false, 50.0))
          .AddMode(MakeDisplayMode(1920, 1080, false, 40.0))
          .AddMode(MakeDisplayMode(1920, 1080, false, 0.0))
          // Interlaced vs non-interlaced.
          .AddMode(MakeDisplayMode(1280, 720, true, 60.0))
          .AddMode(MakeDisplayMode(1280, 720, false, 40.0))
          // Interlaced only.
          .AddMode(MakeDisplayMode(1024, 768, true, 0.0))
          .AddMode(MakeDisplayMode(1024, 768, true, 40.0))
          .AddMode(MakeDisplayMode(1024, 768, true, 60.0))
          // Mixed.
          .AddMode(MakeDisplayMode(1024, 600, true, 60.0))
          .AddMode(MakeDisplayMode(1024, 600, false, 40.0))
          .AddMode(MakeDisplayMode(1024, 600, false, 50.0))
          // Just one interlaced mode.
          .AddMode(MakeDisplayMode(640, 480, true, 60.0))
          // Refresh rate not available.
          .AddMode(MakeDisplayMode(320, 200, false, 0.0))
          .Build();

  const std::vector<std::unique_ptr<const DisplayMode>>& modes =
      output->modes();

  // Should pick native over highest refresh rate.
  EXPECT_EQ(modes[1].get(), DisplayConfigurator::FindDisplayModeMatchingSize(
                                *output, gfx::Size(1920, 1200)));

  // Should pick highest refresh rate.
  EXPECT_EQ(modes[3].get(), DisplayConfigurator::FindDisplayModeMatchingSize(
                                *output, gfx::Size(1920, 1080)));

  // Should pick non-interlaced mode.
  EXPECT_EQ(modes[7].get(), DisplayConfigurator::FindDisplayModeMatchingSize(
                                *output, gfx::Size(1280, 720)));

  // Interlaced only. Should pick one with the highest refresh rate in
  // interlaced mode.
  EXPECT_EQ(modes[10].get(), DisplayConfigurator::FindDisplayModeMatchingSize(
                                 *output, gfx::Size(1024, 768)));

  // Mixed: Should pick one with the highest refresh rate in
  // interlaced mode.
  EXPECT_EQ(modes[13].get(), DisplayConfigurator::FindDisplayModeMatchingSize(
                                 *output, gfx::Size(1024, 600)));

  // Just one interlaced mode.
  EXPECT_EQ(modes[14].get(), DisplayConfigurator::FindDisplayModeMatchingSize(
                                 *output, gfx::Size(640, 480)));

  // Refresh rate not available.
  EXPECT_EQ(modes[15].get(), DisplayConfigurator::FindDisplayModeMatchingSize(
                                 *output, gfx::Size(320, 200)));

  // No mode found.
  EXPECT_EQ(nullptr, DisplayConfigurator::FindDisplayModeMatchingSize(
                         *output, gfx::Size(1440, 900)));
}

TEST_F(DisplayConfiguratorTest, ConnectSecondOutput) {
  InitWithOutputs(&small_mode_);

  // Connect a second output and check that the configurator enters
  // extended mode.
  observer_.Reset();
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  UpdateOutputs(2, true);

  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());

  observer_.Reset();
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Disconnect the second output.
  observer_.Reset();
  UpdateOutputs(1, true);
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Get rid of shared modes to force software mirroring.
  SetOutput(1, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[1])
                   .SetNativeMode(big_mode_.Clone())
                   .SetCurrentMode(big_mode_.Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                   .SetIsAspectPreservingScaling(true)
                   .Build());

  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  UpdateOutputs(2, true);
  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());

  observer_.Reset();
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());
  EXPECT_TRUE(mirroring_controller_.SoftwareMirroringEnabled());

  EXPECT_EQ(1, observer_.num_changes());

  // Setting MULTIPLE_DISPLAY_STATE_DUAL_MIRROR should try to reconfigure.
  observer_.Reset();
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Set back to software mirror mode.
  observer_.Reset();
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());
  EXPECT_TRUE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Disconnect the second output.
  observer_.Reset();
  UpdateOutputs(1, true);
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());
}

TEST_F(DisplayConfiguratorTest, SetDisplayPower) {
  InitWithOutputs(&small_mode_);

  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  observer_.Reset();
  UpdateOutputs(2, true);
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Turning off the internal display should switch the external display to
  // its native mode.
  observer_.Reset();
  config_waiter_.Reset();
  configurator_.SetDisplayPower(
      chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
      DisplayConfigurator::kSetDisplayPowerNoFlags,
      config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(
      JoinActions(kTestModesetStr, GetCrtcActions(nullptr, &big_mode_).c_str(),
                  kModesetOutcomeSuccess, kCommitModesetStr,
                  GetCrtcActions(nullptr, &big_mode_).c_str(),
                  kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_SINGLE, configurator_.display_state());
  EXPECT_EQ(1, observer_.num_changes());

  // When all displays are turned off, the framebuffer should switch back
  // to the mirrored size.
  observer_.Reset();
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(
      JoinActions(kTestModesetStr,
                  GetCrtcActions(DisplayConfig::kOff, nullptr, nullptr).c_str(),
                  kModesetOutcomeSuccess, kCommitModesetStr,
                  GetCrtcActions(DisplayConfig::kOff, nullptr, nullptr).c_str(),
                  kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR, configurator_.display_state());
  EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Turn all displays on and check that mirroring is still used.
  observer_.Reset();
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR, configurator_.display_state());
  EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Get rid of shared modes to force software mirroring.
  SetOutput(1, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[1])
                   .SetNativeMode(big_mode_.Clone())
                   .SetCurrentMode(big_mode_.Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                   .SetIsAspectPreservingScaling(true)
                   .Build());

  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  observer_.Reset();
  UpdateOutputs(2, true);

  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());
  EXPECT_TRUE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Turning off the internal display should switch the external display to
  // its native mode.
  observer_.Reset();
  config_waiter_.Reset();
  configurator_.SetDisplayPower(
      chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
      DisplayConfigurator::kSetDisplayPowerNoFlags,
      config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(
      JoinActions(kTestModesetStr, GetCrtcActions(nullptr, &big_mode_).c_str(),
                  kModesetOutcomeSuccess, kCommitModesetStr,
                  GetCrtcActions(nullptr, &big_mode_).c_str(),
                  kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_SINGLE, configurator_.display_state());
  EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());

  // When all displays are turned off, the framebuffer should switch back
  // to the extended + software mirroring.
  observer_.Reset();
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kOff, &small_mode_, &big_mode_).c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kOff, &small_mode_, &big_mode_).c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());
  EXPECT_TRUE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Turn all displays on and check that mirroring is still used.
  observer_.Reset();
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());
  EXPECT_TRUE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());
}

TEST_F(DisplayConfiguratorTest, SuspendAndResume) {
  InitWithOutputs(&small_mode_);

  // Set the initial power state to on.
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());

  // No preparation is needed before suspending when the display is already
  // on.  The configurator should still reprobe on resume in case a display
  // was connected while suspended.
  config_waiter_.Reset();
  configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(nullptr).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(nullptr).c_str(), kModesetOutcomeSuccess,
                        nullptr),
            log_->GetActionsAndClear());

  // No resume delay in single display mode.
  config_waiter_.Reset();
  configurator_.ResumeDisplays();
  // The timer should not be running.
  EXPECT_EQ(base::TimeDelta::Max(), config_waiter_.Wait());
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());

  // Now turn the display off before suspending and check that the
  // configurator turns it back on and syncs with the server.
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(nullptr).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(nullptr).c_str(), kModesetOutcomeSuccess,
                        nullptr),
            log_->GetActionsAndClear());

  config_waiter_.Reset();
  configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  config_waiter_.Reset();
  configurator_.ResumeDisplays();
  // The timer should not be running.
  EXPECT_EQ(base::TimeDelta::Max(), config_waiter_.Wait());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());

  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  UpdateOutputs(2, true);
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());

  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR, configurator_.display_state());
  EXPECT_EQ(
      JoinActions(kTestModesetStr,
                  GetCrtcActions(DisplayConfig::kOff, nullptr, nullptr).c_str(),
                  kModesetOutcomeSuccess, kCommitModesetStr,
                  GetCrtcActions(DisplayConfig::kOff, nullptr, nullptr).c_str(),
                  kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());

  // No delay in suspend.
  config_waiter_.Reset();
  configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_OFF,
            configurator_.current_power_state());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR, configurator_.display_state());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  // If a display is disconnected while suspended, the configurator should
  // pick up the change and only turn on the internal display. The should be
  // a longer configuration delay when we set the displays back to on.
  UpdateOutputs(1, false);
  config_waiter_.Reset();
  configurator_.ResumeDisplays();
  // Since we were in dual display mirror mode before suspend, the timer should
  // be running with kMinLongDelayMs.
  EXPECT_EQ(kLongDelay, test_api_.GetConfigureDelay());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(CALLBACK_NOT_CALLED, config_waiter_.callback_result());
  EXPECT_EQ(kLongDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, Headless) {
  InitWithOutputs();

  // Not much should happen when the display power state is changed while
  // no displays are connected.
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  // Connect an external display and check that it's configured correctly.
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(big_mode_.Clone())
                   .SetCurrentMode(big_mode_.Clone())
                   .AddMode(small_mode_.Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetIsAspectPreservingScaling(true)
                   .Build());

  UpdateOutputs(1, true);
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(&big_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&big_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());

  UpdateOutputs(0, true);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, StartWithTwoOutputs) {
  UpdateOutputs(2, false);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
  Init(false);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  configurator_.ForceInitialConfigure();
  EXPECT_EQ(
      JoinActions(
          kInit, kTestModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, InvalidMultipleDisplayStates) {
  UpdateOutputs(0, false);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
  Init(false);
  configurator_.ForceInitialConfigure();
  observer_.Reset();
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_HEADLESS);
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(0, observer_.num_failures());
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_SINGLE);
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(3, observer_.num_failures());

  UpdateOutputs(1, true);
  observer_.Reset();
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_HEADLESS);
  EXPECT_EQ(0, observer_.num_changes());
  EXPECT_EQ(1, observer_.num_failures());
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_SINGLE);
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(1, observer_.num_failures());
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(3, observer_.num_failures());

  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  UpdateOutputs(2, true);
  observer_.Reset();
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_HEADLESS);
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_SINGLE);
  EXPECT_EQ(0, observer_.num_changes());
  EXPECT_EQ(2, observer_.num_failures());
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  EXPECT_EQ(2, observer_.num_changes());
  EXPECT_EQ(2, observer_.num_failures());
}

TEST_F(DisplayConfiguratorTest, GetMultipleDisplayStateForHWMirroredDisplays) {
  UpdateOutputs(2, false);
  Init(false);
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  configurator_.ForceInitialConfigure();
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR, configurator_.display_state());
  EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
}

TEST_F(DisplayConfiguratorTest, GetMultipleDisplayStateForSWMirroredDisplays) {
  // Disable hardware mirroring.
  scoped_feature_list_.Reset();
  scoped_feature_list_.InitAndDisableFeature(
      features::kEnableHardwareMirrorMode);
  UpdateOutputs(2, false);
  Init(false);
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  configurator_.ForceInitialConfigure();
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());
  EXPECT_TRUE(mirroring_controller_.SoftwareMirroringEnabled());
}

TEST_F(DisplayConfiguratorTest, UpdateCachedOutputsEvenAfterFailure) {
  InitWithOutputs(&small_mode_);

  const DisplayConfigurator::DisplayStateList& cached =
      configurator_.cached_displays();
  ASSERT_EQ(static_cast<size_t>(1), cached.size());
  EXPECT_EQ(GetOutput(0)->current_mode(), cached[0]->current_mode());

  // After connecting a second output, check that it shows up in
  // |cached_displays_| even if an invalid state is requested.
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
  UpdateOutputs(2, true);
  ASSERT_EQ(static_cast<size_t>(2), cached.size());
  EXPECT_EQ(GetOutput(0)->current_mode(), cached[0]->current_mode());
  EXPECT_EQ(GetOutput(1)->current_mode(), cached[1]->current_mode());
}

TEST_F(DisplayConfiguratorTest, VerifyInternalPanelIsAtTheTopOfTheList) {
  InitWithOutputs(&small_mode_);

  // Initialize with 3 displays where the internal panel is not at the top of
  // the display list.
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(1L)
                   .SetType(DISPLAY_CONNECTION_TYPE_DISPLAYPORT)
                   .SetNativeMode(big_mode_.Clone())
                   .Build());
  SetOutput(1, FakeDisplaySnapshot::Builder()
                   .SetId(2L)
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetNativeMode(small_mode_.Clone())
                   .Build());
  SetOutput(2, FakeDisplaySnapshot::Builder()
                   .SetId(3L)
                   .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                   .SetNativeMode(big_mode_.Clone())
                   .Build());

  native_display_delegate_->set_max_configurable_pixels(
      big_mode_.size().GetArea());
  UpdateOutputs(3, true);

  // We expect the internal display to be at the top of DisplayConfigurator's
  // |cached_displays_| list post configuration. The rest of the display should
  // be in the original order from DRM.
  const DisplayConfigurator::DisplayStateList& cached =
      configurator_.cached_displays();
  ASSERT_EQ(cached.size(), 3U);

  EXPECT_EQ(cached[0]->display_id(), 2L);
  EXPECT_EQ(cached[0]->type(), DISPLAY_CONNECTION_TYPE_INTERNAL);

  EXPECT_EQ(cached[1]->display_id(), 1L);
  EXPECT_EQ(cached[1]->type(), DISPLAY_CONNECTION_TYPE_DISPLAYPORT);

  EXPECT_EQ(cached[2]->display_id(), 3L);
  EXPECT_EQ(cached[2]->type(), DISPLAY_CONNECTION_TYPE_HDMI);
}

TEST_F(DisplayConfiguratorTest, DoNotConfigureWithSuspendedDisplays) {
  InitWithOutputs(&small_mode_);

  // The DisplayConfigurator may occasionally receive OnConfigurationChanged()
  // after the displays have been suspended.  This event should be ignored since
  // the DisplayConfigurator will force a probe and reconfiguration of displays
  // at resume time.
  config_waiter_.Reset();
  configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(nullptr).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(nullptr).c_str(), kModesetOutcomeSuccess,
                        nullptr),
            log_->GetActionsAndClear());

  // The configuration timer should not be started when the displays
  // are suspended.
  configurator_.OnConfigurationChanged();
  EXPECT_FALSE(test_api_.TriggerConfigureTimeout());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  // Calls to SetDisplayPower should do nothing if the power state doesn't
  // change.
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());

  UpdateOutputs(2, false);
  configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());

  // The DisplayConfigurator should do nothing at resume time if there is no
  // state change.
  config_waiter_.Reset();
  UpdateOutputs(1, false);
  configurator_.ResumeDisplays();
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  // If a configuration task is pending when the displays are suspended, that
  // task should not run either and the timer should be stopped. The displays
  // should be turned off by suspend.
  configurator_.OnConfigurationChanged();
  config_waiter_.Reset();
  configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(nullptr).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(nullptr).c_str(), kModesetOutcomeSuccess,
                        nullptr),
            log_->GetActionsAndClear());
  EXPECT_FALSE(test_api_.TriggerConfigureTimeout());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  config_waiter_.Reset();
  configurator_.ResumeDisplays();
  // The timer should not be running.
  EXPECT_EQ(base::TimeDelta::Max(), config_waiter_.Wait());
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, HandleConfigureCrtcFailure) {
  InitWithOutputs(&small_mode_);

  std::vector<std::unique_ptr<const DisplayMode>> modes;
  // The first mode is the mode we are requesting DisplayConfigurator to choose.
  // The test will be setup so that this mode will fail and it will have to
  // choose the next best option.
  modes.push_back(MakeDisplayMode(2560, 1600, false, 60.0));
  modes.push_back(MakeDisplayMode(1024, 768, false, 60.0));
  modes.push_back(MakeDisplayMode(1280, 720, false, 60.0));
  modes.push_back(MakeDisplayMode(1920, 1080, false, 60.0));
  modes.push_back(MakeDisplayMode(1920, 1080, false, 40.0));

  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .AddMode(modes[2]->Clone())
                   .AddMode(modes[3]->Clone())
                   .AddMode(modes[4]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());

  // Since Chrome restricts the internal display to its native mode it should
  // not attempt other available modes. The likelihood of an internal display
  // failing to pass a modeset test is low, but we cover this case here.
  native_display_delegate_->set_max_configurable_pixels(
      modes[2]->size().GetArea());
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
  UpdateOutputs(1, true);

  EXPECT_EQ(JoinActions(
                // Initial attempt fails.
                kTestModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               GetOutput(0)->native_mode()})
                    .c_str(),
                kModesetOutcomeFailure,
                // Initiate retry logic, which fails since it cannot downgrade
                // the internal display.
                kTestModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               GetOutput(0)->native_mode()})
                    .c_str(),
                kModesetOutcomeFailure, nullptr),
            log_->GetActionsAndClear());

  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .AddMode(modes[2]->Clone())
                   .AddMode(modes[3]->Clone())
                   .AddMode(modes[4]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_DISPLAYPORT)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());

  // This test simply fails in MULTIPLE_DISPLAY_STATE_SINGLE mode for an
  // external display (assuming the internal display is disabled; e.g. the lid
  // is closed).
  UpdateOutputs(1, true);

  EXPECT_EQ(JoinActions(
                // Initial attempt fails.
                kTestModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[0].get()})
                    .c_str(),
                kModesetOutcomeFailure,
                // Initiate retry logic.
                kTestModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[0].get()})
                    .c_str(),
                kModesetOutcomeFailure,
                // Retry attempts trying all available modes.
                kTestModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[3].get()})
                    .c_str(),
                kModesetOutcomeFailure, kTestModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[4].get()})
                    .c_str(),
                kModesetOutcomeFailure,
                // Test-modeset passes for this mode.
                kTestModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[2].get()})
                    .c_str(),
                kModesetOutcomeSuccess, kCommitModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[2].get()})
                    .c_str(),
                kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());

  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .AddMode(modes[2]->Clone())
                   .AddMode(modes[3]->Clone())
                   .AddMode(modes[4]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());

  SetOutput(1, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[1])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .AddMode(modes[2]->Clone())
                   .AddMode(modes[3]->Clone())
                   .AddMode(modes[4]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                   .SetBaseConnectorId(kSecondConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());

  // This test should attempt to configure a mirror mode that will not succeed
  // and should end up in extended mode.
  native_display_delegate_->set_max_configurable_pixels(
      modes[1]->size().GetArea());
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  UpdateOutputs(2, true);

  EXPECT_EQ(
      JoinActions(
          // Initial attempt fails. Initiate retry logic.
          kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                         GetOutput(0)->native_mode()})
              .c_str(),
          GetCrtcAction(
              {GetOutput(1)->display_id(), gfx::Point(0, 0), modes[0].get()})
              .c_str(),
          kModesetOutcomeFailure,
          // We first test-modeset the internal display with all other displays
          // disabled, which will fail.
          kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                         GetOutput(0)->native_mode()})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          kModesetOutcomeFailure,
          // Since internal displays are restricted to their preferred mode,
          // there are no other modes to try. Disable the internal display so we
          // can attempt to modeset displays that are connected to other
          // connectors. Next, the external display will cycle through all its
          // available modes before failing completely.
          kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction(
              {GetOutput(1)->display_id(), gfx::Point(0, 0), modes[0].get()})
              .c_str(),
          kModesetOutcomeFailure, kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction(
              {GetOutput(1)->display_id(), gfx::Point(0, 0), modes[3].get()})
              .c_str(),
          kModesetOutcomeFailure, kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction(
              {GetOutput(1)->display_id(), gfx::Point(0, 0), modes[4].get()})
              .c_str(),
          kModesetOutcomeFailure, kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction(
              {GetOutput(1)->display_id(), gfx::Point(0, 0), modes[2].get()})
              .c_str(),
          kModesetOutcomeFailure,
          // This configuration still passes intermediate test-modeset.
          kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction(
              {GetOutput(1)->display_id(), gfx::Point(0, 0), modes[1].get()})
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction(
              {GetOutput(1)->display_id(), gfx::Point(0, 0), modes[1].get()})
              .c_str(),
          kModesetOutcomeSuccess,
          // Since mirror mode configuration failed it should now attempt to
          // configure in extended mode. However, initial attempt fails.
          // Initiate retry logic.
          kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                         GetOutput(0)->native_mode()})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, modes[0]->size().height() +
                                           DisplayConfigurator::kVerticalGap),
                         modes[0].get()})
              .c_str(),
          kModesetOutcomeFailure,
          // We first test-modeset the internal display with all other displays
          // disabled, which will fail.
          kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                         GetOutput(0)->native_mode()})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, modes[0]->size().height() +
                                           DisplayConfigurator::kVerticalGap),
                         nullptr})
              .c_str(),
          kModesetOutcomeFailure,
          // The configuration fails completely but still attempts to modeset
          // the external display.
          kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, modes[0]->size().height() +
                                           DisplayConfigurator::kVerticalGap),
                         modes[0].get()})
              .c_str(),
          kModesetOutcomeFailure, kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, modes[0]->size().height() +
                                           DisplayConfigurator::kVerticalGap),
                         modes[3].get()})
              .c_str(),
          kModesetOutcomeFailure, kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, modes[0]->size().height() +
                                           DisplayConfigurator::kVerticalGap),
                         modes[4].get()})
              .c_str(),
          kModesetOutcomeFailure, kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, modes[0]->size().height() +
                                           DisplayConfigurator::kVerticalGap),
                         modes[2].get()})
              .c_str(),
          kModesetOutcomeFailure,
          // This configuration passes test-modeset.
          kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, modes[0]->size().height() +
                                           DisplayConfigurator::kVerticalGap),
                         modes[1].get()})
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, modes[0]->size().height() +
                                           DisplayConfigurator::kVerticalGap),
                         modes[1].get()})
              .c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
}

// Tests that power state requests are saved after failed configuration attempts
// so they can be reused later: http://crosbug.com/p/31571
TEST_F(DisplayConfiguratorTest, SaveDisplayPowerStateOnConfigFailure) {
  // Start out with two displays in extended mode.
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  Init(false);
  configurator_.ForceInitialConfigure();
  log_->GetActionsAndClear();
  observer_.Reset();

  // Turn off the internal display, simulating docked mode.
  config_waiter_.Reset();
  configurator_.SetDisplayPower(
      chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
      DisplayConfigurator::kSetDisplayPowerNoFlags,
      config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(0, observer_.num_failures());
  log_->GetActionsAndClear();

  // Make all subsequent configuration requests fail and try to turn the
  // internal display back on.
  config_waiter_.Reset();
  native_display_delegate_->set_max_configurable_pixels(1);
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_FAILURE, config_waiter_.callback_result());
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(1, observer_.num_failures());
  log_->GetActionsAndClear();

  // Simulate the external display getting disconnected and check that the
  // internal display is turned on (i.e. DISPLAY_POWER_ALL_ON is used) rather
  // than the earlier DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON state.
  native_display_delegate_->set_max_configurable_pixels(0);
  UpdateOutputs(1, true);
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
}

// Tests that the SetDisplayPowerState() task posted by HandleResume() doesn't
// use a stale state if a new state is requested before it runs:
// http://crosbug.com/p/32393
TEST_F(DisplayConfiguratorTest, DontRestoreStalePowerStateAfterResume) {
  // Start out with two displays in mirrored mode.
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
  Init(false);
  configurator_.ForceInitialConfigure();
  log_->GetActionsAndClear();
  observer_.Reset();

  // Turn off the internal display, simulating docked mode.
  configurator_.SetDisplayPower(
      chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
      DisplayConfigurator::kSetDisplayPowerNoFlags,
      config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(0, observer_.num_failures());
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, nullptr, &big_mode_).c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, nullptr, &big_mode_).c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());

  // Suspend and resume the system. Resuming should restore the previous power
  // state and force a probe. Suspend should turn off the displays since an
  // external monitor is connected.
  config_waiter_.Reset();
  configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(2, observer_.num_changes());
  EXPECT_EQ(
      JoinActions(kTestModesetStr,
                  GetCrtcActions(DisplayConfig::kOff, nullptr, nullptr).c_str(),
                  kModesetOutcomeSuccess, kCommitModesetStr,
                  GetCrtcActions(DisplayConfig::kOff, nullptr, nullptr).c_str(),
                  kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());

  // Before the task runs, exit docked mode.
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(3, observer_.num_changes());
  EXPECT_EQ(0, observer_.num_failures());
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());

  // Check that the display states are not changed after resuming.
  config_waiter_.Reset();
  // Since we are in dual display mode, a configuration task is scheduled after
  // kMinLongDelayMs delay.
  configurator_.ResumeDisplays();
  EXPECT_EQ(kLongDelay, test_api_.GetConfigureDelay());
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON,
            configurator_.current_power_state());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
  // Now trigger that delayed configuration.
  EXPECT_EQ(kLongDelay, config_waiter_.Wait());
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kMirror, &small_mode_, &small_mode_)
              .c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, ExternalControl) {
  InitWithOutputs(&small_mode_);
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);

  // Set the initial power state and verify that it is restored when control is
  // taken.
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());

  configurator_.RelinquishControl(
      base::BindOnce(&DisplayConfiguratorTest::OnDisplayControlUpdated,
                     base::Unretained(this)));
  EXPECT_EQ(CALLBACK_SUCCESS, PopDisplayControlResult());
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(nullptr).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(nullptr).c_str(), kModesetOutcomeSuccess,
                        kRelinquishDisplayControl, nullptr),
            log_->GetActionsAndClear());
  configurator_.TakeControl(
      base::BindOnce(&DisplayConfiguratorTest::OnDisplayControlUpdated,
                     base::Unretained(this)));
  EXPECT_EQ(CALLBACK_SUCCESS, PopDisplayControlResult());
  EXPECT_EQ(
      JoinActions(kTakeDisplayControl, kTestModesetStr,
                  GetCrtcActions(&small_mode_).c_str(), kModesetOutcomeSuccess,
                  kCommitModesetStr, GetCrtcActions(&small_mode_).c_str(),
                  kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest,
       SetDisplayPowerWhilePendingConfigurationTaskRunning) {
  // Start out with two displays in extended mode.
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  Init(false);
  configurator_.ForceInitialConfigure();
  log_->GetActionsAndClear();
  observer_.Reset();

  native_display_delegate_->set_run_async(true);

  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(CALLBACK_NOT_CALLED, config_waiter_.callback_result());

  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());

  EXPECT_EQ(CALLBACK_NOT_CALLED, config_waiter_.callback_result());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(0, observer_.num_failures());

  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kOff, &small_mode_, &big_mode_).c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kOff, &small_mode_, &big_mode_).c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());

  config_waiter_.Reset();
  EXPECT_EQ(base::Milliseconds(DisplayConfigurator::kConfigureDelayMs),
            config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_NOT_CALLED, config_waiter_.callback_result());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(2, observer_.num_changes());
  EXPECT_EQ(0, observer_.num_failures());

  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest,
       SetDisplayPowerAfterFailedDisplayConfiguration) {
  // Start out with two displays in extended mode.
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  Init(false);
  configurator_.ForceInitialConfigure();
  log_->GetActionsAndClear();
  observer_.Reset();

  // Fail display configuration.
  native_display_delegate_->set_max_configurable_pixels(-1);

  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_FAILURE, config_waiter_.callback_result());
  EXPECT_EQ(0, observer_.num_changes());
  EXPECT_EQ(1, observer_.num_failures());

  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kOff, &small_mode_, &big_mode_).c_str(),
          kModesetOutcomeFailure, kTestModesetStr,
          GetCrtcActions(DisplayConfig::kOff, &small_mode_, &big_mode_).c_str(),
          kModesetOutcomeFailure, kTestModesetStr,
          GetCrtcActions(DisplayConfig::kOff, &small_mode_, &big_mode_).c_str(),
          kModesetOutcomeFailure, nullptr),
      log_->GetActionsAndClear());

  // This configuration should trigger a display configuration since the
  // previous configuration failed.
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());

  EXPECT_EQ(0, observer_.num_changes());
  EXPECT_EQ(2, observer_.num_failures());
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr, GetCrtcActions(&small_mode_, &big_mode_).c_str(),
          kModesetOutcomeFailure,
          // We first attempt to modeset the internal display with all
          // other displays disabled, which will fail.
          kTestModesetStr, GetCrtcActions(&small_mode_).c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, small_mode_.size().height() +
                                           DisplayConfigurator::kVerticalGap),
                         nullptr})
              .c_str(),
          kModesetOutcomeFailure,
          // Since internal displays are restricted to their preferred mode,
          // there are no other modes to try. Disable the internal display while
          // we attempt to modeset displays that are connected to other
          // connectors. Configuration will fail.
          kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, small_mode_.size().height() +
                                           DisplayConfigurator::kVerticalGap),
                         &big_mode_})
              .c_str(),
          kModesetOutcomeFailure, kTestModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0), nullptr})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, small_mode_.size().height() +
                                           DisplayConfigurator::kVerticalGap),
                         &small_mode_})
              .c_str(),
          kModesetOutcomeFailure, nullptr),
      log_->GetActionsAndClear());

  // Allow configuration to succeed.
  native_display_delegate_->set_max_configurable_pixels(0);

  // Validate that a configuration event has the proper power state (displays
  // should be on).
  configurator_.OnConfigurationChanged();
  EXPECT_TRUE(test_api_.TriggerConfigureTimeout());

  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(2, observer_.num_failures());

  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, TestWithThreeDisplays) {
  // Start out with two displays in extended mode.
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  Init(false);
  configurator_.ForceInitialConfigure();
  log_->GetActionsAndClear();
  observer_.Reset();

  UpdateOutputs(3, true);
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);

  EXPECT_EQ(JoinActions(
                kTestModesetStr,
                GetCrtcActions(&small_mode_, &big_mode_, &small_mode_).c_str(),
                kModesetOutcomeSuccess, kCommitModesetStr,
                GetCrtcActions(&small_mode_, &big_mode_, &small_mode_).c_str(),
                kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());

  // Verify that turning the power off works.
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcActions(DisplayConfig::kOff, &small_mode_,
                                       &big_mode_, &small_mode_)
                            .c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(DisplayConfig::kOff, &small_mode_,
                                       &big_mode_, &small_mode_)
                            .c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());

  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(JoinActions(
                kTestModesetStr,
                GetCrtcActions(&small_mode_, &big_mode_, &small_mode_).c_str(),
                kModesetOutcomeSuccess, kCommitModesetStr,
                GetCrtcActions(&small_mode_, &big_mode_, &small_mode_).c_str(),
                kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());

  // Disconnect the third output.
  observer_.Reset();
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  UpdateOutputs(2, true);
  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
}

// Tests the suspend and resume behavior when in dual or multi display modes.
TEST_F(DisplayConfiguratorTest, SuspendResumeWithMultipleDisplays) {
  InitWithOutputs(&small_mode_);

  // Set the initial power state and verify that it is restored on resume.
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());

  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  observer_.Reset();
  UpdateOutputs(2, true);
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());
  EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON,
            configurator_.current_power_state());

  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());

  // Suspending displays should result in an immediate configuration without
  // delays, even in dual display mode.
  config_waiter_.Reset();
  configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_OFF,
            configurator_.current_power_state());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kOff, &small_mode_, &big_mode_).c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kOff, &small_mode_, &big_mode_).c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());

  // Resuming from suspend with dual displays. Configuration should be done
  // after a long delay. Afterwards, we should still expect to be in a dual
  // display mode.
  config_waiter_.Reset();
  configurator_.ResumeDisplays();
  EXPECT_EQ(kLongDelay, config_waiter_.Wait());
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON,
            configurator_.current_power_state());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());
  EXPECT_EQ(JoinActions(kTestModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_, &big_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());

  // Suspend displays and disconnect one of them while in suspend.
  config_waiter_.Reset();
  configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_OFF,
            configurator_.current_power_state());
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr,
          GetCrtcActions(DisplayConfig::kOff, &small_mode_, &big_mode_).c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr,
          GetCrtcActions(DisplayConfig::kOff, &small_mode_, &big_mode_).c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
  UpdateOutputs(1, false);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  // Now resume, and expect that we'll still have a long delay since we were in
  // dual mode before suspend. The configurator should pick up the change and
  // detect that we are in single display mode now.
  config_waiter_.Reset();
  configurator_.ResumeDisplays();
  EXPECT_EQ(kLongDelay, config_waiter_.Wait());
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON,
            configurator_.current_power_state());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_SINGLE, configurator_.display_state());
  EXPECT_EQ(JoinActions(kTestModesetStr, GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        GetCrtcActions(&small_mode_).c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());

  // Verify that the above is the exact same behavior for 3+ displays.
  UpdateOutputs(3, true);
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());

  // Suspend.
  config_waiter_.Reset();
  configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_OFF,
            configurator_.current_power_state());

  // Resume and expect the correct delay.
  config_waiter_.Reset();
  configurator_.ResumeDisplays();
  EXPECT_EQ(kLongDelay, config_waiter_.Wait());
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON,
            configurator_.current_power_state());
  EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
            configurator_.display_state());
}

TEST_F(DisplayConfiguratorTest, PowerStateChange) {
  InitWithOutputs(&small_mode_);

  native_display_delegate_->set_run_async(true);

  // Set the initial power state and verify that it is restored on resume.
  config_waiter_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                DisplayConfigurator::kSetDisplayPowerNoFlags,
                                config_waiter_.on_configuration_callback());

  // SuspendDisplays causes notifying the DISPLAY_POWER_ALL_OFF state to the
  // observer.
  config_waiter_.Reset();
  observer_.Reset();
  configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  EXPECT_EQ(1, observer_.num_power_state_changes());
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_OFF, observer_.latest_power_state());

  // ResumeDisplays causes notifying the DISPLAY_POWER_ALL_ON state to the
  // observer.
  config_waiter_.Reset();
  configurator_.ResumeDisplays();
  EXPECT_EQ(base::TimeDelta::Max(), config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_NOT_CALLED, config_waiter_.callback_result());
  EXPECT_EQ(2, observer_.num_power_state_changes());
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON, observer_.latest_power_state());

  // SuspendDisplays and ResumeDisplays before running the configuration task
  // causes notifying only one power state change to the observer.
  config_waiter_.Reset();
  observer_.Reset();
  configurator_.SuspendDisplays(config_waiter_.on_configuration_callback());
  EXPECT_EQ(0, observer_.num_power_state_changes());
  configurator_.ResumeDisplays();
  // Run the task posted by TestNativeDisplayDelegate::GetDisplays() which is
  // called by SuspendDisplays().
  EXPECT_EQ(kNoDelay, config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_SUCCESS, config_waiter_.callback_result());
  config_waiter_.Reset();
  // Run the task posted by OnConfigured().
  EXPECT_EQ(base::Milliseconds(DisplayConfigurator::kConfigureDelayMs),
            config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_NOT_CALLED, config_waiter_.callback_result());
  config_waiter_.Reset();
  // Run the task posted by TestNativeDisplayDelegate::GetDisplays().
  EXPECT_EQ(base::TimeDelta::Max(), config_waiter_.Wait());
  EXPECT_EQ(CALLBACK_NOT_CALLED, config_waiter_.callback_result());
  EXPECT_EQ(1, observer_.num_power_state_changes());
  EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_ON, observer_.latest_power_state());
}

TEST_F(DisplayConfiguratorTest, RefreshRateThrottle_SingleDisplay) {
  InitWithOutputs(&small_mode_);
  // Set up display with HRR native mode and eligible throttle candidate mode.
  std::vector<std::unique_ptr<const DisplayMode>> modes;
  modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
  modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
  UpdateOutputs(1, true);
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  log_->GetActionsAndClear();
  observer_.Reset();

  // Set throttle state noop.
  configurator_.SetRefreshRateOverrides({});
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(0, observer_.num_changes());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  // Set throttle state enabled.
  configurator_.SetRefreshRateOverrides(
      {std::make_pair(GetOutput(0)->display_id(), 60.f)});
  EXPECT_EQ(60.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(JoinActions(kTestModesetStr, kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[1].get(), false})
                            .c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[1].get(), false})
                            .c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  observer_.Reset();

  // Set throttle state disabled.
  configurator_.SetRefreshRateOverrides({});
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(JoinActions(kTestModesetStr, kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[0].get()})
                            .c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[0].get()})
                            .c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, RefreshRateThrottle_AlreadyThrottled) {
  InitWithOutputs(&small_mode_);
  // Set up display with HRR native mode and eligible throttle candidate mode.
  std::vector<std::unique_ptr<const DisplayMode>> modes;
  modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
  modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
  UpdateOutputs(1, true);
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  log_->GetActionsAndClear();
  observer_.Reset();

  // Set throttle state enabled.
  configurator_.SetRefreshRateOverrides(
      {std::make_pair(GetOutput(0)->display_id(), 60.f)});
  EXPECT_EQ(60.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(JoinActions(kTestModesetStr, kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[1].get(), false})
                            .c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[1].get(), false})
                            .c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  observer_.Reset();

  // Set the same throttle state. This should be a no-op.
  configurator_.SetRefreshRateOverrides(
      {std::make_pair(GetOutput(0)->display_id(), 60.f)});
  EXPECT_EQ(60.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(0, observer_.num_changes());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, RefreshRateThrottle_OverrideWithNative) {
  InitWithOutputs(&small_mode_);
  // Set up display with HRR native mode and eligible throttle candidate mode.
  std::vector<std::unique_ptr<const DisplayMode>> modes;
  modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
  modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
  UpdateOutputs(1, true);
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  log_->GetActionsAndClear();
  observer_.Reset();

  // Set throttle state enabled.
  configurator_.SetRefreshRateOverrides(
      {std::make_pair(GetOutput(0)->display_id(), 60.f)});
  EXPECT_EQ(60.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(JoinActions(kTestModesetStr, kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[1].get(), false})
                            .c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[1].get(), false})
                            .c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  observer_.Reset();

  // Specifying the native mode's refresh rate as an override.
  configurator_.SetRefreshRateOverrides(
      {std::make_pair(GetOutput(0)->display_id(), 120.f)});
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());

  // No override should be set.
  EXPECT_EQ(JoinActions(kTestModesetStr, kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[0].get(), false})
                            .c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[0].get(), false})
                            .c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  observer_.Reset();
}

TEST_F(DisplayConfiguratorTest, RefreshRateThrottle_WrongDisplayId) {
  InitWithOutputs(&small_mode_);
  // Set up display with HRR native mode and eligible throttle candidate mode.
  std::vector<std::unique_ptr<const DisplayMode>> modes;
  modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
  modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
  UpdateOutputs(1, true);
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  log_->GetActionsAndClear();
  observer_.Reset();

  // Set throttle state for wrong display id.
  configurator_.SetRefreshRateOverrides(
      {std::make_pair(GetOutput(0)->display_id() + 100, 60.f)});
  EXPECT_EQ(0, observer_.num_changes());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, RefreshRateThrottle_MultipleDisplays) {
  InitWithOutputs(&small_mode_, &big_mode_);
  // Set up each display with HRR native mode and eligible throttle candidate
  // mode.
  std::vector<std::unique_ptr<const DisplayMode>> modes;
  modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
  modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());
  // External display should never be throttled irregardless of its modes.
  SetOutput(1, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[1])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                   .SetBaseConnectorId(kSecondConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  UpdateOutputs(2, true);
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(120.0f, GetOutput(1)->current_mode()->refresh_rate());
  log_->GetActionsAndClear();
  observer_.Reset();

  // Set refresh rate override noop.
  configurator_.SetRefreshRateOverrides({});
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(120.0f, GetOutput(1)->current_mode()->refresh_rate());
  EXPECT_EQ(0, observer_.num_changes());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  // Set refresh rate override for internal display.
  configurator_.SetRefreshRateOverrides(
      {std::make_pair(GetOutput(0)->display_id(), 60.f)});
  EXPECT_EQ(60.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(120.0f, GetOutput(1)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());
  int vertical_offset = GetOutput(0)->native_mode()->size().height() +
                        DisplayConfigurator::kVerticalGap;
  EXPECT_EQ(JoinActions(
                kTestModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[1].get()})
                    .c_str(),
                GetCrtcAction({GetOutput(1)->display_id(),
                               gfx::Point(0, vertical_offset), modes[0].get()})
                    .c_str(),
                kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[1].get()})
                    .c_str(),
                GetCrtcAction({GetOutput(1)->display_id(),
                               gfx::Point(0, vertical_offset), modes[0].get()})
                    .c_str(),
                kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  observer_.Reset();
  observer_.Reset();

  // Remove refresh rate overrides.
  configurator_.SetRefreshRateOverrides({});
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(120.0f, GetOutput(1)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(JoinActions(
                kTestModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[0].get()})
                    .c_str(),
                GetCrtcAction({GetOutput(1)->display_id(),
                               gfx::Point(0, vertical_offset), modes[0].get()})
                    .c_str(),
                kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[0].get()})
                    .c_str(),
                GetCrtcAction({GetOutput(1)->display_id(),
                               gfx::Point(0, vertical_offset), modes[0].get()})
                    .c_str(),
                kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, RefreshRateThrottle_RaceWithDockMode) {
  InitWithOutputs(&small_mode_, &big_mode_);
  // Set up two displays with HRR native mode and eligible throttle candidate
  // mode.
  std::vector<std::unique_ptr<const DisplayMode>> modes;
  modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
  modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());
  SetOutput(1, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[1])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                   .SetBaseConnectorId(kSecondConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());
  UpdateOutputs(2, true);
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(120.0f, GetOutput(1)->current_mode()->refresh_rate());
  log_->GetActionsAndClear();
  observer_.Reset();

  // Get DisplayConfigureRequests for a configuration that simultaneously
  // attempts to enable refresh rate throttling, and docked mode.
  std::vector<DisplayConfigureRequest> requests;
  test_api_.GetDisplayLayoutManager()->GetDisplayLayout(
      native_display_delegate_->GetOutputs(), MULTIPLE_DISPLAY_STATE_SINGLE,
      chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
      /*new_vrr_enabled_state=*/{}, &requests);

  bool has_internal_request = false;
  for (auto& request: requests) {
    if (request.display->type() == DISPLAY_CONNECTION_TYPE_INTERNAL) {
        has_internal_request = true;
        EXPECT_EQ(request.mode, nullptr);
    }
  }
  EXPECT_TRUE(has_internal_request);
}

TEST_F(DisplayConfiguratorTest,
       RefreshRateThrottle_StaysThrottledForSeamlessConfig) {
  InitWithOutputs(&small_mode_);
  // Set up display with HRR native mode, eligible throttle candidate mode, and
  // VRR.
  std::vector<std::unique_ptr<const DisplayMode>> modes;
  modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
  modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .SetVariableRefreshRateState(
                       VariableRefreshRateState::kVrrDisabled)
                   .Build());
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
  UpdateOutputs(1, true);
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  log_->GetActionsAndClear();
  observer_.Reset();

  // Set throttle state enabled.
  configurator_.SetRefreshRateOverrides(
      {std::make_pair(GetOutput(0)->display_id(), 60.f)});

  EXPECT_EQ(60.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(JoinActions(kTestModesetStr, kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[1].get()})
                            .c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[1].get()})
                            .c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  observer_.Reset();

  // Set VRR enabled. VRR should be enabled, and downclock mode should be used.
  configurator_.SetVrrEnabled({GetOutput(0)->display_id()});
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_TRUE(GetOutput(0)->IsVrrEnabled());
  EXPECT_EQ(JoinActions(
                kTestModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[1].get(), /*enable_vrr=*/true})
                    .c_str(),
                kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[1].get(), /*enable_vrr=*/true})
                    .c_str(),
                kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  observer_.Reset();

  // Set throttle state disabled.
  configurator_.SetRefreshRateOverrides({});

  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_TRUE(GetOutput(0)->IsVrrEnabled());
  EXPECT_EQ(JoinActions(
                kTestModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[0].get(), /*enable_vrr=*/true})
                    .c_str(),
                kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[0].get(), /*enable_vrr=*/true})
                    .c_str(),
                kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  observer_.Reset();
}

TEST_F(DisplayConfiguratorTest,
       RefreshRateThrottle_ResetThrottleForFullConfig) {
  InitWithOutputs(&small_mode_);
  // Set up display with HRR native mode, eligible throttle candidate mode.
  std::vector<std::unique_ptr<const DisplayMode>> modes;
  modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
  modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .Build());
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
  UpdateOutputs(1, true);
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  log_->GetActionsAndClear();
  observer_.Reset();

  // Set throttle state enabled.
  configurator_.SetRefreshRateOverrides(
      {std::make_pair(GetOutput(0)->display_id(), 60.f)});

  EXPECT_EQ(60.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(JoinActions(kTestModesetStr, kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[1].get()})
                            .c_str(),
                        kModesetOutcomeSuccess, kCommitModesetStr,
                        kSeamlessModesetStr,
                        GetCrtcAction({GetOutput(0)->display_id(),
                                       gfx::Point(0, 0), modes[1].get()})
                            .c_str(),
                        kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  observer_.Reset();

  // Plug in new display.
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  UpdateOutputs(2, true);

  // Configuration should use preferred mode rather than throttled mode.
  int vertical_offset = GetOutput(0)->native_mode()->size().height() +
                        DisplayConfigurator::kVerticalGap;
  EXPECT_EQ(
      JoinActions(kTestModesetStr,
                  GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                                 modes[0].get()})
                      .c_str(),
                  GetCrtcAction({GetOutput(1)->display_id(),
                                 gfx::Point(0, vertical_offset), &big_mode_})
                      .c_str(),
                  kModesetOutcomeSuccess, kCommitModesetStr,
                  GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                                 modes[0].get()})
                      .c_str(),
                  GetCrtcAction({GetOutput(1)->display_id(),
                                 gfx::Point(0, vertical_offset), &big_mode_})
                      .c_str(),
                  kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, SetVrrEnabled) {
  InitWithOutputs(&small_mode_);
  UpdateOutputs(2, true);
  EXPECT_FALSE(GetOutput(0)->IsVrrEnabled());
  EXPECT_FALSE(GetOutput(1)->IsVrrEnabled());
  log_->GetActionsAndClear();
  observer_.Reset();

  // Set VRR noop.
  configurator_.SetVrrEnabled({});
  EXPECT_EQ(0, observer_.num_changes());
  EXPECT_FALSE(GetOutput(0)->IsVrrEnabled());
  EXPECT_FALSE(GetOutput(1)->IsVrrEnabled());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  // Set VRR enabled.
  configurator_.SetVrrEnabled(
      {GetOutput(0)->display_id(), GetOutput(1)->display_id()});
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_TRUE(GetOutput(0)->IsVrrEnabled());
  EXPECT_FALSE(GetOutput(1)->IsVrrEnabled());
  int vertical_offset = GetOutput(0)->native_mode()->size().height() +
                        DisplayConfigurator::kVerticalGap;
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr, kSeamlessModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                         GetOutput(0)->native_mode(), /*enable_vrr=*/true})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, vertical_offset),
                         GetOutput(1)->native_mode(), /*enable_vrr=*/false})
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                         GetOutput(0)->native_mode(), /*enable_vrr=*/true})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, vertical_offset),
                         GetOutput(1)->native_mode(), /*enable_vrr=*/false})
              .c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
  observer_.Reset();

  // Set VRR disabled.
  configurator_.SetVrrEnabled({});
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_FALSE(GetOutput(0)->IsVrrEnabled());
  EXPECT_FALSE(GetOutput(1)->IsVrrEnabled());
  EXPECT_EQ(
      JoinActions(
          kTestModesetStr, kSeamlessModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                         GetOutput(0)->native_mode(), /*enable_vrr=*/false})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, vertical_offset),
                         GetOutput(1)->native_mode(), /*enable_vrr=*/false})
              .c_str(),
          kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
          GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                         GetOutput(0)->native_mode(), /*enable_vrr=*/false})
              .c_str(),
          GetCrtcAction({GetOutput(1)->display_id(),
                         gfx::Point(0, vertical_offset),
                         GetOutput(1)->native_mode(), /*enable_vrr=*/false})
              .c_str(),
          kModesetOutcomeSuccess, nullptr),
      log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, SetVrrEnabled_NotCapable) {
  std::unique_ptr<DisplaySnapshot> output = GetOutput(0)->Clone();
  output->set_variable_refresh_rate_state(
      VariableRefreshRateState::kVrrNotCapable);
  SetOutput(0, std::move(output));
  InitWithOutputs(&small_mode_);
  UpdateOutputs(2, true);
  EXPECT_FALSE(GetOutput(0)->IsVrrEnabled());
  EXPECT_FALSE(GetOutput(1)->IsVrrEnabled());
  log_->GetActionsAndClear();
  observer_.Reset();

  configurator_.SetVrrEnabled(
      {GetOutput(0)->display_id(), GetOutput(1)->display_id()});
  EXPECT_EQ(0, observer_.num_changes());
  EXPECT_FALSE(GetOutput(0)->IsVrrEnabled());
  EXPECT_FALSE(GetOutput(1)->IsVrrEnabled());
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest, RefreshRateThrottle_VrrEnabled) {
  InitWithOutputs(&small_mode_);
  // Set up display with HRR native mode and eligible throttle candidate mode.
  std::vector<std::unique_ptr<const DisplayMode>> modes;
  modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
  modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .SetVariableRefreshRateState(
                       VariableRefreshRateState::kVrrDisabled)
                   .Build());
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
  UpdateOutputs(1, true);
  // Enable VRR on internal display.
  configurator_.SetVrrEnabled({GetOutput(0)->display_id()});
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_TRUE(GetOutput(0)->IsVrrEnabled());
  log_->GetActionsAndClear();
  observer_.Reset();

  // Set throttle state enabled.
  configurator_.SetRefreshRateOverrides(
      {std::make_pair(GetOutput(0)->display_id(), 60.f)});

  EXPECT_EQ(60.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());
  // Throttling should be unaffected by the internal display VRR state and still
  // result in seamless modesets.
  EXPECT_EQ(JoinActions(
                kTestModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[1].get(), /*enable_vrr=*/true})
                    .c_str(),
                kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[1].get(), /*enable_vrr=*/true})
                    .c_str(),
                kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  observer_.Reset();

  // Set throttle state disabled.
  configurator_.SetRefreshRateOverrides({});

  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());
  // Unthrottling should be unaffected by the internal display VRR state and
  // still result in seamless modesets.
  EXPECT_EQ(JoinActions(
                kTestModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[0].get(), /*enable_vrr=*/true})
                    .c_str(),
                kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[0].get(), /*enable_vrr=*/true})
                    .c_str(),
                kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
}

TEST_F(DisplayConfiguratorTest,
       RefreshRateThrottle_VrrEnabledOnExternalDisplay) {
  InitWithOutputs(&small_mode_, &big_mode_);
  // Set up each display with HRR native mode and eligible throttle candidate
  // mode.
  std::vector<std::unique_ptr<const DisplayMode>> modes;
  modes.push_back(MakeDisplayMode(1366, 768, false, 120.0));
  modes.push_back(MakeDisplayMode(1366, 768, false, 60.0));
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetNativeMode(modes[0]->Clone())
                   .SetCurrentMode(modes[0]->Clone())
                   .AddMode(modes[1]->Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetBaseConnectorId(kEdpConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .SetVariableRefreshRateState(
                       VariableRefreshRateState::kVrrNotCapable)
                   .Build());
  SetOutput(1, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[1])
                   .SetNativeMode(big_mode_.Clone())
                   .SetCurrentMode(big_mode_.Clone())
                   .AddMode(small_mode_.Clone())
                   .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                   .SetBaseConnectorId(kSecondConnectorId)
                   .SetIsAspectPreservingScaling(true)
                   .SetVariableRefreshRateState(
                       VariableRefreshRateState::kVrrDisabled)
                   .Build());
  state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
  UpdateOutputs(2, true);
  // Enable VRR when only the external display is VRR-capable.
  configurator_.SetVrrEnabled(
      {GetOutput(0)->display_id(), GetOutput(1)->display_id()});
  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(60.0f, GetOutput(1)->current_mode()->refresh_rate());
  EXPECT_FALSE(GetOutput(0)->IsVrrEnabled());
  EXPECT_TRUE(GetOutput(1)->IsVrrEnabled());
  log_->GetActionsAndClear();
  observer_.Reset();

  // Set throttle state enabled.
  configurator_.SetRefreshRateOverrides(
      {std::make_pair(GetOutput(0)->display_id(), 60.f)});

  EXPECT_EQ(60.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(60.0f, GetOutput(1)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());
  int vertical_offset = GetOutput(0)->native_mode()->size().height() +
                        DisplayConfigurator::kVerticalGap;
  // Throttling should be unaffected by the external display VRR state and still
  // result in seamless modesets.
  EXPECT_EQ(JoinActions(
                kTestModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[1].get(), /*enable_vrr=*/false})
                    .c_str(),
                GetCrtcAction({GetOutput(1)->display_id(),
                               gfx::Point(0, vertical_offset), &big_mode_,
                               /*enable_vrr=*/true})
                    .c_str(),
                kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[1].get(), /*enable_vrr=*/false})
                    .c_str(),
                GetCrtcAction({GetOutput(1)->display_id(),
                               gfx::Point(0, vertical_offset), &big_mode_,
                               /*enable_vrr=*/true})
                    .c_str(),
                kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
  observer_.Reset();

  // Set throttle state disabled.
  configurator_.SetRefreshRateOverrides({});

  EXPECT_EQ(120.0f, GetOutput(0)->current_mode()->refresh_rate());
  EXPECT_EQ(60.0f, GetOutput(1)->current_mode()->refresh_rate());
  EXPECT_EQ(1, observer_.num_changes());
  // Unthrottling should be unaffected by the external display VRR state and
  // still result in seamless modesets.
  EXPECT_EQ(JoinActions(
                kTestModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[0].get(), /*enable_vrr=*/false})
                    .c_str(),
                GetCrtcAction({GetOutput(1)->display_id(),
                               gfx::Point(0, vertical_offset), &big_mode_,
                               /*enable_vrr=*/true})
                    .c_str(),
                kModesetOutcomeSuccess, kCommitModesetStr, kSeamlessModesetStr,
                GetCrtcAction({GetOutput(0)->display_id(), gfx::Point(0, 0),
                               modes[0].get(), /*enable_vrr=*/false})
                    .c_str(),
                GetCrtcAction({GetOutput(1)->display_id(),
                               gfx::Point(0, vertical_offset), &big_mode_,
                               /*enable_vrr=*/true})
                    .c_str(),
                kModesetOutcomeSuccess, nullptr),
            log_->GetActionsAndClear());
}

class DisplayConfiguratorMultiMirroringTest : public DisplayConfiguratorTest {
 public:
  DisplayConfiguratorMultiMirroringTest() = default;

  DisplayConfiguratorMultiMirroringTest(
      const DisplayConfiguratorMultiMirroringTest&) = delete;
  DisplayConfiguratorMultiMirroringTest& operator=(
      const DisplayConfiguratorMultiMirroringTest&) = delete;

  ~DisplayConfiguratorMultiMirroringTest() override = default;

  void SetUp() override { DisplayConfiguratorTest::SetUp(); }

  // Test that setting mirror mode with current outputs, all displays are set to
  // expected mirror mode.
  void TestHardwareMirrorModeExist(
      std::unique_ptr<DisplayMode> expected_mirror_mode) {
    UpdateOutputs(3, true);
    log_->GetActionsAndClear();
    observer_.Reset();
    configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
    EXPECT_EQ(
        JoinActions(kTestModesetStr,
                    GetCrtcActions(
                        DisplayConfig::kMirror, expected_mirror_mode.get(),
                        expected_mirror_mode.get(), expected_mirror_mode.get())
                        .c_str(),
                    kModesetOutcomeSuccess, kCommitModesetStr,
                    GetCrtcActions(
                        DisplayConfig::kMirror, expected_mirror_mode.get(),
                        expected_mirror_mode.get(), expected_mirror_mode.get())
                        .c_str(),
                    kModesetOutcomeSuccess, nullptr),
        log_->GetActionsAndClear());
    EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
    EXPECT_EQ(1, observer_.num_changes());
  }

  // Test that setting mirror mode with current outputs, no matching mirror mode
  // is found.
  void TestHardwareMirrorModeNotExist() {
    UpdateOutputs(3, true);
    log_->GetActionsAndClear();
    observer_.Reset();
    configurator_.SetMultipleDisplayState(MULTIPLE_DISPLAY_STATE_MULTI_MIRROR);
    EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
    EXPECT_TRUE(mirroring_controller_.SoftwareMirroringEnabled());
    EXPECT_EQ(1, observer_.num_changes());
  }
};

TEST_F(DisplayConfiguratorMultiMirroringTest,
       FindMirrorModeWithInternalDisplay) {
  // Initialize with one internal display and two external displays.
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                   .SetNativeMode(MakeDisplayMode(1920, 1600, false, 60.0))
                   .AddMode(MakeDisplayMode(1920, 1600, false, 60.0))
                   .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))
                   .AddMode(MakeDisplayMode(1920, 1080, true, 60.0))
                   .AddMode(MakeDisplayMode(1440, 900, true, 60.0))
                   .Build());
  SetOutput(1,
            FakeDisplaySnapshot::Builder()
                .SetId(kDisplayIds[1])
                .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                .SetNativeMode(MakeDisplayMode(1920, 1200, true, 60.0))
                .AddMode(MakeDisplayMode(1920, 1200, true, 60.0))  // same AR
                .AddMode(MakeDisplayMode(1920, 1080, true, 60.0))
                .AddMode(MakeDisplayMode(1680, 1050, false, 60.0))  // same AR
                .AddMode(MakeDisplayMode(1440, 900, true, 60.0))    // same AR
                .AddMode(MakeDisplayMode(500, 500, false, 60.0))
                .Build());
  SetOutput(2,
            FakeDisplaySnapshot::Builder()
                .SetId(kDisplayIds[2])
                .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                .SetNativeMode(MakeDisplayMode(1920, 1200, false, 60.0))
                .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))  // same AR
                .AddMode(MakeDisplayMode(1920, 1080, true, 60.0))
                .AddMode(MakeDisplayMode(1680, 1050, false, 60.0))  // same AR
                .AddMode(MakeDisplayMode(1440, 900, true, 60.0))    // same AR
                .Build());

  // Find an exactly matching mirror mode while preserving aspect.
  TestHardwareMirrorModeExist(MakeDisplayMode(1440, 900, true, 60.0));

  // Find an exactly matching mirror mode while not preserving aspect.
  SetOutput(2,
            FakeDisplaySnapshot::Builder()
                .SetId(kDisplayIds[2])
                .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                .SetNativeMode(MakeDisplayMode(1920, 1200, false, 60.0))
                .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))  // same AR
                .AddMode(MakeDisplayMode(1920, 1080, true, 60.0))
                .Build());
  TestHardwareMirrorModeExist(MakeDisplayMode(1920, 1080, true, 60.0));

  // Cannot find a matching mirror mode, so enable software mirroring.
  SetOutput(2,
            FakeDisplaySnapshot::Builder()
                .SetId(kDisplayIds[2])
                .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                .SetNativeMode(MakeDisplayMode(1920, 1200, false, 60.0))
                .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))  // same AR
                .AddMode(MakeDisplayMode(500, 500, true, 60.0))
                .Build());
  TestHardwareMirrorModeNotExist();
}

TEST_F(DisplayConfiguratorMultiMirroringTest,
       FindMirrorModeWithoutInternalDisplay) {
  // Initialize with 3 external displays.
  SetOutput(0, FakeDisplaySnapshot::Builder()
                   .SetId(kDisplayIds[0])
                   .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                   .SetNativeMode(MakeDisplayMode(1920, 1200, true, 60.0))
                   .AddMode(MakeDisplayMode(1920, 1200, true, 60.0))  // same AR
                   .AddMode(MakeDisplayMode(1920, 1080, false, 60.0))
                   .AddMode(MakeDisplayMode(1680, 1050, true, 60.0))  // same AR
                   .Build());
  SetOutput(1,
            FakeDisplaySnapshot::Builder()
                .SetId(kDisplayIds[1])
                .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                .SetNativeMode(MakeDisplayMode(1920, 1200, false, 60.0))
                .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))  // same AR
                .AddMode(MakeDisplayMode(1920, 1080, false, 60.0))
                .AddMode(MakeDisplayMode(1680, 1050, true, 60.0))  // same AR
                .Build());
  SetOutput(2,
            FakeDisplaySnapshot::Builder()
                .SetId(kDisplayIds[2])
                .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                .SetNativeMode(MakeDisplayMode(1920, 1200, false, 60.0))
                .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))  // same AR
                .AddMode(MakeDisplayMode(1920, 1080, false, 60.0))
                .AddMode(MakeDisplayMode(1680, 1050, true, 60.0))  // same AR
                .Build());

  // Find an exactly matching mirror mode while preserving aspect.
  TestHardwareMirrorModeExist(MakeDisplayMode(1680, 1050, true, 60.0));

  // Find an exactly matching mirror mode while not preserving aspect.
  SetOutput(2,
            FakeDisplaySnapshot::Builder()
                .SetId(kDisplayIds[2])
                .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                .SetNativeMode(MakeDisplayMode(1920, 1600, false, 60.0))
                .AddMode(MakeDisplayMode(1920, 1600, false, 60.0))  // same AR
                .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))
                .AddMode(MakeDisplayMode(1920, 1080, false, 60.0))
                .Build());
  TestHardwareMirrorModeExist(MakeDisplayMode(1920, 1080, false, 60.0));

  // Cannot find a matching mirror mode, so enable software mirroring.
  SetOutput(2,
            FakeDisplaySnapshot::Builder()
                .SetId(kDisplayIds[2])
                .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                .SetNativeMode(MakeDisplayMode(1920, 1600, false, 60.0))
                .AddMode(MakeDisplayMode(1920, 1600, false, 60.0))  // same AR
                .AddMode(MakeDisplayMode(1920, 1200, false, 60.0))
                .Build());
  TestHardwareMirrorModeNotExist();
}

}  // namespace display::test