Renamed OutputConfigurator to DisplayConfigurator
BUG=333413
Review URL: https://codereview.chromium.org/226183004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@262183 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/ui/display/chromeos/display_configurator_unittest.cc b/ui/display/chromeos/display_configurator_unittest.cc
new file mode 100644
index 0000000..a3833c4b
--- /dev/null
+++ b/ui/display/chromeos/display_configurator_unittest.cc
@@ -0,0 +1,1356 @@
+// 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 <stdint.h>
+
+#include <cmath>
+#include <cstdarg>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/format_macros.h"
+#include "base/memory/scoped_vector.h"
+#include "base/message_loop/message_loop.h"
+#include "base/strings/stringprintf.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/display/chromeos/display_mode.h"
+#include "ui/display/chromeos/native_display_delegate.h"
+#include "ui/display/chromeos/test/test_display_snapshot.h"
+
+namespace ui {
+
+namespace {
+
+// Strings returned by TestNativeDisplayDelegate::GetActionsAndClear() to
+// describe various actions that were performed.
+const char kInitXRandR[] = "init";
+const char kGrab[] = "grab";
+const char kUngrab[] = "ungrab";
+const char kSync[] = "sync";
+const char kForceDPMS[] = "dpms";
+
+// String returned by TestNativeDisplayDelegate::GetActionsAndClear() if no
+// actions were requested.
+const char kNoActions[] = "";
+
+std::string DisplaySnapshotToString(const DisplaySnapshot& output) {
+ return base::StringPrintf("id=%" PRId64, output.display_id());
+}
+
+// Returns a string describing a TestNativeDisplayDelegate::SetBackgroundColor()
+// call.
+std::string GetBackgroundAction(uint32_t color_argb) {
+ return base::StringPrintf("background(0x%x)", color_argb);
+}
+
+// Returns a string describing a TestNativeDisplayDelegate::AddOutputMode()
+// call.
+std::string GetAddOutputModeAction(const DisplaySnapshot& output,
+ const DisplayMode* mode) {
+ return base::StringPrintf("add_mode(output=%" PRId64 ",mode=%s)",
+ output.display_id(),
+ mode->ToString().c_str());
+}
+
+// Returns a string describing a TestNativeDisplayDelegate::Configure()
+// call.
+std::string GetCrtcAction(const DisplaySnapshot& output,
+ const DisplayMode* mode,
+ const gfx::Point& origin) {
+ return base::StringPrintf("crtc(display=[%s],x=%d,y=%d,mode=[%s])",
+ DisplaySnapshotToString(output).c_str(),
+ origin.x(),
+ origin.y(),
+ mode ? mode->ToString().c_str() : "NULL");
+}
+
+// Returns a string describing a TestNativeDisplayDelegate::CreateFramebuffer()
+// call.
+std::string GetFramebufferAction(const gfx::Size& size,
+ const DisplaySnapshot* out1,
+ const DisplaySnapshot* out2) {
+ return base::StringPrintf(
+ "framebuffer(width=%d,height=%d,display1=%s,display2=%s)",
+ size.width(),
+ size.height(),
+ out1 ? DisplaySnapshotToString(*out1).c_str() : "NULL",
+ out2 ? DisplaySnapshotToString(*out2).c_str() : "NULL");
+}
+
+// Returns a string describing a TestNativeDisplayDelegate::ConfigureCTM() call.
+std::string GetCTMAction(
+ int device_id,
+ const DisplayConfigurator::CoordinateTransformation& ctm) {
+ return base::StringPrintf("ctm(id=%d,transform=(%f,%f,%f,%f))",
+ device_id,
+ ctm.x_scale,
+ ctm.x_offset,
+ ctm.y_scale,
+ ctm.y_offset);
+}
+
+// Returns a string describing a TestNativeDisplayDelegate::SetHDCPState() call.
+std::string GetSetHDCPStateAction(const DisplaySnapshot& output,
+ HDCPState state) {
+ return base::StringPrintf(
+ "set_hdcp(id=%" PRId64 ",state=%d)", output.display_id(), state);
+}
+
+// Joins a sequence of strings describing actions (e.g. kScreenDim) such
+// that they can be compared against a string returned by
+// ActionLogger::GetActionsAndClear(). The list of actions must be
+// terminated by a NULL pointer.
+std::string JoinActions(const char* action, ...) {
+ std::string actions;
+
+ va_list arg_list;
+ va_start(arg_list, action);
+ while (action) {
+ if (!actions.empty())
+ actions += ",";
+ actions += action;
+ action = va_arg(arg_list, const char*);
+ }
+ va_end(arg_list);
+ return actions;
+}
+
+class ActionLogger {
+ public:
+ ActionLogger() {}
+
+ void AppendAction(const std::string& action) {
+ if (!actions_.empty())
+ actions_ += ",";
+ actions_ += action;
+ }
+
+ // Returns a comma-separated string describing the actions that were
+ // requested since the previous call to GetActionsAndClear() (i.e.
+ // results are non-repeatable).
+ std::string GetActionsAndClear() {
+ std::string actions = actions_;
+ actions_.clear();
+ return actions;
+ }
+
+ private:
+ std::string actions_;
+
+ DISALLOW_COPY_AND_ASSIGN(ActionLogger);
+};
+
+class TestTouchscreenDelegate
+ : public DisplayConfigurator::TouchscreenDelegate {
+ public:
+ // Ownership of |log| remains with the caller.
+ explicit TestTouchscreenDelegate(ActionLogger* log)
+ : log_(log),
+ configure_touchscreens_(false) {}
+ virtual ~TestTouchscreenDelegate() {}
+
+ const DisplayConfigurator::CoordinateTransformation& GetCTM(
+ int touch_device_id) {
+ return ctms_[touch_device_id];
+ }
+
+ void set_configure_touchscreens(bool state) {
+ configure_touchscreens_ = state;
+ }
+
+ // DisplayConfigurator::TouchscreenDelegate implementation:
+ virtual void AssociateTouchscreens(
+ DisplayConfigurator::DisplayStateList* outputs) OVERRIDE {
+ if (configure_touchscreens_) {
+ for (size_t i = 0; i < outputs->size(); ++i)
+ (*outputs)[i].touch_device_id = i + 1;
+ }
+ }
+ virtual void ConfigureCTM(
+ int touch_device_id,
+ const DisplayConfigurator::CoordinateTransformation& ctm) OVERRIDE {
+ log_->AppendAction(GetCTMAction(touch_device_id, ctm));
+ ctms_[touch_device_id] = ctm;
+ }
+
+ private:
+ ActionLogger* log_; // Not owned.
+
+ bool configure_touchscreens_;
+
+ // Most-recently-configured transformation matrices, keyed by touch device ID.
+ std::map<int, DisplayConfigurator::CoordinateTransformation> ctms_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestTouchscreenDelegate);
+};
+
+class TestNativeDisplayDelegate : public NativeDisplayDelegate {
+ public:
+ // Ownership of |log| remains with the caller.
+ explicit TestNativeDisplayDelegate(ActionLogger* log)
+ : max_configurable_pixels_(0),
+ hdcp_state_(HDCP_STATE_UNDESIRED),
+ log_(log) {}
+ virtual ~TestNativeDisplayDelegate() {}
+
+ const std::vector<DisplaySnapshot*>& outputs() const { return outputs_; }
+ void set_outputs(const std::vector<DisplaySnapshot*>& outputs) {
+ outputs_ = outputs;
+ }
+
+ void set_max_configurable_pixels(int pixels) {
+ max_configurable_pixels_ = pixels;
+ }
+
+ void set_hdcp_state(HDCPState state) { hdcp_state_ = state; }
+
+ // DisplayConfigurator::Delegate overrides:
+ virtual void Initialize() OVERRIDE { log_->AppendAction(kInitXRandR); }
+ virtual void GrabServer() OVERRIDE { log_->AppendAction(kGrab); }
+ virtual void UngrabServer() OVERRIDE { log_->AppendAction(kUngrab); }
+ virtual void SyncWithServer() OVERRIDE { log_->AppendAction(kSync); }
+ virtual void SetBackgroundColor(uint32_t color_argb) OVERRIDE {
+ log_->AppendAction(GetBackgroundAction(color_argb));
+ }
+ virtual void ForceDPMSOn() OVERRIDE { log_->AppendAction(kForceDPMS); }
+ virtual std::vector<DisplaySnapshot*> GetOutputs() OVERRIDE {
+ return outputs_;
+ }
+ virtual void AddMode(const DisplaySnapshot& output,
+ const DisplayMode* mode) OVERRIDE {
+ log_->AppendAction(GetAddOutputModeAction(output, mode));
+ }
+ virtual bool Configure(const DisplaySnapshot& output,
+ const DisplayMode* mode,
+ const gfx::Point& origin) OVERRIDE {
+ log_->AppendAction(GetCrtcAction(output, mode, origin));
+
+ if (max_configurable_pixels_ == 0)
+ return true;
+
+ if (!mode)
+ return false;
+
+ return mode->size().GetArea() <= max_configurable_pixels_;
+ }
+ virtual void CreateFrameBuffer(const gfx::Size& size) OVERRIDE {
+ log_->AppendAction(
+ GetFramebufferAction(size,
+ outputs_.size() >= 1 ? outputs_[0] : NULL,
+ outputs_.size() >= 2 ? outputs_[1] : NULL));
+ }
+ virtual bool GetHDCPState(const DisplaySnapshot& output,
+ HDCPState* state) OVERRIDE {
+ *state = hdcp_state_;
+ return true;
+ }
+
+ virtual bool SetHDCPState(const DisplaySnapshot& output,
+ HDCPState state) OVERRIDE {
+ log_->AppendAction(GetSetHDCPStateAction(output, state));
+ return true;
+ }
+
+ virtual std::vector<ui::ColorCalibrationProfile>
+ GetAvailableColorCalibrationProfiles(const DisplaySnapshot& output) OVERRIDE {
+ return std::vector<ui::ColorCalibrationProfile>();
+ }
+
+ virtual bool SetColorCalibrationProfile(
+ const DisplaySnapshot& output,
+ ui::ColorCalibrationProfile new_profile) OVERRIDE {
+ return false;
+ }
+
+ virtual void AddObserver(NativeDisplayObserver* observer) OVERRIDE {}
+
+ virtual void RemoveObserver(NativeDisplayObserver* observer) OVERRIDE {}
+
+ private:
+ // Outputs to be returned by GetOutputs().
+ std::vector<DisplaySnapshot*> outputs_;
+
+ // |max_configurable_pixels_| represents the maximum number of pixels that
+ // Configure will support. Tests can use this to force Configure
+ // to fail if attempting to set a resolution that is higher than what
+ // a device might support under a given circumstance.
+ // A value of 0 means that no limit is enforced and Configure will
+ // return success regardless of the resolution.
+ int max_configurable_pixels_;
+
+ // Result value of GetHDCPState().
+ HDCPState hdcp_state_;
+
+ ActionLogger* log_; // Not owned.
+
+ DISALLOW_COPY_AND_ASSIGN(TestNativeDisplayDelegate);
+};
+
+class TestObserver : public DisplayConfigurator::Observer {
+ public:
+ explicit TestObserver(DisplayConfigurator* configurator)
+ : configurator_(configurator) {
+ Reset();
+ configurator_->AddObserver(this);
+ }
+ virtual ~TestObserver() { 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_;
+ }
+ OutputState latest_failed_state() const { return latest_failed_state_; }
+
+ void Reset() {
+ num_changes_ = 0;
+ num_failures_ = 0;
+ latest_outputs_.clear();
+ latest_failed_state_ = OUTPUT_STATE_INVALID;
+ }
+
+ // DisplayConfigurator::Observer overrides:
+ virtual void OnDisplayModeChanged(
+ const DisplayConfigurator::DisplayStateList& outputs) OVERRIDE {
+ num_changes_++;
+ latest_outputs_ = outputs;
+ }
+
+ virtual void OnDisplayModeChangeFailed(OutputState 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_;
+ OutputState latest_failed_state_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestObserver);
+};
+
+class TestStateController : public DisplayConfigurator::StateController {
+ public:
+ TestStateController() : state_(OUTPUT_STATE_DUAL_EXTENDED) {}
+ virtual ~TestStateController() {}
+
+ void set_state(OutputState state) { state_ = state; }
+
+ // DisplayConfigurator::StateController overrides:
+ virtual OutputState GetStateForDisplayIds(
+ const std::vector<int64_t>& outputs) const OVERRIDE {
+ return state_;
+ }
+ virtual bool GetResolutionForDisplayId(int64_t display_id,
+ gfx::Size* size) const OVERRIDE {
+ return false;
+ }
+
+ private:
+ OutputState state_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestStateController);
+};
+
+class TestMirroringController
+ : public DisplayConfigurator::SoftwareMirroringController {
+ public:
+ TestMirroringController() : software_mirroring_enabled_(false) {}
+ virtual ~TestMirroringController() {}
+
+ virtual void SetSoftwareMirroring(bool enabled) OVERRIDE {
+ software_mirroring_enabled_ = enabled;
+ }
+
+ bool software_mirroring_enabled() const {
+ return software_mirroring_enabled_;
+ }
+
+ private:
+ bool software_mirroring_enabled_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestMirroringController);
+};
+
+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_) {}
+ virtual ~DisplayConfiguratorTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ log_.reset(new ActionLogger());
+
+ native_display_delegate_ = new TestNativeDisplayDelegate(log_.get());
+ configurator_.SetNativeDisplayDelegateForTesting(
+ scoped_ptr<NativeDisplayDelegate>(native_display_delegate_));
+
+ touchscreen_delegate_ = new TestTouchscreenDelegate(log_.get());
+ configurator_.SetTouchscreenDelegateForTesting(
+ scoped_ptr<DisplayConfigurator::TouchscreenDelegate>(
+ touchscreen_delegate_));
+
+ configurator_.set_state_controller(&state_controller_);
+ configurator_.set_mirroring_controller(&mirroring_controller_);
+
+ std::vector<const DisplayMode*> modes;
+ modes.push_back(&small_mode_);
+
+ TestDisplaySnapshot* o = &outputs_[0];
+ o->set_current_mode(&small_mode_);
+ o->set_native_mode(&small_mode_);
+ o->set_modes(modes);
+ o->set_type(OUTPUT_TYPE_INTERNAL);
+ o->set_is_aspect_preserving_scaling(true);
+ o->set_display_id(123);
+ o->set_has_proper_display_id(true);
+
+ o = &outputs_[1];
+ o->set_current_mode(&big_mode_);
+ o->set_native_mode(&big_mode_);
+ modes.push_back(&big_mode_);
+ o->set_modes(modes);
+ o->set_type(OUTPUT_TYPE_HDMI);
+ o->set_is_aspect_preserving_scaling(true);
+ o->set_display_id(456);
+ o->set_has_proper_display_id(true);
+
+ UpdateOutputs(2, false);
+ }
+
+ // 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]);
+ native_display_delegate_->set_outputs(outputs);
+
+ if (send_events) {
+ configurator_.OnConfigurationChanged();
+ test_api_.TriggerConfigureTimeout();
+ }
+ }
+
+ // Initializes |configurator_| with a single internal display.
+ void InitWithSingleOutput() {
+ UpdateOutputs(1, false);
+ EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
+ configurator_.Init(false);
+ EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
+ configurator_.ForceInitialConfigure(0);
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ kInitXRandR,
+ GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL)
+ .c_str(),
+ GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
+ kForceDPMS,
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+ }
+
+ base::MessageLoop message_loop_;
+ TestStateController state_controller_;
+ TestMirroringController mirroring_controller_;
+ DisplayConfigurator configurator_;
+ TestObserver observer_;
+ scoped_ptr<ActionLogger> log_;
+ TestNativeDisplayDelegate* native_display_delegate_; // not owned
+ TestTouchscreenDelegate* touchscreen_delegate_; // not owned
+ DisplayConfigurator::TestApi test_api_;
+
+ TestDisplaySnapshot outputs_[2];
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DisplayConfiguratorTest);
+};
+
+} // namespace
+
+TEST_F(DisplayConfiguratorTest, FindDisplayModeMatchingSize) {
+ ScopedVector<const DisplayMode> modes;
+
+ // Fields are width, height, interlaced, refresh rate.
+ modes.push_back(new DisplayMode(gfx::Size(1920, 1200), false, 60.0));
+ // Different rates.
+ modes.push_back(new DisplayMode(gfx::Size(1920, 1080), false, 30.0));
+ modes.push_back(new DisplayMode(gfx::Size(1920, 1080), false, 50.0));
+ modes.push_back(new DisplayMode(gfx::Size(1920, 1080), false, 40.0));
+ modes.push_back(new DisplayMode(gfx::Size(1920, 1080), false, 0.0));
+ // Interlaced vs non-interlaced.
+ modes.push_back(new DisplayMode(gfx::Size(1280, 720), true, 60.0));
+ modes.push_back(new DisplayMode(gfx::Size(1280, 720), false, 40.0));
+ // Interlaced only.
+ modes.push_back(new DisplayMode(gfx::Size(1024, 768), true, 0.0));
+ modes.push_back(new DisplayMode(gfx::Size(1024, 768), true, 40.0));
+ modes.push_back(new DisplayMode(gfx::Size(1024, 768), true, 60.0));
+ // Mixed.
+ modes.push_back(new DisplayMode(gfx::Size(1024, 600), true, 60.0));
+ modes.push_back(new DisplayMode(gfx::Size(1024, 600), false, 40.0));
+ modes.push_back(new DisplayMode(gfx::Size(1024, 600), false, 50.0));
+ // Just one interlaced mode.
+ modes.push_back(new DisplayMode(gfx::Size(640, 480), true, 60.0));
+ // Refresh rate not available.
+ modes.push_back(new DisplayMode(gfx::Size(320, 200), false, 0.0));
+
+ TestDisplaySnapshot output;
+ output.set_modes(modes.get());
+
+ EXPECT_EQ(modes[0],
+ DisplayConfigurator::FindDisplayModeMatchingSize(
+ output, gfx::Size(1920, 1200)));
+
+ // Should pick highest refresh rate.
+ EXPECT_EQ(modes[2],
+ DisplayConfigurator::FindDisplayModeMatchingSize(
+ output, gfx::Size(1920, 1080)));
+
+ // Should pick non-interlaced mode.
+ EXPECT_EQ(modes[6],
+ DisplayConfigurator::FindDisplayModeMatchingSize(
+ output, gfx::Size(1280, 720)));
+
+ // Interlaced only. Should pick one with the highest refresh rate in
+ // interlaced mode.
+ EXPECT_EQ(modes[9],
+ DisplayConfigurator::FindDisplayModeMatchingSize(
+ output, gfx::Size(1024, 768)));
+
+ // Mixed: Should pick one with the highest refresh rate in
+ // interlaced mode.
+ EXPECT_EQ(modes[12],
+ DisplayConfigurator::FindDisplayModeMatchingSize(
+ output, gfx::Size(1024, 600)));
+
+ // Just one interlaced mode.
+ EXPECT_EQ(modes[13],
+ DisplayConfigurator::FindDisplayModeMatchingSize(
+ output, gfx::Size(640, 480)));
+
+ // Refresh rate not available.
+ EXPECT_EQ(modes[14],
+ DisplayConfigurator::FindDisplayModeMatchingSize(
+ output, gfx::Size(320, 200)));
+
+ // No mode found.
+ EXPECT_EQ(NULL,
+ DisplayConfigurator::FindDisplayModeMatchingSize(
+ output, gfx::Size(1440, 900)));
+}
+
+TEST_F(DisplayConfiguratorTest, ConnectSecondOutput) {
+ InitWithSingleOutput();
+
+ // Connect a second output and check that the configurator enters
+ // extended mode.
+ observer_.Reset();
+ state_controller_.set_state(OUTPUT_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],
+ &outputs_[1]).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,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
+ EXPECT_EQ(1, observer_.num_changes());
+
+ observer_.Reset();
+ EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_MIRROR));
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(small_mode_.size(), &outputs_[0], &outputs_[1])
+ .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,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
+ 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], NULL).c_str(),
+ GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
+ EXPECT_EQ(1, observer_.num_changes());
+
+ // Get rid of shared modes to force software mirroring.
+ outputs_[1].set_modes(std::vector<const DisplayMode*>(1, &big_mode_));
+ state_controller_.set_state(OUTPUT_STATE_DUAL_EXTENDED);
+ UpdateOutputs(2, true);
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
+ &outputs_[0],
+ &outputs_[1]).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,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
+
+ observer_.Reset();
+ EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_MIRROR));
+ EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL), log_->GetActionsAndClear());
+ EXPECT_EQ(OUTPUT_STATE_DUAL_EXTENDED, configurator_.output_state());
+ EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
+ EXPECT_EQ(1, observer_.num_changes());
+
+ // Setting OUTPUT_STATE_DUAL_MIRROR should try to reconfigure.
+ observer_.Reset();
+ EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_EXTENDED));
+ EXPECT_EQ(JoinActions(NULL), log_->GetActionsAndClear());
+ EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
+ EXPECT_EQ(1, observer_.num_changes());
+
+ // Set back to software mirror mode.
+ observer_.Reset();
+ EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_MIRROR));
+ EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL), log_->GetActionsAndClear());
+ EXPECT_EQ(OUTPUT_STATE_DUAL_EXTENDED, configurator_.output_state());
+ EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
+ 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], NULL).c_str(),
+ GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
+ EXPECT_EQ(1, observer_.num_changes());
+}
+
+TEST_F(DisplayConfiguratorTest, SetDisplayPower) {
+ InitWithSingleOutput();
+
+ state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
+ observer_.Reset();
+ UpdateOutputs(2, true);
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(small_mode_.size(), &outputs_[0], &outputs_[1])
+ .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,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
+ EXPECT_EQ(1, observer_.num_changes());
+
+ // Turning off the internal display should switch the external display to
+ // its native mode.
+ observer_.Reset();
+ configurator_.SetDisplayPower(
+ chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
+ DisplayConfigurator::kSetDisplayPowerNoFlags);
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(big_mode_.size(), &outputs_[0], &outputs_[1])
+ .c_str(),
+ GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
+ GetCrtcAction(outputs_[1], &big_mode_, gfx::Point(0, 0)).c_str(),
+ kForceDPMS,
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_EQ(OUTPUT_STATE_SINGLE, configurator_.output_state());
+ EXPECT_EQ(1, observer_.num_changes());
+
+ // When all displays are turned off, the framebuffer should switch back
+ // to the mirrored size.
+ observer_.Reset();
+ configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
+ DisplayConfigurator::kSetDisplayPowerNoFlags);
+ EXPECT_EQ(
+ JoinActions(kGrab,
+ GetFramebufferAction(
+ small_mode_.size(), &outputs_[0], &outputs_[1]).c_str(),
+ GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
+ GetCrtcAction(outputs_[1], NULL, gfx::Point(0, 0)).c_str(),
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_EQ(OUTPUT_STATE_DUAL_MIRROR, configurator_.output_state());
+ EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
+ EXPECT_EQ(1, observer_.num_changes());
+
+ // Turn all displays on and check that mirroring is still used.
+ observer_.Reset();
+ configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
+ DisplayConfigurator::kSetDisplayPowerNoFlags);
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(small_mode_.size(), &outputs_[0], &outputs_[1])
+ .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,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_EQ(OUTPUT_STATE_DUAL_MIRROR, configurator_.output_state());
+ EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
+ EXPECT_EQ(1, observer_.num_changes());
+
+ // Get rid of shared modes to force software mirroring.
+ outputs_[1].set_modes(std::vector<const DisplayMode*>(1, &big_mode_));
+ state_controller_.set_state(OUTPUT_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],
+ &outputs_[1]).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,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_EQ(OUTPUT_STATE_DUAL_EXTENDED, configurator_.output_state());
+ EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
+ EXPECT_EQ(1, observer_.num_changes());
+
+ // Turning off the internal display should switch the external display to
+ // its native mode.
+ observer_.Reset();
+ configurator_.SetDisplayPower(
+ chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
+ DisplayConfigurator::kSetDisplayPowerNoFlags);
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(big_mode_.size(), &outputs_[0], &outputs_[1])
+ .c_str(),
+ GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
+ GetCrtcAction(outputs_[1], &big_mode_, gfx::Point(0, 0)).c_str(),
+ kForceDPMS,
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_EQ(OUTPUT_STATE_SINGLE, configurator_.output_state());
+ EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
+ EXPECT_EQ(1, observer_.num_changes());
+
+ // When all displays are turned off, the framebuffer should switch back
+ // to the extended + software mirroring.
+ observer_.Reset();
+ configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
+ DisplayConfigurator::kSetDisplayPowerNoFlags);
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
+ &outputs_[0],
+ &outputs_[1]).c_str(),
+ GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
+ GetCrtcAction(outputs_[1],
+ NULL,
+ gfx::Point(0,
+ small_mode_.size().height() +
+ DisplayConfigurator::kVerticalGap))
+ .c_str(),
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_EQ(OUTPUT_STATE_DUAL_EXTENDED, configurator_.output_state());
+ EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
+ EXPECT_EQ(1, observer_.num_changes());
+
+ // Turn all displays on and check that mirroring is still used.
+ observer_.Reset();
+ configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
+ DisplayConfigurator::kSetDisplayPowerNoFlags);
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
+ &outputs_[0],
+ &outputs_[1]).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,
+ NULL),
+ log_->GetActionsAndClear());
+ EXPECT_EQ(OUTPUT_STATE_DUAL_EXTENDED, configurator_.output_state());
+ EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
+ 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.
+ configurator_.SuspendDisplays();
+ EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
+ configurator_.ResumeDisplays();
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL).c_str(),
+ GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
+ kForceDPMS,
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+
+ // Now turn the display off before suspending and check that the
+ // configurator turns it back on and syncs with the server.
+ configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
+ DisplayConfigurator::kSetDisplayPowerNoFlags);
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL).c_str(),
+ GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+
+ configurator_.SuspendDisplays();
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL).c_str(),
+ GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
+ kForceDPMS,
+ kUngrab,
+ kSync,
+ NULL),
+ log_->GetActionsAndClear());
+
+ configurator_.ResumeDisplays();
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL).c_str(),
+ GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
+ kForceDPMS,
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+
+ // If a second, external display is connected, the displays shouldn't be
+ // powered back on before suspending.
+ state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
+ UpdateOutputs(2, true);
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(small_mode_.size(), &outputs_[0], &outputs_[1])
+ .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,
+ NULL),
+ log_->GetActionsAndClear());
+
+ configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
+ DisplayConfigurator::kSetDisplayPowerNoFlags);
+ EXPECT_EQ(
+ JoinActions(kGrab,
+ GetFramebufferAction(
+ small_mode_.size(), &outputs_[0], &outputs_[1]).c_str(),
+ GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
+ GetCrtcAction(outputs_[1], NULL, gfx::Point(0, 0)).c_str(),
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+
+ configurator_.SuspendDisplays();
+ EXPECT_EQ(JoinActions(kGrab, kUngrab, kSync, NULL),
+ log_->GetActionsAndClear());
+
+ // If a display is disconnected while suspended, the configurator should
+ // pick up the change.
+ UpdateOutputs(1, false);
+ configurator_.ResumeDisplays();
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL).c_str(),
+ GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+}
+
+TEST_F(DisplayConfiguratorTest, Headless) {
+ UpdateOutputs(0, false);
+ EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
+ configurator_.Init(false);
+ EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
+ configurator_.ForceInitialConfigure(0);
+ EXPECT_EQ(JoinActions(kGrab, kInitXRandR, kForceDPMS, kUngrab, NULL),
+ log_->GetActionsAndClear());
+
+ // Not much should happen when the display power state is changed while
+ // no displays are connected.
+ configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
+ DisplayConfigurator::kSetDisplayPowerNoFlags);
+ EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL), log_->GetActionsAndClear());
+ configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
+ DisplayConfigurator::kSetDisplayPowerNoFlags);
+ EXPECT_EQ(JoinActions(kGrab, kForceDPMS, kUngrab, NULL),
+ log_->GetActionsAndClear());
+
+ // Connect an external display and check that it's configured correctly.
+ outputs_[0].set_current_mode(outputs_[1].current_mode());
+ outputs_[0].set_native_mode(outputs_[1].native_mode());
+ outputs_[0].set_modes(outputs_[1].modes());
+ outputs_[0].set_type(outputs_[1].type());
+
+ UpdateOutputs(1, true);
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(big_mode_.size(), &outputs_[0], NULL).c_str(),
+ GetCrtcAction(outputs_[0], &big_mode_, gfx::Point(0, 0)).c_str(),
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+}
+
+TEST_F(DisplayConfiguratorTest, StartWithTwoOutputs) {
+ UpdateOutputs(2, false);
+ EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
+ configurator_.Init(false);
+ EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
+
+ state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
+ configurator_.ForceInitialConfigure(0);
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ kInitXRandR,
+ GetFramebufferAction(small_mode_.size(), &outputs_[0], &outputs_[1])
+ .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,
+ NULL),
+ log_->GetActionsAndClear());
+}
+
+TEST_F(DisplayConfiguratorTest, InvalidOutputStates) {
+ UpdateOutputs(0, false);
+ EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
+ configurator_.Init(false);
+ configurator_.ForceInitialConfigure(0);
+ observer_.Reset();
+ EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_HEADLESS));
+ EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_SINGLE));
+ EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_MIRROR));
+ EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_EXTENDED));
+ EXPECT_EQ(1, observer_.num_changes());
+ EXPECT_EQ(3, observer_.num_failures());
+
+ UpdateOutputs(1, true);
+ observer_.Reset();
+ EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_HEADLESS));
+ EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_SINGLE));
+ EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_MIRROR));
+ EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_EXTENDED));
+ EXPECT_EQ(1, observer_.num_changes());
+ EXPECT_EQ(3, observer_.num_failures());
+
+ state_controller_.set_state(OUTPUT_STATE_DUAL_EXTENDED);
+ UpdateOutputs(2, true);
+ observer_.Reset();
+ EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_HEADLESS));
+ EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_SINGLE));
+ EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_MIRROR));
+ EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_EXTENDED));
+ EXPECT_EQ(2, observer_.num_changes());
+ EXPECT_EQ(2, observer_.num_failures());
+}
+
+TEST_F(DisplayConfiguratorTest, GetOutputStateForDisplaysWithoutId) {
+ outputs_[0].set_has_proper_display_id(false);
+ UpdateOutputs(2, false);
+ configurator_.Init(false);
+ state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
+ configurator_.ForceInitialConfigure(0);
+ EXPECT_EQ(OUTPUT_STATE_DUAL_EXTENDED, configurator_.output_state());
+}
+
+TEST_F(DisplayConfiguratorTest, GetOutputStateForDisplaysWithId) {
+ outputs_[0].set_has_proper_display_id(true);
+ UpdateOutputs(2, false);
+ configurator_.Init(false);
+ state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
+ configurator_.ForceInitialConfigure(0);
+ EXPECT_EQ(OUTPUT_STATE_DUAL_MIRROR, configurator_.output_state());
+}
+
+TEST_F(DisplayConfiguratorTest, UpdateCachedOutputsEvenAfterFailure) {
+ InitWithSingleOutput();
+ const DisplayConfigurator::DisplayStateList* cached =
+ &configurator_.cached_outputs();
+ ASSERT_EQ(static_cast<size_t>(1), cached->size());
+ EXPECT_EQ(outputs_[0].current_mode(), (*cached)[0].display->current_mode());
+
+ // After connecting a second output, check that it shows up in
+ // |cached_outputs_| even if an invalid state is requested.
+ state_controller_.set_state(OUTPUT_STATE_SINGLE);
+ UpdateOutputs(2, true);
+ cached = &configurator_.cached_outputs();
+ ASSERT_EQ(static_cast<size_t>(2), cached->size());
+ EXPECT_EQ(outputs_[0].current_mode(), (*cached)[0].display->current_mode());
+ EXPECT_EQ(outputs_[1].current_mode(), (*cached)[1].display->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].set_current_mode(&big_mode_);
+ outputs_[0].set_native_mode(&big_mode_);
+ outputs_[0].set_modes(std::vector<const DisplayMode*>(1, &big_mode_));
+
+ outputs_[1].set_current_mode(&small_mode_);
+ outputs_[1].set_native_mode(&small_mode_);
+ outputs_[1].set_modes(std::vector<const DisplayMode*>(1, &small_mode_));
+
+ // The small mode should be added to the internal output when requesting
+ // mirrored mode.
+ UpdateOutputs(2, false);
+ state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
+ configurator_.Init(true /* is_panel_fitting_enabled */);
+ configurator_.ForceInitialConfigure(0);
+ EXPECT_EQ(OUTPUT_STATE_DUAL_MIRROR, configurator_.output_state());
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ kInitXRandR,
+ GetAddOutputModeAction(outputs_[0], &small_mode_).c_str(),
+ GetFramebufferAction(small_mode_.size(), &outputs_[0], &outputs_[1])
+ .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,
+ NULL),
+ 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_, observer_.latest_outputs()[0].mirror_mode);
+ EXPECT_EQ(&small_mode_,
+ observer_.latest_outputs()[0].display->current_mode());
+ EXPECT_EQ(&small_mode_, observer_.latest_outputs()[1].mirror_mode);
+ EXPECT_EQ(&small_mode_,
+ observer_.latest_outputs()[1].display->current_mode());
+
+ // Also check that the newly-added small mode is present in the internal
+ // snapshot that was passed to the observer (http://crbug.com/289159).
+ const DisplayConfigurator::DisplayState& state =
+ observer_.latest_outputs()[0];
+ ASSERT_NE(state.display->modes().end(),
+ std::find(state.display->modes().begin(),
+ state.display->modes().end(),
+ &small_mode_));
+}
+
+TEST_F(DisplayConfiguratorTest, OutputProtection) {
+ configurator_.Init(false);
+ configurator_.ForceInitialConfigure(0);
+ EXPECT_NE(kNoActions, log_->GetActionsAndClear());
+
+ DisplayConfigurator::OutputProtectionClientId id =
+ configurator_.RegisterOutputProtectionClient();
+ EXPECT_NE(0u, id);
+
+ // One output.
+ UpdateOutputs(1, true);
+ EXPECT_NE(kNoActions, log_->GetActionsAndClear());
+ uint32_t link_mask = 0;
+ uint32_t protection_mask = 0;
+ EXPECT_TRUE(configurator_.QueryOutputProtectionStatus(
+ id, outputs_[0].display_id(), &link_mask, &protection_mask));
+ EXPECT_EQ(static_cast<uint32_t>(OUTPUT_TYPE_INTERNAL), link_mask);
+ EXPECT_EQ(static_cast<uint32_t>(OUTPUT_PROTECTION_METHOD_NONE),
+ protection_mask);
+ EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
+
+ // Two outputs.
+ UpdateOutputs(2, true);
+ EXPECT_NE(kNoActions, log_->GetActionsAndClear());
+ EXPECT_TRUE(configurator_.QueryOutputProtectionStatus(
+ id, outputs_[1].display_id(), &link_mask, &protection_mask));
+ EXPECT_EQ(static_cast<uint32_t>(OUTPUT_TYPE_HDMI), link_mask);
+ EXPECT_EQ(static_cast<uint32_t>(OUTPUT_PROTECTION_METHOD_NONE),
+ protection_mask);
+ EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
+
+ EXPECT_TRUE(configurator_.EnableOutputProtection(
+ id, outputs_[1].display_id(), OUTPUT_PROTECTION_METHOD_HDCP));
+ EXPECT_EQ(GetSetHDCPStateAction(outputs_[1], HDCP_STATE_DESIRED),
+ log_->GetActionsAndClear());
+
+ // Enable protection.
+ native_display_delegate_->set_hdcp_state(HDCP_STATE_ENABLED);
+ EXPECT_TRUE(configurator_.QueryOutputProtectionStatus(
+ id, outputs_[1].display_id(), &link_mask, &protection_mask));
+ EXPECT_EQ(static_cast<uint32_t>(OUTPUT_TYPE_HDMI), link_mask);
+ EXPECT_EQ(static_cast<uint32_t>(OUTPUT_PROTECTION_METHOD_HDCP),
+ protection_mask);
+ EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
+
+ // Protections should be disabled after unregister.
+ configurator_.UnregisterOutputProtectionClient(id);
+ EXPECT_EQ(GetSetHDCPStateAction(outputs_[1], HDCP_STATE_UNDESIRED),
+ log_->GetActionsAndClear());
+}
+
+TEST_F(DisplayConfiguratorTest, OutputProtectionTwoClients) {
+ DisplayConfigurator::OutputProtectionClientId client1 =
+ configurator_.RegisterOutputProtectionClient();
+ DisplayConfigurator::OutputProtectionClientId client2 =
+ configurator_.RegisterOutputProtectionClient();
+ EXPECT_NE(client1, client2);
+
+ configurator_.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.
+ EXPECT_TRUE(configurator_.EnableOutputProtection(
+ client1, outputs_[1].display_id(), OUTPUT_PROTECTION_METHOD_HDCP));
+ EXPECT_EQ(GetSetHDCPStateAction(outputs_[1], HDCP_STATE_DESIRED).c_str(),
+ log_->GetActionsAndClear());
+ native_display_delegate_->set_hdcp_state(HDCP_STATE_ENABLED);
+
+ uint32_t link_mask = 0;
+ uint32_t protection_mask = 0;
+ EXPECT_TRUE(configurator_.QueryOutputProtectionStatus(
+ client1, outputs_[1].display_id(), &link_mask, &protection_mask));
+ EXPECT_EQ(static_cast<uint32_t>(OUTPUT_TYPE_HDMI), link_mask);
+ EXPECT_EQ(OUTPUT_PROTECTION_METHOD_HDCP, protection_mask);
+
+ EXPECT_TRUE(configurator_.QueryOutputProtectionStatus(
+ client2, outputs_[1].display_id(), &link_mask, &protection_mask));
+ EXPECT_EQ(static_cast<uint32_t>(OUTPUT_TYPE_HDMI), link_mask);
+ EXPECT_EQ(OUTPUT_PROTECTION_METHOD_NONE, protection_mask);
+
+ // Protections will be disabled only if no more clients request them.
+ EXPECT_TRUE(configurator_.EnableOutputProtection(
+ client2, outputs_[1].display_id(), OUTPUT_PROTECTION_METHOD_NONE));
+ EXPECT_EQ(GetSetHDCPStateAction(outputs_[1], HDCP_STATE_DESIRED).c_str(),
+ log_->GetActionsAndClear());
+ EXPECT_TRUE(configurator_.EnableOutputProtection(
+ client1, outputs_[1].display_id(), OUTPUT_PROTECTION_METHOD_NONE));
+ EXPECT_EQ(GetSetHDCPStateAction(outputs_[1], HDCP_STATE_UNDESIRED).c_str(),
+ log_->GetActionsAndClear());
+}
+
+TEST_F(DisplayConfiguratorTest, CTMForMultiScreens) {
+ touchscreen_delegate_->set_configure_touchscreens(true);
+ UpdateOutputs(2, false);
+ configurator_.Init(false);
+ state_controller_.set_state(OUTPUT_STATE_DUAL_EXTENDED);
+ configurator_.ForceInitialConfigure(0);
+
+ const int kDualHeight = small_mode_.size().height() +
+ DisplayConfigurator::kVerticalGap +
+ big_mode_.size().height();
+ const int kDualWidth = big_mode_.size().width();
+
+ DisplayConfigurator::CoordinateTransformation ctm1 =
+ touchscreen_delegate_->GetCTM(1);
+ DisplayConfigurator::CoordinateTransformation ctm2 =
+ touchscreen_delegate_->GetCTM(2);
+
+ EXPECT_EQ(small_mode_.size().height() - 1,
+ round((kDualHeight - 1) * ctm1.y_scale));
+ EXPECT_EQ(0, round((kDualHeight - 1) * ctm1.y_offset));
+
+ EXPECT_EQ(big_mode_.size().height() - 1,
+ round((kDualHeight - 1) * ctm2.y_scale));
+ EXPECT_EQ(small_mode_.size().height() + DisplayConfigurator::kVerticalGap,
+ round((kDualHeight - 1) * ctm2.y_offset));
+
+ EXPECT_EQ(small_mode_.size().width() - 1,
+ round((kDualWidth - 1) * ctm1.x_scale));
+ EXPECT_EQ(0, round((kDualWidth - 1) * ctm1.x_offset));
+
+ EXPECT_EQ(big_mode_.size().width() - 1,
+ round((kDualWidth - 1) * ctm2.x_scale));
+ EXPECT_EQ(0, round((kDualWidth - 1) * ctm2.x_offset));
+}
+
+TEST_F(DisplayConfiguratorTest, HandleConfigureCrtcFailure) {
+ InitWithSingleOutput();
+
+ ScopedVector<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(new DisplayMode(gfx::Size(2560, 1600), false, 60.0));
+ modes.push_back(new DisplayMode(gfx::Size(1024, 768), false, 60.0));
+ modes.push_back(new DisplayMode(gfx::Size(1280, 720), false, 60.0));
+ modes.push_back(new DisplayMode(gfx::Size(1920, 1080), false, 60.0));
+ modes.push_back(new DisplayMode(gfx::Size(1920, 1080), false, 40.0));
+
+ for (unsigned int i = 0; i < arraysize(outputs_); i++) {
+ outputs_[i].set_modes(modes.get());
+ outputs_[i].set_current_mode(modes[0]);
+ outputs_[i].set_native_mode(modes[0]);
+ }
+
+ configurator_.Init(false);
+
+ // First test simply fails in OUTPUT_STATE_SINGLE mode. This is probably
+ // unrealistic but the 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(OUTPUT_STATE_SINGLE);
+ UpdateOutputs(1, true);
+
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(big_mode_.size(), &outputs_[0], NULL).c_str(),
+ GetCrtcAction(outputs_[0], modes[0], gfx::Point(0, 0)).c_str(),
+ GetCrtcAction(outputs_[0], modes[3], gfx::Point(0, 0)).c_str(),
+ GetCrtcAction(outputs_[0], modes[2], gfx::Point(0, 0)).c_str(),
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+
+ // 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(OUTPUT_STATE_DUAL_MIRROR);
+ UpdateOutputs(2, true);
+
+ EXPECT_EQ(
+ JoinActions(
+ kGrab,
+ GetFramebufferAction(modes[0]->size(), &outputs_[0], &outputs_[1])
+ .c_str(),
+ GetCrtcAction(outputs_[0], modes[0], 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], gfx::Point(0, 0)).c_str(),
+ // Then attempt to configure crtc1 with the first mode.
+ GetCrtcAction(outputs_[1], modes[0], gfx::Point(0, 0)).c_str(),
+ GetCrtcAction(outputs_[1], modes[3], 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],
+ &outputs_[1]).c_str(),
+ GetCrtcAction(outputs_[0], modes[0], gfx::Point(0, 0)).c_str(),
+ GetCrtcAction(outputs_[0], modes[3], gfx::Point(0, 0)).c_str(),
+ GetCrtcAction(outputs_[1],
+ modes[0],
+ gfx::Point(0,
+ modes[0]->size().height() +
+ DisplayConfigurator::kVerticalGap))
+ .c_str(),
+ GetCrtcAction(outputs_[1],
+ modes[3],
+ gfx::Point(0,
+ modes[0]->size().height() +
+ DisplayConfigurator::kVerticalGap))
+ .c_str(),
+ kUngrab,
+ NULL),
+ log_->GetActionsAndClear());
+}
+
+} // namespace ui