chromium/ui/display/manager/display_change_observer_unittest.cc

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

#include "ui/display/manager/display_change_observer.h"

#include <cmath>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <tuple>

#include "base/command_line.h"
#include "base/strings/stringprintf.h"
#include "base/test/gtest_util.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "build/chromeos_buildflags.h"
#include "cc/base/math_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/display.h"
#include "ui/display/display_features.h"
#include "ui/display/display_switches.h"
#include "ui/display/manager/display_configurator.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/manager/managed_display_info.h"
#include "ui/display/manager/test/fake_display_snapshot.h"
#include "ui/display/manager/util/display_manager_util.h"
#include "ui/display/screen.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/types/display_mode.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/rect_f.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/range/range_f.h"

namespace display {

namespace {

float ComputeDpi(float diagonal_inch, const gfx::Size& resolution) {
  // We assume that displays have square pixel.
  float diagonal_pixel = std::sqrt(std::pow(resolution.width(), 2) +
                                   std::pow(resolution.height(), 2));
  return diagonal_pixel / diagonal_inch;
}

float ComputeDeviceScaleFactor(float dpi, const gfx::Size& resolution) {
  return DisplayChangeObserver::FindDeviceScaleFactor(dpi, resolution);
}

std::unique_ptr<DisplayMode> MakeDisplayMode(
    int width,
    int height,
    bool is_interlaced,
    float refresh_rate,
    const std::optional<float>& vsync_rate_min = std::nullopt) {
  return std::make_unique<DisplayMode>(gfx::Size{width, height}, is_interlaced,
                                       refresh_rate, vsync_rate_min);
}

}  // namespace

class DisplayChangeObserverTestBase : public testing::Test {
 public:
  DisplayChangeObserverTestBase() = default;

  DisplayChangeObserverTestBase(const DisplayChangeObserverTestBase&) = delete;
  DisplayChangeObserverTestBase& operator=(
      const DisplayChangeObserverTestBase&) = delete;

  ~DisplayChangeObserverTestBase() override = default;

  // Pass through method to be called by individual test cases.
  ManagedDisplayInfo CreateManagedDisplayInfo(DisplayChangeObserver* observer,
                                              const DisplaySnapshot* snapshot,
                                              const DisplayMode* mode_info) {
    return observer->CreateManagedDisplayInfoInternal(snapshot, mode_info);
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
};

class DisplayChangeObserverTest : public DisplayChangeObserverTestBase,
                                  public testing::WithParamInterface<bool> {
 public:
  DisplayChangeObserverTest() = default;

  DisplayChangeObserverTest(const DisplayChangeObserverTest&) = delete;
  DisplayChangeObserverTest& operator=(const DisplayChangeObserverTest&) =
      delete;

  ~DisplayChangeObserverTest() override = default;

  // DisplayChangeObserverTestBase:
  void SetUp() override {
    if (GetParam()) {
      scoped_feature_list_.InitAndEnableFeature(features::kListAllDisplayModes);
    } else {
      scoped_feature_list_.InitAndDisableFeature(
          features::kListAllDisplayModes);
    }

    DisplayChangeObserverTestBase::SetUp();
  }
};

class DisplayChangeObserverPanelRadiiTest
    : public DisplayChangeObserverTestBase {
 public:
  DisplayChangeObserverPanelRadiiTest() = default;

  DisplayChangeObserverPanelRadiiTest(
      const DisplayChangeObserverPanelRadiiTest&) = delete;
  DisplayChangeObserverPanelRadiiTest& operator=(
      const DisplayChangeObserverPanelRadiiTest&) = delete;

  ~DisplayChangeObserverPanelRadiiTest() override = default;

  // testing::Test:
  void SetUp() override {
    display_manager_ = std::make_unique<DisplayManager>(/*screen=*/nullptr);
    default_display_mode_ = MakeDisplayMode(1920, 1080, true, 60);
    scoped_feature_list_.InitAndEnableFeature(features::kRoundedDisplay);

    ui::DeviceDataManager::CreateInstance();
    DisplayChangeObserverTestBase::SetUp();
  }

  void InitializeDisplayChangeObserver() {
    display_change_observer_ =
        std::make_unique<DisplayChangeObserver>(display_manager_.get());
  }

 protected:
  base::test::ScopedCommandLine command_line_;
  std::unique_ptr<DisplayManager> display_manager_;
  std::unique_ptr<DisplayChangeObserver> display_change_observer_;
  std::unique_ptr<DisplayMode> default_display_mode_;
};

TEST_F(DisplayChangeObserverPanelRadiiTest, RadiiSpecifiedForInternalDisplay) {
  command_line_.GetProcessCommandLine()->AppendSwitchASCII(
      switches::kDisplayProperties,
      "[{\"connector-type\": 14, \"rounded-corners\": {\"bottom-left\": 15, "
      "\"bottom-right\": 15, \"top-left\": 16, \"top-right\": 16}}]");

  InitializeDisplayChangeObserver();

  // Radii specified for the connection protocol.
  std::unique_ptr<DisplaySnapshot> display_snapshot =
      FakeDisplaySnapshot::Builder()
          .SetId(123)
          .SetNativeMode(MakeDisplayMode(1920, 1080, true, 60))
          .SetType(
              display::DisplayConnectionType::DISPLAY_CONNECTION_TYPE_INTERNAL)
          .Build();

  const ManagedDisplayInfo display_info = CreateManagedDisplayInfo(
      display_change_observer_.get(), display_snapshot.get(),
      default_display_mode_.get());

  EXPECT_EQ(display_info.panel_corners_radii(),
            gfx::RoundedCornersF(16, 16, 15, 15));
}

TEST_F(DisplayChangeObserverPanelRadiiTest, IgnoreRadiiIfNotInternalDisplay) {
  command_line_.GetProcessCommandLine()->AppendSwitchASCII(
      switches::kDisplayProperties,
      "[{\"connector-type\": 15, \"rounded-corners\": {\"bottom-left\": 15, "
      "\"bottom-right\": 15, \"top-left\": 16, \"top-right\": 16}}]");

  InitializeDisplayChangeObserver();

  // The snapshot is of a display that is not a internal display.
  std::unique_ptr<DisplaySnapshot> display_snapshot =
      FakeDisplaySnapshot::Builder()
          .SetId(123)
          .SetNativeMode(MakeDisplayMode(1920, 1080, true, 60))
          .SetType(display::DisplayConnectionType::DISPLAY_CONNECTION_TYPE_HDMI)
          .Build();

  const ManagedDisplayInfo display_info = CreateManagedDisplayInfo(
      display_change_observer_.get(), display_snapshot.get(),
      default_display_mode_.get());

  EXPECT_TRUE(display_info.panel_corners_radii().IsEmpty());
}

TEST_P(DisplayChangeObserverTest, GetExternalManagedDisplayModeList) {
  std::unique_ptr<DisplaySnapshot> display_snapshot =
      FakeDisplaySnapshot::Builder()
          .SetId(123)
          .SetNativeMode(MakeDisplayMode(1920, 1200, false, 60))
          // Same size as native mode but with higher refresh rate.
          .AddMode(MakeDisplayMode(1920, 1200, false, 75))
          // All non-interlaced (as would be seen with different refresh rates).
          .AddMode(MakeDisplayMode(1920, 1080, false, 80))
          .AddMode(MakeDisplayMode(1920, 1080, false, 70))
          .AddMode(MakeDisplayMode(1920, 1080, false, 60))
          // Interlaced vs non-interlaced.
          .AddMode(MakeDisplayMode(1280, 720, true, 60))
          .AddMode(MakeDisplayMode(1280, 720, false, 60))
          // Interlaced only.
          .AddMode(MakeDisplayMode(1024, 768, true, 70))
          .AddMode(MakeDisplayMode(1024, 768, true, 60))
          // Mixed.
          .AddMode(MakeDisplayMode(1024, 600, true, 60))
          .AddMode(MakeDisplayMode(1024, 600, false, 70))
          .AddMode(MakeDisplayMode(1024, 600, false, 60))
          // Just one interlaced mode.
          .AddMode(MakeDisplayMode(640, 480, true, 60))
          .Build();

  ManagedDisplayInfo::ManagedDisplayModeList display_modes =
      DisplayChangeObserver::GetExternalManagedDisplayModeList(
          *display_snapshot);

  const bool listing_all_modes = GetParam();
  if (listing_all_modes) {
    ASSERT_EQ(13u, display_modes.size());
    EXPECT_EQ(gfx::Size(640, 480), display_modes[0].size());
    EXPECT_TRUE(display_modes[0].is_interlaced());
    EXPECT_EQ(display_modes[0].refresh_rate(), 60);

    EXPECT_EQ(gfx::Size(1024, 600), display_modes[1].size());
    EXPECT_FALSE(display_modes[1].is_interlaced());
    EXPECT_EQ(display_modes[1].refresh_rate(), 60);
    EXPECT_EQ(gfx::Size(1024, 600), display_modes[2].size());
    EXPECT_TRUE(display_modes[2].is_interlaced());
    EXPECT_EQ(display_modes[2].refresh_rate(), 60);
    EXPECT_EQ(gfx::Size(1024, 600), display_modes[3].size());
    EXPECT_FALSE(display_modes[3].is_interlaced());
    EXPECT_EQ(display_modes[3].refresh_rate(), 70);

    EXPECT_EQ(gfx::Size(1024, 768), display_modes[4].size());
    EXPECT_TRUE(display_modes[4].is_interlaced());
    EXPECT_EQ(display_modes[4].refresh_rate(), 60);
    EXPECT_EQ(gfx::Size(1024, 768), display_modes[5].size());
    EXPECT_TRUE(display_modes[5].is_interlaced());
    EXPECT_EQ(display_modes[5].refresh_rate(), 70);

    EXPECT_EQ(gfx::Size(1280, 720), display_modes[6].size());
    EXPECT_FALSE(display_modes[6].is_interlaced());
    EXPECT_EQ(display_modes[6].refresh_rate(), 60);
    EXPECT_EQ(gfx::Size(1280, 720), display_modes[7].size());
    EXPECT_TRUE(display_modes[7].is_interlaced());
    EXPECT_EQ(display_modes[7].refresh_rate(), 60);

    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[8].size());
    EXPECT_FALSE(display_modes[8].is_interlaced());
    EXPECT_EQ(display_modes[8].refresh_rate(), 60);
    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[9].size());
    EXPECT_FALSE(display_modes[9].is_interlaced());
    EXPECT_EQ(display_modes[9].refresh_rate(), 70);
    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[10].size());
    EXPECT_FALSE(display_modes[10].is_interlaced());
    EXPECT_EQ(display_modes[10].refresh_rate(), 80);

    EXPECT_EQ(gfx::Size(1920, 1200), display_modes[11].size());
    EXPECT_FALSE(display_modes[11].is_interlaced());
    EXPECT_EQ(display_modes[11].refresh_rate(), 60);

    EXPECT_EQ(gfx::Size(1920, 1200), display_modes[12].size());
    EXPECT_FALSE(display_modes[12].is_interlaced());
    EXPECT_EQ(display_modes[12].refresh_rate(), 75);
  } else {
    ASSERT_EQ(6u, display_modes.size());
    EXPECT_EQ(gfx::Size(640, 480), display_modes[0].size());
    EXPECT_TRUE(display_modes[0].is_interlaced());
    EXPECT_EQ(display_modes[0].refresh_rate(), 60);

    EXPECT_EQ(gfx::Size(1024, 600), display_modes[1].size());
    EXPECT_FALSE(display_modes[1].is_interlaced());
    EXPECT_EQ(display_modes[1].refresh_rate(), 70);

    EXPECT_EQ(gfx::Size(1024, 768), display_modes[2].size());
    EXPECT_TRUE(display_modes[2].is_interlaced());
    EXPECT_EQ(display_modes[2].refresh_rate(), 70);

    EXPECT_EQ(gfx::Size(1280, 720), display_modes[3].size());
    EXPECT_FALSE(display_modes[3].is_interlaced());
    EXPECT_EQ(display_modes[3].refresh_rate(), 60);

    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[4].size());
    EXPECT_FALSE(display_modes[4].is_interlaced());
    EXPECT_EQ(display_modes[4].refresh_rate(), 80);

    EXPECT_EQ(gfx::Size(1920, 1200), display_modes[5].size());
    EXPECT_FALSE(display_modes[5].is_interlaced());
    EXPECT_EQ(display_modes[5].refresh_rate(), 60);
  }
}

TEST_P(DisplayChangeObserverTest, GetEmptyExternalManagedDisplayModeList) {
  DisplaySnapshot::ColorInfo color_info;
  FakeDisplaySnapshot display_snapshot(
      /*display_id=*/123, /*port_display_id=*/123, /*edid_display_id=*/456,
      /*connector_index=*/0x0001, gfx::Point(), gfx::Size(),
      DISPLAY_CONNECTION_TYPE_UNKNOWN,
      /*base_connector_id=*/1u, /*path_topology=*/{}, false, false,
      PrivacyScreenState::kNotSupported, false, std::string(), base::FilePath(),
      {}, nullptr, nullptr, 0, gfx::Size(), color_info,
      VariableRefreshRateState::kVrrNotCapable, DrmFormatsAndModifiers());

  ManagedDisplayInfo::ManagedDisplayModeList display_modes =
      DisplayChangeObserver::GetExternalManagedDisplayModeList(
          display_snapshot);
  EXPECT_EQ(0u, display_modes.size());
}

bool IsDpiOutOfRange(float dpi) {
  // http://go/cros-ppi-spectrum
  constexpr gfx::RangeF good_ranges[] = {
      {125.f, 165.f},
      {180.f, 210.f},
      {220.f, 265.f},
      {270.f, 350.f},
  };
  for (auto& range : good_ranges) {
    if (range.start() <= dpi && range.end() > dpi)
      return true;
  }
  return false;
}

TEST_P(DisplayChangeObserverTest, FindDeviceScaleFactor) {
  // Validation check
  EXPECT_EQ(1.25f,
            DisplayChangeObserver::FindDeviceScaleFactor(150, gfx::Size()));
  EXPECT_EQ(1.6f,
            DisplayChangeObserver::FindDeviceScaleFactor(180, gfx::Size()));
  EXPECT_EQ(kDsf_1_777,
            DisplayChangeObserver::FindDeviceScaleFactor(220, gfx::Size()));
  EXPECT_EQ(2.f,
            DisplayChangeObserver::FindDeviceScaleFactor(230, gfx::Size()));
  EXPECT_EQ(2.4f,
            DisplayChangeObserver::FindDeviceScaleFactor(270, gfx::Size()));
  EXPECT_EQ(kDsf_2_252, DisplayChangeObserver::FindDeviceScaleFactor(
                            0, gfx::Size(3000, 2000)));
  EXPECT_EQ(kDsf_2_666,
            DisplayChangeObserver::FindDeviceScaleFactor(310, gfx::Size()));

  std::set<std::tuple<float, int, int>> dup_check;

  for (auto& entry : display_configs) {
    std::tuple<float, int, int> key{entry.diagonal_size,
                                    entry.resolution.width(),
                                    entry.resolution.height()};
    DCHECK(!dup_check.count(key));
    dup_check.emplace(key);

    SCOPED_TRACE(base::StringPrintf(
        "%dx%d, diag=%1.3f inch, expected=%1.10f", entry.resolution.width(),
        entry.resolution.height(), entry.diagonal_size, entry.expected_dsf));

    float dpi = ComputeDpi(entry.diagonal_size, entry.resolution);
    // Check ScaleFactor.
    float scale_factor = ComputeDeviceScaleFactor(dpi, entry.resolution);
    EXPECT_EQ(entry.expected_dsf, scale_factor);
    bool bad_range = !IsDpiOutOfRange(dpi);
    EXPECT_EQ(bad_range, entry.bad_range);

    // Check DP size.
    gfx::ScaleToCeiledSize(entry.resolution, 1.f / scale_factor);

    const gfx::Size dp_size =
        gfx::ScaleToCeiledSize(entry.resolution, 1.f / scale_factor);

    // Check Screenshot size.
    EXPECT_EQ(entry.expected_dp_size, dp_size);
    gfx::Transform transform;
    transform.Scale(scale_factor, scale_factor);
    const gfx::Size screenshot_size =
        cc::MathUtil::MapEnclosingClippedRect(transform, gfx::Rect(dp_size))
            .size();
    switch (entry.screenshot_size_error) {
      case kEpsilon: {
        EXPECT_NE(entry.resolution, screenshot_size);
        constexpr float kEpsilon = 0.001f;
        EXPECT_EQ(entry.resolution,
                  cc::MathUtil::MapEnclosingClippedRectIgnoringError(
                      transform, gfx::Rect(dp_size), kEpsilon)
                      .size());
        break;
      }
      case kExact:
        EXPECT_EQ(entry.resolution, screenshot_size);
        break;
      case kSkip:
        break;
    }
  }

  float max_scale_factor = kDsf_2_666;
  // Erroneous values should still work.
  EXPECT_EQ(1.0f,
            DisplayChangeObserver::FindDeviceScaleFactor(-100.0f, gfx::Size()));
  EXPECT_EQ(1.0f,
            DisplayChangeObserver::FindDeviceScaleFactor(0.0f, gfx::Size()));
  EXPECT_EQ(max_scale_factor, DisplayChangeObserver::FindDeviceScaleFactor(
                                  10000.0f, gfx::Size()));
}

TEST_P(DisplayChangeObserverTest,
       FindExternalDisplayNativeModeWhenOverwritten) {
  std::unique_ptr<DisplaySnapshot> display_snapshot =
      FakeDisplaySnapshot::Builder()
          .SetId(123)
          .SetNativeMode(MakeDisplayMode(1920, 1080, true, 60))
          .AddMode(MakeDisplayMode(1920, 1080, false, 60))
          .Build();

  ManagedDisplayInfo::ManagedDisplayModeList display_modes =
      DisplayChangeObserver::GetExternalManagedDisplayModeList(
          *display_snapshot);

  const bool listing_all_modes = GetParam();

  if (listing_all_modes) {
    ASSERT_EQ(2u, display_modes.size());
    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[0].size());
    EXPECT_FALSE(display_modes[0].is_interlaced());
    EXPECT_FALSE(display_modes[0].native());
    EXPECT_EQ(display_modes[0].refresh_rate(), 60);

    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[1].size());
    EXPECT_TRUE(display_modes[1].is_interlaced());
    EXPECT_TRUE(display_modes[1].native());
    EXPECT_EQ(display_modes[1].refresh_rate(), 60);
  } else {
    // Only the native mode will be listed.
    ASSERT_EQ(1u, display_modes.size());
    EXPECT_EQ(gfx::Size(1920, 1080), display_modes[0].size());
    EXPECT_TRUE(display_modes[0].is_interlaced());
    EXPECT_TRUE(display_modes[0].native());
    EXPECT_EQ(display_modes[0].refresh_rate(), 60);
  }
}

TEST_P(DisplayChangeObserverTest, InvalidDisplayColorSpaces) {
  const std::unique_ptr<DisplaySnapshot> display_snapshot =
      FakeDisplaySnapshot::Builder()
          .SetId(123)
          .SetName("AmazingFakeDisplay")
          .SetNativeMode(MakeDisplayMode(1920, 1080, true, 60))
          .SetColorSpace(gfx::ColorSpace())
          .Build();

  ui::DeviceDataManager::CreateInstance();
  DisplayManager manager(nullptr);
  const auto display_mode = MakeDisplayMode(1920, 1080, true, 60);
  DisplayChangeObserver observer(&manager);
  const ManagedDisplayInfo display_info = CreateManagedDisplayInfo(
      &observer, display_snapshot.get(), display_mode.get());

  EXPECT_EQ(display_info.bits_per_channel(), 8u);
  const auto display_color_spaces = display_info.display_color_spaces();
  EXPECT_FALSE(display_color_spaces.SupportsHDR());

  EXPECT_EQ(
      DisplaySnapshot::PrimaryFormat(),
      display_color_spaces.GetOutputBufferFormat(gfx::ContentColorUsage::kSRGB,
                                                 /*needs_alpha=*/true));

  const auto color_space = display_color_spaces.GetRasterColorSpace();
  // DisplayColorSpaces will fix an invalid ColorSpace to return sRGB.
  EXPECT_TRUE(color_space.IsValid());
  EXPECT_EQ(color_space, gfx::ColorSpace::CreateSRGB());
}

TEST_P(DisplayChangeObserverTest, SDRDisplayColorSpaces) {
  const std::unique_ptr<DisplaySnapshot> display_snapshot =
      FakeDisplaySnapshot::Builder()
          .SetId(123)
          .SetName("AmazingFakeDisplay")
          .SetNativeMode(MakeDisplayMode(1920, 1080, true, 60))
          .SetColorSpace(gfx::ColorSpace::CreateSRGB())
          .Build();

  ui::DeviceDataManager::CreateInstance();
  DisplayManager manager(nullptr);
  const auto display_mode = MakeDisplayMode(1920, 1080, true, 60);
  DisplayChangeObserver observer(&manager);
  const ManagedDisplayInfo display_info = CreateManagedDisplayInfo(
      &observer, display_snapshot.get(), display_mode.get());

  EXPECT_EQ(display_info.bits_per_channel(), 8u);

  const auto display_color_spaces = display_info.display_color_spaces();
  EXPECT_FALSE(display_color_spaces.SupportsHDR());

  EXPECT_EQ(
      DisplaySnapshot::PrimaryFormat(),
      display_color_spaces.GetOutputBufferFormat(gfx::ContentColorUsage::kSRGB,
                                                 /*needs_alpha=*/true));

  const auto color_space = display_color_spaces.GetRasterColorSpace();
  EXPECT_TRUE(color_space.IsValid());
  EXPECT_EQ(color_space.GetPrimaryID(), gfx::ColorSpace::PrimaryID::BT709);
  EXPECT_EQ(color_space.GetTransferID(), gfx::ColorSpace::TransferID::SRGB);
}

TEST_P(DisplayChangeObserverTest, WCGDisplayColorSpaces) {
  const std::unique_ptr<DisplaySnapshot> display_snapshot =
      FakeDisplaySnapshot::Builder()
          .SetId(123)
          .SetName("AmazingFakeDisplay")
          .SetNativeMode(MakeDisplayMode(1920, 1080, true, 60))
          .SetColorSpace(gfx::ColorSpace::CreateDisplayP3D65())
          .Build();

  ui::DeviceDataManager::CreateInstance();
  DisplayManager manager(nullptr);
  const auto display_mode = MakeDisplayMode(1920, 1080, true, 60);
  DisplayChangeObserver observer(&manager);
  const ManagedDisplayInfo display_info = CreateManagedDisplayInfo(
      &observer, display_snapshot.get(), display_mode.get());

  EXPECT_EQ(display_info.bits_per_channel(), 8u);

  const auto display_color_spaces = display_info.display_color_spaces();
  EXPECT_FALSE(display_color_spaces.SupportsHDR());

  EXPECT_EQ(
      DisplaySnapshot::PrimaryFormat(),
      display_color_spaces.GetOutputBufferFormat(gfx::ContentColorUsage::kSRGB,
                                                 /*needs_alpha=*/true));

  const auto color_space = display_color_spaces.GetRasterColorSpace();
  EXPECT_TRUE(color_space.IsValid());
  EXPECT_EQ(color_space.GetPrimaryID(), gfx::ColorSpace::PrimaryID::BT709);
  EXPECT_EQ(color_space.GetTransferID(), gfx::ColorSpace::TransferID::SRGB);
}

#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_P(DisplayChangeObserverTest, HDRDisplayColorSpaces) {
  // TODO(crbug.com/40652358): Remove this flag and provision when HDR is fully
  // supported on ChromeOS.
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(
      features::kEnableExternalDisplayHDR10Mode);

  const auto display_color_space = gfx::ColorSpace::CreateHDR10();
  const std::unique_ptr<DisplaySnapshot> display_snapshot =
      FakeDisplaySnapshot::Builder()
          .SetId(123)
          .SetName("AmazingFakeDisplay")
          .SetNativeMode(MakeDisplayMode(1920, 1080, true, 60))
          .SetColorSpace(display_color_space)
          .SetBitsPerChannel(10u)
          .SetHDRStaticMetadata(
              {609.0, 500.0, 0.01,
               gfx::HDRStaticMetadata::EotfMask({
                   gfx::HDRStaticMetadata::Eotf::kGammaSdrRange,
                   gfx::HDRStaticMetadata::Eotf::kPq,
               })})
          .Build();

  ui::DeviceDataManager::CreateInstance();
  DisplayManager manager(nullptr);
  const auto display_mode = MakeDisplayMode(1920, 1080, true, 60);
  DisplayChangeObserver observer(&manager);
  const ManagedDisplayInfo display_info = CreateManagedDisplayInfo(
      &observer, display_snapshot.get(), display_mode.get());

  EXPECT_EQ(display_info.bits_per_channel(), 10u);

  const auto display_color_spaces = display_info.display_color_spaces();
  EXPECT_TRUE(display_color_spaces.SupportsHDR());

  // Ensure that all spaces be HDR10, and have headroom of 3x (609/203).
  EXPECT_EQ(
      gfx::BufferFormat::RGBA_1010102,
      display_color_spaces.GetOutputBufferFormat(gfx::ContentColorUsage::kSRGB,
                                                 /*needs_alpha=*/true));
  EXPECT_EQ(
      gfx::ColorSpace::CreateHDR10(),
      display_color_spaces.GetOutputColorSpace(gfx::ContentColorUsage::kSRGB,
                                               /*needs_alpha=*/true));
  EXPECT_EQ(
      gfx::BufferFormat::RGBA_1010102,
      display_color_spaces.GetOutputBufferFormat(gfx::ContentColorUsage::kHDR,
                                                 /*needs_alpha=*/true));
  EXPECT_EQ(
      gfx::ColorSpace::CreateHDR10(),
      display_color_spaces.GetOutputColorSpace(gfx::ContentColorUsage::kHDR,
                                               /*needs_alpha=*/true));
  EXPECT_EQ(3.f, display_color_spaces.GetHDRMaxLuminanceRelative());
}
#endif

TEST_P(DisplayChangeObserverTest, VSyncRateMin) {
  ui::DeviceDataManager::CreateInstance();
  DisplayManager manager(nullptr);
  DisplayChangeObserver observer(&manager);

  // Verify that vsync_rate_min is absent from DisplayInfo when it is not
  // present from the DisplayMode.
  {
    const std::unique_ptr<DisplaySnapshot> display_snapshot =
        FakeDisplaySnapshot::Builder()
            .SetId(123)
            .SetName("AmazingFakeDisplay")
            .SetNativeMode(MakeDisplayMode(1920, 1080, true, 60))
            .Build();
    const std::unique_ptr<DisplayMode> display_mode =
        MakeDisplayMode(1920, 1080, true, 60);
    const ManagedDisplayInfo display_info = CreateManagedDisplayInfo(
        &observer, display_snapshot.get(), display_mode.get());

    EXPECT_EQ(display_info.vsync_rate_min(), std::nullopt);
  }

  // Verify that the value of vsync_rate_min is correctly taken from the display
  // mode.
  {
    const std::unique_ptr<DisplaySnapshot> display_snapshot =
        FakeDisplaySnapshot::Builder()
            .SetId(123)
            .SetName("AmazingFakeDisplay")
            .SetNativeMode(MakeDisplayMode(1920, 1080, true, 60))
            .Build();
    const std::unique_ptr<DisplayMode> display_mode =
        MakeDisplayMode(1920, 1080, true, 60, 48.000488f);
    const ManagedDisplayInfo display_info = CreateManagedDisplayInfo(
        &observer, display_snapshot.get(), display_mode.get());

    EXPECT_EQ(display_info.vsync_rate_min(), 48.000488f);
  }
}

TEST_P(DisplayChangeObserverTest, DisplayModeNativeCalculation) {
  ui::DeviceDataManager::CreateInstance();
  DisplayManager manager(nullptr);
  DisplayChangeObserver observer(&manager);

  // For external display, verify that native attribute is determined by
  // comparing current mode with the DisplaySnapshot's native mode. Native is
  // true when they are the same.
  {
    const std::unique_ptr<DisplaySnapshot> display_snapshot =
        FakeDisplaySnapshot::Builder()
            .SetId(123)
            .SetType(DISPLAY_CONNECTION_TYPE_DISPLAYPORT)
            .SetNativeMode(MakeDisplayMode(1920, 1080, true, 60))
            .SetCurrentMode(MakeDisplayMode(1920, 1080, true, 60))
            .Build();

    const DisplayMode* display_mode = display_snapshot->current_mode();
    const ManagedDisplayInfo display_info = CreateManagedDisplayInfo(
        &observer, display_snapshot.get(), display_mode);

    EXPECT_TRUE(display_info.native());
  }

  // For external display, verify that native attribute is determined by
  // comparing current mode with the DisplaySnapshot's native mode. Native is
  // false when they are different.
  {
    const std::unique_ptr<DisplaySnapshot> display_snapshot =
        FakeDisplaySnapshot::Builder()
            .SetId(123)
            .SetType(DISPLAY_CONNECTION_TYPE_DISPLAYPORT)
            .SetNativeMode(MakeDisplayMode(3840, 2160, true, 60))
            .SetCurrentMode(MakeDisplayMode(1920, 1080, true, 60))
            .Build();

    const DisplayMode* display_mode = display_snapshot->current_mode();
    const ManagedDisplayInfo display_info = CreateManagedDisplayInfo(
        &observer, display_snapshot.get(), display_mode);

    EXPECT_FALSE(display_info.native());
  }

  // For internal display, verify that native attribute is always true.
  {
    const std::unique_ptr<DisplaySnapshot> display_snapshot =
        FakeDisplaySnapshot::Builder()
            .SetId(123)
            .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
            .SetCurrentMode(MakeDisplayMode(1920, 1080, true, 60))
            .Build();

    const DisplayMode* display_mode = display_snapshot->current_mode();
    const ManagedDisplayInfo display_info = CreateManagedDisplayInfo(
        &observer, display_snapshot.get(), display_mode);

    EXPECT_TRUE(display_info.native());
  }
}

INSTANTIATE_TEST_SUITE_P(All,
                         DisplayChangeObserverTest,
                         ::testing::Values(false, true));

using DisplayResolutionTest = testing::Test;

auto CreateDisplay = [](const ManagedDisplayInfo& managed_display_info) {
  Display display(/*id=*/1);
  const float effective_scale =
      managed_display_info.GetEffectiveDeviceScaleFactor();
  display.SetScaleAndBounds(effective_scale,
                            managed_display_info.bounds_in_native());
  EXPECT_EQ(effective_scale, display.device_scale_factor());
  return display;
};

TEST_F(DisplayResolutionTest, CheckEffectiveResolutionUMAIndex) {
  std::map<int, gfx::Size> logical_resolutions;
  for (const auto& display_config : display_configs) {
    gfx::Size size = display_config.resolution;
    if (size.width() < size.height())
      size = gfx::Size(size.height(), size.width());

    const float dsf = display_config.expected_dsf;

    std::array<float, kNumOfZoomFactors> zoom_levels;
    bool found = false;
    if (dsf == 1.f) {
      for (const ZoomListBucket& zoom_list_bucket : kZoomListBuckets) {
        if (size.width() >= zoom_list_bucket.first) {
          zoom_levels = zoom_list_bucket.second;
          found = true;
        }
      }
    } else {
      for (const ZoomListBucketDsf& zoom_list_bucket : kZoomListBucketsForDsf) {
        if (cc::MathUtil::IsWithinEpsilon(dsf, zoom_list_bucket.first)) {
          zoom_levels = zoom_list_bucket.second;
          found = true;
        }
      }
    }
    EXPECT_TRUE(found);
    for (float zoom_level : zoom_levels) {
      ManagedDisplayInfo info;
      info.set_device_scale_factor(dsf);
      info.set_zoom_factor(zoom_level);
      info.SetBounds(gfx::Rect(size));

      Display display = CreateDisplay(info);

      gfx::Size logical_resolution = display.size();
      gfx::Size portrait_logical_resolution = logical_resolution;
      portrait_logical_resolution.Transpose();

      const int landscape_key =
          logical_resolution.width() * logical_resolution.height();
      const int portrait_key = landscape_key - 1;

      auto it = logical_resolutions.find(landscape_key);
      if (it != logical_resolutions.end()) {
        EXPECT_EQ(it->second, logical_resolution);
      } else {
        logical_resolutions[landscape_key] = logical_resolution;
      }

      it = logical_resolutions.find(portrait_key);
      if (it != logical_resolutions.end()) {
        EXPECT_EQ(it->second, portrait_logical_resolution);
      } else {
        logical_resolutions[portrait_key] = portrait_logical_resolution;
      }
    }
  }

#if 0
  // Enable this code to re-generate the "EffectiveResolution" in enums.xml.
  for (auto pair : logical_resolutions) {
    std::cout << "  <int value=\"" << pair.first << "\" label=\""
               << pair.second.width() << " x " << pair.second.height()
              << "\"/>" << std::endl;
  }
#endif

  // With the current set of display configs and zoom levels, there are only 322
  // possible effective resolutions for internal displays in chromebooks. Update
  // this value when adding a new display config, and re-generate the
  // EffectiveResolution value in enum.xml.
  EXPECT_EQ(logical_resolutions.size(), 322ul);
}

// Make sure that when display zoom is applied, the effective device scale
// factor (device_scale_factor * zoomfactor) and the rational number (pixel
// width / logical with) is close enough (<kDeviceScaleFactorErrorTolerance).
TEST_F(DisplayResolutionTest, DisplayZoom) {
  // For internal displays
  for (auto& config : display_configs) {
    const float dpi = ComputeDpi(config.diagonal_size, config.resolution);
    const auto snapshot = FakeDisplaySnapshot::Builder()
                              .SetId(10)
                              .SetType(DISPLAY_CONNECTION_TYPE_INTERNAL)
                              .SetNativeMode(config.resolution)
                              .SetCurrentMode(config.resolution)
                              .SetDPI(dpi)
                              .Build();
    const auto* native_mode = snapshot->native_mode();
    auto managed_display_info = DisplayChangeObserver::CreateManagedDisplayInfo(
        snapshot.get(), native_mode,
        /*native=*/true, config.expected_dsf, dpi, std::string());
    const std::vector<float> zooms =
        GetDisplayZoomFactors(managed_display_info.display_modes()[0]);
    // For default scale factor, they should be the same.
    EXPECT_NEAR(config.expected_dsf,
                managed_display_info.GetEffectiveDeviceScaleFactor(),
                0.0000001);

    for (auto zoom : zooms) {
      managed_display_info.set_zoom_factor(zoom);
      const Display display = CreateDisplay(managed_display_info);

      // Emulate how lacros computes the scale factor.
      const float scale_factor = config.resolution.width() /
                                 static_cast<float>(display.size().width());
      EXPECT_NEAR(scale_factor, display.device_scale_factor(),
                  kDeviceScaleFactorErrorTolerance);
    }
  }

  // Typical external display sizes.
  constexpr gfx::Size kExternalDisplaySizes[] = {
      {4096, 2160}, {3840, 2160}, {3440, 1440}, {2560, 1600}, {2560, 1440},
      {1920, 1200}, {1920, 1080}, {1600, 900},  {1440, 900}};
  for (auto& size : kExternalDisplaySizes) {
    const auto snapshot = FakeDisplaySnapshot::Builder()
                              .SetId(10)
                              .SetType(DISPLAY_CONNECTION_TYPE_HDMI)
                              .SetNativeMode(size)
                              .SetCurrentMode(size)
                              .Build();
    const auto* native_mode = snapshot->native_mode();
    auto managed_display_info = DisplayChangeObserver::CreateManagedDisplayInfo(
        snapshot.get(), native_mode,
        /*native=*/true, /*device_scale_factor=*/1.0f, /*dpi=*/160,
        std::string());
    const std::vector<float> zooms =
        GetDisplayZoomFactors(managed_display_info.display_modes()[0]);

    for (auto zoom : zooms) {
      managed_display_info.set_zoom_factor(zoom);
      const Display display = CreateDisplay(managed_display_info);

      // Emulate how lacros computes the scale factor.
      const float scale_factor =
          size.width() / static_cast<float>(display.size().width());
      EXPECT_NEAR(scale_factor, display.device_scale_factor(),
                  kDeviceScaleFactorErrorTolerance);
    }
  }
}

}  // namespace display