// Copyright 2016 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/intents/model/user_activity_browser_agent.h"

#import <CoreSpotlight/CoreSpotlight.h>
#import <Intents/Intents.h>
#import <UIKit/UIKit.h>

#import "base/apple/foundation_util.h"
#import "base/debug/dump_without_crashing.h"
#import "base/functional/callback_helpers.h"
#import "base/ios/block_types.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/bind_post_task.h"
#import "components/crash/core/common/crash_key.h"
#import "components/handoff/handoff_utility.h"
#import "components/prefs/pref_service.h"
#import "components/search_engines/template_url_service.h"
#import "ios/chrome/app/application_mode.h"
#import "ios/chrome/app/profile/profile_init_stage.h"
#import "ios/chrome/app/profile/profile_state.h"
#import "ios/chrome/app/spotlight/actions_spotlight_manager.h"
#import "ios/chrome/app/spotlight/spotlight_util.h"
#import "ios/chrome/app/startup/app_launch_metrics.h"
#import "ios/chrome/app/unexpected_mode_toast_util.h"
#import "ios/chrome/browser/credential_exchange/model/credential_import_manager_swift.h"
#import "ios/chrome/browser/credential_provider/model/features.h"
#import "ios/chrome/browser/intelligence/features/features.h"
#import "ios/chrome/browser/intents/model/intents_constants.h"
#import "ios/chrome/browser/lens/ui_bundled/lens_availability.h"
#import "ios/chrome/browser/lens/ui_bundled/lens_entrypoint.h"
#import "ios/chrome/browser/policy/model/policy_util.h"
#import "ios/chrome/browser/search_engines/model/search_engines_util.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/shared/coordinator/scene/connection_information.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_controller.h"
#import "ios/chrome/browser/shared/coordinator/scene/scene_state.h"
#import "ios/chrome/browser/shared/model/browser/browser.h"
#import "ios/chrome/browser/shared/model/browser/browser_list.h"
#import "ios/chrome/browser/shared/model/browser/browser_list_factory.h"
#import "ios/chrome/browser/shared/model/browser/browser_provider_interface.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/model/web_state_list/web_state_list.h"
#import "ios/chrome/browser/shared/public/commands/command_dispatcher.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/url_loading/model/image_search_param_generator.h"
#import "ios/chrome/browser/url_loading/model/url_loading_params.h"
#import "ios/chrome/common/intents/AddBookmarkToChromeIntent.h"
#import "ios/chrome/common/intents/AddReadingListItemToChromeIntent.h"
#import "ios/chrome/common/intents/OpenInChromeIncognitoIntent.h"
#import "ios/chrome/common/intents/OpenInChromeIntent.h"
#import "ios/chrome/common/intents/SearchInChromeIntent.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ios/components/webui/web_ui_url_constants.h"
#import "net/base/apple/url_conversions.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/page_transition_types.h"

using base::UserMetricsAction;

namespace {

std::vector<GURL> CreateGURLVectorFromIntentURLs(NSArray<NSURL*>* intent_urls) {
  std::vector<GURL> urls;
  for (NSURL* url in intent_urls) {
    GURL gurl = net::GURLWithNSURL(url);
    if (gurl.is_valid() && gurl.SchemeIsHTTPOrHTTPS()) {
      urls.push_back(gurl);
    }
  }
  return urls;
}

// Returns the ProfileState associated to `browser` is ready.
bool IsProfileStateReady(Browser* browser) {
  return browser->GetSceneState().profileState.initStage ==
         ProfileInitStage::kFinal;
}

}  // namespace

UserActivityBrowserAgent::UserActivityBrowserAgent(Browser* browser)
    : BrowserUserData(browser),
      profile_(browser->GetProfile()) {
  SceneState* scene_state = browser_->GetSceneState();
  connection_information_ = scene_state.controller;
  tab_opener_ = scene_state.controller;
  startup_information_ = scene_state.profileState.startupInformation;
}

UserActivityBrowserAgent::~UserActivityBrowserAgent() {}

#pragma mark - Public methods.

// LINT.IfChange
// TODO(crbug.com/462018636): This code will be soon migrated to
// task_request_user_activity.mm, so any change should be reflected also there.
// Contact fedegermi for additional information or support.
BOOL UserActivityBrowserAgent::ContinueUserActivity(
    NSUserActivity* user_activity,
    BOOL application_is_active) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  NSURL* webpage_url = user_activity.webpageURL;

  // Credential exchange activity should only be handled in iOS 26.
  BOOL isCredentialExchangeActivity = NO;
  if (@available(iOS 26, *)) {
    isCredentialExchangeActivity = [user_activity.activityType
        isEqualToString:[CredentialImportManager credentialExchangeActivity]];
  }

  if ([user_activity.activityType
          isEqualToString:handoff::kChromeHandoffActivityType] ||
      [user_activity.activityType
          isEqualToString:NSUserActivityTypeBrowsingWeb]) {
    // App was launched by iOS as a result of Handoff.
    base::UmaHistogramEnumeration(kAppLaunchSource, AppLaunchSource::HANDOFF);
    if (![connection_information_ startupParameters]) {
      AppStartupParameters* startup_params = [[AppStartupParameters alloc]
           initWithExternalURL:net::GURLWithNSURL(webpage_url)
                   completeURL:net::GURLWithNSURL(webpage_url)
               applicationMode:ApplicationModeForTabOpening::NORMAL
          forceApplicationMode:NO];
      [connection_information_ setStartupParameters:startup_params];
    }
  } else if (spotlight::IsSpotlightAvailable() &&
             [user_activity.activityType
                 isEqualToString:CSSearchableItemActionType]) {
    // App was launched by iOS as the result of a tap on a Spotlight Search
    // result.
    NSString* item_id = [user_activity.userInfo
        objectForKey:CSSearchableItemActivityIdentifier];
    spotlight::Domain domain = spotlight::SpotlightDomainFromString(item_id);
    base::UmaHistogramEnumeration("IOS.Spotlight.Origin", domain);

    base::UmaHistogramEnumeration(kAppLaunchSource,
                                  AppLaunchSource::SPOTLIGHT_CHROME);
    if (!item_id || domain == spotlight::DOMAIN_UNKNOWN) {
      return NO;
    }
    if (domain == spotlight::DOMAIN_ACTIONS) {
      webpage_url =
          [NSURL URLWithString:base::SysUTF8ToNSString(kChromeUINewTabURL)];
      AppStartupParameters* startup_params = [[AppStartupParameters alloc]
           initWithExternalURL:GURL(kChromeUINewTabURL)
                   completeURL:GURL(kChromeUINewTabURL)
               applicationMode:ApplicationModeForTabOpening::UNDETERMINED
          forceApplicationMode:NO];
      BOOL startup_params_set =
          spotlight::SetStartupParametersForSpotlightAction(item_id,
                                                            startup_params);
      if (!startup_params_set) {
        return NO;
      }
      [connection_information_ setStartupParameters:startup_params];
    } else if (!webpage_url) {
      spotlight::GetURLForSpotlightItemID(
          item_id,
          base::CallbackToBlock(base::BindPostTask(
              base::SequencedTaskRunner::GetCurrentDefault(),
              base::BindOnce(
                  &UserActivityBrowserAgent::OverloadContinueUserActivityURL,
                  weak_ptr_factory_.GetWeakPtr(),
                  domain == spotlight::DOMAIN_OPEN_TABS))));
      return YES;
    }
  } else if ([user_activity.activityType
                 isEqualToString:kSiriShortcutSearchInChrome]) {
    RecordMetricsForSiriShortcut(IntentType::kSearchInChrome);
    base::RecordAction(UserMetricsAction("IOSLaunchedBySearchInChromeIntent"));

    AppStartupParameters* startup_params;

    if (IsIncognitoModeForced(profile_->GetPrefs())) {
      // Set incognito mode to yes if only incognito mode is available.
      startup_params = [[AppStartupParameters alloc]
           initWithExternalURL:GURL(kChromeUINewTabURL)
                   completeURL:GURL(kChromeUINewTabURL)
               applicationMode:ApplicationModeForTabOpening::INCOGNITO
          forceApplicationMode:YES];
    } else if (IsIncognitoModeDisabled(profile_->GetPrefs())) {
      startup_params = [[AppStartupParameters alloc]
           initWithExternalURL:GURL(kChromeUINewTabURL)
                   completeURL:GURL(kChromeUINewTabURL)
               applicationMode:ApplicationModeForTabOpening::NORMAL
          forceApplicationMode:YES];
    } else {
      startup_params = [[AppStartupParameters alloc]
           initWithExternalURL:GURL(kChromeUINewTabURL)
                   completeURL:GURL(kChromeUINewTabURL)
               applicationMode:ApplicationModeForTabOpening::NORMAL
          forceApplicationMode:NO];
    }

    SearchInChromeIntent* intent =
        base::apple::ObjCCastStrict<SearchInChromeIntent>(
            user_activity.interaction.intent);
    if (!intent) {
      return NO;
    }

    id search_phrase = [intent valueForKey:@"searchPhrase"];

    if ([search_phrase isKindOfClass:[NSString class]] &&
        [search_phrase
            stringByTrimmingCharactersInSet:[NSCharacterSet
                                                whitespaceCharacterSet]]
                .length > 0) {
      startup_params.textQuery = search_phrase;
    } else {
      startup_params.postOpeningAction = FOCUS_OMNIBOX;
    }

    [connection_information_ setStartupParameters:startup_params];
    webpage_url =
        [NSURL URLWithString:base::SysUTF8ToNSString(kChromeUINewTabURL)];

  } else if ([user_activity.activityType
                 isEqualToString:kSiriShortcutOpenInChrome]) {
    RecordMetricsForSiriShortcut(IntentType::kOpenInChrome);
    base::RecordAction(UserMetricsAction("IOSLaunchedByOpenInChromeIntent"));

    OpenInChromeIntent* intent =
        base::apple::ObjCCastStrict<OpenInChromeIntent>(
            user_activity.interaction.intent);

    if (!intent.url) {
      return NO;
    }

    std::vector<GURL> urls;

    if ([intent.url isKindOfClass:[NSURL class]]) {
      // Old intent version where `url` is of type NSURL rather than an array.
      GURL webpage_GURL(
          net::GURLWithNSURL(base::apple::ObjCCastStrict<NSURL>(intent.url)));
      if (!webpage_GURL.is_valid()) {
        return NO;
      }
      urls.push_back(webpage_GURL);
    } else if ([intent.url isKindOfClass:[NSArray class]] &&
               intent.url.count > 0) {
      urls = CreateGURLVectorFromIntentURLs(intent.url);
    } else {
      // Unknown or invalid intent object.
      return NO;
    }

    OpenRequestedURLs(urls, application_is_active, NO);
    return YES;

  } else if ([user_activity.activityType
                 isEqualToString:kSiriShortcutOpenInIncognito]) {
    RecordMetricsForSiriShortcut(IntentType::kOpenInIncognito);
    base::RecordAction(UserMetricsAction("IOSLaunchedByOpenInIncognitoIntent"));

    OpenInChromeIncognitoIntent* intent =
        base::apple::ObjCCastStrict<OpenInChromeIncognitoIntent>(
            user_activity.interaction.intent);

    if (!intent.url || intent.url.count == 0) {
      return NO;
    }

    std::vector<GURL> urls = CreateGURLVectorFromIntentURLs(intent.url);

    OpenRequestedURLs(urls, application_is_active, YES);
    return YES;

  } else if ([user_activity.activityType
                 isEqualToString:kSiriShortcutAddBookmarkToChrome]) {
    base::UmaHistogramEnumeration(kAppLaunchSource,
                                  AppLaunchSource::SIRI_SHORTCUT);
    base::RecordAction(
        UserMetricsAction("IOSLaunchedByAddBookmarkToChromeIntent"));

    AddBookmarkToChromeIntent* intent =
        base::apple::ObjCCastStrict<AddBookmarkToChromeIntent>(
            user_activity.interaction.intent);

    if (!intent || !intent.url || intent.url.count == 0) {
      return NO;
    }

    AppStartupParameters* startup_params =
        StartupParametersForOpeningNewTab(ADD_BOOKMARKS);
    startup_params.inputURLs = intent.url;
    [connection_information_ setStartupParameters:startup_params];
  } else if ([user_activity.activityType
                 isEqualToString:kSiriShortcutAddReadingListItemToChrome]) {
    base::UmaHistogramEnumeration(kAppLaunchSource,
                                  AppLaunchSource::SIRI_SHORTCUT);
    base::RecordAction(
        UserMetricsAction("IOSLaunchedByAddReadingListItemToChromeIntent"));

    AddReadingListItemToChromeIntent* intent =
        base::apple::ObjCCastStrict<AddReadingListItemToChromeIntent>(
            user_activity.interaction.intent);

    if (!intent || !intent.url || intent.url.count == 0) {
      return NO;
    }

    AppStartupParameters* startup_params =
        StartupParametersForOpeningNewTab(ADD_READING_LIST_ITEMS);
    startup_params.inputURLs = intent.url;
    [connection_information_ setStartupParameters:startup_params];
  } else if ([user_activity.activityType isEqualToString:kSiriOpenLatestTab]) {
    RecordMetricsForSiriShortcut(IntentType::kOpenLatestTab);
    AppStartupParameters* startup_params = [[AppStartupParameters alloc]
         initWithExternalURL:GURL()
                 completeURL:GURL()
             applicationMode:ApplicationModeForTabOpening::NORMAL
        forceApplicationMode:NO];

    startup_params.postOpeningAction = OPEN_LATEST_TAB;
    [connection_information_ setStartupParameters:startup_params];
  } else if ([user_activity.activityType
                 isEqualToString:kSiriOpenReadingList]) {
    RecordMetricsForSiriShortcut(IntentType::kOpenReadingList);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(
                                 OPEN_READING_LIST)];
  } else if ([user_activity.activityType isEqualToString:kSiriOpenBookmarks]) {
    RecordMetricsForSiriShortcut(IntentType::kOpenBookmarks);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(OPEN_BOOKMARKS)];
  } else if ([user_activity.activityType isEqualToString:kSiriOpenRecentTabs]) {
    RecordMetricsForSiriShortcut(IntentType::kOpenRecentTabs);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(
                                 OPEN_RECENT_TABS)];
  } else if ([user_activity.activityType isEqualToString:kSiriOpenTabGrid]) {
    RecordMetricsForSiriShortcut(IntentType::kOpenTabGrid);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(OPEN_TAB_GRID)];
  } else if ([user_activity.activityType isEqualToString:kSiriVoiceSearch]) {
    RecordMetricsForSiriShortcut(IntentType::kOpenVoiceSearch);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(
                                 START_VOICE_SEARCH)];
  } else if ([user_activity.activityType isEqualToString:kSiriOpenNewTab]) {
    RecordMetricsForSiriShortcut(IntentType::kOpenNewTab);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(NO_ACTION)];
  } else if ([user_activity.activityType isEqualToString:kSiriPlayDinoGame]) {
    RecordMetricsForSiriShortcut(IntentType::kPlayDinoGame);
    webpage_url =
        [NSURL URLWithString:base::SysUTF8ToNSString(kChromeDinoGameURL)];
    return ContinueUserActivityURL(webpage_url, application_is_active, NO, YES);
  } else if ([user_activity.activityType
                 isEqualToString:kSiriSetChromeDefaultBrowser]) {
    RecordMetricsForSiriShortcut(IntentType::kSetDefaultBrowser);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(
                                 SET_CHROME_DEFAULT_BROWSER)];
  } else if ([user_activity.activityType isEqualToString:kSiriViewHistory]) {
    RecordMetricsForSiriShortcut(IntentType::kViewHistory);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(VIEW_HISTORY)];
  } else if ([user_activity.activityType
                 isEqualToString:kSiriOpenNewIncognitoTab]) {
    RecordMetricsForSiriShortcut(IntentType::kOpenNewIncognitoTab);
    AppStartupParameters* startup_params = [[AppStartupParameters alloc]
         initWithExternalURL:GURL(kChromeUINewTabURL)
                 completeURL:GURL(kChromeUINewTabURL)
             applicationMode:ApplicationModeForTabOpening::INCOGNITO
        forceApplicationMode:NO];
    [connection_information_ setStartupParameters:startup_params];
  } else if ([user_activity.activityType
                 isEqualToString:kSiriManagePaymentMethods]) {
    RecordMetricsForSiriShortcut(IntentType::kManagePaymentMethods);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(
                                 OPEN_PAYMENT_METHODS)];
  } else if ([user_activity.activityType isEqualToString:kSiriRunSafetyCheck]) {
    RecordMetricsForSiriShortcut(IntentType::kRunSafetyCheck);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(
                                 RUN_SAFETY_CHECK)];
  } else if ([user_activity.activityType
                 isEqualToString:kSiriManagePasswords]) {
    RecordMetricsForSiriShortcut(IntentType::kManagePasswords);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(
                                 MANAGE_PASSWORDS)];
  } else if ([user_activity.activityType isEqualToString:kSiriManageSettings]) {
    RecordMetricsForSiriShortcut(IntentType::kManageSettings);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(
                                 MANAGE_SETTINGS)];
  } else if ([user_activity.activityType
                 isEqualToString:kSiriOpenLensFromIntents]) {
    RecordMetricsForSiriShortcut(IntentType::kStartLens);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(
                                 START_LENS_FROM_INTENTS)];
  } else if ([user_activity.activityType
                 isEqualToString:kSiriClearBrowsingData]) {
    RecordMetricsForSiriShortcut(IntentType::kClearBrowsingData);
    [connection_information_
        setStartupParameters:StartupParametersForOpeningNewTab(
                                 OPEN_CLEAR_BROWSING_DATA_DIALOG)];
  } else if (isCredentialExchangeActivity) {
    id UUID;
    if (@available(iOS 26, *)) {
      UUID = [user_activity.userInfo
          objectForKey:[CredentialImportManager credentialImportToken]];
    }
    if (![UUID isKindOfClass:[NSUUID class]]) {
      return NO;
    }

    AppStartupParameters* startup_params = [[AppStartupParameters alloc]
         initWithExternalURL:GURL()
                 completeURL:GURL()
             applicationMode:ApplicationModeForTabOpening::NORMAL
        forceApplicationMode:NO];
    startup_params.postOpeningAction = CREDENTIAL_EXCHANGE_IMPORT;
    startup_params.credentialExchangeImportUUID = (NSUUID*)UUID;
    [connection_information_ setStartupParameters:startup_params];
  } else {
    // Do nothing for unknown activity type.
    return NO;
  }
  return ContinueUserActivityURL(webpage_url, application_is_active, NO, NO);
}
// LINT.ThenChange(//ios/chrome/app/task_request_user_activity.mm)

// LINT.IfChange
// TODO(crbug.com/462018636): This code will be soon migrated to
// task_request_shortcut_item.mm, so any change should be reflected also there.
// Contact fedegermi for additional information or support.
BOOL UserActivityBrowserAgent::Handle3DTouchApplicationShortcuts(
    UIApplicationShortcutItem* shortcut_item) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(!IsEnableNewStartupFlowEnabled());
  const BOOL handled_shortcut_item = HandleShortcutItem(shortcut_item);
  const BOOL is_active = [[UIApplication sharedApplication] applicationState] ==
                         UIApplicationStateActive;
  if (handled_shortcut_item && is_active) {
    RouteToCorrectTab();
  }
  return handled_shortcut_item;
}
// LINT.ThenChange(//ios/chrome/app/task_request_shortcut_item.mm)

void UserActivityBrowserAgent::RouteToCorrectTab() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  // Do not load the external URL if the user has not accepted the terms of
  // service. This corresponds to the case when the user installed Chrome,
  // has never launched it and attempts to open an external URL in Chrome.
  if (!IsProfileStateReady(browser_)) {
    return;
  }
  // Do not handle the parameters that are/were already handled.
  if (connection_information_.startupParametersAreBeingHandled) {
    return;
  }

  connection_information_.startupParametersAreBeingHandled = YES;

  if (!connection_information_.startupParameters.URLs.empty() &&
      !connection_information_.startupParameters.isUnexpectedMode) {
    OpenMultipleTabs();
    return;
  }

  GURL external_url = connection_information_.startupParameters.externalURL;

  // If the user intent to open a url in a unavailable mode, don't fulfill the
  // request.
  if (external_url != kChromeUINewTabURL &&
      connection_information_.startupParameters.isUnexpectedMode) {
    return;
  }
  base::OnceCallback<void(ApplicationModeForTabOpening)> completion =
      base::BindOnce(&UserActivityBrowserAgent::HandleRouteToCorrectTab,
                     weak_ptr_factory_.GetWeakPtr());
  [connection_information_.startupParameters
      fetchAppSwitcherParamsWithBlock:base::CallbackToBlock(
                                          std::move(completion))];
}

void UserActivityBrowserAgent::
    ShowToastWhenOpenExternalIntentInUnexpectedMode() {
  PrefService* prefs = profile_->GetPrefs();
  BOOL force_incognito = IsIncognitoModeForced(prefs);
  ApplicationModeForTabOpening target_mode =
      force_incognito ? ApplicationModeForTabOpening::INCOGNITO
                      : ApplicationModeForTabOpening::NORMAL;
  ShowToastWhenOpenInUnexpectedMode(browser_->GetSceneState(), target_mode);
}

#pragma mark - Internal methods.

void UserActivityBrowserAgent::RecordMetricsForSiriShortcut(
    IntentType intent_type) {
  base::UmaHistogramEnumeration(kAppLaunchSource,
                                AppLaunchSource::SIRI_SHORTCUT);
  base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
                                intent_type);
}

AppStartupParameters*
UserActivityBrowserAgent::StartupParametersForOpeningNewTab(
    TabOpeningPostOpeningAction action) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  AppStartupParameters* startup_params = [[AppStartupParameters alloc]
       initWithExternalURL:GURL(kChromeUINewTabURL)
               completeURL:GURL(kChromeUINewTabURL)
           applicationMode:ApplicationModeForTabOpening::NORMAL
      forceApplicationMode:NO];

  startup_params.postOpeningAction = action;
  return startup_params;
}

// LINT.IfChange
// TODO(crbug.com/462018636): This code will be soon migrated to
// task_request_shortcut_item.mm, so any change should be reflected also there.
// Contact fedegermi for additional information or support.
BOOL UserActivityBrowserAgent::HandleShortcutItem(
    UIApplicationShortcutItem* shortcut_item) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  CHECK(!IsEnableNewStartupFlowEnabled());
  if (!IsProfileStateReady(browser_)) {
    return NO;
  }
  base::UmaHistogramEnumeration(kAppLaunchSource,
                                AppLaunchSource::LONG_PRESS_ON_APP_ICON);

  // Lens entry points should not open an extra new tab page.
  GURL startup_url =
      [shortcut_item.type isEqualToString:kShortcutLensFromAppIconLongPress]
          ? GURL()
          : GURL(kChromeUINewTabURL);

  AppStartupParameters* startup_params = [[AppStartupParameters alloc]
       initWithExternalURL:startup_url
               completeURL:startup_url
           applicationMode:ApplicationModeForTabOpening::NORMAL
      forceApplicationMode:NO];

  if ([shortcut_item.type isEqualToString:kShortcutNewSearch]) {
    base::RecordAction(
        UserMetricsAction("ApplicationShortcut.NewSearchPressed"));
    startup_params.postOpeningAction = FOCUS_OMNIBOX;
    connection_information_.startupParameters = startup_params;
    return YES;

  } else if ([shortcut_item.type isEqualToString:kShortcutNewIncognitoSearch]) {
    base::RecordAction(
        UserMetricsAction("ApplicationShortcut.NewIncognitoSearchPressed"));
    [startup_params setApplicationMode:ApplicationModeForTabOpening::INCOGNITO
                  forceApplicationMode:NO];
    startup_params.postOpeningAction = FOCUS_OMNIBOX;
    connection_information_.startupParameters = startup_params;
    return YES;

  } else if ([shortcut_item.type isEqualToString:kShortcutVoiceSearch]) {
    base::RecordAction(
        UserMetricsAction("ApplicationShortcut.VoiceSearchPressed"));
    startup_params.postOpeningAction = START_VOICE_SEARCH;
    connection_information_.startupParameters = startup_params;
    return YES;

  } else if ([shortcut_item.type isEqualToString:kShortcutQRScanner]) {
    base::RecordAction(
        UserMetricsAction("ApplicationShortcut.ScanQRCodePressed"));
    startup_params.postOpeningAction = START_QR_CODE_SCANNER;
    connection_information_.startupParameters = startup_params;
    return YES;
  } else if ([shortcut_item.type
                 isEqualToString:kShortcutLensFromAppIconLongPress]) {
    base::RecordAction(UserMetricsAction(
        "ApplicationShortcut.LensPressedFromAppIconLongPress"));
    startup_params.postOpeningAction = START_LENS_FROM_APP_ICON_LONG_PRESS;
    connection_information_.startupParameters = startup_params;
    return YES;
  } else if ([shortcut_item.type
                 isEqualToString:kShortcutChangeWidgetToAppIcon]) {
    // This intent is already handled by the OS, the default action for this
    // intent is to open the app, no additional handling is needed. Check
    // crbug.com/384806920 for additional info.
    return NO;
  }

  // Use 32 as the maximum length of the reported value for this key (31
  // characters + '\0'). Expected values are UIApplicationShortcutItemType
  // entries in Info.plist.
  static crash_reporter::CrashKeyString<32> key("shortcut-item");
  crash_reporter::ScopedCrashKeyString crash_key(
      &key, base::SysNSStringToUTF8(shortcut_item.type));
  base::debug::DumpWithoutCrashing();
  return NO;
}
// LINT.ThenChange(//ios/chrome/app/task_request_shortcut_item.mm)

void UserActivityBrowserAgent::OpenRequestedURLs(
    const std::vector<GURL>& webpage_urls,
    BOOL application_is_active,
    BOOL incognito) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  ApplicationModeForTabOpening application_mode;
  if (incognito) {
    application_mode = ApplicationModeForTabOpening::INCOGNITO;
  } else {
    application_mode = ApplicationModeForTabOpening::NORMAL;
  }
  AppStartupParameters* startup_params =
      [[AppStartupParameters alloc] initWithURLs:webpage_urls
                                 applicationMode:application_mode
                            forceApplicationMode:NO];
  [connection_information_ setStartupParameters:startup_params];

  if (application_is_active && IsProfileStateReady(browser_)) {
    // The app is already active so the applicationDidBecomeActive: method will
    // never be called. Open the requested URLs immediately.
    OpenMultipleTabs();
    return;
  }

  // Don't record the first action as a user action, since it will not be
  // initiated by the user.
  [startup_information_ resetFirstUserActionRecorder];

  if (![connection_information_ startupParameters]) {
    [startup_params
          setApplicationMode:ApplicationModeForTabOpening::UNDETERMINED
        forceApplicationMode:NO];
    if (incognito) {
      [startup_params setApplicationMode:ApplicationModeForTabOpening::INCOGNITO
                    forceApplicationMode:NO];
    }
    [connection_information_ setStartupParameters:startup_params];
  }
}

// LINT.IfChange
// TODO(crbug.com/462018636): This code will be soon migrated to
// task_request_user_activity.mm, so any change should be reflected also there.
// Contact fedegermi for additional information or support.
BOOL UserActivityBrowserAgent::ContinueUserActivityURL(
    NSURL* webpage_url,
    BOOL application_is_active,
    BOOL open_existing_tab,
    BOOL opened_via_siri_shortcut) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (!webpage_url) {
    return NO;
  }

  GURL webpage_GURL(net::GURLWithNSURL(webpage_url));
  if (!webpage_GURL.is_valid()) {
    return NO;
  }

  if (application_is_active && IsProfileStateReady(browser_)) {
    // The app is already active so the applicationDidBecomeActive: method will
    // never be called. Open the requested URL immediately.
    base::OnceCallback<void(ApplicationModeForTabOpening)> completion =
        base::BindOnce(&UserActivityBrowserAgent::HandleUrlOpening,
                       weak_ptr_factory_.GetWeakPtr(), webpage_GURL);
    [connection_information_.startupParameters
        fetchAppSwitcherParamsWithBlock:base::CallbackToBlock(
                                            std::move(completion))];
    return YES;
  }

  // Don't record the first action as a user action, since it will not be
  // initiated by the user.
  [startup_information_ resetFirstUserActionRecorder];

  if (![connection_information_ startupParameters]) {
    AppStartupParameters* startup_params = [[AppStartupParameters alloc]
         initWithExternalURL:webpage_GURL
                 completeURL:webpage_GURL
             applicationMode:ApplicationModeForTabOpening::NORMAL
        forceApplicationMode:NO];
    startup_params.openExistingTab = open_existing_tab;
    startup_params.openedViaSiriShortcut = opened_via_siri_shortcut;
    [connection_information_ setStartupParameters:startup_params];
  }
  return YES;
}
// LINT.ThenChange(//ios/chrome/app/task_request_user_activity.mm)

void UserActivityBrowserAgent::OpenMultipleTabs() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  const std::vector<GURL>& URLs =
      connection_information_.startupParameters.URLs;
  base::OnceCallback<void(ApplicationModeForTabOpening)> completion =
      base::BindOnce(&UserActivityBrowserAgent::HandleMultipleUrlsOpening,
                     weak_ptr_factory_.GetWeakPtr(), URLs);
  [connection_information_.startupParameters
      fetchAppSwitcherParamsWithBlock:base::CallbackToBlock(
                                          std::move(completion))];
}

GURL UserActivityBrowserAgent::GenerateResultGURLFromSearchQuery(
    NSString* search_query) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  TemplateURLService* template_url_Service =
      ios::TemplateURLServiceFactory::GetForProfile(profile_);

  const TemplateURL* default_url =
      template_url_Service->GetDefaultSearchProvider();
  DCHECK(default_url);
  DCHECK(!default_url->url().empty());
  DCHECK(default_url->url_ref().IsValid(
      template_url_Service->search_terms_data()));
  std::u16string query_string = base::SysNSStringToUTF16(search_query);
  TemplateURLRef::SearchTermsArgs search_args(query_string);

  GURL result(default_url->url_ref().ReplaceSearchTerms(
      search_args, template_url_Service->search_terms_data()));

  return result;
}

void UserActivityBrowserAgent::OverloadContinueUserActivityURL(
    BOOL open_existing_tab,
    NSURL* webpage_url) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  BOOL is_active = [[UIApplication sharedApplication] applicationState] ==
                   UIApplicationStateActive;
  if (!connection_information_.startupParameters) {
    AppStartupParameters* startup_params = [[AppStartupParameters alloc]
         initWithExternalURL:net::GURLWithNSURL(webpage_url)
                 completeURL:net::GURLWithNSURL(webpage_url)
             applicationMode:ApplicationModeForTabOpening::UNDETERMINED
        forceApplicationMode:NO];
    connection_information_.startupParameters = startup_params;
  }
  ContinueUserActivityURL(webpage_url, is_active, open_existing_tab, NO);
}

void UserActivityBrowserAgent::ClearStartupParameters() {
  connection_information_.startupParameters = nil;
}

void UserActivityBrowserAgent::HandleRouteToCorrectTab(
    ApplicationModeForTabOpening target_mode) {
  GURL external_url = connection_information_.startupParameters.externalURL;
  // TODO(crbug.com/41443029): Exacly the same copy of this code is present in
  // +[URLOpener
  // openURL:applicationActive:options:tabOpener:startupInformation:]

  // The app is already active so the applicationDidBecomeActive: method
  // will never be called. Open the requested URL after all modal UIs have
  // been dismissed. `_startupParameters` must be retained until all deferred
  // modal UIs are dismissed and tab opened (or Incognito interstitial shown)
  // with requested URL.
  GURL url;
  GURL virtual_url;
  GURL complete_url = connection_information_.startupParameters.completeURL;
  if (complete_url.SchemeIsFile()) {
    // External URL will be loaded by WebState, which expects `complete_url`.
    // Omnibox however suppose to display `external_url`, which is used as
    // virtual URL.
    url = complete_url;
    virtual_url = external_url;
  } else {
    url = external_url;
  }
  UrlLoadParams params;
  if (connection_information_.startupParameters.openExistingTab) {
    web::NavigationManager::WebLoadParams web_load_params =
        web::NavigationManager::WebLoadParams(url);
    params = UrlLoadParams::SwitchToTab(web_load_params);
  } else {
    params = UrlLoadParams::InNewTab(url, virtual_url);
  }

  params.from_widget_or_siri =
      connection_information_.startupParameters.openedViaWidgetScheme ||
      connection_information_.startupParameters.openedViaSiriShortcut;

  if (connection_information_.startupParameters.imageSearchData) {
    TemplateURLService* template_url_service =
        ios::TemplateURLServiceFactory::GetForProfile(profile_);

    const BOOL useLens =
        lens_availability::CheckAndLogAvailabilityForLensEntryPoint(
            LensEntrypoint::ContextMenu,
            search_engines::SupportsSearchImageWithLens(template_url_service));
    if (!useLens) {
      if (search_engines::SupportsSearchByImage(template_url_service)) {
        NSData* image_data =
            connection_information_.startupParameters.imageSearchData;
        ImageSearchParamGenerator::PrepareImageDataFromDataAsync(
            image_data,
            base::BindOnce(
                &UserActivityBrowserAgent::ContinueOpeningWithImageData,
                weak_ptr_factory_.GetWeakPtr(), std::move(params),
                target_mode));
        return;
      }
    } else {
      connection_information_.startupParameters.postOpeningAction =
          START_LENS_FROM_SHARE_EXTENSION;
    }

  } else if (connection_information_.startupParameters.textQuery) {
    NSString* query = connection_information_.startupParameters.textQuery;

    GURL result = GenerateResultGURLFromSearchQuery(query);
    params.web_params.url = result;
  }

  ContinueOpeningWithParams(std::move(params), target_mode);
}

void UserActivityBrowserAgent::ContinueOpeningWithParams(
    UrlLoadParams params,
    ApplicationModeForTabOpening target_mode) {
  params.from_external = true;

  base::OnceClosure closure =
      base::BindOnce(&UserActivityBrowserAgent::ClearStartupParameters,
                     weak_ptr_factory_.GetWeakPtr());
  [tab_opener_
      dismissModalsAndMaybeOpenSelectedTabInMode:target_mode
                               withUrlLoadParams:params
                                  dismissOmnibox:[[connection_information_
                                                     startupParameters]
                                                     postOpeningAction] !=
                                                 FOCUS_OMNIBOX
                                      completion:base::CallbackToBlock(
                                                     std::move(closure))];
}

void UserActivityBrowserAgent::ContinueOpeningWithImageData(
    UrlLoadParams params,
    ApplicationModeForTabOpening target_mode,
    NSData* image_data) {
  TemplateURLService* template_url_service =
      ios::TemplateURLServiceFactory::GetForProfile(profile_);
  params.web_params = ImageSearchParamGenerator::LoadParamsForResizedImageData(
      image_data, GURL(), template_url_service);
  ContinueOpeningWithParams(std::move(params), target_mode);
}

void UserActivityBrowserAgent::HandleUrlOpening(
    const GURL& webpage_url,
    ApplicationModeForTabOpening target_mode) {
  UrlLoadParams params = UrlLoadParams::InNewTab(webpage_url);

  if (connection_information_.startupParameters.textQuery) {
    NSString* query = connection_information_.startupParameters.textQuery;

    GURL result = GenerateResultGURLFromSearchQuery(query);
    params.web_params.url = result;
  }

  base::OnceClosure closure =
      base::BindOnce(&UserActivityBrowserAgent::ClearStartupParameters,
                     weak_ptr_factory_.GetWeakPtr());
  [tab_opener_
      dismissModalsAndMaybeOpenSelectedTabInMode:target_mode
                               withUrlLoadParams:params
                                  dismissOmnibox:YES
                                      completion:base::CallbackToBlock(
                                                     std::move(closure))];
}

void UserActivityBrowserAgent::HandleMultipleUrlsOpening(
    const std::vector<GURL>& URLs,
    ApplicationModeForTabOpening target_mode) {
  BOOL incognito_mode = target_mode == ApplicationModeForTabOpening::INCOGNITO;
  BOOL dismiss_omnibox = [[connection_information_ startupParameters]
                             postOpeningAction] != FOCUS_OMNIBOX;

  // Using a weak reference to `this` to solve a memory leak issue.
  // `tab_opener_` and `connection_information_` are the same object in
  // some cases (SceneController). This retains the object while the block
  // exists. Then this block is passed around and in some cases it ends up
  // stored in BrowserViewController. This results in a memory leak that looks
  // like this: SceneController -> BrowserLifecycleManager -> BrowserCoordinator
  // -> BrowserViewController -> SceneController
  base::OnceClosure closure =
      base::BindOnce(&UserActivityBrowserAgent::ClearStartupParameters,
                     weak_ptr_factory_.GetWeakPtr());
  [tab_opener_
      dismissModalsAndOpenMultipleTabsWithURLs:URLs
                               inIncognitoMode:incognito_mode
                                dismissOmnibox:dismiss_omnibox
                                    completion:base::CallbackToBlock(
                                                   std::move(closure))];
}
