chromium/ios/chrome/browser/contextual_panel/entrypoint/coordinator/contextual_panel_entrypoint_mediator.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/contextual_panel/entrypoint/coordinator/contextual_panel_entrypoint_mediator.h"

#import "base/check_op.h"
#import "base/memory/weak_ptr.h"
#import "base/metrics/histogram_functions.h"
#import "base/strings/stringprintf.h"
#import "base/strings/sys_string_conversions.h"
#import "base/timer/timer.h"
#import "components/feature_engagement/public/tracker.h"
#import "ios/chrome/browser/contextual_panel/entrypoint/coordinator/contextual_panel_entrypoint_mediator_delegate.h"
#import "ios/chrome/browser/contextual_panel/entrypoint/ui/contextual_panel_entrypoint_consumer.h"
#import "ios/chrome/browser/contextual_panel/model/active_contextual_panel_tab_helper_observation_forwarder.h"
#import "ios/chrome/browser/contextual_panel/model/contextual_panel_item_configuration.h"
#import "ios/chrome/browser/contextual_panel/model/contextual_panel_item_type.h"
#import "ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper.h"
#import "ios/chrome/browser/contextual_panel/model/contextual_panel_tab_helper_observer_bridge.h"
#import "ios/chrome/browser/contextual_panel/utils/contextual_panel_metrics.h"
#import "ios/chrome/browser/infobars/model/infobar_badge_tab_helper.h"
#import "ios/chrome/browser/infobars/model/infobar_badge_tab_helper_observer.h"
#import "ios/chrome/browser/infobars/model/infobar_badge_tab_helper_observer_bridge.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list_observer_bridge.h"
#import "ios/chrome/browser/shared/public/commands/contextual_panel_entrypoint_iph_commands.h"
#import "ios/chrome/browser/shared/public/commands/contextual_sheet_commands.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/shared/ui/symbols/symbols.h"

@interface ContextualPanelEntrypointMediator () <
    ContextualPanelTabHelperObserving,
    InfobarBadgeTabHelperObserving,
    WebStateListObserving>
@end

@implementation ContextualPanelEntrypointMediator {
  // Whether there currently are any Infobar badges being shown.
  BOOL _infobarBadgesCurrentlyShown;

  // The command handler for contextual sheet commands.
  __weak id<ContextualSheetCommands> _contextualSheetHandler;

  // The command handler for entrypoint in-product help commands.
  __weak id<ContextualPanelEntrypointIPHCommands> _entrypointHelpHandler;

  // The engagement tracker for the current browser.
  feature_engagement::Tracker* _engagementTracker;

  // WebStateList to use for observing ContextualPanelTabHelper events.
  raw_ptr<WebStateList> _webStateList;

  // Timer keeping track of when to transition to a loud moment for the
  // entrypoint (large entrypoint or IPH shown).
  std::unique_ptr<base::OneShotTimer> _transitionToEntrypointLoudMomentTimer;

  // Timer to keep track of when to return to the normal small entrypoint after
  // having transitioned to a loud moment.
  std::unique_ptr<base::OneShotTimer> _transitionToDefaultEntrypointTimer;

  // Observer machinery for the web state list.
  std::unique_ptr<WebStateListObserverBridge> _webStateListObserver;
  std::unique_ptr<
      base::ScopedObservation<WebStateList, WebStateListObserverBridge>>
      _webStateListObservation;

  // Bridge for the ContextualPanelTabHelper observation.
  std::unique_ptr<ContextualPanelTabHelperObserverBridge>
      _contextualPanelObserverBridge;

  // Bridge for the InfobarBadgeTabHelper observation.
  std::unique_ptr<InfobarBadgeTabHelperObserverBridge>
      _infobarBadgeObserverBridge;
  std::unique_ptr<base::ScopedObservation<InfobarBadgeTabHelper,
                                          InfobarBadgeTabHelperObserverBridge>>
      _infobarBadgeObservation;

  // Forwarder to always be observing the active ContextualPanelTabHelper.
  std::unique_ptr<ActiveContextualPanelTabHelperObservationForwarder>
      _activeContextualPanelObservationForwarder;
}

- (instancetype)
      initWithWebStateList:(WebStateList*)webStateList
         engagementTracker:(feature_engagement::Tracker*)engagementTracker
    contextualSheetHandler:(id<ContextualSheetCommands>)contextualSheetHandler
     entrypointHelpHandler:
         (id<ContextualPanelEntrypointIPHCommands>)entrypointHelpHandler {
  self = [super init];
  if (self) {
    _webStateList = webStateList;
    _contextualSheetHandler = contextualSheetHandler;
    _entrypointHelpHandler = entrypointHelpHandler;
    _engagementTracker = engagementTracker;

    // Set up web state list observation.
    _webStateListObserver = std::make_unique<WebStateListObserverBridge>(self);
    _webStateListObservation = std::make_unique<
        base::ScopedObservation<WebStateList, WebStateListObserverBridge>>(
        _webStateListObserver.get());
    _webStateListObservation->Observe(_webStateList);

    // Set up active ContextualPanelTabHelper observation.
    _contextualPanelObserverBridge =
        std::make_unique<ContextualPanelTabHelperObserverBridge>(self);
    _activeContextualPanelObservationForwarder =
        std::make_unique<ActiveContextualPanelTabHelperObservationForwarder>(
            webStateList, _contextualPanelObserverBridge.get());

    // Setup InfobarBadgeTabHelper observation.
    _infobarBadgeObserverBridge =
        std::make_unique<InfobarBadgeTabHelperObserverBridge>(self);
    _infobarBadgeObservation = std::make_unique<base::ScopedObservation<
        InfobarBadgeTabHelper, InfobarBadgeTabHelperObserverBridge>>(
        _infobarBadgeObserverBridge.get());

    if (_webStateList->GetActiveWebState()) {
      _infobarBadgeObservation->Observe(
          InfobarBadgeTabHelper::GetOrCreateForWebState(
              _webStateList->GetActiveWebState()));
    }
  }
  return self;
}

- (void)disconnect {
  _infobarBadgeObservation->Reset();
  _infobarBadgeObservation.reset();
  _infobarBadgeObserverBridge.reset();
  _activeContextualPanelObservationForwarder.reset();
  _contextualPanelObserverBridge.reset();
  _webStateListObservation.reset();
  _webStateListObserver.reset();
  _webStateList = nullptr;
}

#pragma mark - ContextualPanelEntrypointMutator

- (void)dismissIPHAnimated:(BOOL)animated {
  [self dismissEntrypointIPHAnimated:animated];
}

- (void)entrypointTapped {
  // Cancel any pending transition timers since user interacted with entrypoint.
  [self resetTimersAndUIStateAnimated:YES];

  ContextualPanelTabHelper* contextualPanelTabHelper =
      ContextualPanelTabHelper::FromWebState(
          _webStateList->GetActiveWebState());

  if (contextualPanelTabHelper->IsContextualPanelCurrentlyOpened()) {
    base::UmaHistogramEnumeration(
        "IOS.ContextualPanel.DismissedReason",
        ContextualPanelDismissedReason::UserDismissed);
    [_contextualSheetHandler closeContextualSheet];
  } else {
    [self logEntrypointFirstTapMetrics];
    [_contextualSheetHandler openContextualSheet];
  }

  base::WeakPtr<ContextualPanelItemConfiguration> config =
      contextualPanelTabHelper->GetFirstCachedConfig();
  if (!config || config->iph_entrypoint_used_event_name.empty()) {
    return;
  }
  _engagementTracker->NotifyEvent(config->iph_entrypoint_used_event_name);
}

- (void)setLocationBarLabelCenteredBetweenContent:(BOOL)centered {
  [self.delegate setLocationBarLabelCenteredBetweenContent:self
                                                  centered:centered];
}

#pragma mark - ContextualPanelTabHelperObserving

- (void)contextualPanel:(ContextualPanelTabHelper*)tabHelper
             hasNewData:
                 (std::vector<base::WeakPtr<ContextualPanelItemConfiguration>>)
                     item_configurations {
  [self activeTabHasNewData:item_configurations.empty()
                                ? nullptr
                                : item_configurations[0]];
}

- (void)contextualPanelTabHelperDestroyed:(ContextualPanelTabHelper*)tabHelper {
  [self activeTabHasNewData:nullptr];
}

- (void)contextualPanelOpened:(ContextualPanelTabHelper*)tabHelper {
  [self.consumer transitionToContextualPanelOpenedState:YES];
}

- (void)contextualPanelClosed:(ContextualPanelTabHelper*)tabHelper {
  [self.consumer transitionToContextualPanelOpenedState:NO];
}

#pragma mark - WebStateListObserving

- (void)didChangeWebStateList:(WebStateList*)webStateList
                       change:(const WebStateListChange&)change
                       status:(const WebStateListStatus&)status {
  // Return early if the active web state is the same as before the change.
  if (!status.active_web_state_change()) {
    return;
  }

  // De-register observer bridge for the old WebState's InfobarBadgeTabHelper.
  _infobarBadgeObservation->Reset();

  if (status.old_active_web_state) {
    // Update old active web state's visible time.
    ContextualPanelTabHelper* contextualPanelTabHelper =
        ContextualPanelTabHelper::FromWebState(status.old_active_web_state);
    std::optional<ContextualPanelTabHelper::EntrypointMetricsData>&
        metricsData = contextualPanelTabHelper->GetMetricsData();
    if (metricsData && metricsData->appearance_time) {
      metricsData->time_visible +=
          base::Time::Now() - metricsData->appearance_time.value();
      metricsData->appearance_time = std::nullopt;
    }
  }

  [self resetTimersAndUIStateAnimated:NO];

  // Return early if no new webstates are active.
  if (!status.new_active_web_state) {
    return;
  }

  // Register observer bridge for the new WebState's InfobarBadgeTabHelper.
  _infobarBadgeObservation->Observe(
      InfobarBadgeTabHelper::GetOrCreateForWebState(
          status.new_active_web_state));

  ContextualPanelTabHelper* contextualPanelTabHelper =
      ContextualPanelTabHelper::FromWebState(status.new_active_web_state);
  [self activeTabHasNewData:contextualPanelTabHelper->GetFirstCachedConfig()];
}

#pragma mark - InfobarBadgeTabHelperObserving

- (void)infobarBadgesUpdated:(InfobarBadgeTabHelper*)tabHelper {
  // Return early if the notification doesn't come from the currently active
  // webstate's tab helper.
  if (tabHelper != InfobarBadgeTabHelper::GetOrCreateForWebState(
                       _webStateList->GetActiveWebState())) {
    return;
  }

  size_t badgesCount = tabHelper->GetInfobarBadgesCount();

  BOOL infobarBadgesCurrentlyShown = badgesCount > 0;
  if (_infobarBadgesCurrentlyShown == infobarBadgesCurrentlyShown) {
    return;
  }
  _infobarBadgesCurrentlyShown = infobarBadgesCurrentlyShown;

  if (_infobarBadgesCurrentlyShown) {
    [self dismissEntrypointIPHAnimated:YES];
  }

  [self.consumer setInfobarBadgesCurrentlyShown:_infobarBadgesCurrentlyShown];
}

#pragma mark - private

// Cancels pending timers, dismisses any showing IPH and removes any active
// fullscreen disabler.
- (void)resetTimersAndUIStateAnimated:(BOOL)animated {
  _transitionToEntrypointLoudMomentTimer = nullptr;
  _transitionToDefaultEntrypointTimer = nullptr;
  [self dismissEntrypointIPHAnimated:animated];
  [self.delegate enableFullscreen];
}

// Updates the entrypoint state whenever the active tab changes or new data is
// provided.
- (void)activeTabHasNewData:
    (base::WeakPtr<ContextualPanelItemConfiguration>)config {
  [self resetTimersAndUIStateAnimated:NO];

  if (!config) {
    [self.consumer hideEntrypoint];
    return;
  }

  ContextualPanelTabHelper* contextualPanelTabHelper =
      ContextualPanelTabHelper::FromWebState(
          _webStateList->GetActiveWebState());

  if (![self metricsData]) {
    ContextualPanelTabHelper::EntrypointMetricsData metricsData;
    metricsData.entrypoint_item_type = config->item_type;
    contextualPanelTabHelper->SetMetricsData(metricsData);
  } else if (![self metricsData]->appearance_time) {
    [self metricsData]->appearance_time = base::Time::Now();
  }

  [self.consumer setEntrypointConfig:config];
  [self.consumer transitionToSmallEntrypoint];
  [self.consumer showEntrypoint];

  [self logEntrypointFirstDisplayMetrics];

  [self.consumer
      transitionToContextualPanelOpenedState:
          contextualPanelTabHelper->IsContextualPanelCurrentlyOpened()];

  // Special case for first entrypoint appearances where an IPH is shown
  // instead of the large entrypoint. If showing the IPH fails, will fallback to
  // showing the large entrypoint.
  if ([self canShowEntrypointIPHWithConfig:config]) {
    [self startEntrypointIPHTimers];
    return;
  }

  if ([self canShowLargeEntrypointWithConfig:config]) {
    [self startLargeEntrypointTimers];
    return;
  }
}

// Changes the UI to the large entrypoint variation and starts the timers to
// transition back to the small variation.
- (void)setupAndTransitionToLargeEntrypoint {
  ContextualPanelTabHelper* contextualPanelTabHelper =
      ContextualPanelTabHelper::FromWebState(
          _webStateList->GetActiveWebState());
  base::WeakPtr<ContextualPanelItemConfiguration> config =
      contextualPanelTabHelper->GetFirstCachedConfig();

  if (![self canShowLargeEntrypointWithConfig:config] ||
      ![self.delegate canShowLargeContextualPanelEntrypoint:self]) {
    // Enable fullscreen in case it was disabled when trying to show the IPH.
    [self.delegate enableFullscreen];
    return;
  }

  std::optional<ContextualPanelTabHelper::EntrypointMetricsData>& metricsData =
      [self metricsData];
  if (metricsData) {
    metricsData->largeEntrypointWasShown = true;
  }
  contextualPanelTabHelper->SetLoudMomentEntrypointShown(true);
  [self.delegate disableFullscreen];
  [self.consumer transitionToLargeEntrypoint];

  // Large entrypoint has been displayed so fire loud display metrics here.
  [self logEntrypointLoudDisplayMetrics];

  __weak ContextualPanelEntrypointMediator* weakSelf = self;

  _transitionToDefaultEntrypointTimer = std::make_unique<base::OneShotTimer>();
  _transitionToDefaultEntrypointTimer->Start(
      FROM_HERE,
      base::Seconds(LargeContextualPanelEntrypointDisplayedInSeconds()),
      base::BindOnce(^{
        [weakSelf cleanupAndTransitionToSmallEntrypoint];
      }));
}

// Changes the UI to the small entrypoint variation.
- (void)cleanupAndTransitionToSmallEntrypoint {
  [self.consumer transitionToSmallEntrypoint];
  [self.delegate enableFullscreen];
}

- (void)startLargeEntrypointTimers {
  __weak ContextualPanelEntrypointMediator* weakSelf = self;
  _transitionToEntrypointLoudMomentTimer =
      std::make_unique<base::OneShotTimer>();
  _transitionToEntrypointLoudMomentTimer->Start(
      FROM_HERE, base::Seconds(LargeContextualPanelEntrypointDelayInSeconds()),
      base::BindOnce(^{
        [weakSelf setupAndTransitionToLargeEntrypoint];
      }));
}

- (void)setupAndShowEntrypointIPH {
  ContextualPanelTabHelper* contextualPanelTabHelper =
      ContextualPanelTabHelper::FromWebState(
          _webStateList->GetActiveWebState());
  base::WeakPtr<ContextualPanelItemConfiguration> config =
      contextualPanelTabHelper->GetFirstCachedConfig();

  // Show the large entrypoint instead if the IPH can't be shown.
  if (!config || ![self canShowEntrypointIPHWithConfig:config]) {
    [self setupAndTransitionToLargeEntrypoint];
    return;
  }

  [self.delegate disableFullscreen];
  NSString* text = base::SysUTF8ToNSString(config->entrypoint_message);

  // Try to show the entrypoint's IPH and capture the result.
  BOOL success = [self attemptShowingEntrypointIPHWithText:text config:config];

  // Show the large entrypoint if showing the IPH was not successful.
  if (!success) {
    [self setupAndTransitionToLargeEntrypoint];
    return;
  }

  [self.consumer setEntrypointColored:YES];

  std::optional<ContextualPanelTabHelper::EntrypointMetricsData>& metricsData =
      [self metricsData];
  if (metricsData) {
    metricsData->iphWasShown = true;
  }

  contextualPanelTabHelper->SetLoudMomentEntrypointShown(true);

  // IPH was shown, so fire loud display metrics here.
  [self logEntrypointLoudDisplayMetrics];

  __weak ContextualPanelEntrypointMediator* weakSelf = self;
  _transitionToDefaultEntrypointTimer = std::make_unique<base::OneShotTimer>();
  _transitionToDefaultEntrypointTimer->Start(
      FROM_HERE,
      base::Seconds(LargeContextualPanelEntrypointDisplayedInSeconds()),
      base::BindOnce(^{
        [weakSelf dismissEntrypointIPHAnimated:YES];
        [weakSelf.delegate enableFullscreen];
      }));
}

- (void)startEntrypointIPHTimers {
  __weak ContextualPanelEntrypointMediator* weakSelf = self;
  _transitionToEntrypointLoudMomentTimer =
      std::make_unique<base::OneShotTimer>();
  _transitionToEntrypointLoudMomentTimer->Start(
      FROM_HERE, base::Seconds(LargeContextualPanelEntrypointDelayInSeconds()),
      base::BindOnce(^{
        [weakSelf setupAndShowEntrypointIPH];
      }));
}

// Tries to show the entrypoint's IPH with the config text, and returns whether
// it was shown successfully. Also passes the current config's entrypoint FET
// feature, which controls whether the IPH can be shown.
- (BOOL)attemptShowingEntrypointIPHWithText:(NSString*)text
                                     config:
                                         (base::WeakPtr<
                                             ContextualPanelItemConfiguration>)
                                             config {
  BOOL isBottomOmnibox = [self.delegate isBottomOmniboxActive];

  CGPoint anchorPoint =
      [self.delegate helpAnchorUsingBottomOmnibox:isBottomOmnibox];

  BOOL shown = [_entrypointHelpHandler
      maybeShowContextualPanelEntrypointIPHWithConfig:config
                                          anchorPoint:anchorPoint
                                      isBottomOmnibox:isBottomOmnibox];

  return shown;
}

- (void)dismissEntrypointIPHAnimated:(BOOL)animated {
  [_entrypointHelpHandler dismissContextualPanelEntrypointIPHAnimated:animated];
  [self.consumer setEntrypointColored:NO];
}

- (BOOL)canShowLargeEntrypointWithConfig:
    (base::WeakPtr<ContextualPanelItemConfiguration>)config {
  return [self canShowLoudEntrypointMoment] && config &&
         config->CanShowLargeEntrypoint();
}

- (BOOL)canShowEntrypointIPHWithConfig:
    (base::WeakPtr<ContextualPanelItemConfiguration>)config {
  return [self canShowLoudEntrypointMoment] && config &&
         config->CanShowEntrypointIPH() &&
         _engagementTracker->WouldTriggerHelpUI(*config->iph_feature);
}

- (BOOL)canShowLoudEntrypointMoment {
  ContextualPanelTabHelper* contextualPanelTabHelper =
      ContextualPanelTabHelper::FromWebState(
          _webStateList->GetActiveWebState());

  return !_infobarBadgesCurrentlyShown &&
         !contextualPanelTabHelper->IsContextualPanelCurrentlyOpened() &&
         !contextualPanelTabHelper->WasLoudMomentEntrypointShown() &&
         [self.delegate canShowLargeContextualPanelEntrypoint:self];
}

- (std::optional<ContextualPanelTabHelper::EntrypointMetricsData>&)metricsData {
  ContextualPanelTabHelper* contextualPanelTabHelper =
      ContextualPanelTabHelper::FromWebState(
          _webStateList->GetActiveWebState());
  return contextualPanelTabHelper->GetMetricsData();
}

#pragma mark - Metrics helpers

// Logs metrics that should be fired when the entrypoint is displayed for the
// first time.
- (void)logEntrypointFirstDisplayMetrics {
  std::optional<ContextualPanelTabHelper::EntrypointMetricsData>&
      optionalMetricsData = [self metricsData];
  if (!optionalMetricsData ||
      optionalMetricsData->entrypoint_regular_display_metrics_fired) {
    return;
  }

  ContextualPanelTabHelper::EntrypointMetricsData& metricsData =
      optionalMetricsData.value();

  metricsData.entrypoint_regular_display_metrics_fired = true;

  base::UmaHistogramEnumeration("IOS.ContextualPanel.EntrypointDisplayed",
                                metricsData.entrypoint_item_type);

  std::string entrypointTypeHistogramName =
      "IOS.ContextualPanel.Entrypoint.Regular";
  base::UmaHistogramEnumeration(entrypointTypeHistogramName,
                                EntrypointInteractionType::Displayed);

  std::string blockTypeEntrypointTypeHistogramName = base::StringPrintf(
      "IOS.ContextualPanel.Entrypoint.Regular.%s",
      StringForItemType(metricsData.entrypoint_item_type).c_str());
  base::UmaHistogramEnumeration(blockTypeEntrypointTypeHistogramName,
                                EntrypointInteractionType::Displayed);
}

// Log any metrics that should be logged when a loud entrypoint is displayed.
- (void)logEntrypointLoudDisplayMetrics {
  std::optional<ContextualPanelTabHelper::EntrypointMetricsData>&
      optionalMetricsData = [self metricsData];
  if (!optionalMetricsData ||
      optionalMetricsData->entrypoint_loud_display_metrics_fired) {
    return;
  }

  ContextualPanelTabHelper::EntrypointMetricsData& metricsData =
      optionalMetricsData.value();

  std::string entrypointTypeString =
      [self loudEntrypointTypeStringForMetrics:metricsData];

  // Either the IPH or Large entrypoint should have been shown by now.
  if (entrypointTypeString == "") {
    return;
  }

  metricsData.entrypoint_loud_display_metrics_fired = true;

  std::string entrypointTypeHistogramName = base::StringPrintf(
      "IOS.ContextualPanel.Entrypoint.%s", entrypointTypeString.c_str());
  base::UmaHistogramEnumeration(entrypointTypeHistogramName,
                                EntrypointInteractionType::Displayed);

  std::string blockTypeEntrypointTypeHistogramName = base::StringPrintf(
      "IOS.ContextualPanel.Entrypoint.%s.%s", entrypointTypeString.c_str(),
      StringForItemType(metricsData.entrypoint_item_type).c_str());
  base::UmaHistogramEnumeration(blockTypeEntrypointTypeHistogramName,
                                EntrypointInteractionType::Displayed);
}

// Logs any metrics fired the first time a given entrypoint is opened via
// tapping.
- (void)logEntrypointFirstTapMetrics {
  std::optional<ContextualPanelTabHelper::EntrypointMetricsData>&
      optionalMetricsData = [self metricsData];
  if (!optionalMetricsData ||
      optionalMetricsData->entrypoint_tap_metrics_fired) {
    return;
  }

  ContextualPanelTabHelper::EntrypointMetricsData& metricsData =
      optionalMetricsData.value();

  base::TimeDelta visibleTimeThisIteration =
      (metricsData.appearance_time)
          ? (base::Time::Now() - metricsData.appearance_time.value())
          : base::Seconds(0);
  base::TimeDelta visibleTime =
      metricsData.time_visible + visibleTimeThisIteration;

  metricsData.entrypoint_tap_metrics_fired = true;

  // Fire metrics saying the entrypoint was tapped.
  base::UmaHistogramEnumeration("IOS.ContextualPanel.EntrypointTapped",
                                metricsData.entrypoint_item_type);

  // Always fire the regular tap events because the regular display events are
  // also always fired.
  base::UmaHistogramEnumeration("IOS.ContextualPanel.Entrypoint.Regular",
                                EntrypointInteractionType::Tapped);

  std::string blockTypeEntrypointTypeHistogramName = base::StringPrintf(
      "IOS.ContextualPanel.Entrypoint.Regular.%s",
      StringForItemType(metricsData.entrypoint_item_type).c_str());
  base::UmaHistogramEnumeration(blockTypeEntrypointTypeHistogramName,
                                EntrypointInteractionType::Tapped);

  // Fire metrics for the time to tap.
  base::UmaHistogramTimes(
      "IOS.ContextualPanel.Entrypoint.Regular.UptimeBeforeTap", visibleTime);

  std::string blockTypeEntrypointTypeUptimeHistogramName = base::StringPrintf(
      "IOS.ContextualPanel.Entrypoint.Regular.%s.UptimeBeforeTap",
      StringForItemType(metricsData.entrypoint_item_type).c_str());
  base::UmaHistogramTimes(blockTypeEntrypointTypeUptimeHistogramName,
                          visibleTime);

  // Additionally fire metrics for the loud entrypoint variant, if one was
  // shown.
  std::string entrypointTypeString =
      [self loudEntrypointTypeStringForMetrics:metricsData];
  if (entrypointTypeString == "") {
    return;
  }
  std::string loudEntrypointTypeHistogramName = base::StringPrintf(
      "IOS.ContextualPanel.Entrypoint.%s", entrypointTypeString.c_str());
  base::UmaHistogramEnumeration(loudEntrypointTypeHistogramName,
                                EntrypointInteractionType::Tapped);

  std::string blockTypeLoudEntrypointTypeHistogramName = base::StringPrintf(
      "IOS.ContextualPanel.Entrypoint.%s.%s", entrypointTypeString.c_str(),
      StringForItemType(metricsData.entrypoint_item_type).c_str());
  base::UmaHistogramEnumeration(blockTypeLoudEntrypointTypeHistogramName,
                                EntrypointInteractionType::Tapped);

  // Time to tap metrics:
  std::string loudEntrypointTypeUptimeHistogramName =
      base::StringPrintf("IOS.ContextualPanel.Entrypoint.%s.UptimeBeforeTap",
                         entrypointTypeString.c_str());
  base::UmaHistogramTimes(loudEntrypointTypeUptimeHistogramName, visibleTime);

  std::string blockTypeLoudEntrypointTypeUptimeHistogramName =
      base::StringPrintf(
          "IOS.ContextualPanel.Entrypoint.%s.%s.UptimeBeforeTap",
          entrypointTypeString.c_str(),
          StringForItemType(metricsData.entrypoint_item_type).c_str());
  base::UmaHistogramTimes(blockTypeLoudEntrypointTypeUptimeHistogramName,
                          visibleTime);
}

// Which type of loud entrypoint was displayed to be used in metric names.
- (std::string)loudEntrypointTypeStringForMetrics:
    (ContextualPanelTabHelper::EntrypointMetricsData&)metricsData {
  if (metricsData.iphWasShown) {
    return "IPH";
  } else if (metricsData.largeEntrypointWasShown) {
    return "Large";
  } else {
    return "";
  }
}

@end