root/ui/display/chromeos/output_configurator_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. DisplaySnapshotToString
  2. GetBackgroundAction
  3. GetAddOutputModeAction
  4. GetCrtcAction
  5. GetFramebufferAction
  6. GetCTMAction
  7. GetSetHDCPStateAction
  8. JoinActions
  9. AppendAction
  10. GetActionsAndClear
  11. configure_touchscreens_
  12. GetCTM
  13. set_configure_touchscreens
  14. AssociateTouchscreens
  15. ConfigureCTM
  16. log_
  17. outputs
  18. set_outputs
  19. set_max_configurable_pixels
  20. set_hdcp_state
  21. Initialize
  22. GrabServer
  23. UngrabServer
  24. SyncWithServer
  25. SetBackgroundColor
  26. ForceDPMSOn
  27. GetOutputs
  28. AddMode
  29. Configure
  30. CreateFrameBuffer
  31. GetHDCPState
  32. SetHDCPState
  33. GetAvailableColorCalibrationProfiles
  34. SetColorCalibrationProfile
  35. AddObserver
  36. RemoveObserver
  37. num_changes
  38. num_failures
  39. latest_outputs
  40. latest_failed_state
  41. Reset
  42. OnDisplayModeChanged
  43. OnDisplayModeChangeFailed
  44. set_state
  45. GetStateForDisplayIds
  46. GetResolutionForDisplayId
  47. SetSoftwareMirroring
  48. software_mirroring_enabled
  49. test_api_
  50. SetUp
  51. UpdateOutputs
  52. InitWithSingleOutput
  53. TEST_F
  54. TEST_F
  55. TEST_F
  56. TEST_F
  57. TEST_F
  58. TEST_F
  59. TEST_F
  60. TEST_F
  61. TEST_F
  62. TEST_F
  63. TEST_F
  64. TEST_F
  65. TEST_F
  66. TEST_F
  67. TEST_F

// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/display/chromeos/output_configurator.h"

#include <stdint.h>

#include <cmath>
#include <cstdarg>
#include <map>
#include <string>
#include <vector>

#include "base/compiler_specific.h"
#include "base/format_macros.h"
#include "base/memory/scoped_vector.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/stringprintf.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/chromeos/display_mode.h"
#include "ui/display/chromeos/native_display_delegate.h"
#include "ui/display/chromeos/test/test_display_snapshot.h"

namespace ui {

namespace {

// Strings returned by TestNativeDisplayDelegate::GetActionsAndClear() to
// describe various actions that were performed.
const char kInitXRandR[] = "init";
const char kGrab[] = "grab";
const char kUngrab[] = "ungrab";
const char kSync[] = "sync";
const char kForceDPMS[] = "dpms";

// String returned by TestNativeDisplayDelegate::GetActionsAndClear() if no
// actions were requested.
const char kNoActions[] = "";

std::string DisplaySnapshotToString(const DisplaySnapshot& output) {
  return base::StringPrintf("id=%" PRId64, output.display_id());
}

// Returns a string describing a TestNativeDisplayDelegate::SetBackgroundColor()
// call.
std::string GetBackgroundAction(uint32_t color_argb) {
  return base::StringPrintf("background(0x%x)", color_argb);
}

// Returns a string describing a TestNativeDisplayDelegate::AddOutputMode()
// call.
std::string GetAddOutputModeAction(const DisplaySnapshot& output,
                                   const DisplayMode* mode) {
  return base::StringPrintf("add_mode(output=%" PRId64 ",mode=%s)",
                            output.display_id(),
                            mode->ToString().c_str());
}

// Returns a string describing a TestNativeDisplayDelegate::Configure()
// call.
std::string GetCrtcAction(const DisplaySnapshot& output,
                          const DisplayMode* mode,
                          const gfx::Point& origin) {
  return base::StringPrintf("crtc(display=[%s],x=%d,y=%d,mode=[%s])",
                            DisplaySnapshotToString(output).c_str(),
                            origin.x(),
                            origin.y(),
                            mode ? mode->ToString().c_str() : "NULL");
}

// Returns a string describing a TestNativeDisplayDelegate::CreateFramebuffer()
// call.
std::string GetFramebufferAction(const gfx::Size& size,
                                 const DisplaySnapshot* out1,
                                 const DisplaySnapshot* out2) {
  return base::StringPrintf(
      "framebuffer(width=%d,height=%d,display1=%s,display2=%s)",
      size.width(),
      size.height(),
      out1 ? DisplaySnapshotToString(*out1).c_str() : "NULL",
      out2 ? DisplaySnapshotToString(*out2).c_str() : "NULL");
}

// Returns a string describing a TestNativeDisplayDelegate::ConfigureCTM() call.
std::string GetCTMAction(
    int device_id,
    const 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 TestNativeDisplayDelegate::SetHDCPState() call.
std::string GetSetHDCPStateAction(const DisplaySnapshot& output,
                                  HDCPState state) {
  return base::StringPrintf(
      "set_hdcp(id=%" PRId64 ",state=%d)", output.display_id(), state);
}

// Joins a sequence of strings describing actions (e.g. kScreenDim) such
// that they can be compared against a string returned by
// ActionLogger::GetActionsAndClear().  The list of actions must be
// terminated by a NULL pointer.
std::string JoinActions(const char* action, ...) {
  std::string actions;

  va_list arg_list;
  va_start(arg_list, action);
  while (action) {
    if (!actions.empty())
      actions += ",";
    actions += action;
    action = va_arg(arg_list, const char*);
  }
  va_end(arg_list);
  return actions;
}

class ActionLogger {
 public:
  ActionLogger() {}

  void AppendAction(const std::string& action) {
    if (!actions_.empty())
      actions_ += ",";
    actions_ += action;
  }

  // Returns a comma-separated string describing the actions that were
  // requested since the previous call to GetActionsAndClear() (i.e.
  // results are non-repeatable).
  std::string GetActionsAndClear() {
    std::string actions = actions_;
    actions_.clear();
    return actions;
  }

 private:
  std::string actions_;

  DISALLOW_COPY_AND_ASSIGN(ActionLogger);
};

class TestTouchscreenDelegate : public OutputConfigurator::TouchscreenDelegate {
 public:
  // Ownership of |log| remains with the caller.
  explicit TestTouchscreenDelegate(ActionLogger* log)
      : log_(log),
        configure_touchscreens_(false) {}
  virtual ~TestTouchscreenDelegate() {}

  const OutputConfigurator::CoordinateTransformation& GetCTM(
      int touch_device_id) {
    return ctms_[touch_device_id];
  }

  void set_configure_touchscreens(bool state) {
    configure_touchscreens_ = state;
  }

  // OutputConfigurator::TouchscreenDelegate implementation:
  virtual void AssociateTouchscreens(
      OutputConfigurator::DisplayStateList* outputs) OVERRIDE {
    if (configure_touchscreens_) {
      for (size_t i = 0; i < outputs->size(); ++i)
        (*outputs)[i].touch_device_id = i + 1;
    }
  }
  virtual void ConfigureCTM(
      int touch_device_id,
      const OutputConfigurator::CoordinateTransformation& ctm) OVERRIDE {
    log_->AppendAction(GetCTMAction(touch_device_id, ctm));
    ctms_[touch_device_id] = ctm;
  }

 private:
  ActionLogger* log_;  // Not owned.

  bool configure_touchscreens_;

  // Most-recently-configured transformation matrices, keyed by touch device ID.
  std::map<int, OutputConfigurator::CoordinateTransformation> ctms_;

  DISALLOW_COPY_AND_ASSIGN(TestTouchscreenDelegate);
};

class TestNativeDisplayDelegate : public NativeDisplayDelegate {
 public:
  // Ownership of |log| remains with the caller.
  explicit TestNativeDisplayDelegate(ActionLogger* log)
      : max_configurable_pixels_(0),
        hdcp_state_(HDCP_STATE_UNDESIRED),
        log_(log) {}
  virtual ~TestNativeDisplayDelegate() {}

  const std::vector<DisplaySnapshot*>& outputs() const { return outputs_; }
  void set_outputs(const std::vector<DisplaySnapshot*>& outputs) {
    outputs_ = outputs;
  }

  void set_max_configurable_pixels(int pixels) {
    max_configurable_pixels_ = pixels;
  }

  void set_hdcp_state(HDCPState state) { hdcp_state_ = state; }

  // OutputConfigurator::Delegate overrides:
  virtual void Initialize() OVERRIDE { log_->AppendAction(kInitXRandR); }
  virtual void GrabServer() OVERRIDE { log_->AppendAction(kGrab); }
  virtual void UngrabServer() OVERRIDE { log_->AppendAction(kUngrab); }
  virtual void SyncWithServer() OVERRIDE { log_->AppendAction(kSync); }
  virtual void SetBackgroundColor(uint32_t color_argb) OVERRIDE {
    log_->AppendAction(GetBackgroundAction(color_argb));
  }
  virtual void ForceDPMSOn() OVERRIDE { log_->AppendAction(kForceDPMS); }
  virtual std::vector<DisplaySnapshot*> GetOutputs() OVERRIDE {
    return outputs_;
  }
  virtual void AddMode(const DisplaySnapshot& output,
                       const DisplayMode* mode) OVERRIDE {
    log_->AppendAction(GetAddOutputModeAction(output, mode));
  }
  virtual bool Configure(const DisplaySnapshot& output,
                         const DisplayMode* mode,
                         const gfx::Point& origin) OVERRIDE {
    log_->AppendAction(GetCrtcAction(output, mode, origin));

    if (max_configurable_pixels_ == 0)
      return true;

    if (!mode)
      return false;

    return mode->size().GetArea() <= max_configurable_pixels_;
  }
  virtual void CreateFrameBuffer(const gfx::Size& size) OVERRIDE {
    log_->AppendAction(
        GetFramebufferAction(size,
                             outputs_.size() >= 1 ? outputs_[0] : NULL,
                             outputs_.size() >= 2 ? outputs_[1] : NULL));
  }
  virtual bool GetHDCPState(const DisplaySnapshot& output,
                            HDCPState* state) OVERRIDE {
    *state = hdcp_state_;
    return true;
  }

  virtual bool SetHDCPState(const DisplaySnapshot& output,
                            HDCPState state) OVERRIDE {
    log_->AppendAction(GetSetHDCPStateAction(output, state));
    return true;
  }

  virtual std::vector<ui::ColorCalibrationProfile>
  GetAvailableColorCalibrationProfiles(
      const DisplaySnapshot& output) OVERRIDE {
    return std::vector<ui::ColorCalibrationProfile>();
  }

  virtual bool SetColorCalibrationProfile(
      const DisplaySnapshot& output,
      ui::ColorCalibrationProfile new_profile) OVERRIDE {
    return false;
  }

  virtual void AddObserver(NativeDisplayObserver* observer) OVERRIDE {}

  virtual void RemoveObserver(NativeDisplayObserver* observer) OVERRIDE {}

 private:
  // Outputs to be returned by GetOutputs().
  std::vector<DisplaySnapshot*> outputs_;

  // |max_configurable_pixels_| represents the maximum number of pixels that
  // Configure will support.  Tests can use this to force Configure
  // to fail if attempting to set a resolution that is higher than what
  // a device might support under a given circumstance.
  // A value of 0 means that no limit is enforced and Configure will
  // return success regardless of the resolution.
  int max_configurable_pixels_;

  // Result value of GetHDCPState().
  HDCPState hdcp_state_;

  ActionLogger* log_;  // Not owned.

  DISALLOW_COPY_AND_ASSIGN(TestNativeDisplayDelegate);
};

class TestObserver : public 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 OutputConfigurator::DisplayStateList& latest_outputs() const {
    return latest_outputs_;
  }
  OutputState latest_failed_state() const { return latest_failed_state_; }

  void Reset() {
    num_changes_ = 0;
    num_failures_ = 0;
    latest_outputs_.clear();
    latest_failed_state_ = OUTPUT_STATE_INVALID;
  }

  // OutputConfigurator::Observer overrides:
  virtual void OnDisplayModeChanged(
      const OutputConfigurator::DisplayStateList& outputs) OVERRIDE {
    num_changes_++;
    latest_outputs_ = outputs;
  }

  virtual void OnDisplayModeChangeFailed(OutputState failed_new_state)
      OVERRIDE {
    num_failures_++;
    latest_failed_state_ = failed_new_state;
  }

 private:
  OutputConfigurator* configurator_;  // Not owned.

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

  // Parameters most recently passed to OnDisplayMode*().
  OutputConfigurator::DisplayStateList latest_outputs_;
  OutputState latest_failed_state_;

  DISALLOW_COPY_AND_ASSIGN(TestObserver);
};

class TestStateController : public OutputConfigurator::StateController {
 public:
  TestStateController() : state_(OUTPUT_STATE_DUAL_EXTENDED) {}
  virtual ~TestStateController() {}

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

  // OutputConfigurator::StateController overrides:
  virtual OutputState GetStateForDisplayIds(
      const std::vector<int64_t>& outputs) const OVERRIDE {
    return state_;
  }
  virtual bool GetResolutionForDisplayId(int64_t display_id,
                                         gfx::Size* size) const OVERRIDE {
    return false;
  }

 private:
  OutputState state_;

  DISALLOW_COPY_AND_ASSIGN(TestStateController);
};

class TestMirroringController
    : public 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:
  OutputConfiguratorTest()
      : small_mode_(gfx::Size(1366, 768), false, 60.0f),
        big_mode_(gfx::Size(2560, 1600), false, 60.0f),
        observer_(&configurator_),
        test_api_(&configurator_) {}
  virtual ~OutputConfiguratorTest() {}

  virtual void SetUp() OVERRIDE {
    log_.reset(new ActionLogger());

    native_display_delegate_ = new TestNativeDisplayDelegate(log_.get());
    configurator_.SetNativeDisplayDelegateForTesting(
        scoped_ptr<NativeDisplayDelegate>(native_display_delegate_));

    touchscreen_delegate_ = new TestTouchscreenDelegate(log_.get());
    configurator_.SetTouchscreenDelegateForTesting(
        scoped_ptr<OutputConfigurator::TouchscreenDelegate>(
            touchscreen_delegate_));

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

    std::vector<const DisplayMode*> modes;
    modes.push_back(&small_mode_);

    TestDisplaySnapshot* o = &outputs_[0];
    o->set_current_mode(&small_mode_);
    o->set_native_mode(&small_mode_);
    o->set_modes(modes);
    o->set_type(OUTPUT_TYPE_INTERNAL);
    o->set_is_aspect_preserving_scaling(true);
    o->set_display_id(123);
    o->set_has_proper_display_id(true);

    o = &outputs_[1];
    o->set_current_mode(&big_mode_);
    o->set_native_mode(&big_mode_);
    modes.push_back(&big_mode_);
    o->set_modes(modes);
    o->set_type(OUTPUT_TYPE_HDMI);
    o->set_is_aspect_preserving_scaling(true);
    o->set_display_id(456);
    o->set_has_proper_display_id(true);

    UpdateOutputs(2, false);
  }

  // Predefined modes that can be used by outputs.
  const DisplayMode small_mode_;
  const DisplayMode big_mode_;

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

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

  // Initializes |configurator_| with a single internal display.
  void InitWithSingleOutput() {
    UpdateOutputs(1, false);
    EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
    configurator_.Init(false);
    EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
    configurator_.ForceInitialConfigure(0);
    EXPECT_EQ(
        JoinActions(
            kGrab,
            kInitXRandR,
            GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL)
                .c_str(),
            GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
            kForceDPMS,
            kUngrab,
            NULL),
        log_->GetActionsAndClear());
  }

  base::MessageLoop message_loop_;
  TestStateController state_controller_;
  TestMirroringController mirroring_controller_;
  OutputConfigurator configurator_;
  TestObserver observer_;
  scoped_ptr<ActionLogger> log_;
  TestNativeDisplayDelegate* native_display_delegate_;  // not owned
  TestTouchscreenDelegate* touchscreen_delegate_;       // not owned
  OutputConfigurator::TestApi test_api_;

  TestDisplaySnapshot outputs_[2];

 private:
  DISALLOW_COPY_AND_ASSIGN(OutputConfiguratorTest);
};

}  // namespace

TEST_F(OutputConfiguratorTest, FindDisplayModeMatchingSize) {
  ScopedVector<const DisplayMode> modes;

  // Fields are width, height, interlaced, refresh rate.
  modes.push_back(new DisplayMode(gfx::Size(1920, 1200), false, 60.0));
  // Different rates.
  modes.push_back(new DisplayMode(gfx::Size(1920, 1080), false, 30.0));
  modes.push_back(new DisplayMode(gfx::Size(1920, 1080), false, 50.0));
  modes.push_back(new DisplayMode(gfx::Size(1920, 1080), false, 40.0));
  modes.push_back(new DisplayMode(gfx::Size(1920, 1080), false, 0.0));
  // Interlaced vs non-interlaced.
  modes.push_back(new DisplayMode(gfx::Size(1280, 720), true, 60.0));
  modes.push_back(new DisplayMode(gfx::Size(1280, 720), false, 40.0));
  // Interlaced only.
  modes.push_back(new DisplayMode(gfx::Size(1024, 768), true, 0.0));
  modes.push_back(new DisplayMode(gfx::Size(1024, 768), true, 40.0));
  modes.push_back(new DisplayMode(gfx::Size(1024, 768), true, 60.0));
  // Mixed.
  modes.push_back(new DisplayMode(gfx::Size(1024, 600), true, 60.0));
  modes.push_back(new DisplayMode(gfx::Size(1024, 600), false, 40.0));
  modes.push_back(new DisplayMode(gfx::Size(1024, 600), false, 50.0));
  // Just one interlaced mode.
  modes.push_back(new DisplayMode(gfx::Size(640, 480), true, 60.0));
  // Refresh rate not available.
  modes.push_back(new DisplayMode(gfx::Size(320, 200), false, 0.0));

  TestDisplaySnapshot output;
  output.set_modes(modes.get());

  EXPECT_EQ(modes[0],
            OutputConfigurator::FindDisplayModeMatchingSize(
                output, gfx::Size(1920, 1200)));

  // Should pick highest refresh rate.
  EXPECT_EQ(modes[2],
            OutputConfigurator::FindDisplayModeMatchingSize(
                output, gfx::Size(1920, 1080)));

  // Should pick non-interlaced mode.
  EXPECT_EQ(modes[6],
            OutputConfigurator::FindDisplayModeMatchingSize(
                output, gfx::Size(1280, 720)));

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

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

  // Just one interlaced mode.
  EXPECT_EQ(modes[13],
            OutputConfigurator::FindDisplayModeMatchingSize(
                output, gfx::Size(640, 480)));

  // Refresh rate not available.
  EXPECT_EQ(modes[14],
            OutputConfigurator::FindDisplayModeMatchingSize(
                output, gfx::Size(320, 200)));

  // No mode found.
  EXPECT_EQ(NULL,
            OutputConfigurator::FindDisplayModeMatchingSize(
                output, gfx::Size(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(OUTPUT_STATE_DUAL_EXTENDED);
  UpdateOutputs(2, true);
  const int kDualHeight = small_mode_.size().height() +
                          OutputConfigurator::kVerticalGap +
                          big_mode_.size().height();
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
                               &outputs_[0],
                               &outputs_[1]).c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1],
                        &big_mode_,
                        gfx::Point(0,
                                   small_mode_.size().height() +
                                       OutputConfigurator::kVerticalGap))
              .c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());

  observer_.Reset();
  EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_MIRROR));
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(small_mode_.size(), &outputs_[0], &outputs_[1])
              .c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Disconnect the second output.
  observer_.Reset();
  UpdateOutputs(1, true);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL).c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Get rid of shared modes to force software mirroring.
  outputs_[1].set_modes(std::vector<const DisplayMode*>(1, &big_mode_));
  state_controller_.set_state(OUTPUT_STATE_DUAL_EXTENDED);
  UpdateOutputs(2, true);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
                               &outputs_[0],
                               &outputs_[1]).c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1],
                        &big_mode_,
                        gfx::Point(0,
                                   small_mode_.size().height() +
                                       OutputConfigurator::kVerticalGap))
              .c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());

  observer_.Reset();
  EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_MIRROR));
  EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL), log_->GetActionsAndClear());
  EXPECT_EQ(OUTPUT_STATE_DUAL_EXTENDED, configurator_.output_state());
  EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Setting OUTPUT_STATE_DUAL_MIRROR should try to reconfigure.
  observer_.Reset();
  EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_EXTENDED));
  EXPECT_EQ(JoinActions(NULL), log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Set back to software mirror mode.
  observer_.Reset();
  EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_MIRROR));
  EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL), log_->GetActionsAndClear());
  EXPECT_EQ(OUTPUT_STATE_DUAL_EXTENDED, configurator_.output_state());
  EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Disconnect the second output.
  observer_.Reset();
  UpdateOutputs(1, true);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL).c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());
}

TEST_F(OutputConfiguratorTest, SetDisplayPower) {
  InitWithSingleOutput();

  state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
  observer_.Reset();
  UpdateOutputs(2, true);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(small_mode_.size(), &outputs_[0], &outputs_[1])
              .c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
  EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Turning off the internal display should switch the external display to
  // its native mode.
  observer_.Reset();
  configurator_.SetDisplayPower(
      chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
      OutputConfigurator::kSetDisplayPowerNoFlags);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(big_mode_.size(), &outputs_[0], &outputs_[1])
              .c_str(),
          GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1], &big_mode_, gfx::Point(0, 0)).c_str(),
          kForceDPMS,
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
  EXPECT_EQ(OUTPUT_STATE_SINGLE, configurator_.output_state());
  EXPECT_EQ(1, observer_.num_changes());

  // When all displays are turned off, the framebuffer should switch back
  // to the mirrored size.
  observer_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                OutputConfigurator::kSetDisplayPowerNoFlags);
  EXPECT_EQ(
      JoinActions(kGrab,
                  GetFramebufferAction(
                      small_mode_.size(), &outputs_[0], &outputs_[1]).c_str(),
                  GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
                  GetCrtcAction(outputs_[1], NULL, gfx::Point(0, 0)).c_str(),
                  kUngrab,
                  NULL),
      log_->GetActionsAndClear());
  EXPECT_EQ(OUTPUT_STATE_DUAL_MIRROR, configurator_.output_state());
  EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Turn all displays on and check that mirroring is still used.
  observer_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                OutputConfigurator::kSetDisplayPowerNoFlags);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(small_mode_.size(), &outputs_[0], &outputs_[1])
              .c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
          kForceDPMS,
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
  EXPECT_EQ(OUTPUT_STATE_DUAL_MIRROR, configurator_.output_state());
  EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Get rid of shared modes to force software mirroring.
  outputs_[1].set_modes(std::vector<const DisplayMode*>(1, &big_mode_));
  state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
  observer_.Reset();
  UpdateOutputs(2, true);
  const int kDualHeight = small_mode_.size().height() +
                          OutputConfigurator::kVerticalGap +
                          big_mode_.size().height();
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
                               &outputs_[0],
                               &outputs_[1]).c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1],
                        &big_mode_,
                        gfx::Point(0,
                                   small_mode_.size().height() +
                                       OutputConfigurator::kVerticalGap))
              .c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
  EXPECT_EQ(OUTPUT_STATE_DUAL_EXTENDED, configurator_.output_state());
  EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Turning off the internal display should switch the external display to
  // its native mode.
  observer_.Reset();
  configurator_.SetDisplayPower(
      chromeos::DISPLAY_POWER_INTERNAL_OFF_EXTERNAL_ON,
      OutputConfigurator::kSetDisplayPowerNoFlags);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(big_mode_.size(), &outputs_[0], &outputs_[1])
              .c_str(),
          GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1], &big_mode_, gfx::Point(0, 0)).c_str(),
          kForceDPMS,
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
  EXPECT_EQ(OUTPUT_STATE_SINGLE, configurator_.output_state());
  EXPECT_FALSE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());

  // When all displays are turned off, the framebuffer should switch back
  // to the extended + software mirroring.
  observer_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                OutputConfigurator::kSetDisplayPowerNoFlags);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
                               &outputs_[0],
                               &outputs_[1]).c_str(),
          GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1],
                        NULL,
                        gfx::Point(0,
                                   small_mode_.size().height() +
                                       OutputConfigurator::kVerticalGap))
              .c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
  EXPECT_EQ(OUTPUT_STATE_DUAL_EXTENDED, configurator_.output_state());
  EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());

  // Turn all displays on and check that mirroring is still used.
  observer_.Reset();
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                OutputConfigurator::kSetDisplayPowerNoFlags);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(gfx::Size(big_mode_.size().width(), kDualHeight),
                               &outputs_[0],
                               &outputs_[1]).c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1],
                        &big_mode_,
                        gfx::Point(0,
                                   small_mode_.size().height() +
                                       OutputConfigurator::kVerticalGap))
              .c_str(),
          kForceDPMS,
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
  EXPECT_EQ(OUTPUT_STATE_DUAL_EXTENDED, configurator_.output_state());
  EXPECT_TRUE(mirroring_controller_.software_mirroring_enabled());
  EXPECT_EQ(1, observer_.num_changes());
}

TEST_F(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, log_->GetActionsAndClear());
  configurator_.ResumeDisplays();
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL).c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          kForceDPMS,
          kUngrab,
          NULL),
      log_->GetActionsAndClear());

  // Now turn the display off before suspending and check that the
  // configurator turns it back on and syncs with the server.
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                OutputConfigurator::kSetDisplayPowerNoFlags);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL).c_str(),
          GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());

  configurator_.SuspendDisplays();
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL).c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          kForceDPMS,
          kUngrab,
          kSync,
          NULL),
      log_->GetActionsAndClear());

  configurator_.ResumeDisplays();
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL).c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          kForceDPMS,
          kUngrab,
          NULL),
      log_->GetActionsAndClear());

  // If a second, external display is connected, the displays shouldn't be
  // powered back on before suspending.
  state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
  UpdateOutputs(2, true);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(small_mode_.size(), &outputs_[0], &outputs_[1])
              .c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());

  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                OutputConfigurator::kSetDisplayPowerNoFlags);
  EXPECT_EQ(
      JoinActions(kGrab,
                  GetFramebufferAction(
                      small_mode_.size(), &outputs_[0], &outputs_[1]).c_str(),
                  GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
                  GetCrtcAction(outputs_[1], NULL, gfx::Point(0, 0)).c_str(),
                  kUngrab,
                  NULL),
      log_->GetActionsAndClear());

  configurator_.SuspendDisplays();
  EXPECT_EQ(JoinActions(kGrab, kUngrab, kSync, NULL),
            log_->GetActionsAndClear());

  // If a display is disconnected while suspended, the configurator should
  // pick up the change.
  UpdateOutputs(1, false);
  configurator_.ResumeDisplays();
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(small_mode_.size(), &outputs_[0], NULL).c_str(),
          GetCrtcAction(outputs_[0], NULL, gfx::Point(0, 0)).c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
}

TEST_F(OutputConfiguratorTest, Headless) {
  UpdateOutputs(0, false);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
  configurator_.Init(false);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
  configurator_.ForceInitialConfigure(0);
  EXPECT_EQ(JoinActions(kGrab, kInitXRandR, kForceDPMS, kUngrab, NULL),
            log_->GetActionsAndClear());

  // Not much should happen when the display power state is changed while
  // no displays are connected.
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_OFF,
                                OutputConfigurator::kSetDisplayPowerNoFlags);
  EXPECT_EQ(JoinActions(kGrab, kUngrab, NULL), log_->GetActionsAndClear());
  configurator_.SetDisplayPower(chromeos::DISPLAY_POWER_ALL_ON,
                                OutputConfigurator::kSetDisplayPowerNoFlags);
  EXPECT_EQ(JoinActions(kGrab, kForceDPMS, kUngrab, NULL),
            log_->GetActionsAndClear());

  // Connect an external display and check that it's configured correctly.
  outputs_[0].set_current_mode(outputs_[1].current_mode());
  outputs_[0].set_native_mode(outputs_[1].native_mode());
  outputs_[0].set_modes(outputs_[1].modes());
  outputs_[0].set_type(outputs_[1].type());

  UpdateOutputs(1, true);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(big_mode_.size(), &outputs_[0], NULL).c_str(),
          GetCrtcAction(outputs_[0], &big_mode_, gfx::Point(0, 0)).c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
}

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

  state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
  configurator_.ForceInitialConfigure(0);
  EXPECT_EQ(
      JoinActions(
          kGrab,
          kInitXRandR,
          GetFramebufferAction(small_mode_.size(), &outputs_[0], &outputs_[1])
              .c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
          kForceDPMS,
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
}

TEST_F(OutputConfiguratorTest, InvalidOutputStates) {
  UpdateOutputs(0, false);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());
  configurator_.Init(false);
  configurator_.ForceInitialConfigure(0);
  observer_.Reset();
  EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_HEADLESS));
  EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_SINGLE));
  EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_MIRROR));
  EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_EXTENDED));
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(3, observer_.num_failures());

  UpdateOutputs(1, true);
  observer_.Reset();
  EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_HEADLESS));
  EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_SINGLE));
  EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_MIRROR));
  EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_EXTENDED));
  EXPECT_EQ(1, observer_.num_changes());
  EXPECT_EQ(3, observer_.num_failures());

  state_controller_.set_state(OUTPUT_STATE_DUAL_EXTENDED);
  UpdateOutputs(2, true);
  observer_.Reset();
  EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_HEADLESS));
  EXPECT_FALSE(configurator_.SetDisplayMode(OUTPUT_STATE_SINGLE));
  EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_MIRROR));
  EXPECT_TRUE(configurator_.SetDisplayMode(OUTPUT_STATE_DUAL_EXTENDED));
  EXPECT_EQ(2, observer_.num_changes());
  EXPECT_EQ(2, observer_.num_failures());
}

TEST_F(OutputConfiguratorTest, GetOutputStateForDisplaysWithoutId) {
  outputs_[0].set_has_proper_display_id(false);
  UpdateOutputs(2, false);
  configurator_.Init(false);
  state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
  configurator_.ForceInitialConfigure(0);
  EXPECT_EQ(OUTPUT_STATE_DUAL_EXTENDED, configurator_.output_state());
}

TEST_F(OutputConfiguratorTest, GetOutputStateForDisplaysWithId) {
  outputs_[0].set_has_proper_display_id(true);
  UpdateOutputs(2, false);
  configurator_.Init(false);
  state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
  configurator_.ForceInitialConfigure(0);
  EXPECT_EQ(OUTPUT_STATE_DUAL_MIRROR, configurator_.output_state());
}

TEST_F(OutputConfiguratorTest, UpdateCachedOutputsEvenAfterFailure) {
  InitWithSingleOutput();
  const OutputConfigurator::DisplayStateList* cached =
      &configurator_.cached_outputs();
  ASSERT_EQ(static_cast<size_t>(1), cached->size());
  EXPECT_EQ(outputs_[0].current_mode(), (*cached)[0].display->current_mode());

  // After connecting a second output, check that it shows up in
  // |cached_outputs_| even if an invalid state is requested.
  state_controller_.set_state(OUTPUT_STATE_SINGLE);
  UpdateOutputs(2, true);
  cached = &configurator_.cached_outputs();
  ASSERT_EQ(static_cast<size_t>(2), cached->size());
  EXPECT_EQ(outputs_[0].current_mode(), (*cached)[0].display->current_mode());
  EXPECT_EQ(outputs_[1].current_mode(), (*cached)[1].display->current_mode());
}

TEST_F(OutputConfiguratorTest, PanelFitting) {
  // Configure the internal display to support only the big mode and the
  // external display to support only the small mode.
  outputs_[0].set_current_mode(&big_mode_);
  outputs_[0].set_native_mode(&big_mode_);
  outputs_[0].set_modes(std::vector<const DisplayMode*>(1, &big_mode_));

  outputs_[1].set_current_mode(&small_mode_);
  outputs_[1].set_native_mode(&small_mode_);
  outputs_[1].set_modes(std::vector<const DisplayMode*>(1, &small_mode_));

  // The small mode should be added to the internal output when requesting
  // mirrored mode.
  UpdateOutputs(2, false);
  state_controller_.set_state(OUTPUT_STATE_DUAL_MIRROR);
  configurator_.Init(true /* is_panel_fitting_enabled */);
  configurator_.ForceInitialConfigure(0);
  EXPECT_EQ(OUTPUT_STATE_DUAL_MIRROR, configurator_.output_state());
  EXPECT_EQ(
      JoinActions(
          kGrab,
          kInitXRandR,
          GetAddOutputModeAction(outputs_[0], &small_mode_).c_str(),
          GetFramebufferAction(small_mode_.size(), &outputs_[0], &outputs_[1])
              .c_str(),
          GetCrtcAction(outputs_[0], &small_mode_, gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1], &small_mode_, gfx::Point(0, 0)).c_str(),
          kForceDPMS,
          kUngrab,
          NULL),
      log_->GetActionsAndClear());

  // Both outputs should be using the small mode.
  ASSERT_EQ(1, observer_.num_changes());
  ASSERT_EQ(static_cast<size_t>(2), observer_.latest_outputs().size());
  EXPECT_EQ(&small_mode_, observer_.latest_outputs()[0].mirror_mode);
  EXPECT_EQ(&small_mode_,
            observer_.latest_outputs()[0].display->current_mode());
  EXPECT_EQ(&small_mode_, observer_.latest_outputs()[1].mirror_mode);
  EXPECT_EQ(&small_mode_,
            observer_.latest_outputs()[1].display->current_mode());

  // Also check that the newly-added small mode is present in the internal
  // snapshot that was passed to the observer (http://crbug.com/289159).
  const OutputConfigurator::DisplayState& state = observer_.latest_outputs()[0];
  ASSERT_NE(state.display->modes().end(),
            std::find(state.display->modes().begin(),
                      state.display->modes().end(),
                      &small_mode_));
}

TEST_F(OutputConfiguratorTest, OutputProtection) {
  configurator_.Init(false);
  configurator_.ForceInitialConfigure(0);
  EXPECT_NE(kNoActions, log_->GetActionsAndClear());

  OutputConfigurator::OutputProtectionClientId id =
      configurator_.RegisterOutputProtectionClient();
  EXPECT_NE(0u, id);

  // One output.
  UpdateOutputs(1, true);
  EXPECT_NE(kNoActions, log_->GetActionsAndClear());
  uint32_t link_mask = 0;
  uint32_t protection_mask = 0;
  EXPECT_TRUE(configurator_.QueryOutputProtectionStatus(
      id, outputs_[0].display_id(), &link_mask, &protection_mask));
  EXPECT_EQ(static_cast<uint32_t>(OUTPUT_TYPE_INTERNAL), link_mask);
  EXPECT_EQ(static_cast<uint32_t>(OUTPUT_PROTECTION_METHOD_NONE),
            protection_mask);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  // Two outputs.
  UpdateOutputs(2, true);
  EXPECT_NE(kNoActions, log_->GetActionsAndClear());
  EXPECT_TRUE(configurator_.QueryOutputProtectionStatus(
      id, outputs_[1].display_id(), &link_mask, &protection_mask));
  EXPECT_EQ(static_cast<uint32_t>(OUTPUT_TYPE_HDMI), link_mask);
  EXPECT_EQ(static_cast<uint32_t>(OUTPUT_PROTECTION_METHOD_NONE),
            protection_mask);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  EXPECT_TRUE(configurator_.EnableOutputProtection(
      id, outputs_[1].display_id(), OUTPUT_PROTECTION_METHOD_HDCP));
  EXPECT_EQ(GetSetHDCPStateAction(outputs_[1], HDCP_STATE_DESIRED),
            log_->GetActionsAndClear());

  // Enable protection.
  native_display_delegate_->set_hdcp_state(HDCP_STATE_ENABLED);
  EXPECT_TRUE(configurator_.QueryOutputProtectionStatus(
      id, outputs_[1].display_id(), &link_mask, &protection_mask));
  EXPECT_EQ(static_cast<uint32_t>(OUTPUT_TYPE_HDMI), link_mask);
  EXPECT_EQ(static_cast<uint32_t>(OUTPUT_PROTECTION_METHOD_HDCP),
            protection_mask);
  EXPECT_EQ(kNoActions, log_->GetActionsAndClear());

  // Protections should be disabled after unregister.
  configurator_.UnregisterOutputProtectionClient(id);
  EXPECT_EQ(GetSetHDCPStateAction(outputs_[1], HDCP_STATE_UNDESIRED),
            log_->GetActionsAndClear());
}

TEST_F(OutputConfiguratorTest, OutputProtectionTwoClients) {
  OutputConfigurator::OutputProtectionClientId client1 =
      configurator_.RegisterOutputProtectionClient();
  OutputConfigurator::OutputProtectionClientId client2 =
      configurator_.RegisterOutputProtectionClient();
  EXPECT_NE(client1, client2);

  configurator_.Init(false);
  configurator_.ForceInitialConfigure(0);
  UpdateOutputs(2, true);
  EXPECT_NE(kNoActions, log_->GetActionsAndClear());

  // Clients never know state enableness for methods that they didn't request.
  EXPECT_TRUE(configurator_.EnableOutputProtection(
      client1, outputs_[1].display_id(), OUTPUT_PROTECTION_METHOD_HDCP));
  EXPECT_EQ(GetSetHDCPStateAction(outputs_[1], HDCP_STATE_DESIRED).c_str(),
            log_->GetActionsAndClear());
  native_display_delegate_->set_hdcp_state(HDCP_STATE_ENABLED);

  uint32_t link_mask = 0;
  uint32_t protection_mask = 0;
  EXPECT_TRUE(configurator_.QueryOutputProtectionStatus(
      client1, outputs_[1].display_id(), &link_mask, &protection_mask));
  EXPECT_EQ(static_cast<uint32_t>(OUTPUT_TYPE_HDMI), link_mask);
  EXPECT_EQ(OUTPUT_PROTECTION_METHOD_HDCP, protection_mask);

  EXPECT_TRUE(configurator_.QueryOutputProtectionStatus(
      client2, outputs_[1].display_id(), &link_mask, &protection_mask));
  EXPECT_EQ(static_cast<uint32_t>(OUTPUT_TYPE_HDMI), link_mask);
  EXPECT_EQ(OUTPUT_PROTECTION_METHOD_NONE, protection_mask);

  // Protections will be disabled only if no more clients request them.
  EXPECT_TRUE(configurator_.EnableOutputProtection(
      client2, outputs_[1].display_id(), OUTPUT_PROTECTION_METHOD_NONE));
  EXPECT_EQ(GetSetHDCPStateAction(outputs_[1], HDCP_STATE_DESIRED).c_str(),
            log_->GetActionsAndClear());
  EXPECT_TRUE(configurator_.EnableOutputProtection(
      client1, outputs_[1].display_id(), OUTPUT_PROTECTION_METHOD_NONE));
  EXPECT_EQ(
      GetSetHDCPStateAction(outputs_[1], HDCP_STATE_UNDESIRED).c_str(),
      log_->GetActionsAndClear());
}

TEST_F(OutputConfiguratorTest, CTMForMultiScreens) {
  touchscreen_delegate_->set_configure_touchscreens(true);
  UpdateOutputs(2, false);
  configurator_.Init(false);
  state_controller_.set_state(OUTPUT_STATE_DUAL_EXTENDED);
  configurator_.ForceInitialConfigure(0);

  const int kDualHeight = small_mode_.size().height() +
                          OutputConfigurator::kVerticalGap +
                          big_mode_.size().height();
  const int kDualWidth = big_mode_.size().width();

  OutputConfigurator::CoordinateTransformation ctm1 =
      touchscreen_delegate_->GetCTM(1);
  OutputConfigurator::CoordinateTransformation ctm2 =
      touchscreen_delegate_->GetCTM(2);

  EXPECT_EQ(small_mode_.size().height() - 1,
            round((kDualHeight - 1) * ctm1.y_scale));
  EXPECT_EQ(0, round((kDualHeight - 1) * ctm1.y_offset));

  EXPECT_EQ(big_mode_.size().height() - 1,
            round((kDualHeight - 1) * ctm2.y_scale));
  EXPECT_EQ(small_mode_.size().height() + OutputConfigurator::kVerticalGap,
            round((kDualHeight - 1) * ctm2.y_offset));

  EXPECT_EQ(small_mode_.size().width() - 1,
            round((kDualWidth - 1) * ctm1.x_scale));
  EXPECT_EQ(0, round((kDualWidth - 1) * ctm1.x_offset));

  EXPECT_EQ(big_mode_.size().width() - 1,
            round((kDualWidth - 1) * ctm2.x_scale));
  EXPECT_EQ(0, round((kDualWidth - 1) * ctm2.x_offset));
}

TEST_F(OutputConfiguratorTest, HandleConfigureCrtcFailure) {
  InitWithSingleOutput();

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

  for (unsigned int i = 0; i < arraysize(outputs_); i++) {
    outputs_[i].set_modes(modes.get());
    outputs_[i].set_current_mode(modes[0]);
    outputs_[i].set_native_mode(modes[0]);
  }

  configurator_.Init(false);

  // First test simply fails in OUTPUT_STATE_SINGLE mode.   This is probably
  // unrealistic but the want to make sure any assumptions don't
  // creep in.
  native_display_delegate_->set_max_configurable_pixels(
      modes[2]->size().GetArea());
  state_controller_.set_state(OUTPUT_STATE_SINGLE);
  UpdateOutputs(1, true);

  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(big_mode_.size(), &outputs_[0], NULL).c_str(),
          GetCrtcAction(outputs_[0], modes[0], gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[0], modes[3], gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[0], modes[2], gfx::Point(0, 0)).c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());

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

  EXPECT_EQ(
      JoinActions(
          kGrab,
          GetFramebufferAction(modes[0]->size(), &outputs_[0], &outputs_[1])
              .c_str(),
          GetCrtcAction(outputs_[0], modes[0], gfx::Point(0, 0)).c_str(),
          // First mode tried is expected to fail and it will
          // retry wil the 4th mode in the list.
          GetCrtcAction(outputs_[0], modes[3], gfx::Point(0, 0)).c_str(),
          // Then attempt to configure crtc1 with the first mode.
          GetCrtcAction(outputs_[1], modes[0], gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1], modes[3], gfx::Point(0, 0)).c_str(),
          // Since it was requested to go into mirror mode
          // and the configured modes were different, it
          // should now try and setup a valid configurable
          // extended mode.
          GetFramebufferAction(
              gfx::Size(modes[0]->size().width(),
                        modes[0]->size().height() + modes[0]->size().height() +
                            OutputConfigurator::kVerticalGap),
              &outputs_[0],
              &outputs_[1]).c_str(),
          GetCrtcAction(outputs_[0], modes[0], gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[0], modes[3], gfx::Point(0, 0)).c_str(),
          GetCrtcAction(outputs_[1],
                        modes[0],
                        gfx::Point(0,
                                   modes[0]->size().height() +
                                       OutputConfigurator::kVerticalGap))
              .c_str(),
          GetCrtcAction(outputs_[1],
                        modes[3],
                        gfx::Point(0,
                                   modes[0]->size().height() +
                                       OutputConfigurator::kVerticalGap))
              .c_str(),
          kUngrab,
          NULL),
      log_->GetActionsAndClear());
}

}  // namespace ui

/* [<][>][^][v][top][bottom][index][help] */