blob: 5b965148ad9df360817fe49cfbbce3840f34c58e [file] [log] [blame]
// 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/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.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/browser/intents/model/intent_type.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/metrics/model/first_user_action_recorder.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/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 "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;
}
// 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.
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
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]) {
base::UmaHistogramEnumeration(kAppLaunchSource,
AppLaunchSource::SIRI_SHORTCUT);
base::RecordAction(UserMetricsAction("IOSLaunchedBySearchInChromeIntent"));
base::UmaHistogramEnumeration("IOS.Spotlight.LaunchedIntentType",
IntentType::kSearchInChrome);
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]) {
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
forceApplicationMode:NO];
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
forceApplicationMode:NO];
[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_);
// 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;
}
if (base::FeatureList::IsEnabled(kChromeStartupParametersAsync)) {
base::OnceCallback<void(ApplicationModeForTabOpening)> completion =
base::BindOnce(&UserActivityBrowserAgent::HandleRouteToCorrectTab,
weak_ptr_factory_.GetWeakPtr());
[connection_information_.startupParameters
requestApplicationModeWithBlock:base::CallbackToBlock(
std::move(completion))];
} else {
HandleRouteToCorrectTab(
[connection_information_.startupParameters applicationMode]);
}
}
BOOL UserActivityBrowserAgent::ProceedWithUserActivity(
NSUserActivity* user_activity) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
NSArray* array = CompatibleModeForActivityType(user_activity.activityType);
PrefService* pref_service = profile_->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
forceApplicationMode:NO];
startup_params.postOpeningAction = action;
return startup_params;
}
BOOL UserActivityBrowserAgent::HandleShortcutItem(
UIApplicationShortcutItem* shortcut_item) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
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] ||
[shortcut_item.type isEqualToString:kShortcutLensFromSpotlight])
? 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:kShortcutLensFromSpotlight]) {
base::RecordAction(
UserMetricsAction("ApplicationShortcut.LensPressedFromSpotlight"));
startup_params.postOpeningAction = START_LENS_FROM_SPOTLIGHT;
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;
}
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];
}
}
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;
}
if (application_is_active && IsProfileStateReady(browser_)) {
// The app is already active so the applicationDidBecomeActive: method will
// never be called. Open the requested URL immediately.
if (base::FeatureList::IsEnabled(kChromeStartupParametersAsync)) {
base::OnceCallback<void(ApplicationModeForTabOpening)> completion =
base::BindOnce(&UserActivityBrowserAgent::HandleUrlOpening,
weak_ptr_factory_.GetWeakPtr(), webpage_GURL);
[connection_information_.startupParameters
requestApplicationModeWithBlock:base::CallbackToBlock(
std::move(completion))];
} else {
HandleUrlOpening(
webpage_GURL,
[connection_information_.startupParameters applicationMode]);
}
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;
[connection_information_ setStartupParameters:startup_params];
}
return YES;
}
void UserActivityBrowserAgent::OpenMultipleTabs() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const std::vector<GURL>& URLs =
connection_information_.startupParameters.URLs;
if (base::FeatureList::IsEnabled(kChromeStartupParametersAsync)) {
base::OnceCallback<void(ApplicationModeForTabOpening)> completion =
base::BindOnce(&UserActivityBrowserAgent::HandleMultipleUrlsOpening,
weak_ptr_factory_.GetWeakPtr(), URLs);
[connection_information_.startupParameters
requestApplicationModeWithBlock:base::CallbackToBlock(
std::move(completion))];
} else {
HandleMultipleUrlsOpening(
URLs, [connection_information_.startupParameters applicationMode]);
}
}
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;
ContinueUserActivityURL(webpage_url, is_active, open_existing_tab);
}
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);
}
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;
web::NavigationManager::WebLoadParams web_load_params =
ImageSearchParamGenerator::LoadParamsForImageData(
image_data, GURL(), template_url_service);
params.web_params = web_load_params;
}
} 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;
}
params.from_external = true;
if (target_mode != 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))];
}
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;
}
if (target_mode != ApplicationModeForTabOpening::INCOGNITO &&
[tab_opener_ URLIsOpenedInRegularMode:webpage_url]) {
// 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))];
}
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 -> BrowserViewWrangler -> 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))];
}