chromium/ios/chrome/browser/ui/authentication/history_sync/history_sync_mediator.mm

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

#import "ios/chrome/browser/ui/authentication/history_sync/history_sync_mediator.h"

#import "base/check.h"
#import "base/check_op.h"
#import "base/feature_list.h"
#import "base/functional/bind.h"
#import "base/memory/raw_ptr.h"
#import "base/strings/sys_string_conversions.h"
#import "components/signin/public/base/signin_switches.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"
#import "components/sync/service/sync_service.h"
#import "components/sync/service/sync_user_settings.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service.h"
#import "ios/chrome/browser/signin/model/chrome_account_manager_service_observer_bridge.h"
#import "ios/chrome/browser/ui/authentication/account_capabilities_latency_tracker.h"
#import "ios/chrome/browser/ui/authentication/history_sync/history_sync_capabilities_fetcher.h"
#import "ios/chrome/browser/ui/authentication/history_sync/history_sync_consumer.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util.h"

// Mediator that handles the sync operations.
@interface HistorySyncMediator () <ChromeAccountManagerServiceObserver,
                                   IdentityManagerObserverBridgeDelegate>
@end

@implementation HistorySyncMediator {
  raw_ptr<AuthenticationService> _authenticationService;
  // Account manager service with observer.
  raw_ptr<ChromeAccountManagerService> _accountManagerService;
  std::unique_ptr<ChromeAccountManagerServiceObserverBridge>
      _accountManagerServiceObserver;
  raw_ptr<signin::IdentityManager> _identityManager;
  // Observer for `IdentityManager`.
  std::unique_ptr<signin::IdentityManagerObserverBridge>
      _identityManagerObserver;
  // Sync service.
  raw_ptr<syncer::SyncService> _syncService;
  // `YES` if the user's email should be shown in the footer text.
  BOOL _showUserEmail;
  // Records the latency of capabilities fetch for this view.
  std::unique_ptr<signin::AccountCapabilitiesLatencyTracker>
      _accountCapabilitiesLatencyTracker;
  // Capabilities fetcher to determine minor mode restriction.
  HistorySyncCapabilitiesFetcher* _capabilitiesFetcher;
}

- (instancetype)
    initWithAuthenticationService:(AuthenticationService*)authenticationService
      chromeAccountManagerService:
          (ChromeAccountManagerService*)chromeAccountManagerService
                  identityManager:(signin::IdentityManager*)identityManager
                      syncService:(syncer::SyncService*)syncService
                    showUserEmail:(BOOL)showUserEmail {
  self = [super init];
  if (self) {
    _authenticationService = authenticationService;
    _accountManagerService = chromeAccountManagerService;
    _accountManagerServiceObserver =
        std::make_unique<ChromeAccountManagerServiceObserverBridge>(
            self, _accountManagerService);
    _identityManager = identityManager;
    _identityManagerObserver =
        std::make_unique<signin::IdentityManagerObserverBridge>(identityManager,
                                                                self);
    _syncService = syncService;
    _showUserEmail = showUserEmail;

    if ([self useMinorModeRestrictions]) {
      _capabilitiesFetcher = [[HistorySyncCapabilitiesFetcher alloc]
          initWithIdentityManager:identityManager];
    } else {
      _accountCapabilitiesLatencyTracker =
          std::make_unique<signin::AccountCapabilitiesLatencyTracker>(
              identityManager);
    }
  }

  return self;
}

- (void)disconnect {
  _accountCapabilitiesLatencyTracker.reset();
  _accountManagerServiceObserver.reset();
  _identityManagerObserver.reset();
  [_capabilitiesFetcher shutdown];
  _capabilitiesFetcher = nil;
  _authenticationService = nullptr;
  _accountManagerService = nullptr;
  _identityManager = nullptr;
  _syncService = nullptr;
}

- (void)enableHistorySyncOptin {
  id<SystemIdentity> identity =
      _authenticationService->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
  CHECK(identity);
  // TODO(crbug.com/40068130): Record the history sync opt-in when the new
  // consent type will be available.
  syncer::SyncUserSettings* syncUserSettings = _syncService->GetUserSettings();
  syncUserSettings->SetSelectedType(syncer::UserSelectableType::kHistory, true);
  syncUserSettings->SetSelectedType(syncer::UserSelectableType::kTabs, true);
}

#pragma mark - Properties

- (void)setConsumer:(id<HistorySyncConsumer>)consumer {
  _consumer = consumer;
  if (!_consumer) {
    return;
  }
  id<SystemIdentity> identity =
      _authenticationService->GetPrimaryIdentity(signin::ConsentLevel::kSignin);
  if (!identity) {
    // This can happen if identity is removed from the device while the history
    // sync screen is open. There is no point in updating the UI since the
    // dialog will be automatically closed.
    return;
  }

  [self updateAvatarImageWithIdentity:identity];
  NSString* footerText =
      _showUserEmail
          ? l10n_util::GetNSStringF(
                IDS_IOS_HISTORY_SYNC_FOOTER_WITH_EMAIL,
                base::SysNSStringToUTF16(identity.userEmail))
          : l10n_util::GetNSString(IDS_IOS_HISTORY_SYNC_FOOTER_WITHOUT_EMAIL);
  [_consumer setFooterText:footerText];

  if ([self useMinorModeRestrictions]) {
    [self.consumer
        displayButtonsWithRestrictionCapability:
            [_capabilitiesFetcher canShowUnrestrictedOptInsCapability]];
  }
}

#pragma mark - ChromeAccountManagerServiceObserver

- (void)identityUpdated:(id<SystemIdentity>)identity {
  [self updateAvatarImageWithIdentity:identity];
}

- (void)onChromeAccountManagerServiceShutdown:
    (ChromeAccountManagerService*)accountManagerService {
  // TODO(crbug.com/40284086): Remove `[self disconnect]`.
  [self disconnect];
}

#pragma mark - IdentityManagerObserverBridgeDelegate

- (void)onPrimaryAccountChanged:
    (const signin::PrimaryAccountChangeEvent&)event {
  if (event.GetEventTypeFor(signin::ConsentLevel::kSignin) ==
      signin::PrimaryAccountChangeEvent::Type::kCleared) {
    [self.delegate historySyncMediatorPrimaryAccountCleared:self];
  }
}

- (void)onExtendedAccountInfoUpdated:(const AccountInfo&)info {
  if (![self useMinorModeRestrictions]) {
    _accountCapabilitiesLatencyTracker->OnExtendedAccountInfoUpdated(info);
  }
}

#pragma mark - Private

// Updates the avatar image for the consumer from `identity`.
- (void)updateAvatarImageWithIdentity:(id<SystemIdentity>)identity {
  UIImage* image = _accountManagerService->GetIdentityAvatarWithIdentity(
      identity, IdentityAvatarSize::Large);
  [self.consumer setPrimaryIdentityAvatarImage:image];

  NSString* accessibilityLabel = nil;
  if (identity.userFullName.length == 0) {
    accessibilityLabel = identity.userEmail;
  } else {
    accessibilityLabel = [NSString
        stringWithFormat:@"%@ %@", identity.userFullName, identity.userEmail];
  }
  [self.consumer setPrimaryIdentityAvatarAccessibilityLabel:accessibilityLabel];
}

- (BOOL)useMinorModeRestrictions {
  return base::FeatureList::IsEnabled(
      switches::kMinorModeRestrictionsForHistorySyncOptIn);
}

@end