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

// Copyright 2024 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_capabilities_fetcher.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/metrics/histogram_functions.h"
#import "base/task/sequenced_task_runner.h"
#import "base/timer/elapsed_timer.h"
#import "components/signin/public/base/signin_switches.h"
#import "components/signin/public/identity_manager/account_info.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/signin/public/identity_manager/objc/identity_manager_observer_bridge.h"

using signin::Tribool;

namespace {

// Fallback value for the capability
// CanShowHistorySyncOptInsWithoutMinorModeRestrictions if it is not available
// after `kMinorModeRestrictionsFetchDeadlineMs`.
const Tribool kCanShowUnrestrictedOptInsFallbackValue = Tribool::kUnknown;

}  // namespace

@interface HistorySyncCapabilitiesFetcher () <
    IdentityManagerObserverBridgeDelegate>
@end

@implementation HistorySyncCapabilitiesFetcher {
  raw_ptr<signin::IdentityManager> _identityManager;
  // Observer for `IdentityManager`.
  std::unique_ptr<signin::IdentityManagerObserverBridge>
      _identityManagerObserver;
  CapabilityFetchCompletionCallback _completionCallback;
  std::unique_ptr<base::ElapsedTimer> _fetchLatencyTimer;
  // Check that `onRestrictionCapabilityReceived` is called from the correct
  // sequence.
  SEQUENCE_CHECKER(_sequenceChecker);
}

- (instancetype)initWithIdentityManager:
    (signin::IdentityManager*)identityManager {
  self = [super init];
  if (self) {
    _identityManager = identityManager;
    _identityManagerObserver =
        std::make_unique<signin::IdentityManagerObserverBridge>(identityManager,
                                                                self);
  }
  return self;
}

- (void)shutdown {
  _completionCallback = CapabilityFetchCompletionCallback();
  _identityManagerObserver.reset();
  _identityManager = nullptr;
}

- (void)startFetchingRestrictionCapabilityWithCallback:
    (CapabilityFetchCompletionCallback)callback {
  // Existing non-null callback should not be replaced.
  CHECK(_completionCallback.is_null());
  _completionCallback = std::move(callback);

  // Manually fetch AccountInfo::capabilities. The capability might have been
  // available and onExtendedAccountInfoUpdated would not be triggered.
  Tribool capability = [self canShowUnrestrictedOptInsCapability];

  if (capability != Tribool::kUnknown) {
    [self onRestrictionCapabilityReceived:capability];
    // The AccountInfo capability was immediately available and the callback was
    // executed.
    base::UmaHistogramBoolean("Signin.AccountCapabilities.ImmediatelyAvailable",
                              true);
    base::UmaHistogramTimes("Signin.AccountCapabilities.UserVisibleLatency",
                            base::Seconds(0));
  } else {
    // The AccountInfo capability was not available; start the latency timer.
    base::UmaHistogramBoolean("Signin.AccountCapabilities.ImmediatelyAvailable",
                              false);
    _fetchLatencyTimer = std::make_unique<base::ElapsedTimer>();

    // Set timeout with fallback capability value.
    __weak __typeof(self) weakSelf = self;
    base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
        FROM_HERE, base::BindOnce(^{
          [weakSelf onRestrictionCapabilityReceived:
                        kCanShowUnrestrictedOptInsFallbackValue];
        }),
        base::Milliseconds(
            switches::kMinorModeRestrictionsFetchDeadlineMs.Get()));
  }
}

- (Tribool)canShowUnrestrictedOptInsCapability {
  DCHECK(_identityManager);
  CoreAccountInfo primaryAccount =
      _identityManager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
  AccountInfo accountInfo =
      _identityManager->FindExtendedAccountInfo(primaryAccount);
  return accountInfo.capabilities
      .can_show_history_sync_opt_ins_without_minor_mode_restrictions();
}

#pragma mark - IdentityManagerObserverBridgeDelegate

- (void)onExtendedAccountInfoUpdated:(const AccountInfo&)accountInfo {
  if (base::FeatureList::IsEnabled(
          switches::kMinorModeRestrictionsForHistorySyncOptIn)) {
    signin::Tribool capability =
        accountInfo.capabilities
            .can_show_history_sync_opt_ins_without_minor_mode_restrictions();
    // Only process known capability values.
    if (capability != Tribool::kUnknown) {
      [self onRestrictionCapabilityReceived:capability];
    }
  }
}

#pragma mark - Private

- (void)onRestrictionCapabilityReceived:(Tribool)capability {
  DCHECK_CALLED_ON_VALID_SEQUENCE(_sequenceChecker);
  if (!_completionCallback.is_null()) {
    // Stop listening to AccountInfo updates.
    _identityManagerObserver.reset();
    // Record latency metrics
    if (_fetchLatencyTimer) {
      base::TimeDelta elapsed = _fetchLatencyTimer->Elapsed();
      base::UmaHistogramTimes("Signin.AccountCapabilities.UserVisibleLatency",
                              elapsed);
      base::UmaHistogramTimes("Signin.AccountCapabilities.FetchLatency",
                              elapsed);
    }
    std::move(_completionCallback).Run(capability);
  }
}

@end