chromium/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_ambient_provider_impl_unittest.cc

// Copyright 2022 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/ash/system_web_apps/apps/personalization_app/personalization_app_ambient_provider_impl.h"

#include <memory>
#include <string_view>
#include <vector>

#include "ash/ambient/ambient_controller.h"
#include "ash/ambient/ambient_ui_settings.h"
#include "ash/ambient/test/ambient_ash_test_helper.h"
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/constants/geolocation_access_level.h"
#include "ash/public/cpp/ambient/ambient_prefs.h"
#include "ash/public/cpp/ambient/common/ambient_settings.h"
#include "ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h"
#include "ash/public/cpp/personalization_app/time_of_day_test_utils.h"
#include "ash/shell.h"
#include "ash/system/privacy_hub/privacy_hub_controller.h"
#include "ash/test/ash_test_base.h"
#include "ash/wallpaper/test_wallpaper_controller_client.h"
#include "ash/wallpaper/wallpaper_controller_impl.h"
#include "ash/webui/personalization_app/mojom/personalization_app.mojom.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_future.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/ambient_video_albums.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_metrics.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_web_ui.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/webui/web_ui_util.h"
#include "url/gurl.h"

namespace ash::personalization_app {

namespace {

using ::testing::_;
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::Eq;
using ::testing::Field;
using ::testing::IsFalse;
using ::testing::IsSupersetOf;
using ::testing::IsTrue;
using ::testing::Pointee;

constexpr char kFakeTestEmail[] = "[email protected]";
const AccountId kFakeTestAccountId =
    AccountId::FromUserEmailGaiaId(kFakeTestEmail, kFakeTestEmail);

class TestAmbientObserver
    : public ash::personalization_app::mojom::AmbientObserver {
 public:
  void OnAmbientModeEnabledChanged(bool ambient_mode_enabled) override {
    ambient_mode_enabled_ = ambient_mode_enabled;
  }

  void OnAmbientThemeChanged(mojom::AmbientTheme ambient_theme) override {
    ambient_theme_ = ambient_theme;
  }

  void OnTopicSourceChanged(mojom::TopicSource topic_source) override {
    topic_source_ = topic_source;
  }

  void OnAlbumsChanged(
      std::vector<ash::personalization_app::mojom::AmbientModeAlbumPtr> albums)
      override {
    albums_ = std::move(albums);
  }

  void OnScreenSaverDurationChanged(uint32_t minutes) override {
    duration_ = minutes;
  }

  void OnTemperatureUnitChanged(
      ash::AmbientModeTemperatureUnit temperature_unit) override {
    temperature_unit_ = temperature_unit;
  }

  void OnPreviewsFetched(const std::vector<GURL>& previews) override {
    previews_ = std::move(previews);
  }

  void OnAmbientUiVisibilityChanged(
      ash::AmbientUiVisibility visibility) override {
    ambient_ui_visibility_ = visibility;
  }

  void OnGeolocationPermissionForSystemServicesChanged(bool enabled) override {
    geolocation_permission_enabled_ = enabled;
  }

  mojo::PendingRemote<ash::personalization_app::mojom::AmbientObserver>
  pending_remote() {
    if (ambient_observer_receiver_.is_bound()) {
      ambient_observer_receiver_.reset();
    }

    return ambient_observer_receiver_.BindNewPipeAndPassRemote();
  }

  bool is_ambient_mode_enabled() {
    ambient_observer_receiver_.FlushForTesting();
    return ambient_mode_enabled_;
  }

  mojom::AmbientTheme ambient_theme() {
    ambient_observer_receiver_.FlushForTesting();
    return ambient_theme_;
  }

  mojom::TopicSource topic_source() {
    ambient_observer_receiver_.FlushForTesting();
    return topic_source_;
  }

  const std::vector<ash::personalization_app::mojom::AmbientModeAlbumPtr>&
  albums() {
    ambient_observer_receiver_.FlushForTesting();
    return albums_;
  }

  ash::AmbientModeTemperatureUnit temperature_unit() {
    ambient_observer_receiver_.FlushForTesting();
    return temperature_unit_;
  }

  ash::AmbientUiVisibility visibility() {
    ambient_observer_receiver_.FlushForTesting();
    return ambient_ui_visibility_;
  }

  std::vector<GURL> previews() {
    ambient_observer_receiver_.FlushForTesting();
    return previews_;
  }

  bool is_geolocation_enabled() {
    ambient_observer_receiver_.FlushForTesting();
    return geolocation_permission_enabled_;
  }

 private:
  mojo::Receiver<ash::personalization_app::mojom::AmbientObserver>
      ambient_observer_receiver_{this};

  bool ambient_mode_enabled_ = false;

  mojom::AmbientTheme ambient_theme_ = mojom::AmbientTheme::kSlideshow;
  uint32_t duration_ = 10;
  mojom::TopicSource topic_source_ = mojom::TopicSource::kArtGallery;
  ash::AmbientModeTemperatureUnit temperature_unit_ =
      ash::AmbientModeTemperatureUnit::kFahrenheit;
  ash::AmbientUiVisibility ambient_ui_visibility_ =
      ash::AmbientUiVisibility::kClosed;
  bool geolocation_permission_enabled_ = true;
  std::vector<ash::personalization_app::mojom::AmbientModeAlbumPtr> albums_;
  std::vector<GURL> previews_;
};

}  // namespace

class PersonalizationAppAmbientProviderImplTest : public ash::AshTestBase {
 public:
  PersonalizationAppAmbientProviderImplTest()
      : ash::AshTestBase(std::unique_ptr<base::test::TaskEnvironment>(
            std::make_unique<content::BrowserTaskEnvironment>(
                base::test::TaskEnvironment::TimeSource::MOCK_TIME))),
        profile_manager_(TestingBrowserProcess::GetGlobal()) {
    scoped_feature_list_.InitWithFeatures(
        personalization_app::GetTimeOfDayEnabledFeatures(), {});
  }
  PersonalizationAppAmbientProviderImplTest(
      const PersonalizationAppAmbientProviderImplTest&) = delete;
  PersonalizationAppAmbientProviderImplTest& operator=(
      const PersonalizationAppAmbientProviderImplTest&) = delete;
  ~PersonalizationAppAmbientProviderImplTest() override = default;

 protected:
  // testing::Test:
  void SetUp() override {
    ash::AshTestBase::SetUp();

    ASSERT_TRUE(profile_manager_.SetUp());
    profile_ = profile_manager_.CreateTestingProfile(kFakeTestEmail);

    ash::FakeChromeUserManager* user_manager =
        static_cast<ash::FakeChromeUserManager*>(
            user_manager::UserManager::Get());
    user_manager->AddUser(kFakeTestAccountId);

    web_contents_ = content::WebContents::Create(
        content::WebContents::CreateParams(profile_));
    web_ui_.set_web_contents(web_contents_.get());

    ambient_provider_ =
        std::make_unique<PersonalizationAppAmbientProviderImpl>(&web_ui_);

    ambient_provider_->BindInterface(
        ambient_provider_remote_.BindNewPipeAndPassReceiver());

    SetEnabledPref(true);
    GetAmbientAshTestHelper()->ambient_client().SetAutomaticalyIssueToken(true);

    Shell::Get()->ambient_controller()->set_backend_controller_for_testing(
        nullptr);

    fake_backend_controller_ =
        std::make_unique<ash::FakeAmbientBackendControllerImpl>();
  }

  void TearDown() override {
    // The PersonalizationAppAmbientProviderImpl holds a pointer to the
    // AmbientController the Shell owns (which is destructed in
    // AshTestBase::Teardown), so reset it first.
    ambient_provider_.reset();
    ash::AshTestBase::TearDown();
  }

  TestingProfile* profile() { return profile_; }

  mojo::Remote<ash::personalization_app::mojom::AmbientProvider>&
  ambient_provider_remote() {
    return ambient_provider_remote_;
  }

  content::TestWebUI* web_ui() { return &web_ui_; }

  const base::HistogramTester& histogram_tester() const {
    return histogram_tester_;
  }

  void SetAmbientObserver() {
    ambient_provider_remote_->SetAmbientObserver(
        test_ambient_observer_.pending_remote());
  }

  bool ObservedAmbientModeEnabled() {
    ambient_provider_remote_.FlushForTesting();
    return test_ambient_observer_.is_ambient_mode_enabled();
  }

  mojom::AmbientTheme ObservedAmbientTheme() {
    ambient_provider_remote_.FlushForTesting();
    return test_ambient_observer_.ambient_theme();
  }

  mojom::TopicSource ObservedTopicSource() {
    ambient_provider_remote_.FlushForTesting();
    return test_ambient_observer_.topic_source();
  }

  const std::vector<ash::personalization_app::mojom::AmbientModeAlbumPtr>&
  ObservedAlbums() {
    ambient_provider_remote_.FlushForTesting();
    return test_ambient_observer_.albums();
  }

  ash::AmbientModeTemperatureUnit ObservedTemperatureUnit() {
    ambient_provider_remote_.FlushForTesting();
    return test_ambient_observer_.temperature_unit();
  }

  ash::AmbientUiVisibility ObservedAmbientUiVisibility() {
    ambient_provider_remote_.FlushForTesting();
    return test_ambient_observer_.visibility();
  }

  std::vector<GURL> ObservedPreviews() {
    ambient_provider_remote_.FlushForTesting();
    return test_ambient_observer_.previews();
  }

  bool ObservedGeolocationPermissionEnabled() {
    ambient_provider_remote_.FlushForTesting();
    return test_ambient_observer_.is_geolocation_enabled();
  }

  std::optional<ash::AmbientSettings>& settings() {
    return ambient_provider_->settings_;
  }

  void SetEnabledPref(bool enabled) {
    profile()->GetPrefs()->SetBoolean(ash::ambient::prefs::kAmbientModeEnabled,
                                      enabled);
  }

  void SetGeolocationPref(bool enabled) {
    GeolocationAccessLevel level;
    if (enabled) {
      level = GeolocationAccessLevel::kOnlyAllowedForSystem;
    } else {
      level = GeolocationAccessLevel::kDisallowed;
    }

    profile()->GetPrefs()->SetInteger(ash::prefs::kUserGeolocationAccessLevel,
                                      static_cast<int>(level));
  }

  void SetAmbientTheme(mojom::AmbientTheme ambient_theme) {
    ambient_provider_->SetAmbientTheme(ambient_theme);
  }

  void FetchSettings() {
    ambient_provider_remote()->FetchSettingsAndAlbums();
    ambient_provider_remote().FlushForTesting();
  }

  void UpdateSettings() {
    if (!ambient_provider_->settings_) {
      ambient_provider_->settings_ = ash::AmbientSettings();
    }

    ambient_provider_->UpdateSettings();
  }

  void SetScreenSaverDuration(int minutes) {
    ambient_provider_->SetScreenSaverDuration(minutes);
  }

  void SetTopicSource(mojom::TopicSource topic_source) {
    ambient_provider_->SetTopicSource(topic_source);
  }

  void SetAlbumSelected(std::string_view id,
                        mojom::TopicSource topic_source,
                        bool selected) {
    ambient_provider_->SetAlbumSelected(std::string(id), topic_source,
                                        selected);
  }

  void FetchPreviewImages() { ambient_provider_->FetchPreviewImages(); }

  mojom::TopicSource TopicSource() {
    return ambient_provider_->settings_->topic_source;
  }

  std::vector<std::string> SelectedAlbumIds() {
    return ambient_provider_->settings_->selected_album_ids;
  }

  void SetSelectedAlbumIds(const std::vector<std::string>& ids) {
    ambient_provider_->settings_->selected_album_ids = ids;
  }

  void SetTemperatureUnit(ash::AmbientModeTemperatureUnit temperature_unit) {
    ambient_provider_->SetTemperatureUnit(temperature_unit);
  }

  ash::AmbientModeTemperatureUnit TemperatureUnit() {
    return ambient_provider_->settings_->temperature_unit;
  }

  std::vector<ash::ArtSetting> ArtSettings() {
    return ambient_provider_->settings_->art_settings;
  }

  bool IsUpdateSettingsPendingAtProvider() const {
    return ambient_provider_->is_updating_backend_;
  }

  base::TimeDelta GetFetchSettingsDelay() {
    return ambient_provider_->fetch_settings_retry_backoff_
        .GetTimeUntilRelease();
  }

  base::TimeDelta GetUpdateSettingsDelay() {
    return ambient_provider_->update_settings_retry_backoff_
        .GetTimeUntilRelease();
  }

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

  bool IsFetchSettingsPendingAtBackend() const {
    return fake_backend_controller_->IsFetchSettingsAndAlbumsPending();
  }

  void ReplyFetchSettingsAndAlbums(
      bool success,
      std::optional<ash::AmbientSettings> settings = std::nullopt) {
    fake_backend_controller_->ReplyFetchSettingsAndAlbums(success,
                                                          std::move(settings));
  }

  bool IsUpdateSettingsPendingAtBackend() const {
    return fake_backend_controller_->IsUpdateSettingsPending();
  }

  void ReplyUpdateSettings(bool success) {
    fake_backend_controller_->ReplyUpdateSettings(success);
  }

  void EnableUpdateSettingsAutoReply(bool success) {
    fake_backend_controller_->EnableUpdateSettingsAutoReply(success);
  }

  AmbientModeTemperatureUnit GetCurrentTemperatureUnitInServer() const {
    return fake_backend_controller_->current_temperature_unit();
  }

  bool ShouldShowTimeOfDayBanner() const {
    base::test::TestFuture<bool> future;
    ambient_provider_remote_->ShouldShowTimeOfDayBanner(future.GetCallback());
    return future.Take();
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  TestingProfileManager profile_manager_;
  content::TestWebUI web_ui_;
  std::unique_ptr<content::WebContents> web_contents_;
  raw_ptr<TestingProfile> profile_;
  mojo::Remote<ash::personalization_app::mojom::AmbientProvider>
      ambient_provider_remote_;
  std::unique_ptr<PersonalizationAppAmbientProviderImpl> ambient_provider_;
  TestAmbientObserver test_ambient_observer_;

  std::unique_ptr<ash::FakeAmbientBackendControllerImpl>
      fake_backend_controller_;
  base::HistogramTester histogram_tester_;
};

TEST_F(PersonalizationAppAmbientProviderImplTest, IsAmbientModeEnabled) {
  PrefService* pref_service = profile()->GetPrefs();
  EXPECT_TRUE(pref_service);
  pref_service->SetBoolean(ash::ambient::prefs::kAmbientModeEnabled, true);
  bool called = false;
  ambient_provider_remote()->IsAmbientModeEnabled(
      base::BindLambdaForTesting([&called](bool enabled) {
        called = true;
        EXPECT_TRUE(enabled);
      }));
  ambient_provider_remote().FlushForTesting();
  EXPECT_TRUE(called);

  called = false;
  pref_service->SetBoolean(ash::ambient::prefs::kAmbientModeEnabled, false);
  ambient_provider_remote()->IsAmbientModeEnabled(
      base::BindLambdaForTesting([&called](bool enabled) {
        called = true;
        EXPECT_FALSE(enabled);
      }));
  ambient_provider_remote().FlushForTesting();
  EXPECT_TRUE(called);
}

TEST_F(PersonalizationAppAmbientProviderImplTest, SetAmbientModeEnabled) {
  PrefService* pref_service = profile()->GetPrefs();
  EXPECT_TRUE(pref_service);
  // Clear pref.
  pref_service->SetBoolean(ash::ambient::prefs::kAmbientModeEnabled, false);

  ambient_provider_remote()->SetAmbientModeEnabled(true);
  ambient_provider_remote().FlushForTesting();
  EXPECT_TRUE(
      pref_service->GetBoolean(ash::ambient::prefs::kAmbientModeEnabled));

  ambient_provider_remote()->SetAmbientModeEnabled(false);
  ambient_provider_remote().FlushForTesting();
  EXPECT_FALSE(
      pref_service->GetBoolean(ash::ambient::prefs::kAmbientModeEnabled));
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       ShouldCallOnAmbientModeEnabledChanged) {
  PrefService* pref_service = profile()->GetPrefs();
  EXPECT_TRUE(pref_service);
  pref_service->SetBoolean(ash::ambient::prefs::kAmbientModeEnabled, false);
  SetAmbientObserver();
  FetchSettings();
  EXPECT_FALSE(ObservedAmbientModeEnabled());

  pref_service->SetBoolean(ash::ambient::prefs::kAmbientModeEnabled, true);
  SetAmbientObserver();
  ambient_provider_remote().FlushForTesting();
  EXPECT_TRUE(ObservedAmbientModeEnabled());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       OnAmbientModeEnabled_ShouldCancelDelayedUpdateSettingsRequest) {
  PrefService* pref_service = profile()->GetPrefs();
  EXPECT_TRUE(pref_service);
  UpdateSettings();
  // A failed response to UpdateSettings creates a new scheduled request to
  // UpdateSettings.
  ReplyUpdateSettings(/*success=*/false);

  pref_service->SetBoolean(ash::ambient::prefs::kAmbientModeEnabled, false);

  base::TimeDelta delay1 = GetUpdateSettingsDelay();
  FastForwardBy(delay1 * 1.5);
  // Since ambient mode has been disabled, the pending update has been cleared.
  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       ShouldCallOnAmbientThemeChanged) {
  // When ambient mode is first enabled during test set up, the video theme
  // should become active by default since the corresponding experiment flags
  // are on. That should count as +1 in the usage metrics for the video theme.
  histogram_tester().ExpectBucketCount(kAmbientModeAnimationThemeHistogramName,
                                       mojom::AmbientTheme::kVideo, 1);
  histogram_tester().ExpectBucketCount(kAmbientModeVideoHistogramName,
                                       ash::kDefaultAmbientVideo, 1);

  SetAmbientObserver();
  FetchSettings();
  SetAmbientTheme(mojom::AmbientTheme::kSlideshow);
  EXPECT_EQ(mojom::AmbientTheme::kSlideshow, ObservedAmbientTheme());
  histogram_tester().ExpectBucketCount(kAmbientModeAnimationThemeHistogramName,
                                       mojom::AmbientTheme::kSlideshow, 1);

  SetAmbientTheme(mojom::AmbientTheme::kFeelTheBreeze);
  EXPECT_EQ(mojom::AmbientTheme::kFeelTheBreeze, ObservedAmbientTheme());
  histogram_tester().ExpectBucketCount(kAmbientModeAnimationThemeHistogramName,
                                       mojom::AmbientTheme::kFeelTheBreeze, 1);

  SetAmbientTheme(mojom::AmbientTheme::kVideo);
  EXPECT_EQ(mojom::AmbientTheme::kVideo, ObservedAmbientTheme());
  histogram_tester().ExpectBucketCount(kAmbientModeAnimationThemeHistogramName,
                                       mojom::AmbientTheme::kVideo, 2);
  histogram_tester().ExpectBucketCount(kAmbientModeVideoHistogramName,
                                       ash::kDefaultAmbientVideo, 2);
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       RestoresOldThemeAfterReenabling) {
  SetAmbientObserver();
  FetchSettings();
  SetAmbientTheme(mojom::AmbientTheme::kFeelTheBreeze);
  SetEnabledPref(false);
  SetEnabledPref(true);
  EXPECT_EQ(mojom::AmbientTheme::kFeelTheBreeze, ObservedAmbientTheme());
  histogram_tester().ExpectBucketCount(kAmbientModeAnimationThemeHistogramName,
                                       mojom::AmbientTheme::kFeelTheBreeze, 2);
}

TEST_F(PersonalizationAppAmbientProviderImplTest, FetchPreviewImages) {
  SetAmbientObserver();
  EXPECT_TRUE(ObservedPreviews().empty());
  FetchPreviewImages();
  EXPECT_FALSE(ObservedPreviews().empty());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       ShouldCallOnTopicSourceChanged) {
  SetAmbientObserver();
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);
  // The default theme is video theme.
  EXPECT_EQ(mojom::TopicSource::kVideo, ObservedTopicSource());
  EXPECT_FALSE(ObservedPreviews().empty());

  // The other topic sources do not apply to the video theme, so all other
  // `SetTopicSource()` calls should be rejected.
  SetTopicSource(mojom::TopicSource::kArtGallery);
  EXPECT_EQ(mojom::TopicSource::kVideo, ObservedTopicSource());
  SetTopicSource(mojom::TopicSource::kGooglePhotos);
  EXPECT_EQ(mojom::TopicSource::kVideo, ObservedTopicSource());

  // Set to a different theme and select different topic source.
  SetAmbientTheme(mojom::AmbientTheme::kSlideshow);
  EXPECT_EQ(mojom::TopicSource::kGooglePhotos, ObservedTopicSource());

  SetTopicSource(mojom::TopicSource::kArtGallery);
  EXPECT_EQ(mojom::TopicSource::kArtGallery, ObservedTopicSource());

  // The `kVideo` topic source is exclusive to the `kVideo` theme. It does not
  // apply to any of the other themes, so the existing topic source sticks.
  SetTopicSource(mojom::TopicSource::kVideo);
  EXPECT_EQ(mojom::TopicSource::kArtGallery, ObservedTopicSource());
}

TEST_F(PersonalizationAppAmbientProviderImplTest, ShouldCallOnAlbumsChanged) {
  SetAmbientObserver();
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);
  // The fake albums are set in FakeAmbientBackendControllerImpl. Hidden setting
  // will be sent to JS side.
  EXPECT_EQ(6u, ObservedAlbums().size());
  EXPECT_FALSE(ObservedPreviews().empty());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       ShouldCallOnTemperatureUnitChanged) {
  SetAmbientObserver();
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius,
            ObservedTemperatureUnit());
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius,
            GetCurrentTemperatureUnitInServer());

  SetTemperatureUnit(ash::AmbientModeTemperatureUnit::kFahrenheit);
  ReplyUpdateSettings(/*success=*/true);
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kFahrenheit,
            ObservedTemperatureUnit());
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kFahrenheit,
            GetCurrentTemperatureUnitInServer());

  // Even while the video topic source is active, temperature settings changes
  // should still be sent to the backend.
  SetAmbientTheme(mojom::AmbientTheme::kVideo);
  SetTemperatureUnit(ash::AmbientModeTemperatureUnit::kCelsius);
  ReplyUpdateSettings(/*success=*/true);
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius,
            ObservedTemperatureUnit());
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius,
            GetCurrentTemperatureUnitInServer());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       ShouldCallOnAmbientUiVisibilityChanged) {
  SetAmbientObserver();
  EXPECT_EQ(ash::AmbientUiVisibility::kClosed, ObservedAmbientUiVisibility());
  Shell::Get()->ambient_controller()->ambient_ui_model()->SetUiVisibility(
      ash::AmbientUiVisibility::kPreview);
  EXPECT_EQ(ash::AmbientUiVisibility::kPreview, ObservedAmbientUiVisibility());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       ShouldCallOnGeolocationPermissionForSystemServicesChanged) {
  SetAmbientObserver();
  EXPECT_TRUE(ObservedGeolocationPermissionEnabled());
  SetGeolocationPref(/*enabled=*/false);
  EXPECT_FALSE(ObservedGeolocationPermissionEnabled());
  SetGeolocationPref(/*enabled=*/true);
  EXPECT_TRUE(ObservedGeolocationPermissionEnabled());
}

TEST_F(PersonalizationAppAmbientProviderImplTest, SetTopicSource) {
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);
  // Default screen saver is video theme with only kVideo topic source option.
  // Switch to other theme (kSlideshow) to try different topic sources.
  SetAmbientTheme(mojom::AmbientTheme::kSlideshow);

  EXPECT_EQ(mojom::TopicSource::kGooglePhotos, TopicSource());

  SetTopicSource(mojom::TopicSource::kArtGallery);
  EXPECT_EQ(mojom::TopicSource::kArtGallery, TopicSource());

  SetTopicSource(mojom::TopicSource::kGooglePhotos);
  EXPECT_EQ(mojom::TopicSource::kGooglePhotos, TopicSource());

  // If `settings_->selected_album_ids` is empty, will fallback to kArtGallery.
  SetSelectedAlbumIds(/*ids=*/{});
  SetTopicSource(mojom::TopicSource::kGooglePhotos);
  EXPECT_EQ(mojom::TopicSource::kArtGallery, TopicSource());

  SetSelectedAlbumIds(/*ids=*/{"1"});
  SetTopicSource(mojom::TopicSource::kGooglePhotos);
  EXPECT_EQ(mojom::TopicSource::kGooglePhotos, TopicSource());
}

TEST_F(PersonalizationAppAmbientProviderImplTest, SetTemperatureUnit) {
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius, TemperatureUnit());

  SetTemperatureUnit(ash::AmbientModeTemperatureUnit::kFahrenheit);
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kFahrenheit, TemperatureUnit());

  SetTemperatureUnit(ash::AmbientModeTemperatureUnit::kCelsius);
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius, TemperatureUnit());
}

TEST_F(PersonalizationAppAmbientProviderImplTest, TestFetchSettings) {
  FetchSettings();
  EXPECT_TRUE(IsFetchSettingsPendingAtBackend());

  ReplyFetchSettingsAndAlbums(/*success=*/true);
  EXPECT_FALSE(IsFetchSettingsPendingAtBackend());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       TestFetchSettingsFailedWillRetry) {
  FetchSettings();
  EXPECT_TRUE(IsFetchSettingsPendingAtBackend());

  ReplyFetchSettingsAndAlbums(/*success=*/false);
  EXPECT_FALSE(IsFetchSettingsPendingAtBackend());

  FastForwardBy(GetFetchSettingsDelay() * 1.5);
  EXPECT_TRUE(IsFetchSettingsPendingAtBackend());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       TestFetchSettingsSecondRetryWillBackoff) {
  FetchSettings();
  EXPECT_TRUE(IsFetchSettingsPendingAtBackend());

  ReplyFetchSettingsAndAlbums(/*success=*/false);
  EXPECT_FALSE(IsFetchSettingsPendingAtBackend());

  base::TimeDelta delay1 = GetFetchSettingsDelay();
  FastForwardBy(delay1 * 1.5);
  EXPECT_TRUE(IsFetchSettingsPendingAtBackend());

  ReplyFetchSettingsAndAlbums(/*success=*/false);
  EXPECT_FALSE(IsFetchSettingsPendingAtBackend());

  base::TimeDelta delay2 = GetFetchSettingsDelay();
  EXPECT_GT(delay2, delay1);

  FastForwardBy(delay2 * 1.5);
  EXPECT_TRUE(IsFetchSettingsPendingAtBackend());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       TestFetchSettingsWillNotRetryMoreThanThreeTimes) {
  FetchSettings();
  EXPECT_TRUE(IsFetchSettingsPendingAtBackend());

  ReplyFetchSettingsAndAlbums(/*success=*/false);
  EXPECT_FALSE(IsFetchSettingsPendingAtBackend());

  // 1st retry.
  FastForwardBy(GetFetchSettingsDelay() * 1.5);
  EXPECT_TRUE(IsFetchSettingsPendingAtBackend());

  ReplyFetchSettingsAndAlbums(/*success=*/false);
  EXPECT_FALSE(IsFetchSettingsPendingAtBackend());

  // 2nd retry.
  FastForwardBy(GetFetchSettingsDelay() * 1.5);
  EXPECT_TRUE(IsFetchSettingsPendingAtBackend());

  ReplyFetchSettingsAndAlbums(/*success=*/false);
  EXPECT_FALSE(IsFetchSettingsPendingAtBackend());

  // 3rd retry.
  FastForwardBy(GetFetchSettingsDelay() * 1.5);
  EXPECT_TRUE(IsFetchSettingsPendingAtBackend());

  ReplyFetchSettingsAndAlbums(/*success=*/false);
  EXPECT_FALSE(IsFetchSettingsPendingAtBackend());

  // Will not retry.
  FastForwardBy(GetFetchSettingsDelay() * 1.5);
  EXPECT_FALSE(IsFetchSettingsPendingAtBackend());
}

TEST_F(PersonalizationAppAmbientProviderImplTest, TestUpdateSettings) {
  UpdateSettings();
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());

  ReplyUpdateSettings(/*success=*/true);
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());
  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       TestUpdateSettingsTwice_CancelsPreviousRequests) {
  SetAmbientObserver();
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius,
            ObservedTemperatureUnit());
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius,
            GetCurrentTemperatureUnitInServer());

  SetTemperatureUnit(ash::AmbientModeTemperatureUnit::kFahrenheit);
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());

  SetTemperatureUnit(ash::AmbientModeTemperatureUnit::kCelsius);
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());

  ReplyUpdateSettings(/*success=*/true);
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());
  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());

  // The newer temperature unit is used. The second call to UpdateSettings
  // cancels the first request.
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius,
            ObservedTemperatureUnit());
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius,
            GetCurrentTemperatureUnitInServer());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       TestUpdateSettingsFailedWillRetry) {
  UpdateSettings();
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());

  ReplyUpdateSettings(/*success=*/false);
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());
  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());

  FastForwardBy(GetUpdateSettingsDelay() * 1.5);
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       TestUpdateSettingsSecondRetryWillBackoff) {
  UpdateSettings();
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());

  ReplyUpdateSettings(/*success=*/false);
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());
  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());

  base::TimeDelta delay1 = GetUpdateSettingsDelay();
  FastForwardBy(delay1 * 1.5);
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());

  ReplyUpdateSettings(/*success=*/false);
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());
  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());

  base::TimeDelta delay2 = GetUpdateSettingsDelay();
  EXPECT_GT(delay2, delay1);

  FastForwardBy(delay2 * 1.5);
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       TestUpdateSettingsWillNotRetryMoreThanThreeTimes) {
  UpdateSettings();
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());

  ReplyUpdateSettings(/*success=*/false);
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());
  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());

  // 1st retry.
  FastForwardBy(GetUpdateSettingsDelay() * 1.5);
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());

  ReplyUpdateSettings(/*success=*/false);
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());
  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());

  // 2nd retry.
  FastForwardBy(GetUpdateSettingsDelay() * 1.5);
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());

  ReplyUpdateSettings(/*success=*/false);
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());
  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());

  // 3rd retry.
  FastForwardBy(GetUpdateSettingsDelay() * 1.5);
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());

  ReplyUpdateSettings(/*success=*/false);
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());
  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());

  // Will not retry.
  FastForwardBy(GetUpdateSettingsDelay() * 1.5);
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());
  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       TestNoFetchRequestWhenUpdatingSettings) {
  UpdateSettings();
  FetchSettings();

  EXPECT_FALSE(IsFetchSettingsPendingAtBackend());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       TestSetSelectedGooglePhotosAlbum) {
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);

  // The fake data has album '1' as selected.
  std::vector<std::string> selected_ids = SelectedAlbumIds();
  EXPECT_TRUE(base::Contains(selected_ids, "1"));

  ash::personalization_app::mojom::AmbientModeAlbumPtr album =
      ash::personalization_app::mojom::AmbientModeAlbum::New();
  album->id = '1';
  album->topic_source = mojom::TopicSource::kGooglePhotos;
  album->checked = false;
  SetAlbumSelected(album->id, album->topic_source, album->checked);

  selected_ids = SelectedAlbumIds();
  EXPECT_TRUE(selected_ids.empty());
  // Will fallback to Art topic source if no selected Google Photos.
  EXPECT_EQ(mojom::TopicSource::kArtGallery, TopicSource());

  album = ash::personalization_app::mojom::AmbientModeAlbum::New();
  album->id = '1';
  album->topic_source = mojom::TopicSource::kGooglePhotos;
  album->checked = true;
  SetAlbumSelected(album->id, album->topic_source, album->checked);

  selected_ids = SelectedAlbumIds();
  EXPECT_EQ(1u, selected_ids.size());
  EXPECT_TRUE(base::Contains(selected_ids, "1"));
  EXPECT_EQ(mojom::TopicSource::kGooglePhotos, TopicSource());
}

TEST_F(PersonalizationAppAmbientProviderImplTest, TestSetSelectedArtAlbum) {
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);

  // The fake data has art setting '0' as enabled.
  std::vector<ash::ArtSetting> art_settings = ArtSettings();
  auto it = base::ranges::find_if(art_settings, &ash::ArtSetting::enabled);
  EXPECT_NE(it, art_settings.end());
  EXPECT_EQ(it->album_id, "0");

  ash::personalization_app::mojom::AmbientModeAlbumPtr album =
      ash::personalization_app::mojom::AmbientModeAlbum::New();
  album->id = '0';
  album->topic_source = mojom::TopicSource::kArtGallery;
  album->checked = false;
  SetAlbumSelected(album->id, album->topic_source, album->checked);

  art_settings = ArtSettings();
  EXPECT_TRUE(base::ranges::none_of(art_settings, &ash::ArtSetting::enabled));

  album = ash::personalization_app::mojom::AmbientModeAlbum::New();
  album->id = '1';
  album->topic_source = mojom::TopicSource::kArtGallery;
  album->checked = true;
  SetAlbumSelected(album->id, album->topic_source, album->checked);

  art_settings = ArtSettings();
  it = base::ranges::find_if(art_settings, &ash::ArtSetting::enabled);
  EXPECT_NE(it, art_settings.end());
  EXPECT_EQ(it->album_id, "1");
}

TEST_F(PersonalizationAppAmbientProviderImplTest, TestSetSelectedVideo) {
  auto expect_videos_selected = [this](bool clouds_selected,
                                       bool new_mexico_select) {
    EXPECT_THAT(
        ObservedAlbums(),
        IsSupersetOf({Pointee(AllOf(Field(&mojom::AmbientModeAlbum::id,
                                          Eq(kCloudsAlbumId)),
                                    Field(&mojom::AmbientModeAlbum::checked,
                                          Eq(clouds_selected)))),
                      Pointee(AllOf(Field(&mojom::AmbientModeAlbum::id,
                                          Eq(kNewMexicoAlbumId)),
                                    Field(&mojom::AmbientModeAlbum::checked,
                                          Eq(new_mexico_select))))}));
  };

  // When ambient mode is first enabled during test set up, the video theme
  // should become active by default since the corresponding experiment flags
  // are on. That should count as +1 in the usage metrics for the video theme.
  histogram_tester().ExpectBucketCount(kAmbientModeVideoHistogramName,
                                       ash::AmbientVideo::kNewMexico, 1);

  SetAmbientObserver();
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);
  // As Time of Day features are enabled, the default theme should be kVideo.
  EXPECT_EQ(ObservedTopicSource(), mojom::TopicSource::kVideo);

  // The default video should be checked.
  expect_videos_selected(/*clouds_selected=*/false,
                         /*new_mexico_selected=*/true);

  // Switch video to clouds.
  SetAlbumSelected(kCloudsAlbumId, mojom::TopicSource::kVideo, true);
  expect_videos_selected(/*clouds_selected=*/true,
                         /*new_mexico_selected=*/false);

  // Switch back to new mexico.
  SetAlbumSelected(kNewMexicoAlbumId, mojom::TopicSource::kVideo, true);
  expect_videos_selected(/*clouds_selected=*/false,
                         /*new_mexico_selected=*/true);

  // Should never be in a state where there are no videos selected.
  SetAlbumSelected(kNewMexicoAlbumId, mojom::TopicSource::kVideo, false);
  expect_videos_selected(/*clouds_selected=*/false,
                         /*new_mexico_selected=*/true);

  histogram_tester().ExpectBucketCount(kAmbientModeVideoHistogramName,
                                       ash::AmbientVideo::kNewMexico, 2);
  histogram_tester().ExpectBucketCount(kAmbientModeVideoHistogramName,
                                       ash::AmbientVideo::kClouds, 1);
}

TEST_F(PersonalizationAppAmbientProviderImplTest, TestAlbumNumbersAreRecorded) {
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);

  ash::personalization_app::mojom::AmbientModeAlbumPtr album =
      ash::personalization_app::mojom::AmbientModeAlbum::New();
  album->id = '0';
  album->topic_source = mojom::TopicSource::kGooglePhotos;
  SetAlbumSelected(album->id, album->topic_source, album->checked);
  histogram_tester().ExpectTotalCount("Ash.AmbientMode.TotalNumberOfAlbums",
                                      /*count=*/1);
  histogram_tester().ExpectTotalCount("Ash.AmbientMode.SelectedNumberOfAlbums",
                                      /*count=*/1);
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       TestEnabledPrefChangeUpdatesSettings) {
  // Simulate initial page request.
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);

  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());

  // Should not trigger |UpdateSettings|.
  SetEnabledPref(/*enabled=*/false);
  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());

  // Settings this to true should trigger |UpdateSettings|.
  SetEnabledPref(/*enabled=*/true);
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       TestWeatherFalseTriggersUpdateSettings) {
  ash::AmbientSettings weather_off_settings;
  weather_off_settings.show_weather = false;

  // Simulate initial page request with weather settings false. Because Ambient
  // mode pref is enabled and |settings.show_weather| is false, this should
  // trigger a call to |UpdateSettings| that sets |settings.show_weather| to
  // true.
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true, weather_off_settings);

  // A call to |UpdateSettings| should have happened.
  EXPECT_TRUE(IsUpdateSettingsPendingAtProvider());
  EXPECT_TRUE(IsUpdateSettingsPendingAtBackend());

  ReplyUpdateSettings(/*success=*/true);

  EXPECT_FALSE(IsUpdateSettingsPendingAtProvider());
  EXPECT_FALSE(IsUpdateSettingsPendingAtBackend());

  // |settings.show_weather| should now be true after the successful settings
  // update.
  EXPECT_TRUE(settings()->show_weather);
}

// b/236723933
TEST_F(PersonalizationAppAmbientProviderImplTest,
       DoesNotCrashWithEmptyGooglePhotosAlbums) {
  SetEnabledPref(/*enabled=*/false);
  FetchSettings();
  // Reply with settings with |kGooglePhotos| but empty |selected_album_ids|.
  ash::AmbientSettings settings;
  settings.topic_source = mojom::TopicSource::kGooglePhotos;
  ReplyFetchSettingsAndAlbums(/*success=*/true,
                              /*settings=*/std::move(settings));
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       HandlesTransitionToFromVideoTopicSource) {
  // Start with the video topic source already active on boot.
  AmbientUiSettings(mojom::AmbientTheme::kVideo, AmbientVideo::kClouds)
      .WriteToPrefService(*profile()->GetPrefs());

  SetAmbientObserver();
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);

  EXPECT_EQ(ObservedTopicSource(), mojom::TopicSource::kVideo);
  EXPECT_THAT(ObservedAlbums(),
              Contains(Pointee(
                  AllOf(Field(&mojom::AmbientModeAlbum::id, Eq(kCloudsAlbumId)),
                        Field(&mojom::AmbientModeAlbum::checked, IsTrue())))));

  // Switch to slide show mode and change settings to some custom configuration.
  SetAmbientTheme(mojom::AmbientTheme::kSlideshow);
  SetTopicSource(mojom::TopicSource::kArtGallery);
  SetAlbumSelected("1", mojom::TopicSource::kArtGallery, /*selected=*/true);
  ReplyUpdateSettings(/*success=*/true);

  // Switch back to video theme. Same video settings should remain.
  SetAmbientTheme(mojom::AmbientTheme::kVideo);
  EXPECT_EQ(ObservedTopicSource(), mojom::TopicSource::kVideo);
  EXPECT_THAT(ObservedAlbums(),
              Contains(Pointee(
                  AllOf(Field(&mojom::AmbientModeAlbum::id, Eq(kCloudsAlbumId)),
                        Field(&mojom::AmbientModeAlbum::checked, IsTrue())))));

  // Switch back to slide show. The custom setting set previously should stick.
  SetAmbientTheme(mojom::AmbientTheme::kSlideshow);
  EXPECT_EQ(ObservedTopicSource(), mojom::TopicSource::kArtGallery);
  EXPECT_THAT(ObservedAlbums(),
              Contains(Pointee(
                  AllOf(Field(&mojom::AmbientModeAlbum::id, Eq("1")),
                        Field(&mojom::AmbientModeAlbum::checked, IsTrue())))));
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       HandlesFailedSettingsUpdateForVideo) {
  EnableUpdateSettingsAutoReply(/*success=*/false);

  SetAmbientObserver();
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);

  SetAmbientTheme(mojom::AmbientTheme::kVideo);
  // Let retries happen and try to expose any erroneous settings changes.
  task_environment()->FastForwardBy(base::Minutes(1));
  // Should not get stuck in a state where video theme is active with a
  // non-video topic source.
  ASSERT_EQ(ObservedAmbientTheme(), mojom::AmbientTheme::kVideo);
  EXPECT_EQ(ObservedTopicSource(), mojom::TopicSource::kVideo);
  EXPECT_THAT(ObservedAlbums(),
              Contains(Pointee(AllOf(
                  Field(&mojom::AmbientModeAlbum::id, Eq(kNewMexicoAlbumId)),
                  Field(&mojom::AmbientModeAlbum::checked, IsTrue())))));
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       HideBannerForPolicyManagedUsers) {
  WallpaperControllerImpl* wallpaper_controller =
      Shell::Get()->wallpaper_controller();
  base::ScopedTempDir user_wallpaper_dir;
  ASSERT_TRUE(user_wallpaper_dir.CreateUniqueTempDir());
  wallpaper_controller->Init(
      user_wallpaper_dir.GetPath(), user_wallpaper_dir.GetPath(),
      user_wallpaper_dir.GetPath(), user_wallpaper_dir.GetPath());
  TestWallpaperControllerClient client;
  wallpaper_controller->SetClient(&client);
  client.set_fake_files_id_for_account_id(kFakeTestAccountId,
                                          "wallpaper_files_id");
  wallpaper_controller->set_bypass_decode_for_testing();

  // Set default wallpaper for the user. Banner should be shown.
  wallpaper_controller->ShowDefaultWallpaperForTesting();
  ASSERT_FALSE(
      wallpaper_controller->IsWallpaperControlledByPolicy(kFakeTestAccountId));
  EXPECT_TRUE(ShouldShowTimeOfDayBanner());

  // Set policy managed wallpaper for the user. Banner should be hidden.
  wallpaper_controller->SetPolicyWallpaper(kFakeTestAccountId,
                                           user_manager::UserType::kRegular,
                                           std::string() /*data=*/);
  ASSERT_TRUE(
      wallpaper_controller->IsWallpaperControlledByPolicy(kFakeTestAccountId));
  EXPECT_FALSE(ShouldShowTimeOfDayBanner());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       DismissingBannerHidesItForever) {
  WallpaperControllerImpl* wallpaper_controller =
      Shell::Get()->wallpaper_controller();
  wallpaper_controller->set_bypass_decode_for_testing();
  wallpaper_controller->ShowDefaultWallpaperForTesting();
  ASSERT_FALSE(
      wallpaper_controller->IsWallpaperControlledByPolicy(kFakeTestAccountId));
  EXPECT_TRUE(ShouldShowTimeOfDayBanner());

  ambient_provider_remote()->HandleTimeOfDayBannerDismissed();

  EXPECT_FALSE(ShouldShowTimeOfDayBanner());
}

TEST_F(PersonalizationAppAmbientProviderImplTest,
       UpdateSettingsFailure_ShowsCachedSettings) {
  SetAmbientObserver();
  FetchSettings();
  ReplyFetchSettingsAndAlbums(/*success=*/true);
  // The cached settings have Celsius stored.
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius,
            ObservedTemperatureUnit());
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius,
            GetCurrentTemperatureUnitInServer());

  SetTemperatureUnit(ash::AmbientModeTemperatureUnit::kFahrenheit);

  // The value updates to Fahrenheit optimistically.
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kFahrenheit,
            ObservedTemperatureUnit());

  // Fail through all the retries.
  ReplyUpdateSettings(/*success=*/false);
  FastForwardBy(GetUpdateSettingsDelay() * 1.5);
  ReplyUpdateSettings(/*success=*/false);
  FastForwardBy(GetUpdateSettingsDelay() * 1.5);
  ReplyUpdateSettings(/*success=*/false);
  FastForwardBy(GetUpdateSettingsDelay() * 1.5);
  ReplyUpdateSettings(/*success=*/false);
  FastForwardBy(GetUpdateSettingsDelay() * 1.5);

  // After all the failures, restore to the cached value.
  EXPECT_EQ(ash::AmbientModeTemperatureUnit::kCelsius,
            ObservedTemperatureUnit());
}
}  // namespace ash::personalization_app