// Copyright 2015 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/app/startup/chrome_app_startup_parameters.h"

#import "base/apple/bundle_locations.h"
#import "base/apple/foundation_util.h"
#import "base/check.h"
#import "base/metrics/histogram_functions.h"
#import "base/metrics/histogram_macros.h"
#import "base/metrics/user_metrics.h"
#import "base/metrics/user_metrics_action.h"
#import "base/strings/stringprintf.h"
#import "base/strings/sys_string_conversions.h"
#import "components/password_manager/core/browser/manage_passwords_referrer.h"
#import "ios/chrome/browser/default_browser/model/utils.h"
#import "ios/chrome/browser/google_one/shared/google_one_deep_link_util.h"
#import "ios/chrome/browser/intelligence/features/features.h"
#import "ios/chrome/browser/shared/model/url/chrome_url_constants.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/common/app_group/app_group_constants.h"
#import "ios/chrome/common/app_group/widget_constants.h"
#import "ios/chrome/common/x_callback_url.h"
#import "ios/components/webui/web_ui_url_constants.h"
#import "net/base/apple/url_conversions.h"
#import "net/base/url_util.h"
#import "url/gurl.h"

using base::UmaHistogramEnumeration;

namespace {

const char kApplicationGroupCommandDelay[] =
    "Startup.ApplicationGroupCommandDelay";

// Host string used to detect an "external action" scheme URL.
NSString* const kExternalActionURLHost = @"ChromeExternalAction";

// Action path string for launching the default browser settings using external
// actions.
NSString* const kExternalActionDefaultBrowserSettings =
    @"DefaultBrowserSettings";

// Action path string for Opening an NTP using external actions.
NSString* const kExternalActionOpenNTP = @"OpenNTP";

// Action path string for Gemini Promo using external actions.
NSString* const kExternalActionAppStoreGeminiPromo = @"appstoregeminipromo";

// Action path string for App Switcher testing using external actions.
NSString* const kExternalActionAppSwitcherTesting = @"appswitchertesting";

// URL Query String parameter to indicate that this openURL: request arrived
// here due to a Smart App Banner presentation on a Google.com page.
NSString* const kSmartAppBannerKey = @"safarisab";

const CGFloat kAppGroupTriggersVoiceSearchTimeout = 15.0;

// Histogram helper to log the UMA IOS.WidgetKit.Action histogram.
void LogWidgetKitAction(WidgetKitExtensionAction action) {
  UmaHistogramEnumeration(kWidgetKitActionHistogram, action);
}

bool CallerAppIsFirstParty(MobileSessionCallerApp callerApp) {
  switch (callerApp) {
    case CALLER_APP_GOOGLE_SEARCH:
    case CALLER_APP_GOOGLE_GMAIL:
    case CALLER_APP_GOOGLE_PLUS:
    case CALLER_APP_GOOGLE_DRIVE:
    case CALLER_APP_GOOGLE_EARTH:
    case CALLER_APP_GOOGLE_OTHER:
    case CALLER_APP_GOOGLE_YOUTUBE:
    case CALLER_APP_GOOGLE_MAPS:
    case CALLER_APP_GOOGLE_CHROME_SHARE_EXTENSION:
    case CALLER_APP_GOOGLE_CHROME_OPEN_EXTENSION:
    case CALLER_APP_GOOGLE_CHROME:
      return true;
    case CALLER_APP_OTHER:
    case CALLER_APP_APPLE_MOBILESAFARI:
    case CALLER_APP_APPLE_OTHER:
    case CALLER_APP_THIRD_PARTY:
    case CALLER_APP_NOT_AVAILABLE:
    case MOBILE_SESSION_CALLER_APP_COUNT:
      return false;
  }
}

// LINT.IfChange(IsShowDefaultBrowserSettings)
// TODO(crbug.com/462018636): This code will be soon migrated to
// task_request_url_context.mm, so any change should be reflected also there.
// Contact fedegermi for additional information or support.
TabOpeningPostOpeningAction XCallbackPoaToPostOpeningAction(
    const std::string& poa_param) {
  if (poa_param == "default-browser-settings") {
    return SHOW_DEFAULT_BROWSER_SETTINGS;
  }
  return NO_ACTION;
}
// LINT.ThenChange(//ios/chrome/app/task_request_url_context.mm:IsShowDefaultBrowserSettings)

}  // namespace

@implementation ChromeAppStartupParameters {
  NSString* _secureSourceApp;
  NSString* _declaredSourceApp;
}

- (instancetype)initWithExternalURL:(const GURL&)externalURL
                  declaredSourceApp:(NSString*)declaredSourceApp
                    secureSourceApp:(NSString*)secureSourceApp
                        completeURL:(NSURL*)completeURL
                    applicationMode:(ApplicationModeForTabOpening)mode
               forceApplicationMode:(BOOL)forceApplicationMode {
  CHECK(!IsEnableNewStartupFlowEnabled());
  self = [super initWithExternalURL:externalURL
                        completeURL:net::GURLWithNSURL(completeURL)
                        sourceAppID:declaredSourceApp
                    applicationMode:mode
               forceApplicationMode:forceApplicationMode];
  if (self) {
    _declaredSourceApp = [declaredSourceApp copy];
    _secureSourceApp = [secureSourceApp copy];
  }
  return self;
}

+ (instancetype)startupParametersWithURL:(NSURL*)completeURL
                       sourceApplication:(NSString*)appID
                         applicationMode:(ApplicationModeForTabOpening)mode
                    forceApplicationMode:(BOOL)forceApplicationMode {
  GURL parsedURL = net::GURLWithNSURL(completeURL);

  if (!parsedURL.is_valid() || parsedURL.GetScheme().length() == 0) {
    return nil;
  }

  // LINT.IfChange(WidgetKitScheme)
  // TODO(crbug.com/462018636): This code will be soon migrated to
  // task_request_url_context.mm, so any change should be reflected also there.
  // Contact fedegermi for additional information or support.
  if ([completeURL.scheme isEqualToString:kWidgetKitSchemeChrome]) {
    UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartActionHistogram,
                              START_ACTION_WIDGET_KIT_COMMAND,
                              MOBILE_SESSION_START_ACTION_COUNT);

    base::UmaHistogramEnumeration(kAppLaunchSource, AppLaunchSource::WIDGET);

    const char* command = "";
    NSString* sourceWidget = completeURL.host;
    NSString* externalText = nil;

    if ([completeURL.path isEqualToString:kWidgetKitActionSearch]) {
      command = app_group::kChromeAppGroupFocusOmniboxCommand;
    } else if ([completeURL.path isEqualToString:kWidgetKitActionIncognito]) {
      command = app_group::kChromeAppGroupIncognitoSearchCommand;
    } else if ([completeURL.path isEqualToString:kWidgetKitActionVoiceSearch]) {
      command = app_group::kChromeAppGroupVoiceSearchCommand;
    } else if ([completeURL.path isEqualToString:kWidgetKitActionQRReader]) {
      command = app_group::kChromeAppGroupQRScannerCommand;
    } else if ([completeURL.path isEqualToString:kWidgetKitActionLens]) {
      command = app_group::kChromeAppGroupLensCommand;
    } else if ([completeURL.path isEqual:kWidgetKitActionOpenURL]) {
      command = app_group::kChromeAppGroupOpenURLCommand;
      std::string URLQueryParam;
      if (net::GetValueForKeyInQuery(net::GURLWithNSURL(completeURL), "url",
                                     &URLQueryParam)) {
        externalText = base::SysUTF8ToNSString(URLQueryParam);
      }
    } else if ([completeURL.path isEqual:kWidgetKitActionSearchPasswords]) {
      command = app_group::kChromeAppGroupSearchPasswordsCommand;
    } else if ([completeURL.path isEqualToString:kWidgetKitActionGame]) {
      if ([sourceWidget isEqualToString:kWidgetKitHostDinoGameWidget]) {
        LogWidgetKitAction(WidgetKitExtensionAction::ACTION_DINO_WIDGET_GAME);
      } else if ([sourceWidget
                     isEqualToString:kWidgetKitHostLockscreenLauncherWidget]) {
        LogWidgetKitAction(
            WidgetKitExtensionAction::ACTION_LOCKSCREEN_LAUNCHER_GAME);
      }

      GURL URL(
          base::StringPrintf("%s://%s", kChromeUIScheme, kChromeUIDinoHost));
      ChromeAppStartupParameters* appStartupParameters =
          [[ChromeAppStartupParameters alloc]
               initWithExternalURL:URL
                 declaredSourceApp:appID
                   secureSourceApp:sourceWidget
                       completeURL:completeURL
                   applicationMode:ApplicationModeForTabOpening::NORMAL
              forceApplicationMode:forceApplicationMode];
      appStartupParameters.openedViaWidgetScheme = YES;
      return appStartupParameters;
    }

    NSString* commandString = base::SysUTF8ToNSString(command);
    ChromeAppStartupParameters* appStartupParameters =
        [self startupParametersForCommand:commandString
                         withExternalText:externalText
                             externalData:nil
                                    index:0
                                      URL:nil
                        sourceApplication:appID
                  secureSourceApplication:sourceWidget
                     forceApplicationMode:forceApplicationMode];
    appStartupParameters.openedViaWidgetScheme = YES;
    return appStartupParameters;
    // LINT.ThenChange(//ios/chrome/app/task_request_url_context.mm:WidgetKitScheme)
    // LINT.IfChange(XCallbackURL)
    // TODO(crbug.com/462018636): This code will be soon migrated to
    // task_request_url_context.mm, so any change should be reflected also
    // there. Contact fedegermi for additional information or support.
  } else if (IsXCallbackURL(parsedURL)) {
    base::UmaHistogramEnumeration(kAppLaunchSource,
                                  AppLaunchSource::X_CALLBACK);
    // TODO(crbug.com/41004788): Temporary fix.
    NSString* action = [completeURL path];
    // Currently only "open" and "app-group-command" are supported.
    if ([action
            isEqualToString:
                [NSString
                    stringWithFormat:
                        @"/%s", app_group::kChromeAppGroupXCallbackCommand]]) {
      UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartActionHistogram,
                                START_ACTION_XCALLBACK_APPGROUP_COMMAND,
                                MOBILE_SESSION_START_ACTION_COUNT);
      return [ChromeAppStartupParameters
          startupParametersForExtensionCommandWithURL:completeURL
                                    sourceApplication:appID
                                 forceApplicationMode:forceApplicationMode];
    }

    if (![action isEqualToString:@"/open"]) {
      UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartActionHistogram,
                                START_ACTION_XCALLBACK_OTHER,
                                MOBILE_SESSION_START_ACTION_COUNT);
      return nil;
    }

    UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartActionHistogram,
                              START_ACTION_XCALLBACK_OPEN,
                              MOBILE_SESSION_START_ACTION_COUNT);
    // LINT.ThenChange(//ios/chrome/app/task_request_url_context.mm:XCallbackURL)

    std::map<std::string, std::string> parameters =
        ExtractQueryParametersFromXCallbackURL(parsedURL);
    GURL URLQueryParam = GURL(parameters["url"]);
    if (!URLQueryParam.is_valid() ||
        (!URLQueryParam.SchemeIs(url::kHttpScheme) &&
         !URLQueryParam.SchemeIs(url::kHttpsScheme))) {
      return nil;
    }
    TabOpeningPostOpeningAction postOpeningAction =
        XCallbackPoaToPostOpeningAction(parameters["poa"]);

    ChromeAppStartupParameters* startupParameters =
        [[ChromeAppStartupParameters alloc]
             initWithExternalURL:URLQueryParam
               declaredSourceApp:appID
                 secureSourceApp:nil
                     completeURL:completeURL
                 applicationMode:ApplicationModeForTabOpening::UNDETERMINED
            forceApplicationMode:forceApplicationMode];
    // postOpeningAction can only be NO_ACTION or SHOW_DEFAULT_BROWSER_SETTINGS
    // (these are the only values returned by `XCallbackPoaToPostOpeningAction`)
    // so this assignment should not DCHECK, no matter what the URL is.
    startupParameters.postOpeningAction = postOpeningAction;
    return startupParameters;
  } else if ([self isChromeExternalActionURL:completeURL]) {
    base::RecordAction(
        base::UserMetricsAction("MobileExternalActionURLOpened"));
    UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartActionHistogram,
                              START_EXTERNAL_ACTION,
                              MOBILE_SESSION_START_ACTION_COUNT);
    base::UmaHistogramEnumeration(kAppLaunchSource,
                                  AppLaunchSource::EXTERNAL_ACTION);

    return
        [self startupParametersForExternalActionWithAppID:appID
                                              completeURL:completeURL
                                     forceApplicationMode:forceApplicationMode];
    // LINT.IfChange(SimpleURLContext)
    // TODO(crbug.com/462018636): This code will be soon migrated to
    // task_request_url_context.mm, so any change should be reflected also
    // there. Contact fedegermi for additional information or support.
  } else if (parsedURL.SchemeIsFile()) {
    UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartActionHistogram,
                              START_ACTION_OPEN_FILE,
                              MOBILE_SESSION_START_ACTION_COUNT);
    // `url` is the path to a file received from another application.
    GURL::Replacements replacements;
    const std::string host(kChromeUIExternalFileHost);
    std::string filename = parsedURL.ExtractFileName();
    replacements.SetPathStr(filename);
    replacements.SetSchemeStr(kChromeUIScheme);
    replacements.SetHostStr(host);
    GURL externalURL = parsedURL.ReplaceComponents(replacements);
    if (!externalURL.is_valid()) {
      return nil;
    }
    return [[ChromeAppStartupParameters alloc]
         initWithExternalURL:externalURL
           declaredSourceApp:appID
             secureSourceApp:nil
                 completeURL:completeURL
             applicationMode:ApplicationModeForTabOpening::NORMAL
        forceApplicationMode:forceApplicationMode];
  } else {
    GURL externalURL = parsedURL;
    BOOL openedViaSpecificScheme = NO;
    MobileSessionStartAction action = START_ACTION_OTHER;
    if (parsedURL.SchemeIs(url::kHttpScheme)) {
      action = START_ACTION_OPEN_HTTP_FROM_OS;
      base::RecordAction(
          base::UserMetricsAction("MobileDefaultBrowserViewIntent"));
    } else if (parsedURL.SchemeIs(url::kHttpsScheme)) {
      action = START_ACTION_OPEN_HTTPS_FROM_OS;
      base::RecordAction(
          base::UserMetricsAction("MobileDefaultBrowserViewIntent"));
    } else {
      // Replace the scheme with https or http depending on whether the input
      // `url` scheme ends with an 's'.
      BOOL useHttps =
          parsedURL.GetScheme()[parsedURL.GetScheme().length() - 1] == 's';
      action = useHttps ? START_ACTION_OPEN_HTTPS : START_ACTION_OPEN_HTTP;
      base::UmaHistogramEnumeration(kAppLaunchSource,
                                    AppLaunchSource::LINK_OPENED_FROM_APP);
      base::RecordAction(base::UserMetricsAction("MobileFirstPartyViewIntent"));

      GURL::Replacements replaceScheme;
      if (useHttps) {
        replaceScheme.SetSchemeStr(url::kHttpsScheme);
      } else {
        replaceScheme.SetSchemeStr(url::kHttpScheme);
      }
      externalURL = parsedURL.ReplaceComponents(replaceScheme);
      openedViaSpecificScheme = YES;
    }
    UMA_HISTOGRAM_ENUMERATION(kUMAMobileSessionStartActionHistogram, action,
                              MOBILE_SESSION_START_ACTION_COUNT);

    if (action == START_ACTION_OPEN_HTTP_FROM_OS ||
        action == START_ACTION_OPEN_HTTPS_FROM_OS) {
      base::UmaHistogramEnumeration(kAppLaunchSource,
                                    AppLaunchSource::LINK_OPENED_FROM_OS);
      LogOpenHTTPURLFromExternalURL();
    }
    // LINT.ThenChange(//ios/chrome/app/task_request_for_url_context_simple.mm:SimpleURLContext)

    if (!externalURL.is_valid()) {
      return nil;
    }
    GURL urlToOpen = externalURL;
    GURL googleOneURL;
    BOOL isGoogleOneDeepLink = IsGoogleOneDeepLinkEnabled() &&
                               IsGoogleOneDeepLinkURL(parsedURL, &googleOneURL);
    if (isGoogleOneDeepLink) {
      urlToOpen = GURL();
      completeURL = net::NSURLWithGURL(googleOneURL);
    }
    ChromeAppStartupParameters* params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:urlToOpen
           declaredSourceApp:appID
             secureSourceApp:nil
                 completeURL:completeURL
             applicationMode:mode
        forceApplicationMode:forceApplicationMode];
    params.openedWithURL = YES;
    params.openedViaFirstPartyScheme =
        openedViaSpecificScheme && CallerAppIsFirstParty(params.callerApp);
    if (isGoogleOneDeepLink) {
      params.postOpeningAction = SHOW_GOOGLE_ONE_SCREEN;
    }
    return params;
  }
}

// Returns true if the URL passed is an external action URL, defined by having
// the `kExternalActionURLHost` as host.
+ (BOOL)isChromeExternalActionURL:(NSURL*)URL {
  return [URL.host isEqualToString:kExternalActionURLHost];
}

// Returns a new `ChromeAppStartupParameters` for a given `appID`, `completeURL`
// and `externalURL`.
+ (ChromeAppStartupParameters*)
    startupParametersForExternalActionWithAppID:(NSString*)appID
                                    completeURL:(NSURL*)completeURL
                                    externalURL:(const GURL&)externalURL
                           forceApplicationMode:(BOOL)forceApplicationMode {
  return [[ChromeAppStartupParameters alloc]
       initWithExternalURL:externalURL
         declaredSourceApp:appID
           secureSourceApp:nil
               completeURL:completeURL
           applicationMode:ApplicationModeForTabOpening::UNDETERMINED
      forceApplicationMode:forceApplicationMode];
}

// Returns the correct startup parameters for a given external action passed as
// path to the external action "scheme". Returns nil (no-op) if the action is
// not recognized.
// LINT.IfChange(ExternalAction)
// TODO(crbug.com/462018636): This code will be soon migrated to
// task_request_url_context.mm, so any change should be reflected also
// there. Contact fedegermi for additional information or support.
+ (instancetype)startupParametersForExternalActionWithAppID:(NSString*)appID
                                                completeURL:(NSURL*)completeURL
                                       forceApplicationMode:
                                           (BOOL)forceApplicationMode {
  ChromeAppStartupParameters* params;
  IOSExternalAction action;
  NSString* path;

  // Separate the path into its components and ensure there is only one
  // component after the first "/".
  NSArray<NSString*>* pathComponents = completeURL.pathComponents;
  if ([pathComponents count] == 2 && [pathComponents[0] isEqualToString:@"/"]) {
    path = pathComponents[1];
  }

  if ([path isEqualToString:kExternalActionOpenNTP]) {
    base::RecordAction(
        base::UserMetricsAction("MobileExternalActionURLOpenedWithOpenNTP"));
    action = IOSExternalAction::ACTION_OPEN_NTP;
    params = [self
        startupParametersForExternalActionWithAppID:appID
                                        completeURL:completeURL
                                        externalURL:GURL(kChromeUINewTabURL)
                               forceApplicationMode:forceApplicationMode];
  } else if ([path isEqualToString:kExternalActionDefaultBrowserSettings]) {
    base::RecordAction(base::UserMetricsAction(
        "MobileExternalActionURLOpenedWithDefaultBrowserSettings"));

    // If Chrome is already set as default browser, just open the NTP.
    if (IsChromeLikelyDefaultBrowser()) {
      action =
          IOSExternalAction::ACTION_SKIPPED_DEFAULT_BROWSER_SETTINGS_FOR_NTP;
      params = [self
          startupParametersForExternalActionWithAppID:appID
                                          completeURL:completeURL
                                          externalURL:GURL(kChromeUINewTabURL)
                                 forceApplicationMode:forceApplicationMode];
    } else {
      action = IOSExternalAction::ACTION_DEFAULT_BROWSER_SETTINGS;
      params = [self
          startupParametersForExternalActionWithAppID:appID
                                          completeURL:completeURL
                                          externalURL:GURL()
                                 forceApplicationMode:forceApplicationMode];
      params.postOpeningAction = EXTERNAL_ACTION_SHOW_BROWSER_SETTINGS;
    }
  } else if (IsAppStoreInAppEventsEnabled() &&
             [path isEqualToString:kExternalActionAppStoreGeminiPromo]) {
    base::RecordAction(base::UserMetricsAction(
        "MobileExternalActionURLOpenedWithAppStoreGeminiPromo"));
    action = IOSExternalAction::ACTION_APP_STORE_GEMINI_PROMO;
    params = [self
        startupParametersForExternalActionWithAppID:appID
                                        completeURL:completeURL
                                        externalURL:GURL(
                                                        kGeminiAppStorePromoURL)
                               forceApplicationMode:forceApplicationMode];
    params.postOpeningAction = TRIGGER_GEMINI_PROMO;
  } else if (IsAppSwitcherAISummarizationEnabled() &&
             [path isEqualToString:kExternalActionAppSwitcherTesting]) {
    // TODO(crbug.com/527016607): Remove this entire testing path when the
    // feature is enabled by default.
    action = IOSExternalAction::ACTION_START_GEMINI_AI_SUMMARIZATION;

    GURL externalURL = GURL(kGeminiAppStorePromoURL);
    std::string queryURLString;
    if (net::GetValueForKeyInQuery(net::GURLWithNSURL(completeURL), "url",
                                   &queryURLString)) {
      GURL parsedQueryURL(queryURLString);
      if (parsedQueryURL.is_valid() && parsedQueryURL.SchemeIsHTTPOrHTTPS()) {
        externalURL = parsedQueryURL;
      }
    }

    params =
        [self startupParametersForExternalActionWithAppID:appID
                                              completeURL:completeURL
                                              externalURL:externalURL
                                     forceApplicationMode:forceApplicationMode];
    params.postOpeningAction = START_GEMINI_AI_SUMMARIZATION;
  } else {
    action = IOSExternalAction::ACTION_INVALID;
    params = nil;
  }

  base::UmaHistogramEnumeration(kExternalActionHistogram, action);
  params.openedViaFirstPartyScheme = CallerAppIsFirstParty(params.callerApp);
  return params;
}
// LINT.ThenChange(//ios/chrome/app/task_request_for_standard_url_context.mm:ExternalAction)

+ (instancetype)startupParametersForExtensionCommandWithURL:(NSURL*)URL
                                          sourceApplication:(NSString*)appID
                                       forceApplicationMode:
                                           (BOOL)forceApplicationMode {
  NSUserDefaults* sharedDefaults = app_group::GetGroupUserDefaults();

  NSString* commandDictionaryPreference =
      base::SysUTF8ToNSString(app_group::kChromeAppGroupCommandPreference);
  NSDictionary* commandDictionary = base::apple::ObjCCast<NSDictionary>(
      [sharedDefaults objectForKey:commandDictionaryPreference]);

  [sharedDefaults removeObjectForKey:commandDictionaryPreference];

  // `sharedDefaults` is used for communication between apps. Synchronize to
  // avoid synchronization issues (like removing the next order).
  [sharedDefaults synchronize];

  if (!commandDictionary) {
    return nil;
  }

  NSString* commandCallerPreference =
      base::SysUTF8ToNSString(app_group::kChromeAppGroupCommandAppPreference);
  NSString* commandCaller = base::apple::ObjCCast<NSString>(
      [commandDictionary objectForKey:commandCallerPreference]);

  NSString* commandPreference = base::SysUTF8ToNSString(
      app_group::kChromeAppGroupCommandCommandPreference);
  NSString* command = base::apple::ObjCCast<NSString>(
      [commandDictionary objectForKey:commandPreference]);

  NSString* commandTimePreference =
      base::SysUTF8ToNSString(app_group::kChromeAppGroupCommandTimePreference);
  id commandTime = base::apple::ObjCCast<NSDate>(
      [commandDictionary objectForKey:commandTimePreference]);

  NSString* commandTextPreference =
      base::SysUTF8ToNSString(app_group::kChromeAppGroupCommandTextPreference);
  NSString* externalText = base::apple::ObjCCast<NSString>(
      [commandDictionary objectForKey:commandTextPreference]);

  NSString* commandDataPreference =
      base::SysUTF8ToNSString(app_group::kChromeAppGroupCommandDataPreference);
  NSData* externalData = base::apple::ObjCCast<NSData>(
      [commandDictionary objectForKey:commandDataPreference]);

  NSString* commandIndexPreference =
      base::SysUTF8ToNSString(app_group::kChromeAppGroupCommandIndexPreference);
  NSNumber* index = base::apple::ObjCCast<NSNumber>(
      [commandDictionary objectForKey:commandIndexPreference]);

  if (!commandCaller || !command || !commandTimePreference) {
    return nil;
  }

  // Check the time of the last request to avoid app from intercepting old
  // open url request and replay it later.
  NSTimeInterval delay = [[NSDate date] timeIntervalSinceDate:commandTime];
  UMA_HISTOGRAM_COUNTS_100(kApplicationGroupCommandDelay, delay);
  if (delay > kAppGroupTriggersVoiceSearchTimeout) {
    return nil;
  }
  return [ChromeAppStartupParameters
      startupParametersForCommand:command
                 withExternalText:externalText
                     externalData:externalData
                            index:index
                              URL:URL
                sourceApplication:appID
          secureSourceApplication:commandCaller
             forceApplicationMode:forceApplicationMode];
}

+ (instancetype)startupParametersForCommand:(NSString*)command
                           withExternalText:(NSString*)externalText
                               externalData:(NSData*)externalData
                                      index:(NSNumber*)index
                                        URL:(NSURL*)URL
                          sourceApplication:(NSString*)appID
                    secureSourceApplication:(NSString*)secureAppID
                       forceApplicationMode:(BOOL)forceApplicationMode {
  ExtensionAction action = ACTION_NO_ACTION;
  ChromeAppStartupParameters* params = nil;

  if ([command
          isEqualToString:base::SysUTF8ToNSString(
                              app_group::kChromeAppGroupVoiceSearchCommand)]) {
    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:GURL(kChromeUINewTabURL)
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::NORMAL
        forceApplicationMode:forceApplicationMode];
    [params setPostOpeningAction:START_VOICE_SEARCH];
    action = ACTION_NEW_VOICE_SEARCH;
  }

  if ([command isEqualToString:base::SysUTF8ToNSString(
                                   app_group::kChromeAppGroupNewTabCommand)]) {
    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:GURL(kChromeUINewTabURL)
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::NORMAL
        forceApplicationMode:forceApplicationMode];
    action = ACTION_NO_ACTION;
  }

  if ([command
          isEqualToString:base::SysUTF8ToNSString(
                              app_group::kChromeAppGroupFocusOmniboxCommand)]) {
    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:GURL(kChromeUINewTabURL)
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::NORMAL
        forceApplicationMode:forceApplicationMode];
    [params setPostOpeningAction:FOCUS_OMNIBOX];
    action = ACTION_NEW_SEARCH;
  }

  if ([command isEqualToString:base::SysUTF8ToNSString(
                                   app_group::kChromeAppGroupOpenURLCommand)]) {
    if (!externalText) {
      return nil;
    }
    GURL externalGURL(base::SysNSStringToUTF8(externalText));
    if (!externalGURL.is_valid() || !externalGURL.SchemeIsHTTPOrHTTPS()) {
      return nil;
    }
    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:externalGURL
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::UNDETERMINED
        forceApplicationMode:forceApplicationMode];
    action = ACTION_OPEN_URL;
  }

  if ([command
          isEqualToString:app_group::kChromeAppGroupOpenURLInIcognitoCommand]) {
    if (!externalText) {
      return nil;
    }
    GURL externalGURL(base::SysNSStringToUTF8(externalText));
    if (!externalGURL.is_valid() || !externalGURL.SchemeIsHTTPOrHTTPS()) {
      return nil;
    }
    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:externalGURL
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::INCOGNITO
        forceApplicationMode:forceApplicationMode];
    action = ACTION_OPEN_URL;
  }

  if ([command
          isEqualToString:base::SysUTF8ToNSString(
                              app_group::kChromeAppGroupSearchTextCommand)]) {
    if (!externalText) {
      return nil;
    }

    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:GURL(kChromeUINewTabURL)
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::UNDETERMINED
        forceApplicationMode:forceApplicationMode];

    params.textQuery = externalText;
    params.openedViaShareExtensionScheme = YES;
    action = ACTION_SEARCH_TEXT;
  }

  if ([command isEqualToString:app_group::
                                   kChromeAppGroupIncognitoSearchTextCommand]) {
    if (!externalText) {
      return nil;
    }

    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:GURL(kChromeUINewTabURL)
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::INCOGNITO
        forceApplicationMode:forceApplicationMode];

    params.textQuery = externalText;
    params.openedViaShareExtensionScheme = YES;
    action = ACTION_SEARCH_TEXT;
  }

  if ([command
          isEqualToString:base::SysUTF8ToNSString(
                              app_group::kChromeAppGroupSearchImageCommand)]) {
    if (!externalData) {
      return nil;
    }

    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:GURL(kChromeUINewTabURL)
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::UNDETERMINED
        forceApplicationMode:forceApplicationMode];

    params.imageSearchData = externalData;
    params.openedViaShareExtensionScheme = YES;

    action = ACTION_SEARCH_IMAGE;
  }

  if ([command isEqualToString:
                   app_group::kChromeAppGroupIncognitoSearchImageCommand]) {
    if (!externalData) {
      return nil;
    }

    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:GURL(kChromeUINewTabURL)
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::INCOGNITO
        forceApplicationMode:forceApplicationMode];

    params.imageSearchData = externalData;
    params.openedViaShareExtensionScheme = YES;
    action = ACTION_SEARCH_IMAGE;
  }

  if ([command
          isEqualToString:base::SysUTF8ToNSString(
                              app_group::kChromeAppGroupQRScannerCommand)]) {
    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:GURL(kChromeUINewTabURL)
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::NORMAL
        forceApplicationMode:forceApplicationMode];
    [params setPostOpeningAction:START_QR_CODE_SCANNER];

    action = ACTION_NEW_QR_CODE_SEARCH;
  }

  if ([command isEqualToString:base::SysUTF8ToNSString(
                                   app_group::kChromeAppGroupLensCommand)]) {
    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:GURL()
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::NORMAL
        forceApplicationMode:forceApplicationMode];
    [params setPostOpeningAction:START_LENS_FROM_HOME_SCREEN_WIDGET];
    action = ACTION_LENS;
  }

  if ([command isEqualToString:
                   base::SysUTF8ToNSString(
                       app_group::kChromeAppGroupIncognitoSearchCommand)]) {
    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:GURL(kChromeUINewTabURL)
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::INCOGNITO
        forceApplicationMode:forceApplicationMode];
    [params setPostOpeningAction:FOCUS_OMNIBOX];
    action = ACTION_NEW_INCOGNITO_SEARCH;
  }

  if ([command isEqualToString:
                   base::SysUTF8ToNSString(
                       app_group::kChromeAppGroupSearchPasswordsCommand)]) {
    params = [[ChromeAppStartupParameters alloc]
         initWithExternalURL:GURL()
           declaredSourceApp:appID
             secureSourceApp:secureAppID
                 completeURL:URL
             applicationMode:ApplicationModeForTabOpening::NORMAL
        forceApplicationMode:forceApplicationMode];
    [params setPostOpeningAction:SEARCH_PASSWORDS];
    action = ACTION_NO_ACTION;
  }

  // LINT.IfChange(WidgetKitAction)
  // TODO(crbug.com/462018636): This code will be soon migrated to
  // task_request_url_context.mm, so any change should be reflected also there.
  // Contact fedegermi for additional information or support.
  if ([secureAppID isEqualToString:kWidgetKitHostSearchWidget]) {
    LogWidgetKitAction(WidgetKitExtensionAction::ACTION_SEARCH_WIDGET_SEARCH);
  }
  if ([secureAppID isEqualToString:kWidgetKitHostQuickActionsWidget]) {
    switch (action) {
      case ACTION_NEW_VOICE_SEARCH:
        LogWidgetKitAction(
            WidgetKitExtensionAction::ACTION_QUICK_ACTIONS_VOICE_SEARCH);
        break;

      case ACTION_NEW_SEARCH:
        LogWidgetKitAction(
            WidgetKitExtensionAction::ACTION_QUICK_ACTIONS_SEARCH);
        break;

      case ACTION_NEW_QR_CODE_SEARCH:
        LogWidgetKitAction(
            WidgetKitExtensionAction::ACTION_QUICK_ACTIONS_QR_READER);
        break;

      case ACTION_LENS:
        LogWidgetKitAction(WidgetKitExtensionAction::ACTION_QUICK_ACTIONS_LENS);
        break;

      case ACTION_NEW_INCOGNITO_SEARCH:
        LogWidgetKitAction(
            WidgetKitExtensionAction::ACTION_QUICK_ACTIONS_INCOGNITO);
        break;

      default:
        NOTREACHED();
    }
  }
  if ([secureAppID isEqualToString:kWidgetKitHostLockscreenLauncherWidget]) {
    switch (action) {
      case ACTION_NEW_SEARCH:
        LogWidgetKitAction(
            WidgetKitExtensionAction::ACTION_LOCKSCREEN_LAUNCHER_SEARCH);
        break;

      case ACTION_NEW_INCOGNITO_SEARCH:
        LogWidgetKitAction(
            WidgetKitExtensionAction::ACTION_LOCKSCREEN_LAUNCHER_INCOGNITO);
        break;

      case ACTION_NEW_VOICE_SEARCH:
        LogWidgetKitAction(
            WidgetKitExtensionAction::ACTION_LOCKSCREEN_LAUNCHER_VOICE_SEARCH);
        break;

      default:
        NOTREACHED();
    }
  }
  if ([secureAppID isEqualToString:kWidgetKitHostShortcutsWidget]) {
    switch (action) {
      case ACTION_NEW_SEARCH:
        LogWidgetKitAction(WidgetKitExtensionAction::ACTION_SHORTCUTS_SEARCH);
        break;
      case ACTION_OPEN_URL:
        LogWidgetKitAction(WidgetKitExtensionAction::ACTION_SHORTCUTS_OPEN);
        break;
      default:
        NOTREACHED();
    }
  }
  if ([secureAppID isEqualToString:kWidgetKitHostSearchPasswordsWidget]) {
    LogWidgetKitAction(WidgetKitExtensionAction::
                           ACTION_SEARCH_PASSWORDS_WIDGET_SEARCH_PASSWORDS);
    UMA_HISTOGRAM_ENUMERATION(
        "PasswordManager.ManagePasswordsReferrer",
        password_manager::ManagePasswordsReferrer::kSearchPasswordsWidget);
    base::RecordAction(base::UserMetricsAction(
        "MobileSearchPasswordsWidgetOpenPasswordManager"));
  }
  // LINT.ThenChange(//ios/chrome/app/task_request_url_context.mm:WidgetKitScheme)
  return params;
}

// LINT.IfChange(GetCallerApp)
// TODO(crbug.com/462018636): This code will be soon migrated to
// task_request_url_context.mm, so any change should be reflected also there.
// Contact fedegermi for additional information or support.
- (MobileSessionCallerApp)callerApp {
  if ([_secureSourceApp
          isEqualToString:app_group::kOpenCommandSourceShareExtension]) {
    return CALLER_APP_GOOGLE_CHROME_SHARE_EXTENSION;
  }
  if ([_secureSourceApp
          isEqualToString:app_group::kOpenCommandSourceOpenExtension]) {
    return CALLER_APP_GOOGLE_CHROME_OPEN_EXTENSION;
  }

  if (![_declaredSourceApp length]) {
    if (self.completeURL.SchemeIs(url::kHttpScheme) ||
        self.completeURL.SchemeIs(url::kHttpsScheme)) {
      // If Chrome is opened via the system default browser mechanism, the
      // action should be differentiated from the case where the source is
      // unknown.
      return CALLER_APP_THIRD_PARTY;
    }
    return CALLER_APP_NOT_AVAILABLE;
  }

  if ([_declaredSourceApp
          isEqualToString:[base::apple::FrameworkBundle() bundleIdentifier]]) {
    return CALLER_APP_GOOGLE_CHROME;
  }
  if ([_declaredSourceApp isEqualToString:@"com.google.GoogleMobile"]) {
    return CALLER_APP_GOOGLE_SEARCH;
  }
  if ([_declaredSourceApp isEqualToString:@"com.google.Gmail"]) {
    return CALLER_APP_GOOGLE_GMAIL;
  }
  if ([_declaredSourceApp isEqualToString:@"com.google.GooglePlus"]) {
    return CALLER_APP_GOOGLE_PLUS;
  }
  if ([_declaredSourceApp isEqualToString:@"com.google.Drive"]) {
    return CALLER_APP_GOOGLE_DRIVE;
  }
  if ([_declaredSourceApp isEqualToString:@"com.google.b612"]) {
    return CALLER_APP_GOOGLE_EARTH;
  }
  if ([_declaredSourceApp isEqualToString:@"com.google.ios.youtube"]) {
    return CALLER_APP_GOOGLE_YOUTUBE;
  }
  if ([_declaredSourceApp isEqualToString:@"com.google.Maps"]) {
    return CALLER_APP_GOOGLE_MAPS;
  }
  if ([_declaredSourceApp hasPrefix:@"com.google."]) {
    return CALLER_APP_GOOGLE_OTHER;
  }
  if ([_declaredSourceApp isEqualToString:@"com.apple.mobilesafari"]) {
    return CALLER_APP_APPLE_MOBILESAFARI;
  }
  if ([_declaredSourceApp hasPrefix:@"com.apple."]) {
    return CALLER_APP_APPLE_OTHER;
  }

  return CALLER_APP_OTHER;
}
// LINT.ThenChange(//ios/chrome/app/task_request_url_context.mm:GetCallerApp)

// LINT.IfChange(GetLaunchSource)
// TODO(crbug.com/462018636): This code will be soon migrated to
// task_request_url_context.mm, so any change should be reflected also there.
// Contact fedegermi for additional information or support.
- (first_run::ExternalLaunch)launchSource {
  if ([self callerApp] != CALLER_APP_APPLE_MOBILESAFARI) {
    return first_run::LAUNCH_BY_OTHERS;
  }

  NSString* query = base::SysUTF8ToNSString(self.completeURL.GetQuery());
  // Takes care of degenerated case of no QUERY_STRING.
  if (![query length]) {
    return first_run::LAUNCH_BY_MOBILESAFARI;
  }
  // Look for `kSmartAppBannerKey` anywhere within the query string.
  NSRange found = [query rangeOfString:kSmartAppBannerKey];
  if (found.location == NSNotFound) {
    return first_run::LAUNCH_BY_MOBILESAFARI;
  }
  // `kSmartAppBannerKey` can be at the beginning or end of the query
  // string and may also be optionally followed by a equal sign and a value.
  // For now, just look for the presence of the key and ignore the value.
  if (found.location + found.length < [query length]) {
    // There are characters following the found location.
    unichar charAfter =
        [query characterAtIndex:(found.location + found.length)];
    if (charAfter != '&' && charAfter != '=') {
      return first_run::LAUNCH_BY_MOBILESAFARI;
    }
  }
  if (found.location > 0) {
    unichar charBefore = [query characterAtIndex:(found.location - 1)];
    if (charBefore != '&') {
      return first_run::LAUNCH_BY_MOBILESAFARI;
    }
  }
  return first_run::LAUNCH_BY_SMARTAPPBANNER;
}
// LINT.ThenChange(//ios/chrome/app/task_request_url_context.mm:GetLaunchSource)

@end
