blob: 61692539c2b7bb093b1d0027b174e0088865d2ca [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/display/chromeos/display_configurator.h"
#include <stddef.h>
#include <stdint.h>
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/chromeos/test/action_logger_util.h"
#include "ui/display/chromeos/test/test_native_display_delegate.h"
#include "ui/display/fake_display_snapshot.h"
#include "ui/display/util/display_util.h"
namespace ui {
namespace test {
namespace {
int64_t kDisplayIds[3] = {123, 456, 789};
std::unique_ptr<ui::DisplayMode> MakeDisplayMode(int width,
int height,
bool is_interlaced,
float refresh_rate) {
return base::MakeUnique<ui::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::TimeDelta::FromMilliseconds(0);
// The expected configuration delay when resuming from suspend while in 2+
// display mode.
constexpr base::TimeDelta kLongDelay = base::TimeDelta::FromMilliseconds(
DisplayConfigurator::kResumeConfigureMultiDisplayDelayMs);
class TestObserver : public DisplayConfigurator::Observer {
public:
explicit TestObserver(DisplayConfigurator* configurator)
: configurator_(configurator) {
Reset();
configurator_->AddObserver(this);
}
~TestObserver() override { configurator_->RemoveObserver(this); }
int num_changes() const { return num_changes_; }
int num_failures() const { return num_failures_; }
const DisplayConfigurator::DisplayStateList& latest_outputs() const {
return latest_outputs_;
}
MultipleDisplayState latest_failed_state() const {
return latest_failed_state_;
}
void Reset() {
num_changes_ = 0;
num_failures_ = 0;
latest_outputs_.clear();
latest_failed_state_ = MULTIPLE_DISPLAY_STATE_INVALID;
}
// DisplayConfigurator::Observer overrides:
void OnDisplayModeChanged(
const DisplayConfigurator::DisplayStateList& outputs) override {
num_changes_++;
latest_outputs_ = outputs;
}
void OnDisplayModeChangeFailed(
const DisplayConfigurator::DisplayStateList& outputs,
MultipleDisplayState failed_new_state) override {
num_failures_++;
latest_failed_state_ = failed_new_state;
}
private:
DisplayConfigurator* configurator_; // Not owned.
// Number of times that OnDisplayMode*() has been called.
int num_changes_;
int num_failures_;
// Parameters most recently passed to OnDisplayMode*().
DisplayConfigurator::DisplayStateList latest_outputs_;
MultipleDisplayState latest_failed_state_;
DISALLOW_COPY_AND_ASSIGN(TestObserver);
};
class TestStateController : public DisplayConfigurator::StateController {
public:
TestStateController() : state_(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED) {}
~TestStateController() override {}
void set_state(MultipleDisplayState state) { state_ = state; }
// DisplayConfigurator::StateController overrides:
MultipleDisplayState GetStateForDisplayIds(
const DisplayConfigurator::DisplayStateList& outputs) const override {
return state_;
}
bool GetResolutionForDisplayId(int64_t display_id,
gfx::Size* size) const override {
return false;
}
private:
MultipleDisplayState state_;
DISALLOW_COPY_AND_ASSIGN(TestStateController);
};
class TestMirroringController
: public DisplayConfigurator::SoftwareMirroringController {
public:
TestMirroringController() : software_mirroring_enabled_(false) {}
~TestMirroringController() override {}
void SetSoftwareMirroring(bool enabled) override {
software_mirroring_enabled_ = enabled;
}
bool SoftwareMirroringEnabled() const override {
return software_mirroring_enabled_;
}
private:
bool software_mirroring_enabled_;
DISALLOW_COPY_AND_ASSIGN(TestMirroringController);
};
// Abstracts waiting for the display configuration to be completed and getting
// the time it took to complete.
class ConfigurationWaiter {
public:
ConfigurationWaiter(DisplayConfigurator::TestApi* test_api)
: on_configured_callback_(base::Bind(&ConfigurationWaiter::OnConfigured,
base::Unretained(this))),
test_api_(test_api),
callback_result_(CALLBACK_NOT_CALLED) {}
~ConfigurationWaiter() = default;
const DisplayConfigurator::ConfigurationCallback& on_configuration_callback()
const {
return on_configured_callback_;
}
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().
base::TimeDelta Wait() WARN_UNUSED_RESULT {
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;
}
// Passed with configuration requests to run OnConfigured().
const DisplayConfigurator::ConfigurationCallback on_configured_callback_;
DisplayConfigurator::TestApi* test_api_; // Not owned.
// The status of the display configuration.
CallbackResult callback_result_;
DISALLOW_COPY_AND_ASSIGN(ConfigurationWaiter);
};
class DisplayConfiguratorTest : public testing::Test {
public:
DisplayConfiguratorTest()
: small_mode_(gfx::Size(1366, 768), false, 60.0f),
big_mode_(gfx::Size(2560, 1600), false, 60.0f),
observer_(&configurator_),
test_api_(&configurator_),
config_waiter_(&test_api_),
enable_content_protection_status_(0),
enable_content_protection_call_count_(0),
query_content_protection_call_count_(0),
display_control_result_(CALLBACK_NOT_CALLED) {}
~DisplayConfiguratorTest() override {}
void SetUp() override {
log_.reset(new ActionLogger());
native_display_delegate_ = new TestNativeDisplayDelegate(log_.get());
configurator_.SetDelegateForTesting(
std::unique_ptr<NativeDisplayDelegate>(native_display_delegate_));
configurator_.set_state_controller(&state_controller_);
configurator_.set_mirroring_controller(&mirroring_controller_);
outputs_[0] = display::FakeDisplaySnapshot::Builder()
.SetId(kDisplayIds[0])
.SetNativeMode(small_mode_.Clone())
.SetCurrentMode(small_mode_.Clone())
.SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
.SetIsAspectPerservingScaling(true)
.Build();
outputs_[1] = display::FakeDisplaySnapshot::Builder()
.SetId(kDisplayIds[1])
.SetNativeMode(big_mode_.Clone())
.SetCurrentMode(big_mode_.Clone())
.AddMode(small_mode_.Clone())
.SetType(DISPLAY_CONNECTION_TYPE_HDMI)
.SetIsAspectPerservingScaling(true)
.Build();
outputs_[2] = display::FakeDisplaySnapshot::Builder()
.SetId(kDisplayIds[2])
.SetNativeMode(small_mode_.Clone())
.SetCurrentMode(small_mode_.Clone())
.SetType(DISPLAY_CONNECTION_TYPE_HDMI)
.SetIsAspectPerservingScaling(true)
.Build();
UpdateOutputs(2, false);
}
void OnDisplayControlUpdated(bool status) {
display_control_result_ = (status ? CALLBACK_SUCCESS : CALLBACK_FAILURE);
}
void EnableContentProtectionCallback(bool status) {
enable_content_protection_status_ = status;
enable_content_protection_call_count_++;
}
void QueryContentProtectionCallback(
const DisplayConfigurator::QueryProtectionResponse& response) {
query_content_protection_response_ = response;
query_content_protection_call_count_++;
}
// Predefined modes that can be used by outputs.
const DisplayMode small_mode_;
const DisplayMode big_mode_;
protected:
// 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, arraysize(outputs_));
std::vector<DisplaySnapshot*> outputs;
for (size_t i = 0; i < num_outputs; ++i)
outputs.push_back(outputs_[i].get());
native_display_delegate_->set_outputs(outputs);
if (send_events) {
configurator_.OnConfigurationChanged();
EXPECT_TRUE(test_api_.TriggerConfigureTimeout());
}
}
void Init(bool panel_fitting_enabled) {
configurator_.Init(nullptr, panel_fitting_enabled);
}
// Initializes |configurator_| with a single internal display.
void InitWithSingleOutput() {
UpdateOutputs(1, false);
EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
configurator_.Init(nullptr, false);
EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
configurator_.ForceInitialConfigure(0);
EXPECT_EQ(
JoinActions(
kInitXRandR, kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
}
CallbackResult PopDisplayControlResult() {
CallbackResult result = display_control_result_;
display_control_result_ = CALLBACK_NOT_CALLED;
return result;
}
base::MessageLoop message_loop_;
TestStateController state_controller_;
TestMirroringController mirroring_controller_;
DisplayConfigurator configurator_;
TestObserver observer_;
std::unique_ptr<ActionLogger> log_;
TestNativeDisplayDelegate* native_display_delegate_; // not owned
DisplayConfigurator::TestApi test_api_;
ConfigurationWaiter config_waiter_;
bool enable_content_protection_status_;
int enable_content_protection_call_count_;
DisplayConfigurator::QueryProtectionResponse
query_content_protection_response_;
int query_content_protection_call_count_;
std::unique_ptr<DisplaySnapshot> outputs_[3];
CallbackResult display_control_result_;
private:
DISALLOW_COPY_AND_ASSIGN(DisplayConfiguratorTest);
};
} // namespace
TEST_F(DisplayConfiguratorTest, FindDisplayModeMatchingSize) {
std::unique_ptr<ui::DisplaySnapshot> output =
display::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, EnableVirtualDisplay) {
InitWithSingleOutput();
observer_.Reset();
const DisplayConfigurator::DisplayStateList& cached =
configurator_.cached_displays();
ASSERT_EQ(static_cast<size_t>(1u), cached.size());
EXPECT_EQ(small_mode_.size(), cached[0]->current_mode()->size());
// Add virtual display.
int64_t virtual_display_id =
configurator_.AddVirtualDisplay(big_mode_.size());
EXPECT_EQ(display::GenerateDisplayID(0x8000, 0x0, 1), virtual_display_id);
EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED,
configurator_.display_state());
// Virtual should not trigger addition of added crtc but does change FB
// height.
const int kVirtualHeight = small_mode_.size().height() +
DisplayConfigurator::kVerticalGap +
big_mode_.size().height();
EXPECT_EQ(
JoinActions(
kGrab, GetFramebufferAction(
gfx::Size(big_mode_.size().width(), kVirtualHeight),
outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_EQ(1, observer_.num_changes());
ASSERT_EQ(static_cast<size_t>(2u), cached.size());
EXPECT_EQ(small_mode_.size(), cached[0]->current_mode()->size());
EXPECT_EQ(big_mode_.size(), cached[1]->current_mode()->size());
EXPECT_EQ(virtual_display_id, cached[1]->display_id());
// Remove virtual display.
observer_.Reset();
EXPECT_TRUE(configurator_.RemoveVirtualDisplay(virtual_display_id));
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_EQ(1, observer_.num_changes());
ASSERT_EQ(static_cast<size_t>(1u), cached.size());
EXPECT_EQ(small_mode_.size(), cached[0]->current_mode()->size());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_SINGLE, configurator_.display_state());
}
TEST_F(DisplayConfiguratorTest, EnableTwoVirtualDisplays) {
InitWithSingleOutput();
observer_.Reset();
const DisplayConfigurator::DisplayStateList& cached =
configurator_.cached_displays();
ASSERT_EQ(static_cast<size_t>(1u), cached.size());
EXPECT_EQ(small_mode_.size(), cached[0]->current_mode()->size());
// Add 1st virtual display.
int64_t virtual_display_id_1 =
configurator_.AddVirtualDisplay(big_mode_.size());
EXPECT_EQ(display::GenerateDisplayID(0x8000, 0x0, 1), virtual_display_id_1);
EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED,
configurator_.display_state());
// Virtual should not trigger addition of added crtc but does change FB
// height.
const int kSingleVirtualHeight = small_mode_.size().height() +
DisplayConfigurator::kVerticalGap +
big_mode_.size().height();
EXPECT_EQ(
JoinActions(
kGrab, GetFramebufferAction(
gfx::Size(big_mode_.size().width(), kSingleVirtualHeight),
outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_EQ(1, observer_.num_changes());
ASSERT_EQ(static_cast<size_t>(2), cached.size());
EXPECT_EQ(small_mode_.size(), cached[0]->current_mode()->size());
EXPECT_EQ(big_mode_.size(), cached[1]->current_mode()->size());
EXPECT_EQ(virtual_display_id_1, cached[1]->display_id());
// Add 2nd virtual display
int64_t virtual_display_id_2 =
configurator_.AddVirtualDisplay(big_mode_.size());
EXPECT_EQ(display::GenerateDisplayID(0x8000, 0x0, 2), virtual_display_id_2);
EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED,
configurator_.display_state());
const int kDualVirtualHeight =
small_mode_.size().height() +
(DisplayConfigurator::kVerticalGap + big_mode_.size().height()) * 2;
EXPECT_EQ(
JoinActions(
kGrab, GetFramebufferAction(
gfx::Size(big_mode_.size().width(), kDualVirtualHeight),
outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_EQ(2, observer_.num_changes());
ASSERT_EQ(static_cast<size_t>(3u), cached.size());
EXPECT_EQ(small_mode_.size(), cached[0]->current_mode()->size());
EXPECT_EQ(big_mode_.size(), cached[1]->current_mode()->size());
EXPECT_EQ(big_mode_.size(), cached[2]->current_mode()->size());
EXPECT_EQ(virtual_display_id_1, cached[1]->display_id());
EXPECT_EQ(virtual_display_id_2, cached[2]->display_id());
// Remove 1st virtual display.
observer_.Reset();
EXPECT_TRUE(configurator_.RemoveVirtualDisplay(virtual_display_id_1));
EXPECT_EQ(
JoinActions(
kGrab, GetFramebufferAction(
gfx::Size(big_mode_.size().width(), kSingleVirtualHeight),
outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_EQ(1, observer_.num_changes());
ASSERT_EQ(static_cast<size_t>(2u), cached.size());
EXPECT_EQ(small_mode_.size(), cached[0]->current_mode()->size());
EXPECT_EQ(big_mode_.size(), cached[1]->current_mode()->size());
EXPECT_EQ(virtual_display_id_2, cached[1]->display_id());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED,
configurator_.display_state());
// Remove 2nd virtual display.
observer_.Reset();
EXPECT_TRUE(configurator_.RemoveVirtualDisplay(virtual_display_id_2));
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_EQ(1, observer_.num_changes());
ASSERT_EQ(static_cast<size_t>(1), cached.size());
EXPECT_EQ(small_mode_.size(), cached[0]->current_mode()->size());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_SINGLE, configurator_.display_state());
}
TEST_F(DisplayConfiguratorTest, ConnectSecondOutput) {
InitWithSingleOutput();
// Connect a second output and check that the configurator enters
// extended mode.
observer_.Reset();
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED);
UpdateOutputs(2, true);
const int kDualHeight = small_mode_.size().height() +
DisplayConfigurator::kVerticalGap +
big_mode_.size().height();
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
EXPECT_EQ(1, observer_.num_changes());
observer_.Reset();
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
EXPECT_EQ(
JoinActions(
kGrab, GetFramebufferAction(small_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, 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(
kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
EXPECT_EQ(1, observer_.num_changes());
// Get rid of shared modes to force software mirroring.
outputs_[1] = display::FakeDisplaySnapshot::Builder()
.SetId(kDisplayIds[1])
.SetNativeMode(big_mode_.Clone())
.SetCurrentMode(big_mode_.Clone())
.SetType(DISPLAY_CONNECTION_TYPE_HDMI)
.SetIsAspectPerservingScaling(true)
.Build();
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED);
UpdateOutputs(2, true);
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
const gfx::Size framebuffer_size = configurator_.framebuffer_size();
DCHECK(!framebuffer_size.IsEmpty());
observer_.Reset();
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
EXPECT_EQ(JoinActions(kGrab, kUngrab, nullptr), log_->GetActionsAndClear());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED,
configurator_.display_state());
EXPECT_TRUE(mirroring_controller_.SoftwareMirroringEnabled());
EXPECT_EQ(framebuffer_size.ToString(),
configurator_.framebuffer_size().ToString());
EXPECT_EQ(1, observer_.num_changes());
// Setting MULTIPLE_DISPLAY_STATE_DUAL_MIRROR should try to reconfigure.
observer_.Reset();
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED);
EXPECT_EQ(JoinActions(nullptr), log_->GetActionsAndClear());
EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
EXPECT_EQ(1, observer_.num_changes());
// Set back to software mirror mode.
observer_.Reset();
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
EXPECT_EQ(JoinActions(kGrab, kUngrab, nullptr), log_->GetActionsAndClear());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_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(
kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
EXPECT_EQ(1, observer_.num_changes());
}
TEST_F(DisplayConfiguratorTest, SetDisplayPower) {
InitWithSingleOutput();
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
observer_.Reset();
UpdateOutputs(2, true);
EXPECT_EQ(
JoinActions(
kGrab, GetFramebufferAction(small_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, 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(
kGrab, GetFramebufferAction(big_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, 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(
kGrab, GetFramebufferAction(small_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], nullptr, gfx::Point(0, 0)).c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_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(
kGrab, GetFramebufferAction(small_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR, configurator_.display_state());
EXPECT_FALSE(mirroring_controller_.SoftwareMirroringEnabled());
EXPECT_EQ(1, observer_.num_changes());
// Get rid of shared modes to force software mirroring.
outputs_[1] = display::FakeDisplaySnapshot::Builder()
.SetId(kDisplayIds[1])
.SetNativeMode(big_mode_.Clone())
.SetCurrentMode(big_mode_.Clone())
.SetType(DISPLAY_CONNECTION_TYPE_HDMI)
.SetIsAspectPerservingScaling(true)
.Build();
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
observer_.Reset();
UpdateOutputs(2, true);
const int kDualHeight = small_mode_.size().height() +
DisplayConfigurator::kVerticalGap +
big_mode_.size().height();
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_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(
kGrab, GetFramebufferAction(big_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, 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(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], nullptr,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_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(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED,
configurator_.display_state());
EXPECT_TRUE(mirroring_controller_.SoftwareMirroringEnabled());
EXPECT_EQ(1, observer_.num_changes());
}
TEST_F(DisplayConfiguratorTest, SuspendAndResume) {
InitWithSingleOutput();
// 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.
const gfx::Size framebuffer_size = configurator_.framebuffer_size();
DCHECK(!framebuffer_size.IsEmpty());
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(framebuffer_size.ToString(),
configurator_.framebuffer_size().ToString());
EXPECT_EQ(JoinActions(
kGrab, GetFramebufferAction(small_mode_.size(),
outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
kUngrab, kSync, 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(
kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, 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(
kGrab, GetFramebufferAction(small_mode_.size(),
outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
kUngrab, 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(kSync, 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(
kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
UpdateOutputs(2, true);
EXPECT_EQ(
JoinActions(
kGrab, GetFramebufferAction(small_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, 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_DUAL_MIRROR, configurator_.display_state());
EXPECT_EQ(
JoinActions(
kGrab, GetFramebufferAction(small_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], nullptr, gfx::Point(0, 0)).c_str(),
kUngrab, 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_DUAL_MIRROR, configurator_.display_state());
EXPECT_EQ(kSync, 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(
kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
}
TEST_F(DisplayConfiguratorTest, Headless) {
UpdateOutputs(0, false);
EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
Init(false);
EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
configurator_.ForceInitialConfigure(0);
EXPECT_EQ(JoinActions(kInitXRandR, kGrab, kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
// 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(JoinActions(kGrab, kUngrab, 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(kGrab, kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
// Connect an external display and check that it's configured correctly.
outputs_[0] = display::FakeDisplaySnapshot::Builder()
.SetId(kDisplayIds[0])
.SetNativeMode(big_mode_.Clone())
.SetCurrentMode(big_mode_.Clone())
.AddMode(small_mode_.Clone())
.SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
.SetIsAspectPerservingScaling(true)
.Build();
UpdateOutputs(1, true);
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(big_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &big_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
const gfx::Size framebuffer_size = configurator_.framebuffer_size();
DCHECK(!framebuffer_size.IsEmpty());
UpdateOutputs(0, true);
EXPECT_EQ(JoinActions(kGrab, kUngrab, nullptr), log_->GetActionsAndClear());
EXPECT_EQ(framebuffer_size.ToString(),
configurator_.framebuffer_size().ToString());
}
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_DUAL_MIRROR);
configurator_.ForceInitialConfigure(0);
EXPECT_EQ(
JoinActions(
kInitXRandR, kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
}
TEST_F(DisplayConfiguratorTest, InvalidMultipleDisplayStates) {
UpdateOutputs(0, false);
EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
Init(false);
configurator_.ForceInitialConfigure(0);
observer_.Reset();
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_HEADLESS);
EXPECT_EQ(1, observer_.num_changes());
EXPECT_EQ(0, observer_.num_failures());
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_SINGLE);
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED);
EXPECT_EQ(1, observer_.num_changes());
EXPECT_EQ(3, observer_.num_failures());
UpdateOutputs(1, true);
observer_.Reset();
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_HEADLESS);
EXPECT_EQ(0, observer_.num_changes());
EXPECT_EQ(1, observer_.num_failures());
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_SINGLE);
EXPECT_EQ(1, observer_.num_changes());
EXPECT_EQ(1, observer_.num_failures());
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED);
EXPECT_EQ(1, observer_.num_changes());
EXPECT_EQ(3, observer_.num_failures());
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED);
UpdateOutputs(2, true);
observer_.Reset();
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_HEADLESS);
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_SINGLE);
EXPECT_EQ(0, observer_.num_changes());
EXPECT_EQ(2, observer_.num_failures());
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED);
EXPECT_EQ(2, observer_.num_changes());
EXPECT_EQ(2, observer_.num_failures());
}
TEST_F(DisplayConfiguratorTest, GetMultipleDisplayStateForMirroredDisplays) {
UpdateOutputs(2, false);
Init(false);
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
configurator_.ForceInitialConfigure(0);
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR, configurator_.display_state());
}
TEST_F(DisplayConfiguratorTest, UpdateCachedOutputsEvenAfterFailure) {
InitWithSingleOutput();
const DisplayConfigurator::DisplayStateList& cached =
configurator_.cached_displays();
ASSERT_EQ(static_cast<size_t>(1), cached.size());
EXPECT_EQ(outputs_[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(outputs_[0]->current_mode(), cached[0]->current_mode());
EXPECT_EQ(outputs_[1]->current_mode(), cached[1]->current_mode());
}
TEST_F(DisplayConfiguratorTest, PanelFitting) {
// Configure the internal display to support only the big mode and the
// external display to support only the small mode.
outputs_[0] = display::FakeDisplaySnapshot::Builder()
.SetId(kDisplayIds[0])
.SetNativeMode(big_mode_.Clone())
.SetCurrentMode(big_mode_.Clone())
.SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
.SetIsAspectPerservingScaling(true)
.Build();
outputs_[1] = display::FakeDisplaySnapshot::Builder()
.SetId(kDisplayIds[1])
.SetNativeMode(small_mode_.Clone())
.SetCurrentMode(small_mode_.Clone())
.SetType(DISPLAY_CONNECTION_TYPE_HDMI)
.SetIsAspectPerservingScaling(true)
.Build();
// The small mode should be added to the internal output when requesting
// mirrored mode.
UpdateOutputs(2, false);
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
Init(true /* is_panel_fitting_enabled */);
configurator_.ForceInitialConfigure(0);
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR, configurator_.display_state());
EXPECT_EQ(
JoinActions(
kInitXRandR, kGrab,
GetAddOutputModeAction(*outputs_[0], &small_mode_).c_str(),
GetFramebufferAction(small_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
// Both outputs should be using the small mode.
ASSERT_EQ(1, observer_.num_changes());
ASSERT_EQ(static_cast<size_t>(2), observer_.latest_outputs().size());
EXPECT_EQ(small_mode_.size(),
observer_.latest_outputs()[0]->current_mode()->size());
EXPECT_EQ(small_mode_.size(),
observer_.latest_outputs()[1]->current_mode()->size());
// Also test that there are 2 modes (instead of the initial one) in the
// snapshot that was passed to the observer (http://crbug.com/289159).
DisplaySnapshot* state = observer_.latest_outputs()[0];
ASSERT_EQ(2UL, state->modes().size());
}
TEST_F(DisplayConfiguratorTest, ContentProtection) {
Init(false);
configurator_.ForceInitialConfigure(0);
EXPECT_NE(kNoActions, log_->GetActionsAndClear());
DisplayConfigurator::ContentProtectionClientId id =
configurator_.RegisterContentProtectionClient();
EXPECT_NE(0u, id);
// One output.
UpdateOutputs(1, true);
EXPECT_NE(kNoActions, log_->GetActionsAndClear());
configurator_.QueryContentProtectionStatus(
id, outputs_[0]->display_id(),
base::Bind(&DisplayConfiguratorTest::QueryContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(1, query_content_protection_call_count_);
EXPECT_TRUE(query_content_protection_response_.success);
EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_INTERNAL),
query_content_protection_response_.link_mask);
EXPECT_EQ(static_cast<uint32_t>(CONTENT_PROTECTION_METHOD_NONE),
query_content_protection_response_.protection_mask);
EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
// Two outputs.
UpdateOutputs(2, true);
EXPECT_NE(kNoActions, log_->GetActionsAndClear());
configurator_.QueryContentProtectionStatus(
id, outputs_[1]->display_id(),
base::Bind(&DisplayConfiguratorTest::QueryContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(2, query_content_protection_call_count_);
EXPECT_TRUE(query_content_protection_response_.success);
EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_HDMI),
query_content_protection_response_.link_mask);
EXPECT_EQ(static_cast<uint32_t>(CONTENT_PROTECTION_METHOD_NONE),
query_content_protection_response_.protection_mask);
EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
configurator_.EnableContentProtection(
id, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_HDCP,
base::Bind(&DisplayConfiguratorTest::EnableContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(1, enable_content_protection_call_count_);
EXPECT_TRUE(enable_content_protection_status_);
EXPECT_EQ(GetSetHDCPStateAction(*outputs_[1], HDCP_STATE_DESIRED),
log_->GetActionsAndClear());
// Enable protection.
native_display_delegate_->set_hdcp_state(HDCP_STATE_ENABLED);
configurator_.QueryContentProtectionStatus(
id, outputs_[1]->display_id(),
base::Bind(&DisplayConfiguratorTest::QueryContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(3, query_content_protection_call_count_);
EXPECT_TRUE(query_content_protection_response_.success);
EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_HDMI),
query_content_protection_response_.link_mask);
EXPECT_EQ(static_cast<uint32_t>(CONTENT_PROTECTION_METHOD_HDCP),
query_content_protection_response_.protection_mask);
EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
// Protections should be disabled after unregister.
configurator_.UnregisterContentProtectionClient(id);
EXPECT_EQ(GetSetHDCPStateAction(*outputs_[1], HDCP_STATE_UNDESIRED),
log_->GetActionsAndClear());
}
TEST_F(DisplayConfiguratorTest, DoNotConfigureWithSuspendedDisplays) {
InitWithSingleOutput();
// 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(
kGrab, GetFramebufferAction(small_mode_.size(),
outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
kUngrab, kSync, 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(
kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
UpdateOutputs(2, false);
configurator_.SetDisplayMode(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
EXPECT_EQ(
JoinActions(
kGrab, GetFramebufferAction(small_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, 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(
kGrab, GetFramebufferAction(small_mode_.size(),
outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
kUngrab, kSync, 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(
kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
}
TEST_F(DisplayConfiguratorTest, ContentProtectionTwoClients) {
DisplayConfigurator::ContentProtectionClientId client1 =
configurator_.RegisterContentProtectionClient();
DisplayConfigurator::ContentProtectionClientId client2 =
configurator_.RegisterContentProtectionClient();
EXPECT_NE(client1, client2);
Init(false);
configurator_.ForceInitialConfigure(0);
UpdateOutputs(2, true);
EXPECT_NE(kNoActions, log_->GetActionsAndClear());
// Clients never know state enableness for methods that they didn't request.
configurator_.EnableContentProtection(
client1, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_HDCP,
base::Bind(&DisplayConfiguratorTest::EnableContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(1, enable_content_protection_call_count_);
EXPECT_TRUE(enable_content_protection_status_);
EXPECT_EQ(GetSetHDCPStateAction(*outputs_[1], HDCP_STATE_DESIRED).c_str(),
log_->GetActionsAndClear());
native_display_delegate_->set_hdcp_state(HDCP_STATE_ENABLED);
configurator_.QueryContentProtectionStatus(
client1, outputs_[1]->display_id(),
base::Bind(&DisplayConfiguratorTest::QueryContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(1, query_content_protection_call_count_);
EXPECT_TRUE(query_content_protection_response_.success);
EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_HDMI),
query_content_protection_response_.link_mask);
EXPECT_EQ(CONTENT_PROTECTION_METHOD_HDCP,
query_content_protection_response_.protection_mask);
configurator_.QueryContentProtectionStatus(
client2, outputs_[1]->display_id(),
base::Bind(&DisplayConfiguratorTest::QueryContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(2, query_content_protection_call_count_);
EXPECT_TRUE(query_content_protection_response_.success);
EXPECT_EQ(static_cast<uint32_t>(DISPLAY_CONNECTION_TYPE_HDMI),
query_content_protection_response_.link_mask);
EXPECT_EQ(CONTENT_PROTECTION_METHOD_NONE,
query_content_protection_response_.protection_mask);
// Protections will be disabled only if no more clients request them.
configurator_.EnableContentProtection(
client2, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_NONE,
base::Bind(&DisplayConfiguratorTest::EnableContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(2, enable_content_protection_call_count_);
EXPECT_TRUE(enable_content_protection_status_);
EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
configurator_.EnableContentProtection(
client1, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_NONE,
base::Bind(&DisplayConfiguratorTest::EnableContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(3, enable_content_protection_call_count_);
EXPECT_TRUE(enable_content_protection_status_);
EXPECT_EQ(GetSetHDCPStateAction(*outputs_[1], HDCP_STATE_UNDESIRED).c_str(),
log_->GetActionsAndClear());
}
TEST_F(DisplayConfiguratorTest, ContentProtectionTwoClientsEnable) {
DisplayConfigurator::ContentProtectionClientId client1 =
configurator_.RegisterContentProtectionClient();
DisplayConfigurator::ContentProtectionClientId client2 =
configurator_.RegisterContentProtectionClient();
EXPECT_NE(client1, client2);
Init(false);
configurator_.ForceInitialConfigure(0);
UpdateOutputs(2, true);
log_->GetActionsAndClear();
// Only enable once if HDCP is enabling.
configurator_.EnableContentProtection(
client1, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_HDCP,
base::Bind(&DisplayConfiguratorTest::EnableContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(1, enable_content_protection_call_count_);
EXPECT_TRUE(enable_content_protection_status_);
native_display_delegate_->set_hdcp_state(HDCP_STATE_DESIRED);
configurator_.EnableContentProtection(
client2, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_HDCP,
base::Bind(&DisplayConfiguratorTest::EnableContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(2, enable_content_protection_call_count_);
EXPECT_TRUE(enable_content_protection_status_);
EXPECT_EQ(GetSetHDCPStateAction(*outputs_[1], HDCP_STATE_DESIRED).c_str(),
log_->GetActionsAndClear());
native_display_delegate_->set_hdcp_state(HDCP_STATE_ENABLED);
// Don't enable again if HDCP is already active.
configurator_.EnableContentProtection(
client1, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_HDCP,
base::Bind(&DisplayConfiguratorTest::EnableContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(3, enable_content_protection_call_count_);
EXPECT_TRUE(enable_content_protection_status_);
configurator_.EnableContentProtection(
client2, outputs_[1]->display_id(), CONTENT_PROTECTION_METHOD_HDCP,
base::Bind(&DisplayConfiguratorTest::EnableContentProtectionCallback,
base::Unretained(this)));
EXPECT_EQ(4, enable_content_protection_call_count_);
EXPECT_TRUE(enable_content_protection_status_);
EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
}
TEST_F(DisplayConfiguratorTest, HandleConfigureCrtcFailure) {
InitWithSingleOutput();
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));
outputs_[0] = display::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)
.SetIsAspectPerservingScaling(true)
.Build();
// First test simply fails in MULTIPLE_DISPLAY_STATE_SINGLE mode. This is
// probably unrealistic but we want to make sure any assumptions don't creep
// in.
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(
kGrab,
GetFramebufferAction(big_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], modes[0].get(), gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[0], modes[3].get(), gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[0], modes[2].get(), gfx::Point(0, 0)).c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
outputs_[1] = display::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)
.SetIsAspectPerservingScaling(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[3]->size().GetArea());
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_MIRROR);
UpdateOutputs(2, true);
EXPECT_EQ(
JoinActions(
kGrab, GetFramebufferAction(modes[0]->size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], modes[0].get(), gfx::Point(0, 0)).c_str(),
// Then attempt to configure crtc1 with the first mode.
GetCrtcAction(*outputs_[1], modes[0].get(), gfx::Point(0, 0)).c_str(),
// First mode tried is expected to fail and it will
// retry wil the 4th mode in the list.
GetCrtcAction(*outputs_[0], modes[3].get(), gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], modes[3].get(), gfx::Point(0, 0)).c_str(),
// Since it was requested to go into mirror mode
// and the configured modes were different, it
// should now try and setup a valid configurable
// extended mode.
GetFramebufferAction(
gfx::Size(modes[0]->size().width(),
modes[0]->size().height() + modes[0]->size().height() +
DisplayConfigurator::kVerticalGap),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], modes[0].get(), gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], modes[0].get(),
gfx::Point(0, modes[0]->size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
GetCrtcAction(*outputs_[0], modes[3].get(), gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], modes[3].get(),
gfx::Point(0, modes[0]->size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, 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_DUAL_EXTENDED);
Init(false);
configurator_.ForceInitialConfigure(0);
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(
kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, 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_DUAL_MIRROR);
Init(false);
configurator_.ForceInitialConfigure(0);
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(
kGrab, GetFramebufferAction(big_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, 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(
kGrab, GetFramebufferAction(small_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], nullptr, gfx::Point(0, 0)).c_str(),
kUngrab, kSync, 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(
kGrab, GetFramebufferAction(small_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, 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(
kGrab, GetFramebufferAction(small_mode_.size(), outputs_[0].get(),
outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
}
TEST_F(DisplayConfiguratorTest, ExternalControl) {
InitWithSingleOutput();
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_SINGLE);
configurator_.RelinquishControl(
base::Bind(&DisplayConfiguratorTest::OnDisplayControlUpdated,
base::Unretained(this)));
EXPECT_EQ(CALLBACK_SUCCESS, PopDisplayControlResult());
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
kUngrab, kRelinquishDisplayControl, nullptr),
log_->GetActionsAndClear());
configurator_.TakeControl(
base::Bind(&DisplayConfiguratorTest::OnDisplayControlUpdated,
base::Unretained(this)));
EXPECT_EQ(CALLBACK_SUCCESS, PopDisplayControlResult());
EXPECT_EQ(
JoinActions(
kTakeDisplayControl, kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
}
TEST_F(DisplayConfiguratorTest,
SetDisplayPowerWhilePendingConfigurationTaskRunning) {
// Start out with two displays in extended mode.
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED);
Init(false);
configurator_.ForceInitialConfigure(0);
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());
const int kDualHeight = small_mode_.size().height() +
DisplayConfigurator::kVerticalGap +
big_mode_.size().height();
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], nullptr,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
config_waiter_.Reset();
EXPECT_EQ(
base::TimeDelta::FromMilliseconds(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(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
}
TEST_F(DisplayConfiguratorTest,
SetDisplayPowerAfterFailedDisplayConfiguration) {
// Start out with two displays in extended mode.
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED);
Init(false);
configurator_.ForceInitialConfigure(0);
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());
const int kDualHeight = small_mode_.size().height() +
DisplayConfigurator::kVerticalGap +
big_mode_.size().height();
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], nullptr,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, 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(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
GetCrtcAction(*outputs_[1], &small_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, 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(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
}
TEST_F(DisplayConfiguratorTest, TestWithThreeDisplays) {
// Start out with two displays in extended mode.
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED);
Init(false);
configurator_.ForceInitialConfigure(0);
log_->GetActionsAndClear();
observer_.Reset();
UpdateOutputs(3, true);
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_MULTI_EXTENDED);
const int kDualHeight = small_mode_.size().height() +
DisplayConfigurator::kVerticalGap +
big_mode_.size().height();
const int kTripleHeight = 2 * small_mode_.size().height() +
2 * DisplayConfigurator::kVerticalGap +
big_mode_.size().height();
EXPECT_EQ(
JoinActions(
kGrab, GetFramebufferAction(
gfx::Size(big_mode_.size().width(), kTripleHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
GetCrtcAction(
*outputs_[2], &small_mode_,
gfx::Point(0, small_mode_.size().height() +
big_mode_.size().height() +
2 * DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, 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(
kGrab, GetFramebufferAction(
gfx::Size(big_mode_.size().width(), kTripleHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], nullptr,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
GetCrtcAction(
*outputs_[2], nullptr,
gfx::Point(0, small_mode_.size().height() +
big_mode_.size().height() +
2 * DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, 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(
kGrab, GetFramebufferAction(
gfx::Size(big_mode_.size().width(), kTripleHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
GetCrtcAction(
*outputs_[2], &small_mode_,
gfx::Point(0, small_mode_.size().height() +
big_mode_.size().height() +
2 * DisplayConfigurator::kVerticalGap))
.c_str(),
kForceDPMS, kUngrab, nullptr),
log_->GetActionsAndClear());
// Disconnect the third output.
observer_.Reset();
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED);
UpdateOutputs(2, true);
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, nullptr),
log_->GetActionsAndClear());
}
// Tests the suspend and resume behavior when in dual or multi display modes.
TEST_F(DisplayConfiguratorTest, SuspendResumeWithMultipleDisplays) {
InitWithSingleOutput();
state_controller_.set_state(MULTIPLE_DISPLAY_STATE_DUAL_EXTENDED);
observer_.Reset();
UpdateOutputs(2, true);
EXPECT_EQ(MULTIPLE_DISPLAY_STATE_DUAL_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());
const int kDualHeight = small_mode_.size().height() +
DisplayConfigurator::kVerticalGap +
big_mode_.size().height();
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, 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_DUAL_EXTENDED,
configurator_.display_state());
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], nullptr,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, kSync, 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_DUAL_EXTENDED,
configurator_.display_state());
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], &big_mode_,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kForceDPMS, kUngrab, 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_DUAL_EXTENDED,
configurator_.display_state());
EXPECT_EQ(chromeos::DISPLAY_POWER_ALL_OFF,
configurator_.current_power_state());
EXPECT_EQ(
JoinActions(
kGrab,
GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
outputs_[0].get(), outputs_[1].get())
.c_str(),
GetCrtcAction(*outputs_[0], nullptr, gfx::Point(0, 0)).c_str(),
GetCrtcAction(*outputs_[1], nullptr,
gfx::Point(0, small_mode_.size().height() +
DisplayConfigurator::kVerticalGap))
.c_str(),
kUngrab, kSync, 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(
kGrab,
GetFramebufferAction(small_mode_.size(), outputs_[0].get(), nullptr)
.c_str(),
GetCrtcAction(*outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
kForceDPMS, kUngrab, 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());
}
} // namespace test
} // namespace ui