// 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