chromium/chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_wallpaper_daily_refresh_browsertest.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 <stdint.h>

#include <algorithm>
#include <memory>

#include "ash/constants/ash_features.h"
#include "ash/public/cpp/schedule_enums.h"
#include "ash/public/cpp/wallpaper/wallpaper_controller_observer.h"
#include "ash/public/cpp/wallpaper/wallpaper_info.h"
#include "ash/public/cpp/wallpaper/wallpaper_types.h"
#include "ash/shell.h"
#include "ash/style/dark_light_mode_controller_impl.h"
#include "ash/system/scheduled_feature/scheduled_feature.h"
#include "ash/wallpaper/test_wallpaper_image_downloader.h"
#include "ash/wallpaper/wallpaper_controller_impl.h"
#include "ash/wallpaper/wallpaper_controller_test_api.h"
#include "ash/wallpaper/wallpaper_daily_refresh_scheduler.h"
#include "ash/webui/personalization_app/personalization_app_url_constants.h"
#include "base/functional/callback_forward.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/scoped_observation.h"
#include "base/test/simple_test_clock.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/personalization_app_utils.h"
#include "chrome/browser/ash/system_web_apps/apps/personalization_app/test_personalization_app_webui_provider.h"
#include "chrome/browser/ash/system_web_apps/test_support/system_web_app_browsertest_base.h"
#include "chrome/browser/ash/wallpaper_handlers/mock_wallpaper_handlers.h"
#include "chrome/browser/ash/wallpaper_handlers/test_wallpaper_fetcher_delegate.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/ash/wallpaper/wallpaper_controller_client_impl.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chromeos/constants/chromeos_features.h"
#include "content/public/common/isolated_world_ids.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/scoped_web_ui_controller_factory_registration.h"

namespace ash::personalization_app {

namespace {

// Helper class to block until wallpaper colors have updated.
class WallpaperChangedWaiter : public WallpaperControllerObserver {
 public:
  explicit WallpaperChangedWaiter(base::OnceClosure on_wallpaper_changed)
      : on_wallpaper_changed_(std::move(on_wallpaper_changed)) {
    wallpaper_controller_observation_.Observe(WallpaperController::Get());
  }

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

  ~WallpaperChangedWaiter() override = default;

  void OnWallpaperChanged() override {
    if (on_wallpaper_changed_) {
      std::move(on_wallpaper_changed_).Run();
    }
  }

 private:
  base::OnceClosure on_wallpaper_changed_;
  base::ScopedObservation<WallpaperController, WallpaperControllerObserver>
      wallpaper_controller_observation_{this};
};

class PersonalizationAppWallpaperDailyRefreshBrowserTest
    : public SystemWebAppBrowserTestBase,
      public ScheduledFeature::Clock {
 public:
  PersonalizationAppWallpaperDailyRefreshBrowserTest() {
    base::Time start_time = base::Time::Now();
    clock_.SetNow(start_time);
    tick_clock_.SetNowTicks(base::TimeTicks() + (start_time - base::Time()));
  }

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

  ~PersonalizationAppWallpaperDailyRefreshBrowserTest() override = default;

  // BrowserTestBase:
  void SetUpInProcessBrowserTestFixture() override {
    WallpaperControllerImpl::SetWallpaperImageDownloaderForTesting(
        std::make_unique<TestWallpaperImageDownloader>());
    SystemWebAppBrowserTestBase::SetUpInProcessBrowserTestFixture();
  }

  void SetUpOnMainThread() override {
    SystemWebAppBrowserTestBase::SetUpOnMainThread();

    browser()->window()->Minimize();

    WallpaperControllerClientImpl::Get()->SetWallpaperFetcherDelegateForTesting(
        std::make_unique<wallpaper_handlers::TestWallpaperFetcherDelegate>());

    auto wallpaper_controller_test_api =
        std::make_unique<WallpaperControllerTestApi>(wallpaper_controller());
    wallpaper_controller_test_api->SetDefaultWallpaper(
        GetAccountId(browser()->profile()));

    test_chrome_webui_controller_factory_.AddFactoryOverride(
        kChromeUIPersonalizationAppHost, &test_webui_provider_);

    auto* daily_refresh_scheduler = scheduler();
    // Disable any running timers to set a fake clock.
    daily_refresh_scheduler->SetScheduleType(ScheduleType::kNone);
    daily_refresh_scheduler->SetClockForTesting(this);
    daily_refresh_scheduler->SetScheduleType(ScheduleType::kCustom);

    WaitForTestSystemAppInstall();
  }

  void TearDownOnMainThread() override {
    SystemWebAppBrowserTestBase::TearDownOnMainThread();
  }

  content::WebContents* LaunchAppAtWallpaperSubpage(Browser** browser) {
    apps::AppLaunchParams launch_params =
        LaunchParamsForApp(ash::SystemWebAppType::PERSONALIZATION);
    launch_params.override_url =
        GURL(std::string(kChromeUIPersonalizationAppURL) +
             kWallpaperSubpageRelativeUrl);
    return LaunchApp(std::move(launch_params), browser);
  }

  WallpaperControllerImpl* wallpaper_controller() {
    return Shell::Get()->wallpaper_controller();
  }

  WallpaperDailyRefreshScheduler* scheduler() {
    return Shell::Get()
        ->wallpaper_controller()
        ->daily_refresh_scheduler_for_testing()
        .get();
  }

  // ScheduledFeature::Clock:
  base::Time Now() const override { return clock_.Now(); }

  base::TimeTicks NowTicks() const override { return tick_clock_.NowTicks(); }

  // Returns whether the total triggered a checkpoint change.
  bool FastForwardBy(base::TimeDelta total) {
    const auto advance_time = [this](base::TimeDelta advancement) {
      clock_.Advance(advancement);
      tick_clock_.Advance(advancement);
    };
    bool checkpoint_reached = false;
    auto* timer = Shell::Get()
                      ->wallpaper_controller()
                      ->daily_refresh_scheduler_for_testing()
                      ->timer();
    while (total.is_positive()) {
      base::TimeDelta advance_increment;
      if (timer->IsRunning() &&
          timer->desired_run_time() <= NowTicks() + total) {
        // Emulates the internal timer firing at its scheduled time.
        advance_increment = timer->desired_run_time() - NowTicks();
        advance_time(advance_increment);
        timer->FireNow();
        checkpoint_reached = true;
      } else {
        advance_increment = total;
        advance_time(advance_increment);
      }
      CHECK_LE(advance_increment, total);
      total -= advance_increment;
    }
    return checkpoint_reached;
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  base::SimpleTestClock clock_;
  base::SimpleTestTickClock tick_clock_;
  TestChromeWebUIControllerFactory test_chrome_webui_controller_factory_;
  TestPersonalizationAppWebUIProvider test_webui_provider_;
  content::ScopedWebUIControllerFactoryRegistration
      scoped_controller_factory_registration_{
          &test_chrome_webui_controller_factory_};
};

IN_PROC_BROWSER_TEST_F(PersonalizationAppWallpaperDailyRefreshBrowserTest,
                       DailyWallpaperIsRefreshed) {
  Browser* browser;
  auto* web_contents = LaunchAppAtWallpaperSubpage(&browser);
  ASSERT_EQ(ScheduleType::kCustom, scheduler()->GetScheduleType());
  const char kCollectionId[] = "test_collection";
  {
    // Enables daily refresh.
    base::RunLoop loop;
    WallpaperChangedWaiter waiter(loop.QuitClosure());
    web_contents->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
        u"personalizationTestApi.enableDailyRefresh('test_collection');",
        base::DoNothing(), content::ISOLATED_WORLD_ID_GLOBAL);
    loop.Run();
  }
  WallpaperInfo original_info =
      *wallpaper_controller()->GetActiveUserWallpaperInfo();
  ASSERT_EQ(WallpaperType::kDaily, original_info.type);
  EXPECT_EQ(kCollectionId, original_info.collection_id);
  // Fast forwards to the next day. Extra 1 minuter is used to account for
  // delays.
  const bool checkpoint_change =
      FastForwardBy(base::Hours(24) + base::Minutes(1));
  ASSERT_TRUE(checkpoint_change);
  base::RunLoop loop;
  WallpaperChangedWaiter waiter(loop.QuitClosure());
  loop.Run();
  WallpaperInfo new_info =
      *wallpaper_controller()->GetActiveUserWallpaperInfo();
  EXPECT_FALSE(original_info.MatchesSelection(new_info));
  EXPECT_EQ(original_info.collection_id, new_info.collection_id);
}

IN_PROC_BROWSER_TEST_F(PersonalizationAppWallpaperDailyRefreshBrowserTest,
                       DailyDarkLightWallpaperIsRefreshed) {
  Browser* browser;
  auto* web_contents = LaunchAppAtWallpaperSubpage(&browser);
  ASSERT_EQ(ScheduleType::kCustom, scheduler()->GetScheduleType());
  const char kCollectionId[] = "dark_light_collection";
  auto* dark_light_controller = Shell::Get()->dark_light_mode_controller();
  dark_light_controller->SetAutoScheduleEnabled(false);
  {
    // Enables daily refresh.
    base::RunLoop loop;
    WallpaperChangedWaiter waiter(loop.QuitClosure());
    web_contents->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
        u"personalizationTestApi.enableDailyRefresh('dark_light_collection');",
        base::DoNothing(), content::ISOLATED_WORLD_ID_GLOBAL);
    loop.Run();
  }
  WallpaperInfo original_info =
      *wallpaper_controller()->GetActiveUserWallpaperInfo();
  ASSERT_EQ(kCollectionId, original_info.collection_id);
  ASSERT_EQ(WallpaperType::kDaily, original_info.type);
  {
    // Forwards half day and expects no change in wallpaper.
    FastForwardBy(base::Hours(12));
    base::RunLoop().RunUntilIdle();
    WallpaperInfo new_info =
        *wallpaper_controller()->GetActiveUserWallpaperInfo();
    ASSERT_TRUE(original_info.MatchesAsset(new_info))
        << "No change to asset because not enough time elapsed for daily "
           "refresh."
        << " original_info=" << original_info << " new_info=" << new_info;
  }
  {
    // Toggles color mode and expects new asset from the same wallpaper.
    dark_light_controller->ToggleColorMode();
    base::RunLoop loop;
    WallpaperChangedWaiter waiter(loop.QuitClosure());
    loop.Run();
    WallpaperInfo new_info =
        *wallpaper_controller()->GetActiveUserWallpaperInfo();
    EXPECT_TRUE(original_info.MatchesSelection(new_info))
        << "Expect same wallpaper after color mode changes."
        << " original_info=" << original_info << " new_info=" << new_info;
    EXPECT_FALSE(original_info.MatchesAsset(new_info))
        << "Expect updated variant asset after color mode changes";
    EXPECT_EQ(original_info.date, new_info.date)
        << "The wallpaper's timestamp is unaffected by color mode change";
    EXPECT_EQ(original_info.collection_id, new_info.collection_id)
        << "Expect same collection";
  }
  {
    // Forwards another half day. Extra 1 minute is used to account for delays.
    // Expects a new wallpaper is set.
    const auto checkpoint_change =
        FastForwardBy(base::Hours(12) + base::Minutes(1));
    EXPECT_TRUE(checkpoint_change);
    base::RunLoop loop;
    WallpaperChangedWaiter waiter(loop.QuitClosure());
    loop.Run();
    WallpaperInfo new_info =
        *wallpaper_controller()->GetActiveUserWallpaperInfo();
    EXPECT_FALSE(original_info.MatchesSelection(new_info))
        << "Expect new daily wallpaper after 24 hours have elapsed."
        << " original_info=" << original_info << " new_info=" << new_info;
    EXPECT_EQ(original_info.collection_id, new_info.collection_id)
        << "Expect same collection";
  }
}

IN_PROC_BROWSER_TEST_F(PersonalizationAppWallpaperDailyRefreshBrowserTest,
                       DailyGooglePhotosWallpaperIsRefreshed) {
  Browser* browser;
  auto* web_contents = LaunchAppAtWallpaperSubpage(&browser);
  ASSERT_EQ(ScheduleType::kCustom, scheduler()->GetScheduleType());
  const char kAlbumId[] = "test_album";
  {
    // Enables daily refresh.
    base::RunLoop loop;
    WallpaperChangedWaiter waiter(loop.QuitClosure());
    web_contents->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
        u"personalizationTestApi.enableDailyGooglePhotosRefresh('test_album');",
        base::DoNothing(), content::ISOLATED_WORLD_ID_GLOBAL);
    loop.Run();
  }
  WallpaperInfo original_info =
      *wallpaper_controller()->GetActiveUserWallpaperInfo();
  ASSERT_EQ(WallpaperType::kDailyGooglePhotos, original_info.type);
  EXPECT_EQ(kAlbumId, original_info.collection_id);
  // Fast forwards to the next day. Extra 1 minute is used to account for
  // delays.
  const bool checkpoint_change =
      FastForwardBy(base::Hours(24) + base::Minutes(1));
  ASSERT_TRUE(checkpoint_change);
  base::RunLoop loop;
  WallpaperChangedWaiter waiter(loop.QuitClosure());
  loop.Run();
  WallpaperInfo new_info =
      *wallpaper_controller()->GetActiveUserWallpaperInfo();
  EXPECT_FALSE(original_info.MatchesSelection(new_info))
      << "Expect new Google photo wallpaper after 24 hours have elapsed";
  EXPECT_EQ(original_info.collection_id, new_info.collection_id)
      << "Expect same album";
}

}  // namespace
}  // namespace ash::personalization_app