chromium/chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider_unittest.cc

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

#include "chrome/browser/ui/webui/ash/settings/pages/device/display_settings/display_settings_provider.h"

#include "ash/constants/ash_features.h"
#include "ash/display/display_performance_mode_controller.h"
#include "ash/public/cpp/tablet_mode.h"
#include "ash/shell.h"
#include "ash/system/brightness/brightness_controller_chromeos.h"
#include "ash/system/brightness_control_delegate.h"
#include "base/functional/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "chrome/test/base/chrome_ash_test_base.h"
#include "chromeos/dbus/power_manager/backlight.pb.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/display/screen.h"

namespace ash::settings {

namespace {

constexpr base::TimeDelta kMetricsDelayTimerInterval = base::Seconds(2);

// A mock observer that records current tablet mode status and counts when
// OnTabletModeChanged function is called.
class FakeTabletModeObserver : public mojom::TabletModeObserver {
 public:
  uint32_t num_tablet_mode_change_calls() const {
    return num_tablet_mode_change_calls_;
  }

  bool is_tablet_mode() { return is_tablet_mode_; }

  // mojom::TabletModeObserver:
  void OnTabletModeChanged(bool is_tablet_mode) override {
    ++num_tablet_mode_change_calls_;
    is_tablet_mode_ = is_tablet_mode;

    if (quit_callback_) {
      std::move(quit_callback_).Run();
    }
  }

  void WaitForTabletModeChanged() {
    DCHECK(quit_callback_.is_null());
    base::RunLoop loop;
    quit_callback_ = loop.QuitClosure();
    loop.Run();
  }

  mojo::Receiver<mojom::TabletModeObserver> receiver{this};

 private:
  uint32_t num_tablet_mode_change_calls_ = 0;
  bool is_tablet_mode_ = false;
  base::OnceClosure quit_callback_;
};

// A mock observer that counts when ObserveDisplayConfiguration function is
// called.
class FakeDisplayConfigurationObserver
    : public mojom::DisplayConfigurationObserver {
 public:
  uint32_t num_display_configuration_changed_calls() const {
    return num_display_configuration_changed_calls_;
  }

  // mojom::DisplayConfigurationObserver:
  void OnDisplayConfigurationChanged() override {
    ++num_display_configuration_changed_calls_;

    if (quit_callback_) {
      std::move(quit_callback_).Run();
    }
  }

  void WaitForDisplayConfigurationChanged() {
    DCHECK(quit_callback_.is_null());
    base::RunLoop loop;
    quit_callback_ = loop.QuitClosure();
    loop.Run();
  }

  mojo::Receiver<mojom::DisplayConfigurationObserver> receiver{this};

 private:
  uint32_t num_display_configuration_changed_calls_ = 0;
  base::OnceClosure quit_callback_;
};

// A mock observer that counts when OnDisplayBrightnessChanged function is
// called.
class FakeDisplayBrightnessSettingsObserver
    : public mojom::DisplayBrightnessSettingsObserver {
 public:
  uint32_t num_display_brightness_changed_calls() const {
    return num_display_brightness_changed_calls_;
  }

  double current_brightness() { return current_brightness_; }

  // mojom::DisplayBrightnessSettingsObserver:
  void OnDisplayBrightnessChanged(double brightness_percent,
                                  bool triggered_by_als) override {
    ++num_display_brightness_changed_calls_;
    current_brightness_ = brightness_percent;

    if (quit_callback_) {
      std::move(quit_callback_).Run();
    }
  }

  void WaitForDisplayBrightnessChanged() {
    DCHECK(quit_callback_.is_null());
    base::RunLoop loop;
    quit_callback_ = loop.QuitClosure();
    loop.Run();
  }

  mojo::Receiver<mojom::DisplayBrightnessSettingsObserver> receiver{this};

 private:
  uint32_t num_display_brightness_changed_calls_ = 0;
  double current_brightness_ = 0;
  base::OnceClosure quit_callback_;
};

// A mock observer that counts when OnAmbientLightSensorEnabledChanged function
// is called.
class FakeAmbientLightSensorObserver
    : public mojom::AmbientLightSensorObserver {
 public:
  uint32_t num_ambient_light_sensor_enabled_changed_calls() const {
    return num_ambient_light_sensor_enabled_changed_calls_;
  }

  double is_ambient_light_sensor_enabled() {
    return is_ambient_light_sensor_enabled_;
  }

  // mojom::AmbientLightSensorObserver:
  void OnAmbientLightSensorEnabledChanged(
      bool is_ambient_light_sensor_enabled) override {
    ++num_ambient_light_sensor_enabled_changed_calls_;
    is_ambient_light_sensor_enabled_ = is_ambient_light_sensor_enabled;

    if (quit_callback_) {
      std::move(quit_callback_).Run();
    }
  }

  void WaitForAmbientLightSensorEnabledChanged() {
    DCHECK(quit_callback_.is_null());
    base::RunLoop loop;
    quit_callback_ = loop.QuitClosure();
    loop.Run();
  }

  mojo::Receiver<mojom::AmbientLightSensorObserver> receiver{this};

 private:
  uint32_t num_ambient_light_sensor_enabled_changed_calls_ = 0;
  bool is_ambient_light_sensor_enabled_ = true;
  base::OnceClosure quit_callback_;
};

class FakeBrightnessControlDelegate : public BrightnessControlDelegate {
 public:
  FakeBrightnessControlDelegate() = default;

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

  ~FakeBrightnessControlDelegate() override = default;

  void HandleBrightnessDown() override {}
  void HandleBrightnessUp() override {}
  void SetBrightnessPercent(double percent,
                            bool gradual,
                            BrightnessChangeSource source) override {
    brightness_percent_ = percent;
    last_brightness_change_source_ = source;
  }
  void GetBrightnessPercent(
      base::OnceCallback<void(std::optional<double>)> callback) override {
    std::move(callback).Run(brightness_percent_);
  }
  void SetAmbientLightSensorEnabled(
      bool enabled,
      BrightnessControlDelegate::AmbientLightSensorEnabledChangeSource source)
      override {
    is_ambient_light_sensor_enabled_ = enabled;
  }
  void GetAmbientLightSensorEnabled(
      base::OnceCallback<void(std::optional<bool>)> callback) override {
    std::move(callback).Run(is_ambient_light_sensor_enabled_);
  }
  void HasAmbientLightSensor(
      base::OnceCallback<void(std::optional<bool>)> callback) override {
    std::move(callback).Run(has_ambient_light_sensor_);
  }

  double brightness_percent() const { return brightness_percent_; }
  BrightnessChangeSource last_brightness_change_source() const {
    return last_brightness_change_source_;
  }
  bool is_ambient_light_sensor_enabled() const {
    return is_ambient_light_sensor_enabled_;
  }
  void set_has_ambient_light_sensor(bool has_ambient_light_sensor) {
    has_ambient_light_sensor_ = has_ambient_light_sensor;
  }

 private:
  double brightness_percent_;
  BrightnessChangeSource last_brightness_change_source_ =
      BrightnessChangeSource::kUnknown;
  // Enabled by default to match system behavior.
  bool is_ambient_light_sensor_enabled_ = true;
  bool has_ambient_light_sensor_ = true;
};

}  // namespace

class DisplaySettingsProviderTest : public ChromeAshTestBase {
 public:
  DisplaySettingsProviderTest()
      : ChromeAshTestBase(std::make_unique<content::BrowserTaskEnvironment>(
            content::BrowserTaskEnvironment::TimeSource::MOCK_TIME)) {}
  ~DisplaySettingsProviderTest() override = default;

  void SetUp() override {
    ChromeAshTestBase::SetUp();
    feature_list_.InitAndDisableFeature(
        features::kEnableBrightnessControlInSettings);
    provider_ = std::make_unique<DisplaySettingsProvider>();
    brightness_control_delegate_ =
        std::make_unique<FakeBrightnessControlDelegate>();
  }

  void TearDown() override {
    provider_.reset();
    ChromeAshTestBase::TearDown();
  }

  void FastForwardBy(base::TimeDelta delta) {
    task_environment()->FastForwardBy(delta);
  }

 protected:
  std::unique_ptr<DisplaySettingsProvider> provider_;
  std::unique_ptr<FakeBrightnessControlDelegate> brightness_control_delegate_;
  base::HistogramTester histogram_tester_;
  base::test::ScopedFeatureList feature_list_;
};

// Test the behavior when the tablet mode status has changed. The tablet mode is
// initialized as "not-in-tablet-mode".
TEST_F(DisplaySettingsProviderTest, TabletModeObservation) {
  FakeTabletModeObserver fake_observer;
  base::test::TestFuture<bool> future;

  // Attach a tablet mode observer.
  provider_->ObserveTabletMode(
      fake_observer.receiver.BindNewPipeAndPassRemote(), future.GetCallback());
  base::RunLoop().RunUntilIdle();

  // Default initial state is "not-in-tablet-mode".
  ASSERT_FALSE(future.Get<0>());

  provider_->OnTabletModeEventsBlockingChanged();
  fake_observer.WaitForTabletModeChanged();

  EXPECT_EQ(1u, fake_observer.num_tablet_mode_change_calls());
}

// Test the behavior when the display configuration has changed.
TEST_F(DisplaySettingsProviderTest, DisplayConfigurationObservation) {
  FakeDisplayConfigurationObserver fake_observer;

  // Attach a display configuration observer.
  provider_->ObserveDisplayConfiguration(
      fake_observer.receiver.BindNewPipeAndPassRemote());
  base::RunLoop().RunUntilIdle();

  provider_->OnDidProcessDisplayChanges(/*configuration_change=*/{{}, {}, {}});
  fake_observer.WaitForDisplayConfigurationChanged();

  EXPECT_EQ(1u, fake_observer.num_display_configuration_changed_calls());
}

// Test histogram is recorded when users change display settings.
TEST_F(DisplaySettingsProviderTest, ChangeDisplaySettingsHistogram) {
  // Loop through all display setting types.
  for (int typeInt = static_cast<int>(mojom::DisplaySettingsType::kMinValue);
       typeInt <= static_cast<int>(mojom::DisplaySettingsType::kMaxValue);
       typeInt++) {
    mojom::DisplaySettingsType type =
        static_cast<mojom::DisplaySettingsType>(typeInt);
    // Settings applied to both internal and external displays.
    if (type == mojom::DisplaySettingsType::kDisplayPage ||
        type == mojom::DisplaySettingsType::kMirrorMode ||
        type == mojom::DisplaySettingsType::kUnifiedMode ||
        type == mojom::DisplaySettingsType::kPrimaryDisplay) {
      auto value = mojom::DisplaySettingsValue::New();
      if (type == mojom::DisplaySettingsType::kMirrorMode) {
        value->mirror_mode_status = true;
      } else if (type == mojom::DisplaySettingsType::kUnifiedMode) {
        value->unified_mode_status = true;
      }
      provider_->RecordChangingDisplaySettings(type, std::move(value));
      histogram_tester_.ExpectBucketCount(
          DisplaySettingsProvider::kDisplaySettingsHistogramName, type, 1);
    } else {
      // Settings applied to either internal or external displays.
      for (bool internal : {true, false}) {
        auto value = mojom::DisplaySettingsValue::New();
        value->is_internal_display = internal;
        if (type == mojom::DisplaySettingsType::kOrientation) {
          value->orientation =
              mojom::DisplaySettingsOrientationOption::k90Degree;
        } else if (type == mojom::DisplaySettingsType::kNightLight) {
          value->night_light_status = true;
        } else if (type == mojom::DisplaySettingsType::kNightLightSchedule) {
          value->night_light_schedule =
              mojom::DisplaySettingsNightLightScheduleOption::kSunsetToSunrise;
        }
        provider_->RecordChangingDisplaySettings(type, std::move(value));

        std::string histogram_name(
            DisplaySettingsProvider::kDisplaySettingsHistogramName);
        histogram_name.append(internal ? ".Internal" : ".External");
        histogram_tester_.ExpectBucketCount(histogram_name, type, 1);
      }
    }
  }
}

// Test histogram is recorded when users change display orientation.
TEST_F(DisplaySettingsProviderTest, ChangeDisplayOrientationHistogram) {
  for (int orientation_int =
           static_cast<int>(mojom::DisplaySettingsOrientationOption::kMinValue);
       orientation_int <=
       static_cast<int>(mojom::DisplaySettingsOrientationOption::kMaxValue);
       orientation_int++) {
    mojom::DisplaySettingsOrientationOption orientation =
        static_cast<mojom::DisplaySettingsOrientationOption>(orientation_int);
    // Settings applied to either internal or external displays.
    for (bool internal : {true, false}) {
      auto value = mojom::DisplaySettingsValue::New();
      value->is_internal_display = internal;
      value->orientation = orientation;
      provider_->RecordChangingDisplaySettings(
          mojom::DisplaySettingsType::kOrientation, std::move(value));

      std::string histogram_name(
          DisplaySettingsProvider::kDisplaySettingsHistogramName);
      histogram_name.append(internal ? ".Internal" : ".External");
      histogram_name.append(".Orientation");
      histogram_tester_.ExpectBucketCount(histogram_name, orientation, 1);
    }
  }
}

// Test histogram is recorded when users toggle display night light status.
TEST_F(DisplaySettingsProviderTest, ToggleDisplayNightLightStatusHistogram) {
  for (bool night_light_status : {true, false}) {
    // Settings applied to either internal or external displays.
    for (bool internal : {true, false}) {
      auto value = mojom::DisplaySettingsValue::New();
      value->is_internal_display = internal;
      value->night_light_status = night_light_status;
      provider_->RecordChangingDisplaySettings(
          mojom::DisplaySettingsType::kNightLight, std::move(value));

      std::string histogram_name(
          DisplaySettingsProvider::kDisplaySettingsHistogramName);
      histogram_name.append(internal ? ".Internal" : ".External");
      histogram_name.append(".NightLightStatus");
      histogram_tester_.ExpectBucketCount(histogram_name, night_light_status,
                                          1);
    }
  }
}

// Test histogram is recorded when users change display night light schedule.
TEST_F(DisplaySettingsProviderTest, ToggleDisplayNightLightScheduleHistogram) {
  for (int night_light_schedule_int = static_cast<int>(
           mojom::DisplaySettingsNightLightScheduleOption::kMinValue);
       night_light_schedule_int <=
       static_cast<int>(
           mojom::DisplaySettingsNightLightScheduleOption::kMaxValue);
       night_light_schedule_int++) {
    mojom::DisplaySettingsNightLightScheduleOption night_light_schedule =
        static_cast<mojom::DisplaySettingsNightLightScheduleOption>(
            night_light_schedule_int);
    // Settings applied to either internal or external displays.
    for (bool internal : {true, false}) {
      auto value = mojom::DisplaySettingsValue::New();
      value->is_internal_display = internal;
      value->night_light_schedule = night_light_schedule;
      provider_->RecordChangingDisplaySettings(
          mojom::DisplaySettingsType::kNightLightSchedule, std::move(value));

      std::string histogram_name(
          DisplaySettingsProvider::kDisplaySettingsHistogramName);
      histogram_name.append(internal ? ".Internal" : ".External");
      histogram_name.append(".NightLightSchedule");
      histogram_tester_.ExpectBucketCount(histogram_name, night_light_schedule,
                                          1);
    }
  }
}

// Test histogram is recorded when users toggle display mirror mode status.
TEST_F(DisplaySettingsProviderTest, ToggleDisplayMirrorModeStatusHistogram) {
  for (bool mirror_mode_status : {true, false}) {
    auto value = mojom::DisplaySettingsValue::New();
    value->mirror_mode_status = mirror_mode_status;
    provider_->RecordChangingDisplaySettings(
        mojom::DisplaySettingsType::kMirrorMode, std::move(value));

    std::string histogram_name(
        DisplaySettingsProvider::kDisplaySettingsHistogramName);
    histogram_name.append(".MirrorModeStatus");
    histogram_tester_.ExpectBucketCount(histogram_name, mirror_mode_status, 1);
  }
}

// Test histogram is recorded when users toggle display unified mode status.
TEST_F(DisplaySettingsProviderTest, ToggleDisplayUnifiedModeStatusHistogram) {
  for (bool unified_mode_status : {true, false}) {
    auto value = mojom::DisplaySettingsValue::New();
    value->unified_mode_status = unified_mode_status;
    provider_->RecordChangingDisplaySettings(
        mojom::DisplaySettingsType::kUnifiedMode, std::move(value));

    std::string histogram_name(
        DisplaySettingsProvider::kDisplaySettingsHistogramName);
    histogram_name.append(".UnifiedModeStatus");
    histogram_tester_.ExpectBucketCount(histogram_name, unified_mode_status, 1);
  }
}

// Test histogram is recorded only when a display is connected for the first
// time.
TEST_F(DisplaySettingsProviderTest, NewDisplayConnectedHistogram) {
  // Expect no metrics fired before new display added.
  histogram_tester_.ExpectBucketCount(
      DisplaySettingsProvider::
          kUserOverrideExternalDisplayDefaultSettingsHistogram,
      DisplaySettingsProvider::DisplayDefaultSettingsMeasurement::
          kNewDisplayConnected,
      0);

  int64_t id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
  provider_->OnDisplayAdded(display::Display(id));

  // Expect to count new display is connected.
  histogram_tester_.ExpectBucketCount(
      DisplaySettingsProvider::kNewDisplayConnectedHistogram,
      DisplaySettingsProvider::DisplayType::kExternalDisplay, 1);
  histogram_tester_.ExpectBucketCount(
      DisplaySettingsProvider::
          kUserOverrideExternalDisplayDefaultSettingsHistogram,
      DisplaySettingsProvider::DisplayDefaultSettingsMeasurement::
          kNewDisplayConnected,
      1);

  UpdateDisplay("300x200");
  provider_->OnDisplayAdded(display::Display(id));

  // Expect not to count new display is connected since it's already saved
  // into prefs before.
  histogram_tester_.ExpectBucketCount(
      DisplaySettingsProvider::kNewDisplayConnectedHistogram,
      DisplaySettingsProvider::DisplayType::kExternalDisplay, 1);
  histogram_tester_.ExpectBucketCount(
      DisplaySettingsProvider::
          kUserOverrideExternalDisplayDefaultSettingsHistogram,
      DisplaySettingsProvider::DisplayDefaultSettingsMeasurement::
          kNewDisplayConnected,
      1);

  // Entering unified desk mode should not count new display connected.
  provider_->OnDisplayAdded(display::Display(display::kUnifiedDisplayId));
  histogram_tester_.ExpectBucketCount(
      DisplaySettingsProvider::kNewDisplayConnectedHistogram,
      DisplaySettingsProvider::DisplayType::kExternalDisplay, 1);
}

// Test histogram is recorded when user overrides system default display
// settings.
TEST_F(DisplaySettingsProviderTest, UserOverrideDefaultSettingsHistogram) {
  int64_t id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
  provider_->OnDisplayAdded(display::Display(id));

  constexpr uint16_t kTimeDeltaInMinute = 15;
  FastForwardBy(base::Minutes(kTimeDeltaInMinute));

  auto value = mojom::DisplaySettingsValue::New();
  value->is_internal_display = false;
  value->display_id = id;
  provider_->RecordChangingDisplaySettings(
      mojom::DisplaySettingsType::kResolution, std::move(value));

  histogram_tester_.ExpectTimeBucketCount(
      "ChromeOS.Settings.Display.External."
      "UserOverrideDisplayDefaultSettingsTimeElapsed.Resolution",
      base::Minutes(kTimeDeltaInMinute) / base::Minutes(1).InMilliseconds(),
      /*expected_count=*/1);

  // Expect user override resolution metrics fired.
  histogram_tester_.ExpectBucketCount(
      DisplaySettingsProvider::
          kUserOverrideExternalDisplayDefaultSettingsHistogram,
      DisplaySettingsProvider::DisplayDefaultSettingsMeasurement::
          kOverrideResolution,
      1);

  // Changing resolution again and expect not fire user override resolution
  // metrics.
  value = mojom::DisplaySettingsValue::New();
  value->is_internal_display = false;
  value->display_id = id;
  provider_->RecordChangingDisplaySettings(
      mojom::DisplaySettingsType::kResolution, std::move(value));

  histogram_tester_.ExpectBucketCount(
      DisplaySettingsProvider::
          kUserOverrideExternalDisplayDefaultSettingsHistogram,
      DisplaySettingsProvider::DisplayDefaultSettingsMeasurement::
          kOverrideResolution,
      1);
}

// Test histogram is not recorded when user overrides system default display
// settings after 60 minutes.
TEST_F(DisplaySettingsProviderTest,
       UserOverrideDefaultSettingsHistogramNotFired) {
  int64_t id = display::Screen::GetScreen()->GetPrimaryDisplay().id();
  provider_->OnDisplayAdded(display::Display(id));

  constexpr uint16_t kTimeDeltaInMinute = 61;
  FastForwardBy(base::Minutes(kTimeDeltaInMinute));

  auto value = mojom::DisplaySettingsValue::New();
  value->is_internal_display = false;
  value->display_id = id;
  provider_->RecordChangingDisplaySettings(
      mojom::DisplaySettingsType::kResolution, std::move(value));

  // Expect user override resolution metrics not fired.
  histogram_tester_.ExpectBucketCount(
      DisplaySettingsProvider::
          kUserOverrideExternalDisplayDefaultSettingsHistogram,
      DisplaySettingsProvider::DisplayDefaultSettingsMeasurement::
          kOverrideResolution,
      0);
}

TEST_F(DisplaySettingsProviderTest, UserToggleDisplayPerformance) {
  provider_->SetShinyPerformance(true);
  EXPECT_EQ(Shell::Get()
                ->display_performance_mode_controller()
                ->GetCurrentStateForTesting(),
            DisplayPerformanceModeController::ModeState::kHighPerformance);

  provider_->SetShinyPerformance(false);
  EXPECT_NE(Shell::Get()
                ->display_performance_mode_controller()
                ->GetCurrentStateForTesting(),
            DisplayPerformanceModeController::ModeState::kHighPerformance);
}

// Test the behavior when the display brightness is changed.
TEST_F(DisplaySettingsProviderTest, DisplayBrightnessSettingsObservation) {
  FakeDisplayBrightnessSettingsObserver fake_observer;
  base::test::TestFuture<double> future;

  // Attach a brightness settings observer.
  provider_->ObserveDisplayBrightnessSettings(
      fake_observer.receiver.BindNewPipeAndPassRemote(), future.GetCallback());
  base::RunLoop().RunUntilIdle();

  // Observer should not have been called yet.
  EXPECT_EQ(0u, fake_observer.num_display_brightness_changed_calls());

  double brightness_percent = 55.5;
  power_manager::BacklightBrightnessChange brightness_change;
  brightness_change.set_percent(brightness_percent);
  brightness_change.set_cause(
      power_manager::BacklightBrightnessChange_Cause_USER_REQUEST);
  provider_->ScreenBrightnessChanged(brightness_change);

  fake_observer.WaitForDisplayBrightnessChanged();

  // Observer should have been called.
  EXPECT_EQ(1u, fake_observer.num_display_brightness_changed_calls());
  // The brightness value that the observer received should match the brightness
  // from the provider's observer.
  EXPECT_EQ(brightness_percent, fake_observer.current_brightness());
}

// Test the behavior when setting the internal display screen brightness (when
// the feature flag is disabled).
TEST_F(DisplaySettingsProviderTest,
       SetInternalDisplayScreenBrightness_FeatureDisabled) {
  // No histograms should have been recorded yet.
  histogram_tester_.ExpectTotalCount(
      "ChromeOS.Settings.Display.Internal.BrightnessSliderAdjusted",
      /*expected_count=*/0);

  // Set the brightness with a sentinel value, so we can test that the
  // brightness doesn't change if the feature flag is disabled.
  double brightness_before_setting = 50.0;
  brightness_control_delegate_->SetBrightnessPercent(
      brightness_before_setting,
      /*gradual=*/false, /*source=*/
      BrightnessControlDelegate::BrightnessChangeSource::kQuickSettings);

  provider_->SetBrightnessControlDelegateForTesting(
      brightness_control_delegate_.get());

  double new_brightness_percent = 33.3;
  provider_->SetInternalDisplayScreenBrightness(new_brightness_percent);

  // When feature flag is disabled, setting the brightness has no effect.
  EXPECT_EQ(brightness_before_setting,
            brightness_control_delegate_->brightness_percent());

  // No histograms should have been recorded, because the feature is disabled.
  histogram_tester_.ExpectTotalCount(
      "ChromeOS.Settings.Display.Internal.BrightnessSliderAdjusted",
      /*expected_count=*/0);
}

// Test the behavior when setting the internal display screen brightness (when
// the feature flag is enabled).
TEST_F(DisplaySettingsProviderTest,
       SetInternalDisplayScreenBrightness_FeatureEnabled) {
  feature_list_.Reset();
  feature_list_.InitAndEnableFeature(
      ash::features::kEnableBrightnessControlInSettings);
  provider_->SetBrightnessControlDelegateForTesting(
      brightness_control_delegate_.get());

  // No histograms should have been recorded yet.
  histogram_tester_.ExpectTotalCount(
      "ChromeOS.Settings.Display.Internal.BrightnessSliderAdjusted",
      /*expected_count=*/0);

  double first_brightness_percent = 33.3;
  double second_brightness_percent = 44.4;
  double third_brightness_percent = 55.5;
  // Move the brightness slider rapidly in succession.
  provider_->SetInternalDisplayScreenBrightness(first_brightness_percent);
  FastForwardBy(kMetricsDelayTimerInterval / 4);
  provider_->SetInternalDisplayScreenBrightness(second_brightness_percent);
  FastForwardBy(kMetricsDelayTimerInterval / 4);
  provider_->SetInternalDisplayScreenBrightness(third_brightness_percent);

  // The BrightnessControlDelegate should have been called with the most recent
  // brightness percent.
  EXPECT_EQ(third_brightness_percent,
            brightness_control_delegate_->brightness_percent());
  // The BrightnessChangeSource should indicate that this change came from the
  // Settings app.
  EXPECT_EQ(BrightnessControlDelegate::BrightnessChangeSource::kSettingsApp,
            brightness_control_delegate_->last_brightness_change_source());

  // Wait for the metrics delay timer to resolve.
  FastForwardBy(kMetricsDelayTimerInterval);

  // Histogram should have been recorded for this change, but only for the most
  // recent brightness percent.
  histogram_tester_.ExpectTotalCount(
      "ChromeOS.Settings.Display.Internal.BrightnessSliderAdjusted",
      /*expected_count=*/1);
  histogram_tester_.ExpectBucketCount(
      "ChromeOS.Settings.Display.Internal.BrightnessSliderAdjusted",
      /*sample=*/first_brightness_percent,
      /*expected_count=*/0);
  histogram_tester_.ExpectBucketCount(
      "ChromeOS.Settings.Display.Internal.BrightnessSliderAdjusted",
      /*sample=*/second_brightness_percent,
      /*expected_count=*/0);
  histogram_tester_.ExpectBucketCount(
      "ChromeOS.Settings.Display.Internal.BrightnessSliderAdjusted",
      /*sample=*/third_brightness_percent,
      /*expected_count=*/1);
}

// Test the behavior when setting the internal display screen brightness (when
// the feature flag is disabled).
TEST_F(DisplaySettingsProviderTest,
       SetAmbientLightSensorEnabled_FeatureDisabled) {
  // No histograms should have been recorded.
  histogram_tester_.ExpectTotalCount(
      "ChromeOS.Settings.Display.Internal.AutoBrightnessEnabled",
      /*expected_count=*/0);

  // Set the ambient_light_sensor_enabled with a sentinel value, so we can test
  // that the value doesn't change if the feature flag is disabled.
  bool initial_sensor_enabled = true;
  brightness_control_delegate_->SetAmbientLightSensorEnabled(
      initial_sensor_enabled,
      BrightnessControlDelegate::AmbientLightSensorEnabledChangeSource::
          kSettingsApp);

  provider_->SetBrightnessControlDelegateForTesting(
      brightness_control_delegate_.get());

  bool expected_sensor_enabled = false;
  provider_->SetInternalDisplayAmbientLightSensorEnabled(
      expected_sensor_enabled);

  // When feature flag is disabled, setting the ambient light sensor value has
  // no effect, and the value should be equal to the initial value.
  EXPECT_EQ(initial_sensor_enabled,
            brightness_control_delegate_->is_ambient_light_sensor_enabled());

  // When the feature flag is disabled, metrics should not be recorded either.
  histogram_tester_.ExpectTotalCount(
      "ChromeOS.Settings.Display.Internal.AutoBrightnessEnabled",
      /*expected_count=*/0);
}

// Test the behavior when setting the internal display screen brightness (when
// the feature flag is enabled).
TEST_F(DisplaySettingsProviderTest,
       SetAmbientLightSensorEnabled_FeatureEnabled) {
  feature_list_.Reset();
  feature_list_.InitAndEnableFeature(
      ash::features::kEnableBrightnessControlInSettings);

  // No histograms should have been recorded yet.
  histogram_tester_.ExpectTotalCount(
      "ChromeOS.Settings.Display.Internal.AutoBrightnessEnabled",
      /*expected_count=*/0);

  // Set the ambient_light_sensor_enabled with a sentinel value, so we can test
  // that the value changes if the feature flag is enabled.
  bool initial_sensor_enabled = true;
  brightness_control_delegate_->SetAmbientLightSensorEnabled(
      initial_sensor_enabled,
      BrightnessControlDelegate::AmbientLightSensorEnabledChangeSource::
          kSettingsApp);

  provider_->SetBrightnessControlDelegateForTesting(
      brightness_control_delegate_.get());

  // When feature flag is enabled, setting the ambient light sensor value from
  // the provider should update the actual ambient light sensor value.
  bool expected_sensor_enabled = false;
  provider_->SetInternalDisplayAmbientLightSensorEnabled(
      expected_sensor_enabled);
  EXPECT_EQ(expected_sensor_enabled,
            brightness_control_delegate_->is_ambient_light_sensor_enabled());

  // Metrics should be recorded for this change.
  histogram_tester_.ExpectTotalCount(
      "ChromeOS.Settings.Display.Internal.AutoBrightnessEnabled",
      /*expected_count=*/1);
  histogram_tester_.ExpectBucketCount(
      "ChromeOS.Settings.Display.Internal.AutoBrightnessEnabled",
      /*sample=*/expected_sensor_enabled,
      /*expected_count=*/1);

  // Re-enabling the sensor from the provider should also work.
  bool expected_sensor_enabled2 = true;
  provider_->SetInternalDisplayAmbientLightSensorEnabled(
      expected_sensor_enabled2);
  EXPECT_EQ(expected_sensor_enabled2,
            brightness_control_delegate_->is_ambient_light_sensor_enabled());

  // Metrics should be recorded for this change.
  histogram_tester_.ExpectBucketCount(
      "ChromeOS.Settings.Display.Internal.AutoBrightnessEnabled",
      /*sample=*/expected_sensor_enabled2,
      /*expected_count=*/1);
  histogram_tester_.ExpectTotalCount(
      "ChromeOS.Settings.Display.Internal.AutoBrightnessEnabled",
      /*expected_count=*/2);
}

// Test that the ambient light sensor observer returns the correct information
// when the ambient light sensor status changes.
TEST_F(DisplaySettingsProviderTest, AmbientLightSensorObservation) {
  feature_list_.Reset();
  feature_list_.InitAndEnableFeature(
      ash::features::kEnableBrightnessControlInSettings);

  FakeAmbientLightSensorObserver fake_observer;
  base::test::TestFuture<bool> future;

  provider_->SetInternalDisplayAmbientLightSensorEnabled(false);

  provider_->ObserveAmbientLightSensor(
      fake_observer.receiver.BindNewPipeAndPassRemote(), future.GetCallback());
  base::RunLoop().RunUntilIdle();

  // The TestFuture should have been called with 'false', indicating that the
  // ambient light sensor is not enabled.
  EXPECT_FALSE(future.Get());

  // Observer should not have been called yet.
  EXPECT_EQ(0u, fake_observer.num_ambient_light_sensor_enabled_changed_calls());

  {
    bool is_ambient_light_sensor_enabled = true;
    power_manager::AmbientLightSensorChange change;
    change.set_cause(
        power_manager::AmbientLightSensorChange_Cause_BRIGHTNESS_USER_REQUEST);
    change.set_sensor_enabled(is_ambient_light_sensor_enabled);
    provider_->AmbientLightSensorEnabledChanged(change);

    fake_observer.WaitForAmbientLightSensorEnabledChanged();

    // Observer should have been called.
    EXPECT_EQ(1u,
              fake_observer.num_ambient_light_sensor_enabled_changed_calls());
    EXPECT_EQ(is_ambient_light_sensor_enabled,
              fake_observer.is_ambient_light_sensor_enabled());
  }

  {
    bool is_ambient_light_sensor_enabled = false;
    power_manager::AmbientLightSensorChange change;
    change.set_cause(
        power_manager::AmbientLightSensorChange_Cause_BRIGHTNESS_USER_REQUEST);
    change.set_sensor_enabled(is_ambient_light_sensor_enabled);
    provider_->AmbientLightSensorEnabledChanged(change);

    fake_observer.WaitForAmbientLightSensorEnabledChanged();

    // Observer should have been called a second time.
    EXPECT_EQ(2u,
              fake_observer.num_ambient_light_sensor_enabled_changed_calls());
    EXPECT_EQ(is_ambient_light_sensor_enabled,
              fake_observer.is_ambient_light_sensor_enabled());
  }
}

// Test the behavior when setting the internal display screen brightness (when
// the feature flag is enabled).
TEST_F(DisplaySettingsProviderTest, HasAmbientLightSensor) {
  // Configure the BrightnessControlDelegate to return that the device does have
  // at least one ambient light sensor.
  brightness_control_delegate_->set_has_ambient_light_sensor(true);
  provider_->SetBrightnessControlDelegateForTesting(
      brightness_control_delegate_.get());

  provider_->HasAmbientLightSensor(
      base::BindOnce([](bool has_ambient_light_sensor) {
        EXPECT_TRUE(has_ambient_light_sensor);
      }));

  // Configure the BrightnessControlDelegate to return that the device does not
  // have an ambient light sensor.
  brightness_control_delegate_->set_has_ambient_light_sensor(false);

  provider_->HasAmbientLightSensor(
      base::BindOnce([](bool has_ambient_light_sensor) {
        EXPECT_FALSE(has_ambient_light_sensor);
      }));
}

TEST_F(DisplaySettingsProviderTest, RecordUserInitiatedALSDisabledCause) {
  feature_list_.Reset();
  feature_list_.InitAndEnableFeature(
      ash::features::kEnableBrightnessControlInSettings);

  // No histograms should have been recorded yet.
  histogram_tester_.ExpectTotalCount(
      "ChromeOS.Settings.Display.Internal.UserInitiated."
      "AmbientLightSensorDisabledCause",
      /*expected_count=*/0);

  // Verify histogram recording when ALS is disabled via settings app.
  {
    power_manager::AmbientLightSensorChange cause_settings_app;
    cause_settings_app.set_sensor_enabled(false);
    cause_settings_app.set_cause(
        power_manager::
            AmbientLightSensorChange_Cause_USER_REQUEST_SETTINGS_APP);
    provider_->AmbientLightSensorEnabledChanged(cause_settings_app);
    histogram_tester_.ExpectUniqueSample(
        "ChromeOS.Settings.Display.Internal.UserInitiated."
        "AmbientLightSensorDisabledCause",
        DisplaySettingsProvider::
            UserInitiatedDisplayAmbientLightSensorDisabledCause::
                kUserRequestSettingsApp,
        1);
  }

  // Ensure enabling ALS does not emit histogram.
  {
    power_manager::AmbientLightSensorChange cause_settings_app;
    cause_settings_app.set_sensor_enabled(true);
    cause_settings_app.set_cause(
        power_manager::AmbientLightSensorChange_Cause_BRIGHTNESS_USER_REQUEST);
    provider_->AmbientLightSensorEnabledChanged(cause_settings_app);
    histogram_tester_.ExpectTotalCount(
        "ChromeOS.Settings.Display.Internal.UserInitiated."
        "AmbientLightSensorDisabledCause",
        /*expected_count=*/1);
  }

  // Test histogram update when ALS is disabled due to brightness change.
  {
    power_manager::AmbientLightSensorChange cause_settings_app;
    cause_settings_app.set_sensor_enabled(false);
    cause_settings_app.set_cause(
        power_manager::AmbientLightSensorChange_Cause_BRIGHTNESS_USER_REQUEST);
    provider_->AmbientLightSensorEnabledChanged(cause_settings_app);
    histogram_tester_.ExpectBucketCount(
        "ChromeOS.Settings.Display.Internal.UserInitiated."
        "AmbientLightSensorDisabledCause",
        DisplaySettingsProvider::
            UserInitiatedDisplayAmbientLightSensorDisabledCause::
                kBrightnessUserRequest,
        1);
  }
}

}  // namespace ash::settings