// 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/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/ios/block_types.h"
#import "base/metrics/histogram_functions.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_delegate/app_state_observer.h"
#import "ios/chrome/app/application_mode.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/browser/intents/intent_type.h"
#import "ios/chrome/browser/intents/intents_constants.h"
#import "ios/chrome/browser/metrics/model/first_user_action_recorder.h"
#import "ios/chrome/browser/policy/model/policy_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/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 "net/base/apple/url_conversions.h"
#import "ui/base/page_transition_types.h"
using base::UserMetricsAction;
namespace {
// Constants for compatible mode for user activities.
NSString* const kRegularMode = @"RegularMode";
NSString* const kIncognitoMode = @"IncognitoMode";
std::vector<GURL> CreateGURLVectorFromIntentURLs(NSArray<NSURL*>* intent_urls) {
std::vector<GURL> urls;
for (NSURL* url in intent_urls) {
urls.push_back(net::GURLWithNSURL(url));
}
return urls;
}
// Returns the compatible mode array for an user activity.
NSArray* CompatibleModeForActivityType(NSString* activity_type) {
if ([activity_type isEqualToString:CSSearchableItemActionType] ||
[activity_type isEqualToString:kShortcutNewSearch] ||
[activity_type isEqualToString:kShortcutVoiceSearch] ||
[activity_type isEqualToString:kShortcutQRScanner] ||
[activity_type isEqualToString:kShortcutLensFromAppIconLongPress] ||
[activity_type isEqualToString:kShortcutLensFromSpotlight] ||
[activity_type isEqualToString:kSiriShortcutAddBookmarkToChrome] ||
[activity_type isEqualToString:kSiriShortcutAddReadingListItemToChrome] ||
[activity_type isEqualToString:kSiriShortcutSearchInChrome] ||
[activity_type isEqualToString:NSUserActivityTypeBrowsingWeb]) {
return @[ kRegularMode, kIncognitoMode ];
} else if ([activity_type isEqualToString:kSiriShortcutOpenInChrome]) {
return @[ kRegularMode ];
} else if ([activity_type isEqualToString:kShortcutNewIncognitoSearch] ||
[activity_type isEqualToString:kSiriShortcutOpenInIncognito]) {
return @[ kIncognitoMode ];
} else {
// Use 32 as the maximum length of the reported value for this key (31
// characters + '\0'). See NSUserActivityTypes in Info.plist for the list of
// expected values.
static crash_reporter::CrashKeyString<32> key("activity");
crash_reporter::ScopedCrashKeyString crash_key(
&key, base::SysNSStringToUTF8(activity_type));
base::debug::DumpWithoutCrashing();
}
return nil;
}
} // namespace
BROWSER_USER_DATA_KEY_IMPL(UserActivityBrowserAgent)
UserActivityBrowserAgent::UserActivityBrowserAgent(Browser* browser)
: browser_(browser), browser_state_(browser->GetBrowserState()) {
SceneState* scene_state = browser_->GetSceneState();
connection_information_ = scene_state.controller;
tab_opener_ = scene_state.controller;
startup_information_ = scene_state.appState.startupInformation;
}
UserActivityBrowserAgent::~UserActivityBrowserAgent() {}
#pragma mark - Public methods.
BOOL UserActivityBrowserAgent::ContinueUserActivity(
NSUserActivity* user_activity,
BOOL application_is_active) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NSURL* webpage_url = user_activity.webpageURL;
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);
} 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];
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]) {
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
base::RecordAction(UserMetricsAction("IOSLaunchedBySearchInChromeIntent"));
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kSearchInChrome);
AppStartupParameters* startup_params = [[AppStartupParameters alloc]
initWithExternalURL:GURL(kChromeUINewTabURL)
completeURL:GURL(kChromeUINewTabURL)
applicationMode:ApplicationModeForTabOpening::NORMAL];
if (IsIncognitoModeForced(browser_state_->GetPrefs())) {
// Set incognito mode to yes if only incognito mode is available.
startup_params.applicationMode = ApplicationModeForTabOpening::INCOGNITO;
}
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]) {
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
base::RecordAction(UserMetricsAction("IOSLaunchedByOpenInChromeIntent"));
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kOpenInChrome);
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]) {
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
base::RecordAction(UserMetricsAction("IOSLaunchedByOpenInIncognitoIntent"));
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kOpenInIncognito);
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]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kOpenLatestTab);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
AppStartupParameters* startup_params = [[AppStartupParameters alloc]
initWithExternalURL:GURL()
completeURL:GURL()
applicationMode:ApplicationModeForTabOpening::NORMAL];
startup_params.postOpeningAction = OPEN_LATEST_TAB;
[connection_information_ setStartupParameters:startup_params];
} else if ([user_activity.activityType
isEqualToString:kSiriOpenReadingList]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kOpenReadingList);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(
OPEN_READING_LIST)];
} else if ([user_activity.activityType isEqualToString:kSiriOpenBookmarks]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kOpenBookmarks);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(OPEN_BOOKMARKS)];
} else if ([user_activity.activityType isEqualToString:kSiriOpenRecentTabs]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kOpenRecentTabs);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(
OPEN_RECENT_TABS)];
} else if ([user_activity.activityType isEqualToString:kSiriOpenTabGrid]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kOpenTabGrid);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(OPEN_TAB_GRID)];
} else if ([user_activity.activityType isEqualToString:kSiriVoiceSearch]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kOpenVoiceSearch);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(
START_VOICE_SEARCH)];
} else if ([user_activity.activityType isEqualToString:kSiriOpenNewTab]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kOpenNewTab);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(NO_ACTION)];
} else if ([user_activity.activityType isEqualToString:kSiriPlayDinoGame]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kPlayDinoGame);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
webpage_url =
[NSURL URLWithString:base::SysUTF8ToNSString(kChromeDinoGameURL)];
} else if ([user_activity.activityType
isEqualToString:kSiriSetChromeDefaultBrowser]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kSetDefaultBrowser);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(
SET_CHROME_DEFAULT_BROWSER)];
} else if ([user_activity.activityType isEqualToString:kSiriViewHistory]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kViewHistory);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(VIEW_HISTORY)];
} else if ([user_activity.activityType
isEqualToString:kSiriOpenNewIncognitoTab]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kOpenNewIncognitoTab);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
AppStartupParameters* startup_params = [[AppStartupParameters alloc]
initWithExternalURL:GURL(kChromeUINewTabURL)
completeURL:GURL(kChromeUINewTabURL)
applicationMode:ApplicationModeForTabOpening::INCOGNITO];
[connection_information_ setStartupParameters:startup_params];
} else if ([user_activity.activityType
isEqualToString:kSiriManagePaymentMethods]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kManagePaymentMethods);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(
OPEN_PAYMENT_METHODS)];
} else if ([user_activity.activityType isEqualToString:kSiriRunSafetyCheck]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kRunSafetyCheck);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(
RUN_SAFETY_CHECK)];
} else if ([user_activity.activityType
isEqualToString:kSiriManagePasswords]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kManagePasswords);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(
MANAGE_PASSWORDS)];
} else if ([user_activity.activityType isEqualToString:kSiriManageSettings]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kManageSettings);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(
MANAGE_SETTINGS)];
} else if ([user_activity.activityType
isEqualToString:kSiriOpenLensFromIntents]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kStartLens);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(
START_LENS_FROM_INTENTS)];
} else if ([user_activity.activityType
isEqualToString:kSiriClearBrowsingData]) {
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kClearBrowsingData);
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
[connection_information_
setStartupParameters:StartupParametersForOpeningNewTab(
OPEN_CLEAR_BROWSING_DATA_DIALOG)];
} else {
// Do nothing for unknown activity type.
return NO;
}
return ContinueUserActivityURL(webpage_url, application_is_active, NO);
}
BOOL UserActivityBrowserAgent::Handle3DTouchApplicationShortcuts(
UIApplicationShortcutItem* shortcut_item) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
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;
}
void UserActivityBrowserAgent::RouteToCorrectTab() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
InitStage init_stage = browser_->GetSceneState().appState.initStage;
// 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 (init_stage <= InitStageFirstRun) {
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;
}
// 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.
ApplicationModeForTabOpening target_mode =
[[connection_information_ startupParameters] applicationMode];
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);
}
if (connection_information_.startupParameters.imageSearchData) {
TemplateURLService* template_url_service =
ios::TemplateURLServiceFactory::GetForBrowserState(browser_state_);
NSData* image_data =
connection_information_.startupParameters.imageSearchData;
web::NavigationManager::WebLoadParams web_load_params =
ImageSearchParamGenerator::LoadParamsForImageData(image_data, GURL(),
template_url_service);
params.web_params = web_load_params;
} else if (connection_information_.startupParameters.textQuery) {
NSString* query = connection_information_.startupParameters.textQuery;
GURL result = GenerateResultGURLFromSearchQuery(query);
params.web_params.url = result;
}
params.from_external = true;
if ([[connection_information_ startupParameters] applicationMode] !=
ApplicationModeForTabOpening::INCOGNITO &&
[tab_opener_ URLIsOpenedInRegularMode:params.web_params.url]) {
// Record metric.
}
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))];
}
BOOL UserActivityBrowserAgent::ProceedWithUserActivity(
NSUserActivity* user_activity) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NSArray* array = CompatibleModeForActivityType(user_activity.activityType);
PrefService* pref_service = browser_state_->GetPrefs();
if (IsIncognitoModeDisabled(pref_service)) {
return [array containsObject:kRegularMode];
}
if (IsIncognitoModeForced(pref_service)) {
return [array containsObject:kIncognitoMode];
}
// Return YES if the compatible mode array is not nil.
return array != nil;
}
#pragma mark - Internal methods.
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];
startup_params.postOpeningAction = action;
return startup_params;
}
BOOL UserActivityBrowserAgent::HandleShortcutItem(
UIApplicationShortcutItem* shortcut_item) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SceneState* scene_state = browser_->GetSceneState();
InitStage init_stage = scene_state.appState.initStage;
if (init_stage <= InitStageFirstRun) {
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] ||
[shortcut_item.type isEqualToString:kShortcutLensFromSpotlight])
? GURL()
: GURL(kChromeUINewTabURL);
AppStartupParameters* startup_params = [[AppStartupParameters alloc]
initWithExternalURL:startup_url
completeURL:startup_url
applicationMode:ApplicationModeForTabOpening::NORMAL];
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.applicationMode = ApplicationModeForTabOpening::INCOGNITO;
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:kShortcutLensFromSpotlight]) {
base::RecordAction(
UserMetricsAction("ApplicationShortcut.LensPressedFromSpotlight"));
startup_params.postOpeningAction = START_LENS_FROM_SPOTLIGHT;
connection_information_.startupParameters = startup_params;
return YES;
}
// Use 16 as the maximum length of the reported value for this key (15
// characters + '\0'). Expected values are UIApplicationShortcutItemType
// entries in Info.plist.
static crash_reporter::CrashKeyString<16> key("shortcut-item");
crash_reporter::ScopedCrashKeyString crash_key(
&key, base::SysNSStringToUTF8(shortcut_item.type));
base::debug::DumpWithoutCrashing();
return NO;
}
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];
[connection_information_ setStartupParameters:startup_params];
InitStage init_stage = browser_->GetSceneState().appState.initStage;
if (application_is_active && init_stage > InitStageFirstRun) {
// 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.applicationMode = ApplicationModeForTabOpening::UNDETERMINED;
if (incognito) {
startup_params.applicationMode = ApplicationModeForTabOpening::INCOGNITO;
}
[connection_information_ setStartupParameters:startup_params];
}
}
BOOL UserActivityBrowserAgent::ContinueUserActivityURL(
NSURL* webpage_url,
BOOL application_is_active,
BOOL open_existing_tab) {
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;
}
InitStage init_stage = browser_->GetSceneState().appState.initStage;
if (application_is_active && init_stage > InitStageFirstRun) {
// The app is already active so the applicationDidBecomeActive: method will
// never be called. Open the requested URL immediately.
ApplicationModeForTabOpening target_mode =
[[connection_information_ startupParameters] applicationMode];
UrlLoadParams params = UrlLoadParams::InNewTab(webpage_GURL);
if (connection_information_.startupParameters.textQuery) {
NSString* query = connection_information_.startupParameters.textQuery;
GURL result = GenerateResultGURLFromSearchQuery(query);
params.web_params.url = result;
}
if ([[connection_information_ startupParameters] applicationMode] !=
ApplicationModeForTabOpening::INCOGNITO &&
[tab_opener_ URLIsOpenedInRegularMode:webpage_GURL]) {
// Record metric.
}
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))];
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];
startup_params.openExistingTab = open_existing_tab;
[connection_information_ setStartupParameters:startup_params];
}
return YES;
}
void UserActivityBrowserAgent::OpenMultipleTabs() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
BOOL incognito_mode =
connection_information_.startupParameters.applicationMode ==
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 -> BrowserViewWrangler -> BrowserCoordinator
// -> BrowserViewController -> SceneController
base::OnceClosure closure =
base::BindOnce(&UserActivityBrowserAgent::ClearStartupParameters,
weak_ptr_factory_.GetWeakPtr());
[tab_opener_
dismissModalsAndOpenMultipleTabsWithURLs:connection_information_
.startupParameters.URLs
inIncognitoMode:incognito_mode
dismissOmnibox:dismiss_omnibox
completion:base::CallbackToBlock(
std::move(closure))];
}
GURL UserActivityBrowserAgent::GenerateResultGURLFromSearchQuery(
NSString* search_query) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
TemplateURLService* template_url_Service =
ios::TemplateURLServiceFactory::GetForBrowserState(browser_state_);
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;
ContinueUserActivityURL(webpage_url, is_active, open_existing_tab);
}
void UserActivityBrowserAgent::ClearStartupParameters() {
connection_information_.startupParameters = nil;
}