// Copyright (c) 2012 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 "chromeos/display/output_configurator.h"
#include <cmath>
#include <cstdarg>
#include <map>
#include <string>
#include <vector>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/stringprintf.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace {
// Strings returned by TestDelegate::GetActionsAndClear() to describe various
// actions that were performed.
const char kInitXRandR[] = "init";
const char kUpdateXRandR[] = "update";
const char kGrab[] = "grab";
const char kUngrab[] = "ungrab";
const char kSync[] = "sync";
const char kForceDPMS[] = "dpms";
const char kProjectingOn[] = "projecting";
const char kProjectingOff[] = "not_projecting";
// String returned by TestDelegate::GetActionsAndClear() if no actions were
// requested.
const char kNoActions[] = "";
// Returns a string describing a TestDelegate::SetBackgroundColor() call.
std::string GetBackgroundAction(uint32 color_argb) {
return base::StringPrintf("background(0x%x)", color_argb);
}
// Returns a string describing a TestDelegate::AddOutputMode() call.
std::string GetAddOutputModeAction(RROutput output, RRMode mode) {
return base::StringPrintf("add_mode(output=%lu,mode=%lu)", output, mode);
}
// Returns a string describing a TestDelegate::ConfigureCrtc() call.
std::string GetCrtcAction(RRCrtc crtc,
int x,
int y,
RRMode mode,
RROutput output) {
return base::StringPrintf("crtc(crtc=%lu,x=%d,y=%d,mode=%lu,output=%lu)",
crtc, x, y, mode, output);
}
// Returns a string describing a TestDelegate::CreateFramebuffer() call.
std::string GetFramebufferAction(int width,
int height,
RRCrtc crtc1,
RRCrtc crtc2) {
return base::StringPrintf(
"framebuffer(width=%d,height=%d,crtc1=%lu,crtc2=%lu)",
width, height, crtc1, crtc2);
}
// Returns a string describing a TestDelegate::ConfigureCTM() call.
std::string GetCTMAction(
int device_id,
const OutputConfigurator::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 TestDelegate::SetHDCPState() call.
std::string GetSetHDCPStateAction(RROutput id, HDCPState state) {
return base::StringPrintf("set_hdcp(id=%lu,state=%d)", id, state);
}
// Joins a sequence of strings describing actions (e.g. kScreenDim) such
// that they can be compared against a string returned by
// TestDelegate::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 TestDelegate : public OutputConfigurator::Delegate {
public:
static const int kXRandREventBase = 10;
TestDelegate()
: configure_crtc_result_(true),
hdcp_state_(HDCP_STATE_UNDESIRED) {}
virtual ~TestDelegate() {}
const std::vector<OutputConfigurator::OutputSnapshot>& outputs() const {
return outputs_;
}
void set_outputs(
const std::vector<OutputConfigurator::OutputSnapshot>& outputs) {
outputs_ = outputs;
}
void set_configure_crtc_result(bool result) {
configure_crtc_result_ = result;
}
void set_hdcp_state(HDCPState state) { hdcp_state_ = state; }
// 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;
}
const OutputConfigurator::CoordinateTransformation& get_ctm(
int touch_device_id) {
return ctms_[touch_device_id];
}
// OutputConfigurator::Delegate overrides:
virtual void InitXRandRExtension(int* event_base) OVERRIDE {
AppendAction(kInitXRandR);
*event_base = kXRandREventBase;
}
virtual void UpdateXRandRConfiguration(
const base::NativeEvent& event) OVERRIDE { AppendAction(kUpdateXRandR); }
virtual void GrabServer() OVERRIDE { AppendAction(kGrab); }
virtual void UngrabServer() OVERRIDE { AppendAction(kUngrab); }
virtual void SyncWithServer() OVERRIDE { AppendAction(kSync); }
virtual void SetBackgroundColor(uint32 color_argb) OVERRIDE {
AppendAction(GetBackgroundAction(color_argb));
}
virtual void ForceDPMSOn() OVERRIDE { AppendAction(kForceDPMS); }
virtual std::vector<OutputConfigurator::OutputSnapshot> GetOutputs()
OVERRIDE {
return outputs_;
}
virtual void AddOutputMode(RROutput output, RRMode mode) OVERRIDE {
AppendAction(GetAddOutputModeAction(output, mode));
}
virtual bool ConfigureCrtc(RRCrtc crtc,
RRMode mode,
RROutput output,
int x,
int y) OVERRIDE {
AppendAction(GetCrtcAction(crtc, x, y, mode, output));
return configure_crtc_result_;
}
virtual void CreateFrameBuffer(
int width,
int height,
const std::vector<OutputConfigurator::OutputSnapshot>& outputs) OVERRIDE {
AppendAction(
GetFramebufferAction(width,
height,
outputs.size() >= 1 ? outputs[0].crtc : 0,
outputs.size() >= 2 ? outputs[1].crtc : 0));
}
virtual void ConfigureCTM(
int touch_device_id,
const OutputConfigurator::CoordinateTransformation& ctm) OVERRIDE {
AppendAction(GetCTMAction(touch_device_id, ctm));
ctms_[touch_device_id] = ctm;
}
virtual void SendProjectingStateToPowerManager(bool projecting) OVERRIDE {
AppendAction(projecting ? kProjectingOn : kProjectingOff);
}
virtual bool GetHDCPState(RROutput id, HDCPState* state) OVERRIDE {
*state = hdcp_state_;
return true;
}
virtual bool SetHDCPState(RROutput id, HDCPState state) OVERRIDE {
AppendAction(GetSetHDCPStateAction(id, state));
return true;
}
private:
struct ModeDetails {
ModeDetails() : width(0), height(0), interlaced(false) {}
ModeDetails(int width, int height, bool interlaced)
: width(width),
height(height),
interlaced(interlaced) {}
int width;
int height;
bool interlaced;
};
void AppendAction(const std::string& action) {
if (!actions_.empty())
actions_ += ",";
actions_ += action;
}
std::map<RRMode, ModeDetails> modes_;
// Most-recently-configured transformation matrices, keyed by touch device ID.
std::map<int, OutputConfigurator::CoordinateTransformation> ctms_;
// Outputs to be returned by GetOutputs().
std::vector<OutputConfigurator::OutputSnapshot> outputs_;
std::string actions_;
// Return value returned by ConfigureCrtc().
bool configure_crtc_result_;
// Result value of GetHDCPState().
HDCPState hdcp_state_;
DISALLOW_COPY_AND_ASSIGN(TestDelegate);
};
class TestObserver : public OutputConfigurator::Observer {
public:
explicit TestObserver(OutputConfigurator* 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 std::vector<OutputConfigurator::OutputSnapshot>& 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_ = STATE_INVALID;
}
// OutputConfigurator::Observer overrides:
virtual void OnDisplayModeChanged(
const std::vector<OutputConfigurator::OutputSnapshot>& outputs) OVERRIDE {
num_changes_++;
latest_outputs_ = outputs;
}
virtual void OnDisplayModeChangeFailed(OutputState failed_new_state)
OVERRIDE {
num_failures_++;
latest_failed_state_ = failed_new_state;
}
private:
OutputConfigurator* configurator_; // Not owned.
// Number of times that OnDisplayMode*() has been called.
int num_changes_;
int num_failures_;
// Parameters most recently passed to OnDisplayMode*().
std::vector<OutputConfigurator::OutputSnapshot> latest_outputs_;
OutputState latest_failed_state_;
DISALLOW_COPY_AND_ASSIGN(TestObserver);
};
class TestStateController : public OutputConfigurator::StateController {
public:
TestStateController() : state_(STATE_DUAL_EXTENDED) {}
virtual ~TestStateController() {}
void set_state(OutputState state) { state_ = state; }
// OutputConfigurator::StateController overrides:
virtual OutputState GetStateForDisplayIds(
const std::vector<int64>& outputs) const OVERRIDE { return state_; }
virtual bool GetResolutionForDisplayId(
int64 display_id,
int *width,
int *height) const OVERRIDE {
return false;
}
private:
OutputState state_;
DISALLOW_COPY_AND_ASSIGN(TestStateController);
};
class TestMirroringController
: public OutputConfigurator::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 OutputConfiguratorTest : public testing::Test {
public:
// Predefined modes that can be used by outputs.
static const RRMode kSmallModeId;
static const int kSmallModeWidth;
static const int kSmallModeHeight;
static const RRMode kBigModeId;
static const int kBigModeWidth;
static const int kBigModeHeight;
OutputConfiguratorTest()
: observer_(&configurator_),
test_api_(&configurator_, TestDelegate::kXRandREventBase) {}
virtual ~OutputConfiguratorTest() {}
virtual void SetUp() OVERRIDE {
delegate_ = new TestDelegate();
configurator_.SetDelegateForTesting(
scoped_ptr<OutputConfigurator::Delegate>(delegate_));
configurator_.set_state_controller(&state_controller_);
configurator_.set_mirroring_controller(&mirroring_controller_);
OutputConfigurator::ModeInfo small_mode_info;
small_mode_info.width = kSmallModeWidth;
small_mode_info.height = kSmallModeHeight;
OutputConfigurator::ModeInfo big_mode_info;
big_mode_info.width = kBigModeWidth;
big_mode_info.height = kBigModeHeight;
OutputConfigurator::OutputSnapshot* o = &outputs_[0];
o->output = 1;
o->crtc = 10;
o->current_mode = kSmallModeId;
o->native_mode = kSmallModeId;
o->is_internal = true;
o->type = OUTPUT_TYPE_INTERNAL;
o->is_aspect_preserving_scaling = true;
o->mode_infos[kSmallModeId] = small_mode_info;
o->has_display_id = true;
o->display_id = 123;
o->index = 0;
o = &outputs_[1];
o->output = 2;
o->crtc = 11;
o->current_mode = kBigModeId;
o->native_mode = kBigModeId;
o->is_internal = false;
o->type = OUTPUT_TYPE_HDMI;
o->is_aspect_preserving_scaling = true;
o->mode_infos[kSmallModeId] = small_mode_info;
o->mode_infos[kBigModeId] = big_mode_info;
o->has_display_id = true;
o->display_id = 456;
o->index = 1;
UpdateOutputs(2, false);
}
protected:
// Configures |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<OutputConfigurator::OutputSnapshot> outputs;
for (size_t i = 0; i < num_outputs; ++i)
outputs.push_back(outputs_[i]);
delegate_->set_outputs(outputs);
if (send_events) {
test_api_.SendScreenChangeEvent();
for (size_t i = 0; i < arraysize(outputs_); ++i) {
const OutputConfigurator::OutputSnapshot output = outputs_[i];
bool connected = i < num_outputs;
test_api_.SendOutputChangeEvent(
output.output, output.crtc, output.current_mode, connected);
}
test_api_.TriggerConfigureTimeout();
}
}
// Initializes |configurator_| with a single internal display.
void InitWithSingleOutput() {
UpdateOutputs(1, false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
configurator_.Init(false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
configurator_.Start(0);
EXPECT_EQ(JoinActions(kGrab, kInitXRandR,
GetFramebufferAction(kSmallModeWidth,
kSmallModeHeight, outputs_[0].crtc, 0).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
kForceDPMS, kUngrab, kProjectingOff, NULL),
delegate_->GetActionsAndClear());
}
base::MessageLoop message_loop_;
TestStateController state_controller_;
TestMirroringController mirroring_controller_;
OutputConfigurator configurator_;
TestObserver observer_;
TestDelegate* delegate_; // not owned
OutputConfigurator::TestApi test_api_;
OutputConfigurator::OutputSnapshot outputs_[2];
private:
DISALLOW_COPY_AND_ASSIGN(OutputConfiguratorTest);
};
const RRMode OutputConfiguratorTest::kSmallModeId = 20;
const int OutputConfiguratorTest::kSmallModeWidth = 1366;
const int OutputConfiguratorTest::kSmallModeHeight = 768;
const RRMode OutputConfiguratorTest::kBigModeId = 21;
const int OutputConfiguratorTest::kBigModeWidth = 2560;
const int OutputConfiguratorTest::kBigModeHeight = 1600;
} // namespace
TEST_F(OutputConfiguratorTest, FindOutputModeMatchingSize) {
OutputConfigurator::OutputSnapshot output;
// Fields are width, height, interlaced, refresh rate.
output.mode_infos[11] = OutputConfigurator::ModeInfo(1920, 1200, false, 60.0);
// Different rates.
output.mode_infos[12] = OutputConfigurator::ModeInfo(1920, 1080, false, 30.0);
output.mode_infos[13] = OutputConfigurator::ModeInfo(1920, 1080, false, 50.0);
output.mode_infos[14] = OutputConfigurator::ModeInfo(1920, 1080, false, 40.0);
output.mode_infos[15] = OutputConfigurator::ModeInfo(1920, 1080, false, 0.0);
// Interlaced vs non-interlaced.
output.mode_infos[16] = OutputConfigurator::ModeInfo(1280, 720, true, 60.0);
output.mode_infos[17] = OutputConfigurator::ModeInfo(1280, 720, false, 40.0);
// Interlaced only.
output.mode_infos[18] = OutputConfigurator::ModeInfo(1024, 768, true, 0.0);
output.mode_infos[19] = OutputConfigurator::ModeInfo(1024, 768, true, 40.0);
output.mode_infos[20] = OutputConfigurator::ModeInfo(1024, 768, true, 60.0);
// Mixed.
output.mode_infos[21] = OutputConfigurator::ModeInfo(1024, 600, true, 60.0);
output.mode_infos[22] = OutputConfigurator::ModeInfo(1024, 600, false, 40.0);
output.mode_infos[23] = OutputConfigurator::ModeInfo(1024, 600, false, 50.0);
// Just one interlaced mode.
output.mode_infos[24] = OutputConfigurator::ModeInfo(640, 480, true, 60.0);
// Refresh rate not available.
output.mode_infos[25] = OutputConfigurator::ModeInfo(320, 200, false, 0.0);
EXPECT_EQ(11u, OutputConfigurator::FindOutputModeMatchingSize(output,
1920, 1200));
// Should pick highest refresh rate.
EXPECT_EQ(13u, OutputConfigurator::FindOutputModeMatchingSize(output,
1920, 1080));
// Should pick non-interlaced mode.
EXPECT_EQ(17u, OutputConfigurator::FindOutputModeMatchingSize(output,
1280, 720));
// Interlaced only. Should pick one with the highest refresh rate in
// interlaced mode.
EXPECT_EQ(20u, OutputConfigurator::FindOutputModeMatchingSize(output,
1024, 768));
// Mixed: Should pick one with the highest refresh rate in
// interlaced mode.
EXPECT_EQ(23u, OutputConfigurator::FindOutputModeMatchingSize(output,
1024, 600));
// Just one interlaced mode.
EXPECT_EQ(24u, OutputConfigurator::FindOutputModeMatchingSize(output,
640, 480));
// Refresh rate not available.
EXPECT_EQ(25u, OutputConfigurator::FindOutputModeMatchingSize(output,
320, 200));
// No mode found.
EXPECT_EQ(0u, OutputConfigurator::FindOutputModeMatchingSize(output,
1440, 900));
}
TEST_F(OutputConfiguratorTest, ConnectSecondOutput) {
InitWithSingleOutput();
// Connect a second output and check that the configurator enters
// extended mode.
observer_.Reset();
state_controller_.set_state(STATE_DUAL_EXTENDED);
UpdateOutputs(2, true);
const int kDualHeight =
kSmallModeHeight + OutputConfigurator::kVerticalGap + kBigModeHeight;
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kBigModeWidth, kDualHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0,
kSmallModeHeight + OutputConfigurator::kVerticalGap,
kBigModeId, outputs_[1].output).c_str(),
kUngrab, kProjectingOn, NULL),
delegate_->GetActionsAndClear());
EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
EXPECT_EQ(1, observer_.num_changes());
observer_.Reset();
EXPECT_TRUE(configurator_.SetDisplayMode(STATE_DUAL_MIRROR));
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId,
outputs_[1].output).c_str(),
kUngrab, NULL),
delegate_->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(kUpdateXRandR, kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, 0).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
kUngrab, kProjectingOff, NULL),
delegate_->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].mode_infos.erase(kSmallModeId);
state_controller_.set_state(STATE_DUAL_EXTENDED);
UpdateOutputs(2, true);
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kBigModeWidth, kDualHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0,
kSmallModeHeight + OutputConfigurator::kVerticalGap,
kBigModeId, outputs_[1].output).c_str(),
kUngrab, kProjectingOn, NULL),
delegate_->GetActionsAndClear());
EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
observer_.Reset();
EXPECT_TRUE(configurator_.SetDisplayMode(STATE_DUAL_MIRROR));
EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL), delegate_->GetActionsAndClear());
EXPECT_EQ(STATE_DUAL_EXTENDED, configurator_.output_state());
EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
EXPECT_EQ(1, observer_.num_changes());
// Setting STATE_DUAL_MIRROR should try to reconfigure.
observer_.Reset();
EXPECT_TRUE(configurator_.SetDisplayMode(STATE_DUAL_EXTENDED));
EXPECT_EQ(JoinActions(NULL), delegate_->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(STATE_DUAL_MIRROR));
EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL),
delegate_->GetActionsAndClear());
EXPECT_EQ(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(kUpdateXRandR, kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, 0).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
kUngrab, kProjectingOff, NULL),
delegate_->GetActionsAndClear());
EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
EXPECT_EQ(1, observer_.num_changes());
}
TEST_F(OutputConfiguratorTest, SetDisplayPower) {
InitWithSingleOutput();
state_controller_.set_state(STATE_DUAL_MIRROR);
observer_.Reset();
UpdateOutputs(2, true);
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId,
outputs_[1].output).c_str(),
kUngrab, kProjectingOn, NULL),
delegate_->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(DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
OutputConfigurator::kSetDisplayPowerNoFlags);
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kBigModeWidth, kBigModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, 0,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, kBigModeId,
outputs_[1].output).c_str(),
kForceDPMS, kUngrab, NULL),
delegate_->GetActionsAndClear());
EXPECT_EQ(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(DISPLAY_POWER_ALL_OFF,
OutputConfigurator::kSetDisplayPowerNoFlags);
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, 0,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, 0,
outputs_[1].output).c_str(),
kUngrab, NULL),
delegate_->GetActionsAndClear());
EXPECT_EQ(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(DISPLAY_POWER_ALL_ON,
OutputConfigurator::kSetDisplayPowerNoFlags);
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId,
outputs_[1].output).c_str(),
kForceDPMS, kUngrab, NULL),
delegate_->GetActionsAndClear());
EXPECT_EQ(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].mode_infos.erase(kSmallModeId);
state_controller_.set_state(STATE_DUAL_MIRROR);
observer_.Reset();
UpdateOutputs(2, true);
const int kDualHeight =
kSmallModeHeight + OutputConfigurator::kVerticalGap + kBigModeHeight;
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kBigModeWidth, kDualHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0,
kSmallModeHeight + OutputConfigurator::kVerticalGap,
kBigModeId, outputs_[1].output).c_str(),
kUngrab, kProjectingOn, NULL),
delegate_->GetActionsAndClear());
EXPECT_EQ(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(DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
OutputConfigurator::kSetDisplayPowerNoFlags);
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kBigModeWidth, kBigModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, 0,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, kBigModeId,
outputs_[1].output).c_str(),
kForceDPMS, kUngrab, NULL),
delegate_->GetActionsAndClear());
EXPECT_EQ(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(DISPLAY_POWER_ALL_OFF,
OutputConfigurator::kSetDisplayPowerNoFlags);
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kBigModeWidth, kDualHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, 0,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0,
kSmallModeHeight + OutputConfigurator::kVerticalGap,
0, outputs_[1].output).c_str(),
kUngrab, NULL),
delegate_->GetActionsAndClear());
EXPECT_EQ(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(DISPLAY_POWER_ALL_ON,
OutputConfigurator::kSetDisplayPowerNoFlags);
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kBigModeWidth, kDualHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0,
kSmallModeHeight + OutputConfigurator::kVerticalGap,
kBigModeId, outputs_[1].output).c_str(),
kForceDPMS, kUngrab, NULL),
delegate_->GetActionsAndClear());
EXPECT_EQ(STATE_DUAL_EXTENDED, configurator_.output_state());
EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
EXPECT_EQ(1, observer_.num_changes());
}
TEST_F(OutputConfiguratorTest, 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, delegate_->GetActionsAndClear());
configurator_.ResumeDisplays();
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, 0).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
kForceDPMS, kUngrab, NULL),
delegate_->GetActionsAndClear());
// Now turn the display off before suspending and check that the
// configurator turns it back on and syncs with the server.
configurator_.SetDisplayPower(DISPLAY_POWER_ALL_OFF,
OutputConfigurator::kSetDisplayPowerNoFlags);
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, 0).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, 0,
outputs_[0].output).c_str(),
kUngrab, NULL),
delegate_->GetActionsAndClear());
configurator_.SuspendDisplays();
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, 0).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
kForceDPMS, kUngrab, kSync, NULL),
delegate_->GetActionsAndClear());
configurator_.ResumeDisplays();
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, 0).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
kForceDPMS, kUngrab, NULL),
delegate_->GetActionsAndClear());
// If a second, external display is connected, the displays shouldn't be
// powered back on before suspending.
state_controller_.set_state(STATE_DUAL_MIRROR);
UpdateOutputs(2, true);
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId,
outputs_[1].output).c_str(),
kUngrab, kProjectingOn, NULL),
delegate_->GetActionsAndClear());
configurator_.SetDisplayPower(DISPLAY_POWER_ALL_OFF,
OutputConfigurator::kSetDisplayPowerNoFlags);
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, 0,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, 0,
outputs_[1].output).c_str(),
kUngrab, NULL),
delegate_->GetActionsAndClear());
configurator_.SuspendDisplays();
EXPECT_EQ(JoinActions(kGrab, kUngrab, kSync, NULL),
delegate_->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(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, 0).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, 0,
outputs_[0].output).c_str(),
kUngrab, NULL),
delegate_->GetActionsAndClear());
}
TEST_F(OutputConfiguratorTest, Headless) {
UpdateOutputs(0, false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
configurator_.Init(false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
configurator_.Start(0);
EXPECT_EQ(JoinActions(kGrab, kInitXRandR, kForceDPMS, kUngrab,
kProjectingOff, NULL),
delegate_->GetActionsAndClear());
// Not much should happen when the display power state is changed while
// no displays are connected.
configurator_.SetDisplayPower(DISPLAY_POWER_ALL_OFF,
OutputConfigurator::kSetDisplayPowerNoFlags);
EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL), delegate_->GetActionsAndClear());
configurator_.SetDisplayPower(DISPLAY_POWER_ALL_ON,
OutputConfigurator::kSetDisplayPowerNoFlags);
EXPECT_EQ(JoinActions(kGrab, kForceDPMS, kUngrab, NULL),
delegate_->GetActionsAndClear());
// Connect an external display and check that it's configured correctly.
outputs_[0] = outputs_[1];
UpdateOutputs(1, true);
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kBigModeWidth, kBigModeHeight,
outputs_[0].crtc, 0).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kBigModeId,
outputs_[0].output).c_str(),
kUngrab, kProjectingOff, NULL),
delegate_->GetActionsAndClear());
}
TEST_F(OutputConfiguratorTest, StartWithTwoOutputs) {
UpdateOutputs(2, false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
configurator_.Init(false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
state_controller_.set_state(STATE_DUAL_MIRROR);
configurator_.Start(0);
EXPECT_EQ(JoinActions(kGrab, kInitXRandR,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId,
outputs_[1].output).c_str(),
kForceDPMS, kUngrab, kProjectingOn, NULL),
delegate_->GetActionsAndClear());
}
TEST_F(OutputConfiguratorTest, InvalidOutputStates) {
UpdateOutputs(0, false);
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
configurator_.Init(false);
configurator_.Start(0);
observer_.Reset();
EXPECT_TRUE(configurator_.SetDisplayMode(STATE_HEADLESS));
EXPECT_FALSE(configurator_.SetDisplayMode(STATE_SINGLE));
EXPECT_FALSE(configurator_.SetDisplayMode(STATE_DUAL_MIRROR));
EXPECT_FALSE(configurator_.SetDisplayMode(STATE_DUAL_EXTENDED));
EXPECT_EQ(1, observer_.num_changes());
EXPECT_EQ(3, observer_.num_failures());
UpdateOutputs(1, true);
observer_.Reset();
EXPECT_FALSE(configurator_.SetDisplayMode(STATE_HEADLESS));
EXPECT_TRUE(configurator_.SetDisplayMode(STATE_SINGLE));
EXPECT_FALSE(configurator_.SetDisplayMode(STATE_DUAL_MIRROR));
EXPECT_FALSE(configurator_.SetDisplayMode(STATE_DUAL_EXTENDED));
EXPECT_EQ(1, observer_.num_changes());
EXPECT_EQ(3, observer_.num_failures());
state_controller_.set_state(STATE_DUAL_EXTENDED);
UpdateOutputs(2, true);
observer_.Reset();
EXPECT_FALSE(configurator_.SetDisplayMode(STATE_HEADLESS));
EXPECT_FALSE(configurator_.SetDisplayMode(STATE_SINGLE));
EXPECT_TRUE(configurator_.SetDisplayMode(STATE_DUAL_MIRROR));
EXPECT_TRUE(configurator_.SetDisplayMode(STATE_DUAL_EXTENDED));
EXPECT_EQ(2, observer_.num_changes());
EXPECT_EQ(2, observer_.num_failures());
}
TEST_F(OutputConfiguratorTest, GetOutputStateForDisplaysWithoutId) {
outputs_[0].has_display_id = false;
UpdateOutputs(2, false);
configurator_.Init(false);
state_controller_.set_state(STATE_DUAL_MIRROR);
configurator_.Start(0);
EXPECT_EQ(STATE_DUAL_EXTENDED, configurator_.output_state());
}
TEST_F(OutputConfiguratorTest, GetOutputStateForDisplaysWithId) {
outputs_[0].has_display_id = true;
UpdateOutputs(2, false);
configurator_.Init(false);
state_controller_.set_state(STATE_DUAL_MIRROR);
configurator_.Start(0);
EXPECT_EQ(STATE_DUAL_MIRROR, configurator_.output_state());
}
TEST_F(OutputConfiguratorTest, AvoidUnnecessaryProbes) {
InitWithSingleOutput();
// X sends several events just after the configurator starts. Check that
// the output change events don't trigger an additional probe, which can
// block the UI thread.
test_api_.SendScreenChangeEvent();
EXPECT_EQ(kUpdateXRandR, delegate_->GetActionsAndClear());
test_api_.SendOutputChangeEvent(
outputs_[0].output, outputs_[0].crtc, outputs_[0].current_mode, true);
test_api_.SendOutputChangeEvent(
outputs_[1].output, outputs_[1].crtc, outputs_[1].current_mode, false);
EXPECT_FALSE(test_api_.TriggerConfigureTimeout());
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
// Send an event stating that the second output is connected and check
// that it gets updated.
state_controller_.set_state(STATE_DUAL_MIRROR);
UpdateOutputs(2, false);
test_api_.SendOutputChangeEvent(
outputs_[1].output, outputs_[1].crtc, outputs_[1].current_mode, true);
EXPECT_TRUE(test_api_.TriggerConfigureTimeout());
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId,
outputs_[1].output).c_str(),
kUngrab, kProjectingOn, NULL),
delegate_->GetActionsAndClear());
// An event about the second output changing modes should trigger another
// reconfigure.
test_api_.SendOutputChangeEvent(
outputs_[1].output, outputs_[1].crtc, outputs_[1].native_mode, true);
EXPECT_TRUE(test_api_.TriggerConfigureTimeout());
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId,
outputs_[1].output).c_str(),
kUngrab, kProjectingOn, NULL),
delegate_->GetActionsAndClear());
// Disconnect the second output.
UpdateOutputs(1, true);
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, 0).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
kUngrab, kProjectingOff, NULL),
delegate_->GetActionsAndClear());
// An additional event about the second output being disconnected should
// be ignored.
test_api_.SendOutputChangeEvent(
outputs_[1].output, outputs_[1].crtc, outputs_[1].current_mode, false);
EXPECT_FALSE(test_api_.TriggerConfigureTimeout());
EXPECT_EQ(kNoActions, delegate_->GetActionsAndClear());
// Tell the delegate to report failure, which should result in the
// second output sticking with its native mode.
delegate_->set_configure_crtc_result(false);
UpdateOutputs(2, true);
EXPECT_EQ(JoinActions(kUpdateXRandR, kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId,
outputs_[1].output).c_str(),
kUngrab, kProjectingOn, NULL),
delegate_->GetActionsAndClear());
// An change event reporting a mode change on the second output should
// trigger another reconfigure.
delegate_->set_configure_crtc_result(true);
test_api_.SendOutputChangeEvent(
outputs_[1].output, outputs_[1].crtc, outputs_[1].mirror_mode, true);
EXPECT_TRUE(test_api_.TriggerConfigureTimeout());
EXPECT_EQ(JoinActions(kGrab,
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId,
outputs_[1].output).c_str(),
kUngrab, kProjectingOn, NULL),
delegate_->GetActionsAndClear());
}
TEST_F(OutputConfiguratorTest, UpdateCachedOutputsEvenAfterFailure) {
InitWithSingleOutput();
const std::vector<OutputConfigurator::OutputSnapshot>* cached =
&test_api_.cached_outputs();
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_outputs_| even if an invalid state is requested.
state_controller_.set_state(STATE_SINGLE);
UpdateOutputs(2, true);
cached = &test_api_.cached_outputs();
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(OutputConfiguratorTest, PanelFitting) {
// Configure the internal display to support only the big mode and the
// external display to support only the small mode.
outputs_[0].current_mode = kBigModeId;
outputs_[0].native_mode = kBigModeId;
outputs_[0].mode_infos.clear();
outputs_[0].mode_infos[kBigModeId] = OutputConfigurator::ModeInfo(
kBigModeWidth, kBigModeHeight, false, 60.0);
outputs_[1].current_mode = kSmallModeId;
outputs_[1].native_mode = kSmallModeId;
outputs_[1].mode_infos.clear();
outputs_[1].mode_infos[kSmallModeId] = OutputConfigurator::ModeInfo(
kSmallModeWidth, kSmallModeHeight, false, 60.0);
// The small mode should be added to the internal output when requesting
// mirrored mode.
UpdateOutputs(2, false);
state_controller_.set_state(STATE_DUAL_MIRROR);
configurator_.Init(true /* is_panel_fitting_enabled */);
configurator_.Start(0);
EXPECT_EQ(STATE_DUAL_MIRROR, configurator_.output_state());
EXPECT_EQ(JoinActions(kGrab, kInitXRandR,
GetAddOutputModeAction(
outputs_[0].output, kSmallModeId).c_str(),
GetFramebufferAction(kSmallModeWidth, kSmallModeHeight,
outputs_[0].crtc, outputs_[1].crtc).c_str(),
GetCrtcAction(outputs_[0].crtc, 0, 0, kSmallModeId,
outputs_[0].output).c_str(),
GetCrtcAction(outputs_[1].crtc, 0, 0, kSmallModeId,
outputs_[1].output).c_str(),
kForceDPMS, kUngrab, kProjectingOn, NULL),
delegate_->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(kSmallModeId, observer_.latest_outputs()[0].mirror_mode);
EXPECT_EQ(kSmallModeId, observer_.latest_outputs()[0].current_mode);
EXPECT_EQ(kSmallModeId, observer_.latest_outputs()[1].mirror_mode);
EXPECT_EQ(kSmallModeId, observer_.latest_outputs()[1].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 OutputConfigurator::ModeInfo* info = OutputConfigurator::GetModeInfo(
observer_.latest_outputs()[0], kSmallModeId);
ASSERT_TRUE(info);
EXPECT_EQ(kSmallModeWidth, info->width);
EXPECT_EQ(kSmallModeHeight, info->height);
}
TEST_F(OutputConfiguratorTest, OutputProtection) {
configurator_.Init(false);
configurator_.Start(0);
EXPECT_NE(kNoActions, delegate_->GetActionsAndClear());
OutputConfigurator::OutputProtectionClientId id =
configurator_.RegisterOutputProtectionClient();
EXPECT_NE(0u, id);
// One output.
UpdateOutputs(1, true);
EXPECT_NE(kNoActions, delegate_->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, delegate_->GetActionsAndClear());
// Two outputs.
UpdateOutputs(2, true);
EXPECT_NE(kNoActions, delegate_->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, delegate_->GetActionsAndClear());
EXPECT_TRUE(
configurator_.EnableOutputProtection(id,
outputs_[1].display_id,
OUTPUT_PROTECTION_METHOD_HDCP));
EXPECT_EQ(GetSetHDCPStateAction(outputs_[1].output, HDCP_STATE_DESIRED),
delegate_->GetActionsAndClear());
// Enable protection.
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, delegate_->GetActionsAndClear());
// Protections should be disabled after unregister.
configurator_.UnregisterOutputProtectionClient(id);
EXPECT_EQ(GetSetHDCPStateAction(outputs_[1].output, HDCP_STATE_UNDESIRED),
delegate_->GetActionsAndClear());
}
TEST_F(OutputConfiguratorTest, OutputProtectionTwoClients) {
OutputConfigurator::OutputProtectionClientId client1 =
configurator_.RegisterOutputProtectionClient();
OutputConfigurator::OutputProtectionClientId client2 =
configurator_.RegisterOutputProtectionClient();
EXPECT_NE(client1, client2);
configurator_.Init(false);
configurator_.Start(0);
UpdateOutputs(2, true);
EXPECT_NE(kNoActions, delegate_->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].output,
HDCP_STATE_DESIRED).c_str(),
delegate_->GetActionsAndClear());
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].output,
HDCP_STATE_DESIRED).c_str(),
delegate_->GetActionsAndClear());
EXPECT_TRUE(
configurator_.EnableOutputProtection(client1,
outputs_[1].display_id,
OUTPUT_PROTECTION_METHOD_NONE));
EXPECT_EQ(GetSetHDCPStateAction(outputs_[1].output,
HDCP_STATE_UNDESIRED).c_str(),
delegate_->GetActionsAndClear());
}
TEST_F(OutputConfiguratorTest, CTMForMultiScreens) {
outputs_[0].touch_device_id = 1;
outputs_[1].touch_device_id = 2;
UpdateOutputs(2, false);
configurator_.Init(false);
state_controller_.set_state(STATE_DUAL_EXTENDED);
configurator_.Start(0);
const int kDualHeight =
kSmallModeHeight + OutputConfigurator::kVerticalGap + kBigModeHeight;
const int kDualWidth = kBigModeWidth;
OutputConfigurator::CoordinateTransformation ctm1 = delegate_->get_ctm(1);
OutputConfigurator::CoordinateTransformation ctm2 = delegate_->get_ctm(2);
EXPECT_EQ(kSmallModeHeight - 1, round((kDualHeight - 1) * ctm1.y_scale));
EXPECT_EQ(0, round((kDualHeight - 1) * ctm1.y_offset));
EXPECT_EQ(kBigModeHeight - 1, round((kDualHeight - 1) * ctm2.y_scale));
EXPECT_EQ(kSmallModeHeight + OutputConfigurator::kVerticalGap,
round((kDualHeight - 1) * ctm2.y_offset));
EXPECT_EQ(kSmallModeWidth - 1, round((kDualWidth - 1) * ctm1.x_scale));
EXPECT_EQ(0, round((kDualWidth - 1) * ctm1.x_offset));
EXPECT_EQ(kBigModeWidth - 1, round((kDualWidth - 1) * ctm2.x_scale));
EXPECT_EQ(0, round((kDualWidth - 1) * ctm2.x_offset));
}
} // namespace chromeos