// 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.

#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_mediator.h"

#import <Foundation/Foundation.h>

#import <memory>

#import "base/apple/foundation_util.h"
#import "base/cancelable_callback.h"
#import "base/files/file_util.h"
#import "base/memory/raw_ptr.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/strings/sys_string_conversions.h"
#import "base/task/thread_pool.h"
#import "base/time/time.h"
#import "base/trace_event/trace_event.h"
#import "components/feature_engagement/public/event_constants.h"
#import "components/feature_engagement/public/tracker.h"
#import "components/image_fetcher/core/image_fetcher.h"
#import "components/image_fetcher/core/image_fetcher_service.h"
#import "components/image_fetcher/core/request_metadata.h"
#import "components/ntp_tiles/pref_names.h"
#import "components/omnibox/browser/aim_eligibility_service.h"
#import "components/omnibox/browser/omnibox_pref_names.h"
#import "components/omnibox/browser/omnibox_prefs.h"
#import "components/omnibox/common/omnibox_features.h"
#import "components/prefs/ios/pref_observer_bridge.h"
#import "components/prefs/pref_change_registrar.h"
#import "components/regional_capabilities/regional_capabilities_service.h"
#import "components/search/search.h"
#import "components/signin/public/base/signin_switches.h"
#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
#import "components/strings/grit/components_strings.h"
#import "components/subscription_eligibility/objc/subscription_eligibility_observer_bridge.h"
#import "components/subscription_eligibility/subscription_eligibility_service.h"
#import "ios/chrome/browser/browser_view/model/browser_view_visibility_notifier_browser_agent.h"
#import "ios/chrome/browser/content_suggestions/coordinator/content_suggestions_mediator.h"
#import "ios/chrome/browser/content_suggestions/ui/user_account_image_update_delegate.h"
#import "ios/chrome/browser/discover_feed/model/discover_feed_service.h"
#import "ios/chrome/browser/discover_feed/model/discover_feed_service_factory.h"
#import "ios/chrome/browser/discover_feed/model/discover_feed_visibility_browser_agent.h"
#import "ios/chrome/browser/fullscreen/model/fullscreen_browser_agent.h"
#import "ios/chrome/browser/fullscreen/model/fullscreen_browser_agent_observer_bridge.h"
#import "ios/chrome/browser/home_customization/coordinator/home_customization_data_conversion.h"
#import "ios/chrome/browser/home_customization/model/home_background_customization_service.h"
#import "ios/chrome/browser/home_customization/model/home_background_customization_service_observer_bridge.h"
#import "ios/chrome/browser/home_customization/model/home_background_data.h"
#import "ios/chrome/browser/home_customization/model/user_uploaded_image_manager.h"
#import "ios/chrome/browser/home_customization/ui/home_customization_framing_coordinates.h"
#import "ios/chrome/browser/home_customization/utils/home_customization_constants.h"
#import "ios/chrome/browser/metrics/model/new_tab_page_uma.h"
#import "ios/chrome/browser/ntp/model/new_tab_page_tab_helper.h"
#import "ios/chrome/browser/ntp/model/ntp_background_image_cache_service.h"
#import "ios/chrome/browser/ntp/model/set_up_list_item_type.h"
#import "ios/chrome/browser/ntp/model/set_up_list_prefs.h"
#import "ios/chrome/browser/ntp/search_engine_logo/mediator/search_engine_logo_mediator.h"
#import "ios/chrome/browser/ntp/search_engine_logo/ui/search_engine_logo_state.h"
#import "ios/chrome/browser/ntp/shared/metrics/feed_metrics_constants.h"
#import "ios/chrome/browser/ntp/shared/metrics/feed_metrics_recorder.h"
#import "ios/chrome/browser/ntp/shared/metrics/new_tab_page_metrics_constants.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_control_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/feed_wrapper_view_controller.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_color_palette.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_color_palette_util.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_consumer.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_content_delegate.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_feature.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_header_constants.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_header_consumer.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_image_background_trait.h"
#import "ios/chrome/browser/ntp/ui_bundled/new_tab_page_trait.h"
#import "ios/chrome/browser/ntp/ui_bundled/theme_utils.h"
#import "ios/chrome/browser/omnibox/model/placeholder_service/placeholder_service.h"
#import "ios/chrome/browser/omnibox/model/placeholder_service/placeholder_service_observer_bridge.h"
#import "ios/chrome/browser/policy/model/policy_util.h"
#import "ios/chrome/browser/search_engines/model/search_engine_observer_bridge.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/prefs/pref_backed_boolean.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/model/utils/first_run_util.h"
#import "ios/chrome/browser/shared/model/utils/observable_boolean.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/util/custom_ui_trait_accessor.h"
#import "ios/chrome/browser/shared/ui/util/uikit_ui_util.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/avatar/avatar_provider.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service.h"
#import "ios/chrome/browser/signin/model/constants.h"
#import "ios/chrome/browser/signin/model/signin_util.h"
#import "ios/chrome/browser/sync/model/sync_observer_bridge.h"
#import "ios/chrome/browser/url_loading/model/url_loading_browser_agent.h"
#import "ios/chrome/browser/url_loading/model/url_loading_params.h"
#import "ios/chrome/common/NSString+Chromium.h"
#import "ios/chrome/common/ui/favicon/favicon_attributes.h"
#import "ios/chrome/common/ui/util/image_util.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/public/provider/chrome/browser/voice_search/voice_search_api.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/navigation/referrer.h"
#import "ios/web/public/web_state.h"
#import "skia/ext/skia_utils_ios.h"
#import "ui/base/device_form_factor.h"
#import "ui/base/l10n/l10n_util.h"
#import "url/gurl.h"

@interface NewTabPageMediator (ImageFetcher)
- (image_fetcher::ImageFetcherService*)imageFetcherService;
@end

namespace {

// Histogram name for logging when the 'new' badge on the Lens button is shown
// on the homepage.
constexpr char kNTPLensButtonNewBadgeShownHistogram[] =
    "IOS.NTP.LensButtonNewBadgeShown";

// These values are persisted to `IOS.NTP.LensButtonNewBadgeShown` histograms.
// Entries should not be renumbered and numeric values should never be reused.
enum class IOSNTPNewBadgeShownResult {
  kShown = 0,
  // kNotShownLimitReached = 1,  // Obsolete in M140
  // kNotShownButtonPressed = 2,  // Obsolete in M140
  kMaxValue = kShown,
};

// The point size of the entry point's symbol.
const CGFloat kIconPointSize = 18.0;

// The holdback period to wait after FRE completion before showing new badges
// on the homepage.
constexpr base::TimeDelta kFREBadgeHoldbackPeriod = base::Hours(1);

// Logs when the 'new' badge on the homepage Lens button is shown.
//
// TODO(crbug.com/428691449): Remove once the FET migration for 'new' badges is
// fully validated.
void LogLensButtonNewBadgeShownHistogram(IOSNTPNewBadgeShownResult result) {
  base::UmaHistogramEnumeration(kNTPLensButtonNewBadgeShownHistogram, result);
}

// Key for Image Fetcher UMA metrics.
constexpr char kImageFetcherUmaClient[] = "NtpBackground";

// NetworkTrafficAnnotationTag for fetching ntp background image from Google
// server.
const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
    net::DefineNetworkTrafficAnnotation("ntp_background_custom_image",
                                        R"(
        semantics {
        sender: "NtpBackground"
        description:
            "Sends a request to a Google server to load the ntp's custom "
            "background."
        trigger:
            "A request will be sent when the user opens a new NTP and has a "
            "custom background."
        data: "Only image url, no user data"
        destination: GOOGLE_OWNED_SERVICE
        }
        policy {
        cookies_allowed: NO
        setting:
            "This feature cannot be disabled by settings. However, the "
            "request will only be made if the user has a custom NTP background."
        chrome_policy: {
          NTPCustomBackgroundEnabled {
            NTPCustomBackgroundEnabled: false
          }
        }
        }
        )");

// Enum for the IOS.HomeCustomization.Background.Ntp.CacheCleanupEvent
// histogram.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
// LINT.IfChange(NtpBackgroundCacheCleanupEvent)
enum class NtpBackgroundCacheCleanupEvent {
  // The cleanup was skipped because the user is a new background customization
  // user.
  kCleanupSkippedNewUser = 0,
  // The cleanup was triggered (files deletion started).
  kCleanupTriggered = 1,
  // The cleanup completed successfully (files deleted).
  kCleanupCompletedSuccess = 2,
  // The cleanup failed to delete files.
  kCleanupCompletedFailure = 3,
  // The refetch of the current background was triggered.
  kRefetchTriggered = 4,
  // The refetch of the current background completed successfully.
  kRefetchCompletedSuccess = 5,
  // The refetch of the current background failed.
  kRefetchCompletedFailure = 6,
  kMaxValue = kRefetchCompletedFailure,
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/ios/enums.xml)

// Before M149, an upscaling and transcoding bug caused the image fetcher to
// store unnecessarily large background images, making a cache cleanup
// necessary. However, new background customization users (M149+) bypass this
// step entirely since their caches are clean.
void DisableImageFetcherCacheCleanupIfNeeded(PrefService* pref_service) {
  // The default value for `kIosRecentlyUsedBackgrounds` is a list containing
  // a single boolean `true`, which is used as a signal for a new user.
  const auto& recently_used =
      pref_service->GetList(prefs::kIosRecentlyUsedBackgrounds);
  if (recently_used.size() == 1 && recently_used[0].is_bool() &&
      recently_used[0].GetBool()) {
    pref_service->SetBoolean(prefs::kIosImageFetcherShouldClearCache, false);
    base::UmaHistogramEnumeration(
        "IOS.HomeCustomization.Background.Ntp.CacheCleanupEvent",
        NtpBackgroundCacheCleanupEvent::kCleanupSkippedNewUser);
  }
}

// Before M149, there was an upscale & transcoding issue on background images
// stored in the image fetcher image cache resulting in storing very large files
// increasing the memory footprint for no reason. This function has been
// introduced after the fix to clean up the image cache and will be removed in
// a future version of Chrome.
void CleanupImageFetcherCacheIfNeeded(PrefService* pref_service,
                                      web::BrowserState* browser_state,
                                      NewTabPageMediator* mediator,
                                      HomeCustomBackground custom_background) {
  if (!pref_service->GetBoolean(prefs::kIosImageFetcherShouldClearCache)) {
    return;
  }

  base::UmaHistogramEnumeration(
      "IOS.HomeCustomization.Background.Ntp.CacheCleanupEvent",
      NtpBackgroundCacheCleanupEvent::kCleanupTriggered);

  base::FilePath cache_path = browser_state->GetStatePath();
  base::FilePath storage_path =
      cache_path.Append(FILE_PATH_LITERAL("image_data_storage"));

  __weak NewTabPageMediator* weakMediator = mediator;
  // Refetches the image.
  void (^refetchCurrentImage)(void) = ^{
    NewTabPageMediator* strongMediator = weakMediator;
    if (!strongMediator) {
      return;
    }
    // Refetch the image to populate the cache in the new format.
    if (const sync_pb::NtpCustomBackground* ntpBackground =
            std::get_if<sync_pb::NtpCustomBackground>(&custom_background)) {
      image_fetcher::ImageFetcher* imageFetcher =
          [strongMediator imageFetcherService]->GetImageFetcher(
              image_fetcher::ImageFetcherConfig::kReducedMode);
      GURL imageURL = GURL(ntpBackground->url());
      imageFetcher->FetchImageData(
          imageURL,
          base::BindOnce(^(const std::string& imageData,
                           const image_fetcher::RequestMetadata& metadata) {
            bool success = !imageData.empty();
            base::UmaHistogramEnumeration(
                "IOS.HomeCustomization.Background.Ntp.CacheCleanupEvent",
                success
                    ? NtpBackgroundCacheCleanupEvent::kCleanupCompletedSuccess
                    : NtpBackgroundCacheCleanupEvent::kCleanupCompletedFailure);
          }),
          image_fetcher::ImageFetcherParams(kTrafficAnnotation,
                                            kImageFetcherUmaClient));
      base::UmaHistogramEnumeration(
          "IOS.HomeCustomization.Background.Ntp.CacheCleanupEvent",
          NtpBackgroundCacheCleanupEvent::kRefetchTriggered);
    }
  };

  base::ThreadPool::PostTaskAndReplyWithResult(
      FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
      base::BindOnce(
          ^(const base::FilePath& path) {
            return base::DeletePathRecursively(path) &&
                   base::CreateDirectory(path);
          },
          storage_path),
      base::BindOnce(^(bool success) {
        base::UmaHistogramEnumeration(
            "IOS.HomeCustomization.Background.Ntp.CacheCleanupEvent",
            success ? NtpBackgroundCacheCleanupEvent::kCleanupCompletedSuccess
                    : NtpBackgroundCacheCleanupEvent::kCleanupCompletedFailure);
        refetchCurrentImage();
      }));

  pref_service->SetBoolean(prefs::kIosImageFetcherShouldClearCache, false);
}

}  // namespace

@interface NewTabPageMediator () <BooleanObserver,
                                  FullscreenBrowserAgentObserving,
                                  HomeBackgroundCustomizationServiceObserving,
                                  IdentityManagerObserving,
                                  PlaceholderServiceObserving,
                                  PrefObserverDelegate,
                                  SearchEngineObserving,
                                  SubscriptionEligibilityServiceObserving,
                                  SyncObserverModelBridge>

@property(nonatomic, assign) ChromeAccountManagerService* accountManagerService;
// TemplateURL used to get the search engine.
@property(nonatomic, assign) TemplateURLService* templateURLService;
// Authentication Service to get the current user's avatar.
@property(nonatomic, assign) AuthenticationService* authService;
// DiscoverFeed Service to display the Feed.
@property(nonatomic, assign) DiscoverFeedService* discoverFeedService;

@end

@implementation NewTabPageMediator {
  raw_ptr<FullscreenBrowserAgent> _fullscreenBrowserAgent;
  std::unique_ptr<FullscreenBrowserAgentObserverBridge>
      _fullscreenBrowserAgentObserverBridge;
  // AIM eligibility service.
  raw_ptr<AimEligibilityService> _aimEligibilityService;
  // AIM eligibility subscription.
  base::CallbackListSubscription _aimEligibilitySubscription;
  // Whether AIM is currently allowed.
  BOOL _isAIMAllowed;
  // Listen for default search engine changes.
  std::unique_ptr<SearchEngineObserverBridge> _searchEngineObserver;
  // Observes changes in identity and updates the Identity Disc.
  std::unique_ptr<signin::IdentityManagerObserverBridge>
      _identityObserverBridge;
  // Observes changes in AI subscription tier.
  std::unique_ptr<
      subscription_eligibility::SubscriptionEligibilityObserverBridge>
      _subscriptionEligibilityObserverBridge;
  // Observes changes of the browser view visibility state.
  raw_ptr<BrowserViewVisibilityNotifierBrowserAgent, DanglingUntriaged>
      _browserViewVisibilityNotifierBrowserAgent;
  // Observes changes of the feed visibility state.
  raw_ptr<DiscoverFeedVisibilityBrowserAgent, DanglingUntriaged>
      _discoverFeedVisibilityBrowserAgent;
  // Subscription with BrowserViewVisibilityNotifierBrowserAgent.
  base::CallbackListSubscription _browserViewVisibilityStateChangedSubscription;
  // Used to load URLs.
  raw_ptr<UrlLoadingBrowserAgent, DanglingUntriaged> _URLLoader;
  raw_ptr<PrefService> _prefService;
  // Pref observer to track changes to prefs.
  std::unique_ptr<PrefObserverBridge> _prefObserverBridge;
  // Registrar for pref changes notifications.
  std::unique_ptr<PrefChangeRegistrar> _prefChangeRegistrar;
  // The current default search engine.
  raw_ptr<const TemplateURL, DanglingUntriaged> _defaultSearchEngine;
  // Sync Service.
  raw_ptr<syncer::SyncService> _syncService;
  // Used to check feed configuration based on the country.
  raw_ptr<regional_capabilities::RegionalCapabilitiesService>
      _regionalCapabilitiesService;
  // Used to get and observe the background image or other state.
  raw_ptr<HomeBackgroundCustomizationService> _backgroundCustomizationService;
  // Observer for the customization service.
  std::unique_ptr<HomeBackgroundCustomizationServiceObserverBridge>
      _backgroundCustomizationServiceObserverBridge;
  // Used to cache the background image.
  raw_ptr<NTPBackgroundImageCacheService> _backgroundImageCacheService;
  // Used to fetch and cache images for the background.
  raw_ptr<image_fetcher::ImageFetcherService> _imageFetcherService;
  raw_ptr<UserUploadedImageManager, DanglingUntriaged>
      _userUploadedImageManager;
  // Observer to keep track of the syncing status.
  std::unique_ptr<SyncObserverBridge> _syncObserver;
  raw_ptr<signin::IdentityManager> _identityManager;
  id<SystemIdentity> _signedInIdentity;
  std::unique_ptr<PlaceholderServiceObserverBridge> _placeholderServiceObserver;
  // Feature engagement tracker for handling "new" badge IPH.
  raw_ptr<feature_engagement::Tracker, DanglingUntriaged> _tracker;
  // Tracks whether the NTP was ever in landscape.
  BOOL _wasNTPInLandscape;
  // Whether the mediator has been set up.
  BOOL _mediatorSetUp;
  // Callback for the thumbnail image fetch.
  std::unique_ptr<base::CancelableOnceCallback<
      void(const std::string&, const image_fetcher::RequestMetadata&)>>
      _thumbnailCallback;
  // Callback for the high-resolution image fetch.
  std::unique_ptr<base::CancelableOnceCallback<
      void(const std::string&, const image_fetcher::RequestMetadata&)>>
      _imageCallback;
  // The URL of the background image currently being fetched.
  GURL _pendingBackgroundURL;
  // Sequence number for fetch requests to generate unique flow IDs.
  uint64_t _fetchSequenceNumber;
  // Holds whether the omnibox should be pinned to the bottom position.
  PrefBackedBoolean* _bottomOmniboxEnabled;
  raw_ptr<subscription_eligibility::SubscriptionEligibilityService>
      _subscriptionEligibilityService;
}

// Synthesized from NewTabPageMutator.
@synthesize scrollPositionToSave = _scrollPositionToSave;

- (instancetype)
            initWithTemplateURLService:(TemplateURLService*)templateURLService
                             URLLoader:(UrlLoadingBrowserAgent*)URLLoader
                           authService:(AuthenticationService*)authService
                       identityManager:(signin::IdentityManager*)identityManager
                 accountManagerService:
                     (ChromeAccountManagerService*)accountManagerService
              identityDiscImageUpdater:
                  (id<UserAccountImageUpdateDelegate>)imageUpdater
                   discoverFeedService:(DiscoverFeedService*)discoverFeedService
                           prefService:(PrefService*)prefService
        subscriptionEligibilityService:
            (subscription_eligibility::SubscriptionEligibilityService*)
                subscriptionEligibilityService
                           syncService:(syncer::SyncService*)syncService
           regionalCapabilitiesService:
               (regional_capabilities::RegionalCapabilitiesService*)
                   regionalCapabilitiesService
        backgroundCustomizationService:
            (HomeBackgroundCustomizationService*)backgroundCustomizationService
           backgroundImageCacheService:
               (NTPBackgroundImageCacheService*)backgroundImageCacheService
                   imageFetcherService:
                       (image_fetcher::ImageFetcherService*)imageFetcherService
              userUploadedImageManager:
                  (UserUploadedImageManager*)userUploadedImageManager
         browserViewVisibilityNotifier:
             (BrowserViewVisibilityNotifierBrowserAgent*)
                 browserViewVisibilityNotifierBrowserAgent
    discoverFeedVisibilityBrowserAgent:
        (DiscoverFeedVisibilityBrowserAgent*)discoverFeedVisibilityBrowserAgent
              featureEngagementTracker:(feature_engagement::Tracker*)tracker
                 aimEligibilityService:
                     (AimEligibilityService*)aimEligibilityService
                fullscreenBrowserAgent:
                    (FullscreenBrowserAgent*)fullscreenBrowserAgent {
  self = [super init];
  if (self) {
    CHECK(identityManager);
    CHECK(accountManagerService);
    CHECK(tracker);
    _templateURLService = templateURLService;
    _defaultSearchEngine = templateURLService->GetDefaultSearchProvider();
    _URLLoader = URLLoader;
    _authService = authService;
    _accountManagerService = accountManagerService;
    _identityManager = identityManager;
    _identityObserverBridge =
        std::make_unique<signin::IdentityManagerObserverBridge>(identityManager,
                                                                self);
    CHECK(subscriptionEligibilityService);
    _subscriptionEligibilityObserverBridge = std::make_unique<
        subscription_eligibility::SubscriptionEligibilityObserverBridge>(
        subscriptionEligibilityService, self);
    _browserViewVisibilityNotifierBrowserAgent =
        browserViewVisibilityNotifierBrowserAgent;
    // Listen for default search engine changes.
    _searchEngineObserver = std::make_unique<SearchEngineObserverBridge>(
        self, self.templateURLService);
    _syncService = syncService;
    _syncObserver = std::make_unique<SyncObserverBridge>(self, syncService);
    _imageUpdater = imageUpdater;
    _discoverFeedService = discoverFeedService;
    _discoverFeedVisibilityBrowserAgent = discoverFeedVisibilityBrowserAgent;
    _prefService = prefService;
    _subscriptionEligibilityService = subscriptionEligibilityService;
    _regionalCapabilitiesService = regionalCapabilitiesService;
    _backgroundCustomizationService = backgroundCustomizationService;
    _backgroundImageCacheService = backgroundImageCacheService;
    _imageFetcherService = imageFetcherService;
    _userUploadedImageManager = userUploadedImageManager;
    _signedInIdentity = _authService->GetPrimaryIdentity();
    _tracker = tracker;
    _aimEligibilityService = aimEligibilityService;
    if (_aimEligibilityService) {
      __weak __typeof(self) weakSelf = self;
      _aimEligibilitySubscription =
          _aimEligibilityService->RegisterEligibilityChangedCallback(
              base::BindRepeating(^(void) {
                [weakSelf updateAIMAvailability];
              }));
    }
    if (IsChromeNextIaEnabled() && IsBottomOmniboxAvailable()) {
      _bottomOmniboxEnabled = [[PrefBackedBoolean alloc]
          initWithPrefService:GetApplicationContext()->GetLocalState()
                     prefName:omnibox::kIsOmniboxInBottomPosition];
      _bottomOmniboxEnabled.observer = self;
      [_bottomOmniboxEnabled.observer booleanDidChange:_bottomOmniboxEnabled];
    }
    if (IsFullscreenRefactoringEnabled() && fullscreenBrowserAgent) {
      _fullscreenBrowserAgent = fullscreenBrowserAgent;
      _fullscreenBrowserAgentObserverBridge =
          std::make_unique<FullscreenBrowserAgentObserverBridge>(
              self, _fullscreenBrowserAgent);
    }
  }
  return self;
}

- (void)setConsumer:(id<NewTabPageConsumer>)consumer {
  _consumer = consumer;
  if (IsChromeNextIaEnabled()) {
    if (IsBottomOmniboxAvailable()) {
      [self.consumer setOmniboxInBottomPosition:_bottomOmniboxEnabled.value];
    }
    if (IsFullscreenRefactoringEnabled() && _fullscreenBrowserAgent) {
      [self.consumer
          setFeedBottomInset:_fullscreenBrowserAgent->max_insets().bottom];
    }
  }
}

- (void)setHeaderConsumer:(id<NewTabPageHeaderConsumer>)headerConsumer {
  _headerConsumer = headerConsumer;
  if (IsChromeNextIaEnabled() && IsBottomOmniboxAvailable()) {
    [self.headerConsumer
        setOmniboxInBottomPosition:_bottomOmniboxEnabled.value];
  }
}

#pragma mark - NewTabPageMutator

- (void)notifyNtpDisplayedInLandscape {
  _wasNTPInLandscape = YES;
}

- (void)setIsScrolledToTop:(BOOL)isScrolledToTop {
  if (!self.webState) {
    return;
  }
  NewTabPageTabHelper::FromWebState(self.webState)
      ->SetIsScrolledToTop(isScrolledToTop);
}

- (void)checkNewBadgeEligibility {
  // Notify the badge holdback period has been satisfied if this is not the
  // First Run, or the First Run happened longer than the holdback period.
  if (!IsFirstRun() || !IsFirstRunRecent(kFREBadgeHoldbackPeriod)) {
    _tracker->NotifyEvent(
        feature_engagement::events::kIOSFREBadgeHoldbackPeriodElapsed);
  }
}

- (void)notifyLensBadgeDisplayed {
  LogLensButtonNewBadgeShownHistogram(IOSNTPNewBadgeShownResult::kShown);

  _tracker->Dismissed(feature_engagement::kIPHiOSHomepageLensNewBadge);
}

- (void)notifyCustomizationBadgeDisplayed {
  // TODO(crbug.com/428691449): Remove once the FET migration for 'new' badges
  // is fully validated.
  base::RecordAction(
      base::UserMetricsAction(kNTPCustomizationNewBadgeShownAction));

  _tracker->Dismissed(feature_engagement::kIPHiOSHomepageCustomizationNewBadge);
}

- (BOOL)isFeedHeaderVisible {
  return _discoverFeedVisibilityBrowserAgent->ShouldBeVisible();
}

- (void)setUp {
  DisableImageFetcherCacheCleanupIfNeeded(_prefService);

  self.templateURLService->Load();
  [self updateModuleVisibilityForConsumer];
  [self.headerConsumer
      setVoiceSearchIsEnabled:ios::provider::IsVoiceSearchEnabled()];

  const TemplateURL* defaultSearchEngine =
      self.templateURLService->GetDefaultSearchProvider();
  NSString* dseName =
      defaultSearchEngine
          ? [NSString
                cr_fromString16:defaultSearchEngine
                                    ->AdjustedShortNameForLocaleDirection()]
          : @"";
  [self.headerConsumer setDefaultSearchEngineName:dseName];

  if (self.placeholderService) {
    // The DSE icon might have already been fetched. In this case, no updated
    // will be delivered. Therefore we should query the cache, as the icon store
    // might have already been updated.
    UIImage* fetchedIcon =
        self.placeholderService->GetDefaultSearchEngineIcon(kIconPointSize);
    if (fetchedIcon) {
      [self.headerConsumer setDefaultSearchEngineImage:fetchedIcon];
    }
  }

  [self updateAccountImage];
  [self updateAccountErrorBadge];
  [self startObservingPrefs];
  __weak NewTabPageMediator* weakSelf = self;
  _browserViewVisibilityStateChangedSubscription =
      _browserViewVisibilityNotifierBrowserAgent
          ->RegisterBrowserVisibilityStateChangedCallback(
              base::BindRepeating(^(BrowserViewVisibilityState current_state,
                                    BrowserViewVisibilityState previous_state) {
                [weakSelf browserViewDidChangeToVisibilityState:current_state
                                                      fromState:previous_state];
              }));
  _discoverFeedVisibilityBrowserAgent->AddObserver(self.feedVisibilityObserver);
  _backgroundCustomizationServiceObserverBridge =
      std::make_unique<HomeBackgroundCustomizationServiceObserverBridge>(
          _backgroundCustomizationService, self);
  [self updateAIMAvailability];
  _mediatorSetUp = YES;
}

- (void)shutdown {
  _mediatorSetUp = NO;
  _browserViewVisibilityStateChangedSubscription = {};
  _browserViewVisibilityNotifierBrowserAgent = nullptr;
  _discoverFeedVisibilityBrowserAgent->RemoveObserver(
      self.feedVisibilityObserver);
  _searchEngineObserver.reset();
  _identityObserverBridge.reset();
  _subscriptionEligibilityObserverBridge.reset();
  self.accountManagerService = nil;
  self.discoverFeedService = nullptr;
  _prefChangeRegistrar.reset();
  _prefObserverBridge.reset();
  _prefService = nullptr;
  _subscriptionEligibilityService = nullptr;
  _syncObserver.reset();
  _syncService = nullptr;
  _regionalCapabilitiesService = nullptr;
  _identityManager = nullptr;
  _aimEligibilitySubscription = {};
  _aimEligibilityService = nullptr;
  _isAIMAllowed = NO;
  self.feedControlDelegate = nil;
  _backgroundCustomizationServiceObserverBridge = nullptr;
  _backgroundCustomizationService = nullptr;
  _imageFetcherService = nullptr;
  _backgroundImageCacheService = nullptr;
  self.placeholderService = nullptr;
  _fullscreenBrowserAgentObserverBridge.reset();
  _fullscreenBrowserAgent = nullptr;
  base::UmaHistogramBoolean("IOS.NTP.LandscapeMode", _wasNTPInLandscape);
}

- (void)saveNTPScrollPositionForWebState:(web::WebState*)webState {
  NewTabPageTabHelper::FromWebState(webState)->SetNTPScrollPosition(
      self.scrollPositionToSave);
}

- (void)restoreNTPScrollPositionForWebState:(web::WebState*)webState {
  [self.consumer
      restoreScrollPosition:NewTabPageTabHelper::FromWebState(webState)
                                ->GetNTPScrollPosition()];
}

- (void)setPlaceholderService:(PlaceholderService*)placeholderService {
  _placeholderService = placeholderService;

  if (!placeholderService) {
    _placeholderServiceObserver.reset();
    return;
  }

  _placeholderServiceObserver =
      std::make_unique<PlaceholderServiceObserverBridge>(self,
                                                         placeholderService);
}

- (void)updateBackground {
  [self updateBackgroundForInitialLoad:YES];
}

- (void)browserViewDidChangeToVisibilityState:
            (BrowserViewVisibilityState)currentState
                                    fromState:(BrowserViewVisibilityState)
                                                  previousState {
  if (self.discoverFeedService && self.NTPVisible &&
      [self isFeedHeaderVisible]) {
    self.discoverFeedService->UpdateFeedViewVisibilityState(
        self.contentCollectionView, currentState, previousState);
  }
}

#pragma mark - FullscreenBrowserAgentObserving

- (void)fullscreenDidUpdateObscuredInsetRange:(FullscreenBrowserAgent*)agent {
  [self.consumer setFeedBottomInset:agent->max_insets().bottom];
}

#pragma mark - BooleanObserver

- (void)booleanDidChange:(id<ObservableBoolean>)observableBoolean {
  CHECK(IsChromeNextIaEnabled());
  if (observableBoolean == _bottomOmniboxEnabled) {
    CHECK(IsBottomOmniboxAvailable());
    [self.consumer setOmniboxInBottomPosition:_bottomOmniboxEnabled.value];
    [self.headerConsumer
        setOmniboxInBottomPosition:_bottomOmniboxEnabled.value];
  }
}

#pragma mark - SearchEngineObserving

- (void)searchEngineChanged {
  const TemplateURL* updatedDefaultSearchEngine =
      self.templateURLService->GetDefaultSearchProvider();
  if (_defaultSearchEngine == updatedDefaultSearchEngine) {
    return;
  }
  _defaultSearchEngine = updatedDefaultSearchEngine;
  // AIM availability must be updated before default search engine.
  [self updateAIMAvailability];
  [self.feedControlDelegate updateFeedForDefaultSearchEngineChanged];

  NSString* dseName =
      updatedDefaultSearchEngine
          ? [NSString
                cr_fromString16:updatedDefaultSearchEngine
                                    ->AdjustedShortNameForLocaleDirection()]
          : @"";
  [self.headerConsumer setDefaultSearchEngineName:dseName];
}

#pragma mark - IdentityManagerObserving

- (void)batchOfPrimaryAccountChangesDidEnd {
  _signedInIdentity = self.authService->GetPrimaryIdentity();
  [self updateAccountImage];
  [self updateAccountErrorBadge];
}

- (void)extendedAccountInfoDidUpdate:(const AccountInfo&)info {
  if (info.gaia != _signedInIdentity.gaiaId) {
    return;
  }
  [self updateAccountImage];
  [self updateAccountErrorBadge];
}

#pragma mark - PlaceholderServiceObserving

- (void)placeholderImageUpdated {
  // Show Default Search Engine favicon.
  // Remember what is the Default Search Engine provider that the icon is
  // for, in case the user changes Default Search Engine while this is being
  // loaded.
  __weak __typeof(self) weakSelf = self;
  if (self.placeholderService) {
    self.placeholderService->FetchDefaultSearchEngineIcon(
        kIconPointSize, base::BindRepeating(^(UIImage* image) {
          [weakSelf.headerConsumer setDefaultSearchEngineImage:image];
        }));
  }
}

- (void)placeholderServiceShuttingDown:(PlaceholderService*)service {
  // Removes observation.
  self.placeholderService = nil;
}

#pragma mark - PrefObserverDelegate

- (void)onPreferenceChanged:(const std::string&)preferenceName {
  // Handle customization prefs.
  if (preferenceName == ntp_tiles::prefs::kMostVisitedHomeModuleEnabled ||
      preferenceName == ntp_tiles::prefs::kMagicStackHomeModuleEnabled) {
    [self updateModuleVisibilityForConsumer];
    [self.NTPContentDelegate updateModuleVisibility];
  }
}

#pragma mark - SyncObserverModelBridge

- (void)onSyncStateChanged {
  [self updateAccountErrorBadge];
}

#pragma mark - HomeBackgroundCustomizationServiceObserving

- (void)homeBackgroundCustomizationServiceDidChangeBackground:
    (HomeBackgroundCustomizationService*)service {
  [self updateBackgroundForInitialLoad:NO];
}

#pragma mark - Private

// Returns the framing coordinates for the given custom background.
- (HomeCustomizationFramingCoordinates*)framingCoordinatesForCustomBackground:
    (HomeCustomBackground)customBackground {
  if (std::holds_alternative<HomeUserUploadedBackground>(customBackground)) {
    HomeUserUploadedBackground userBackground =
        std::get<HomeUserUploadedBackground>(customBackground);
    return HomeCustomizationFramingCoordinatesFromFramingCoordinates(
        userBackground.framing_coordinates);
  }
  return nil;
}

// Sets the background image through the consumer and updates the traits.
// Caches the image when `cache` is YES.
- (void)setCustomBackground:(HomeCustomBackground)customBackground
                      image:(UIImage*)image
                      cache:(BOOL)cache {
  [self setCustomBackground:customBackground
                      image:image
          originalImageSize:CGSizeZero
                      cache:cache];
}

- (void)setCustomBackground:(HomeCustomBackground)customBackground
                      image:(UIImage*)image
          originalImageSize:(CGSize)originalImageSize
                      cache:(BOOL)cache {
  if (cache && _backgroundImageCacheService &&
      IsNTPBackgroundImageCacheEnabled()) {
    _backgroundImageCacheService->SetCachedBackgroundImage(image,
                                                           originalImageSize);
  }
  HomeCustomizationFramingCoordinates* coordinates =
      [self framingCoordinatesForCustomBackground:customBackground];
  coordinates.originalImageSize = originalImageSize;
  [self.consumer setBackgroundImage:image framingCoordinates:coordinates];

  CustomUITraitAccessor* traitAccessor = [[CustomUITraitAccessor alloc]
      initWithMutableTraits:self.consumer.traitOverrides];
  [traitAccessor setBoolForNewTabPageImageBackgroundTrait:(image != nil)];
  [traitAccessor setObjectForNewTabPageTrait:[NewTabPageTrait defaultValue]];

  UIColor* tintColor = image ? UIColor.whiteColor : nil;
  [self.logoMediator setLogoTintColor:tintColor];

  if (self.webState) {
    CleanupImageFetcherCacheIfNeeded(
        _prefService, self.webState->GetBrowserState(), self, customBackground);
  }
}

// Attempts to apply the cached background image. Returns YES if a cached image
// was found and applied, NO otherwise.
- (BOOL)applyCachedBackground:(const HomeCustomBackground&)customBackground {
  if (!_backgroundImageCacheService) {
    return NO;
  }
  UIImage* cachedImage =
      _backgroundImageCacheService->GetCachedBackgroundImage();
  if (!cachedImage) {
    return NO;
  }

  CGSize originalImageSize =
      _backgroundImageCacheService->GetCachedOriginalImageSize();
  [self setCustomBackground:customBackground
                      image:cachedImage
          originalImageSize:originalImageSize
                      cache:NO];
  return YES;
}

// Records any necessary logging for after a custom background loaded..
- (void)customBackgroundDidLoad:(HomeCustomBackground)customBackground {
  _tracker->NotifyEvent(feature_engagement::events::kNTPCustomBackgroundLoaded);
  HomeCustomizationBackgroundStyle style =
      HomeCustomizationBackgroundStyle::kPreset;
  if (std::holds_alternative<HomeUserUploadedBackground>(customBackground)) {
    style = HomeCustomizationBackgroundStyle::kUserUploaded;
  }
  base::UmaHistogramEnumeration("IOS.HomeCustomization.Background.Ntp.Loaded",
                                style);
}

- (void)updateAIMAvailability {
  BOOL aimAllowed = NO;
  BOOL fuseboxEligible = NO;
  if (_aimEligibilityService) {
    const BOOL allowedOnDevice =
        ui::GetDeviceFormFactor() == ui::DEVICE_FORM_FACTOR_PHONE ||
        IsAIMNTPEntrypointTabletEnabled();
    aimAllowed = _aimEligibilityService->IsAimEligible() && allowedOnDevice;
    fuseboxEligible = _aimEligibilityService->IsFuseboxEligible();
  }

  [self.consumer setAIMAllowed:aimAllowed];
  [self.headerConsumer setAIMAllowed:aimAllowed];
  [self.headerConsumer setFuseboxEligible:fuseboxEligible];

  if (aimAllowed == _isAIMAllowed) {
    return;
  }
  _isAIMAllowed = aimAllowed;
  // Only update the modules if the mediator has already been set up.
  if (IsAIMEligibilityRefreshNTPModulesEnabled() && _mediatorSetUp) {
    [self.NTPContentDelegate updateModuleVisibility];
  }
}

// Fetches and update user's avatar on NTP, or use default avatar if user is
// not signed in.
- (void)updateAccountImage {
  // Fetches user's identity from Authentication Service.
  if (_signedInIdentity) {
    // Only show an avatar if the user is signed in.
    UIImage* avatarImage =
        GetApplicationContext()->GetIdentityAvatarProvider()->GetIdentityAvatar(
            _signedInIdentity, IdentityAvatarSize::SmallSize);

    BOOL hasAITier =
        IsAiSubscriptionAvatarRingIOSEnabled() &&
        _subscriptionEligibilityService->GetAiSubscriptionTier() > 0;
    [self.imageUpdater updateAccountWithName:_signedInIdentity.userFullName
                                       email:_signedInIdentity.userEmail
                                 avatarImage:avatarImage
                                   hasAITier:hasAITier];
  } else {
    [self.imageUpdater setSignedOutAccountImage];
    signin_metrics::LogSignInOffered(
        signin_metrics::AccessPoint::kNtpSignedOutIcon,
        signin_metrics::PromoAction::PROMO_ACTION_NO_SIGNIN_PROMO);
  }
}

// Opens web page for a menu item in the NTP.
- (void)openMenuItemWebPage:(GURL)URL {
  _URLLoader->Load(UrlLoadParams::InCurrentTab(URL));
  // TODO(crbug.com/40693626): Add metrics.
}

// Updates the consumer with the current visibility of the NTP modules.
- (void)updateModuleVisibilityForConsumer {
  self.consumer.mostVisitedVisible =
      _prefService->GetBoolean(ntp_tiles::prefs::kMostVisitedHomeModuleEnabled);
  self.consumer.magicStackVisible =
      _prefService->GetBoolean(ntp_tiles::prefs::kMagicStackHomeModuleEnabled);
}

// Starts observing some prefs.
- (void)startObservingPrefs {
  _prefChangeRegistrar = std::make_unique<PrefChangeRegistrar>();
  _prefChangeRegistrar->Init(_prefService);
  _prefObserverBridge = std::make_unique<PrefObserverBridge>(self);

  // Observe customization prefs.
  _prefObserverBridge->ObserveChangesForPreference(
      ntp_tiles::prefs::kMostVisitedHomeModuleEnabled,
      _prefChangeRegistrar.get());
  _prefObserverBridge->ObserveChangesForPreference(
      ntp_tiles::prefs::kMagicStackHomeModuleEnabled,
      _prefChangeRegistrar.get());
}

- (void)updateAccountErrorBadge {
  BOOL primaryIdentityHasError =
      _signedInIdentity && _syncService->GetUserActionableError() !=
                               syncer::SyncService::UserActionableError::kNone;
  [self.headerConsumer
      updateADPBadgeWithErrorFound:primaryIdentityHasError
                              name:_signedInIdentity.userFullName
                             email:_signedInIdentity.userEmail];
}

// Helper method to handle the image response after fetching the background
// image for the new tab page.
- (void)handleBackgroundImageFetch:(const gfx::Image&)image {
  [self.consumer setBackgroundImage:image.ToUIImage() framingCoordinates:nil];
}

// Helper method to handle displaying a user-uploaded background image
// with the specified framing coordinates.
- (void)handleUserUploadedImage:(UIImage*)image
             framingCoordinates:
                 (HomeCustomizationFramingCoordinates*)framingCoordinates {
  if (!image) {
    // Clear the corrupted data.
    _backgroundCustomizationService->ClearCurrentUserUploadedBackground();
    _backgroundCustomizationService->StoreCurrentTheme();
    [self.consumer setBackgroundImage:nil framingCoordinates:nil];
    return;
  }

  [self.consumer setBackgroundImage:image
                 framingCoordinates:framingCoordinates];
}

// Updates the background based on the current customization settings.
// `initialLoad` is YES if this is the first time the background is being set.
- (void)updateBackgroundForInitialLoad:(BOOL)initialLoad {
  TRACE_EVENT("startup", "NewTabPageMediator::updateBackgroundForInitialLoad",
              perfetto::Flow::ProcessScoped(reinterpret_cast<uintptr_t>(self)));
  CustomUITraitAccessor* traitAccessor = [[CustomUITraitAccessor alloc]
      initWithMutableTraits:self.consumer.traitOverrides];

  std::optional<HomeCustomBackground> customBackground =
      _backgroundCustomizationService->GetCurrentCustomBackground();

  if (customBackground) {
    if (initialLoad && [self applyCachedBackground:customBackground.value()]) {
      // Cached background image applied. Nothing else to do.
    } else if (std::holds_alternative<sync_pb::NtpCustomBackground>(
                   customBackground.value())) {
      sync_pb::NtpCustomBackground background =
          std::get<sync_pb::NtpCustomBackground>(customBackground.value());
      [self fetchCustomBackground:background];
    } else {
      HomeUserUploadedBackground userBackground =
          std::get<HomeUserUploadedBackground>(customBackground.value());

      __weak __typeof(self) weakSelf = self;
      _userUploadedImageManager->LoadUserUploadedImage(
          base::FilePath(userBackground.image_path), self.screenSize,
          base::BindOnce(^(UIImage* image, CGSize originalSize,
                           UserUploadedImageError error) {
            [weakSelf setCustomBackground:userBackground
                                    image:image
                        originalImageSize:originalSize
                                    cache:YES];
            if (!image) {
              base::UmaHistogramEnumeration("IOS.HomeCustomization.Background."
                                            "Ntp.ImageUserUploadedFetchError",
                                            error);
            }
          }));
    }
    if (initialLoad) {
      [self customBackgroundDidLoad:customBackground.value()];
    }
    return;
  }

  [self.consumer setBackgroundImage:nil framingCoordinates:nil];

  std::optional<sync_pb::UserColorTheme> colorTheme =
      _backgroundCustomizationService->GetCurrentColorTheme();

  if (colorTheme && colorTheme->color()) {
    // Sets the New Tab Page trait to a color palette generated from the current
    // theme.
    NewTabPageColorPalette* colorPalette = CreateColorPaletteFromSeedColor(
        skia::UIColorFromSkColor(colorTheme->color()),
        ProtoEnumToSchemeVariant(colorTheme->browser_color_variant()));

    [traitAccessor setObjectForNewTabPageTrait:colorPalette];
    [traitAccessor setBoolForNewTabPageImageBackgroundTrait:NO];
    UIColor* tintColor = colorPalette.tintColor;
    [self.logoMediator setLogoTintColor:tintColor];
    if (initialLoad) {
      base::UmaHistogramEnumeration(
          "IOS.HomeCustomization.Background.Ntp.Loaded",
          HomeCustomizationBackgroundStyle::kColor);
    }
    return;
  }

  // Clears the color palette associated with the New Tab Page trait,
  // reverting to the default colors defined by the trait.
  [traitAccessor setObjectForNewTabPageTrait:[NewTabPageTrait defaultValue]];
  [traitAccessor setBoolForNewTabPageImageBackgroundTrait:NO];
  [self.logoMediator setLogoTintColor:nil];
  if (initialLoad) {
    base::UmaHistogramEnumeration("IOS.HomeCustomization.Background.Ntp.Loaded",
                                  HomeCustomizationBackgroundStyle::kDefault);
  }
}

// Fetches and applies a custom background image.
- (void)fetchCustomBackground:(sync_pb::NtpCustomBackground)background {
  GURL imageURL = GURL(background.url());
  if (imageURL == _pendingBackgroundURL) {
    return;
  }

  // Cancel any existing fetches.
  if (_imageCallback) {
    _imageCallback->Cancel();
    _imageCallback.reset();
  }
  if (_thumbnailCallback) {
    _thumbnailCallback->Cancel();
    _thumbnailCallback.reset();
  }

  _pendingBackgroundURL = imageURL;

  uint64_t flow_id =
      reinterpret_cast<uint64_t>(self) ^ (++_fetchSequenceNumber);
  TRACE_EVENT("ui", "NewTabPageMediator::fetchCustomBackground",
              perfetto::Flow::ProcessScoped(flow_id));

  GURL thumbnailURL =
      AddOptionsToImageURL(RemoveOptionsFromImageURL(imageURL.spec()).spec(),
                           GetThumbnailImageOptions());

  image_fetcher::ImageFetcher* imageFetcher =
      _imageFetcherService->GetImageFetcher(
          image_fetcher::ImageFetcherConfig::kReducedMode);

  __weak __typeof(self) weakSelf = self;

  _thumbnailCallback = std::make_unique<base::CancelableOnceCallback<void(
      const std::string&, const image_fetcher::RequestMetadata&)>>(
      base::BindOnce(^(const std::string& image_data,
                       const image_fetcher::RequestMetadata& metadata) {
        if (!image_data.empty()) {
          NSData* data = [NSData dataWithBytes:image_data.data()
                                        length:image_data.length()];
          UIImage* image = [UIImage imageWithData:data];
          if (image) {
            // Temporarily sets the thumbnail as the background until the
            // high-resolution image is loaded.
            [weakSelf setCustomBackground:background image:image cache:NO];
          }
          return;
        }
      }));

  _imageCallback = std::make_unique<base::CancelableOnceCallback<void(
      const std::string&, const image_fetcher::RequestMetadata&)>>(
      base::BindOnce(^(const std::string& image_data,
                       const image_fetcher::RequestMetadata& metadata) {
        __typeof(self) strongSelf = weakSelf;
        if (!strongSelf) {
          return;
        }

        TRACE_EVENT(
            "ui",
            "NewTabPageMediator::fetchCustomBackground completion callback",
            perfetto::Flow::ProcessScoped(flow_id));

        // Cancel the thumbnail URL fetch if the high-resolution fetch
        // finished first.
        if (strongSelf->_thumbnailCallback) {
          strongSelf->_thumbnailCallback->Cancel();
          strongSelf->_thumbnailCallback.reset();
        }

        if (!image_data.empty()) {
          NSData* data = [NSData dataWithBytes:image_data.data()
                                        length:image_data.length()];
          UIImage* image = [UIImage imageWithData:data];
          if (image) {
            [strongSelf setCustomBackground:background image:image cache:YES];
          }
        } else {
          base::UmaHistogramSparse(
              "IOS.HomeCustomization.Background.Ntp.ImageDownloadErrorCode",
              metadata.http_response_code);
        }

        // Clear state.
        strongSelf->_pendingBackgroundURL = GURL();
      }));

  // Retrieving the thumbnail URL should hit the cache, so it returns almost
  // instantly.
  imageFetcher->FetchImageData(thumbnailURL, _thumbnailCallback->callback(),
                               image_fetcher::ImageFetcherParams(
                                   kTrafficAnnotation, kImageFetcherUmaClient));

  imageFetcher->FetchImageData(imageURL, _imageCallback->callback(),
                               image_fetcher::ImageFetcherParams(
                                   kTrafficAnnotation, kImageFetcherUmaClient));
}

- (void)markSafariDataImportSetupListItemAsComplete {
  set_up_list_prefs::MarkItemComplete(GetApplicationContext()->GetLocalState(),
                                      SetUpListItemType::kSafariImport);
}

- (image_fetcher::ImageFetcherService*)imageFetcherService {
  return _imageFetcherService;
}

#pragma mark - SubscriptionEligibilityServiceObserving

- (void)aiSubscriptionTierDidUpdate:(int32_t)newSubscriptionTier {
  [self updateAccountImage];
}

@end
