| Index: ios/chrome/app/application_delegate/user_activity_handler.mm
|
| diff --git a/ios/chrome/app/application_delegate/user_activity_handler.mm b/ios/chrome/app/application_delegate/user_activity_handler.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..6b3b9e8c9112b7c4154bce1a118aa998ea483384
|
| --- /dev/null
|
| +++ b/ios/chrome/app/application_delegate/user_activity_handler.mm
|
| @@ -0,0 +1,320 @@
|
| +// Copyright 2016 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#import "ios/chrome/app/application_delegate/user_activity_handler.h"
|
| +
|
| +#import <CoreSpotlight/CoreSpotlight.h>
|
| +#import <UIKit/UIKit.h>
|
| +
|
| +#include "base/ios/block_types.h"
|
| +#include "base/ios/ios_util.h"
|
| +#include "base/ios/weak_nsobject.h"
|
| +#include "base/mac/foundation_util.h"
|
| +#include "base/metrics/histogram_macros.h"
|
| +#include "base/metrics/user_metrics_action.h"
|
| +#include "base/strings/sys_string_conversions.h"
|
| +#include "components/handoff/handoff_utility.h"
|
| +#import "ios/chrome/app/application_delegate/startup_information.h"
|
| +#import "ios/chrome/app/application_delegate/tab_opening.h"
|
| +#include "ios/chrome/app/application_mode.h"
|
| +#import "ios/chrome/app/spotlight/actions_spotlight_manager.h"
|
| +#import "ios/chrome/app/spotlight/spotlight_util.h"
|
| +#include "ios/chrome/browser/app_startup_parameters.h"
|
| +#include "ios/chrome/browser/chrome_url_constants.h"
|
| +#include "ios/chrome/browser/experimental_flags.h"
|
| +#include "ios/chrome/browser/metrics/first_user_action_recorder.h"
|
| +#import "ios/chrome/browser/tabs/tab.h"
|
| +#import "ios/chrome/browser/tabs/tab_model.h"
|
| +#import "ios/chrome/browser/u2f/u2f_controller.h"
|
| +#import "ios/chrome/browser/ui/main/browser_view_information.h"
|
| +#import "net/base/mac/url_conversions.h"
|
| +#include "ui/base/page_transition_types.h"
|
| +#include "url/gurl.h"
|
| +
|
| +using base::UserMetricsAction;
|
| +
|
| +namespace {
|
| +// Constants for 3D touch application static shortcuts.
|
| +NSString* const kShortcutNewTab = @"OpenNewTab";
|
| +NSString* const kShortcutNewIncognitoTab = @"OpenIncognitoTab";
|
| +NSString* const kShortcutVoiceSearch = @"OpenVoiceSearch";
|
| +NSString* const kShortcutQRScanner = @"OpenQRScanner";
|
| +} // namespace
|
| +
|
| +@interface UserActivityHandler ()
|
| +// Handles the 3D touch application static items. Does nothing if in first run.
|
| ++ (BOOL)handleShortcutItem:(UIApplicationShortcutItem*)shortcutItem
|
| + startupInformation:(id<StartupInformation>)startupInformation;
|
| +// Routes Universal 2nd Factor (U2F) callback to the correct Tab.
|
| ++ (void)routeU2FURL:(const GURL&)URL
|
| + browserViewInformation:(id<BrowserViewInformation>)browserViewInformation;
|
| +@end
|
| +
|
| +@implementation UserActivityHandler
|
| +
|
| +#pragma mark - Public methods.
|
| +
|
| ++ (BOOL)continueUserActivity:(NSUserActivity*)userActivity
|
| + applicationIsActive:(BOOL)applicationIsActive
|
| + tabOpener:(id<TabOpening>)tabOpener
|
| + startupInformation:(id<StartupInformation>)startupInformation {
|
| + NSURL* webpageURL = userActivity.webpageURL;
|
| +
|
| + if ([userActivity.activityType
|
| + isEqualToString:handoff::kChromeHandoffActivityType]) {
|
| + // App was launched by iOS as a result of Handoff.
|
| + NSString* originString = base::mac::ObjCCast<NSString>(
|
| + userActivity.userInfo[handoff::kOriginKey]);
|
| + handoff::Origin origin = handoff::OriginFromString(originString);
|
| + UMA_HISTOGRAM_ENUMERATION("IOS.Handoff.Origin", origin,
|
| + handoff::ORIGIN_COUNT);
|
| + } else if ([userActivity.activityType
|
| + isEqualToString:NSUserActivityTypeBrowsingWeb]) {
|
| + // App was launched as the result of a Universal Link navigation. The value
|
| + // of userActivity.webpageURL is not used. The only supported action
|
| + // at this time is opening a New Tab Page.
|
| + GURL newTabURL(kChromeUINewTabURL);
|
| + webpageURL = net::NSURLWithGURL(newTabURL);
|
| + base::scoped_nsobject<AppStartupParameters> startupParams(
|
| + [[AppStartupParameters alloc] initWithExternalURL:newTabURL]);
|
| + [startupInformation setStartupParameters:startupParams];
|
| + base::RecordAction(base::UserMetricsAction("IOSLaunchedByUniversalLink"));
|
| + } else if (spotlight::IsSpotlightAvailable() &&
|
| + [userActivity.activityType
|
| + isEqualToString:CSSearchableItemActionType]) {
|
| + // App was launched by iOS as the result of a tap on a Spotlight Search
|
| + // result.
|
| + NSString* itemID =
|
| + [userActivity.userInfo objectForKey:CSSearchableItemActivityIdentifier];
|
| + spotlight::Domain domain = spotlight::SpotlightDomainFromString(itemID);
|
| + if (domain == spotlight::DOMAIN_ACTIONS &&
|
| + !experimental_flags::IsSpotlightActionsEnabled()) {
|
| + return NO;
|
| + }
|
| + UMA_HISTOGRAM_ENUMERATION("IOS.Spotlight.Origin", domain,
|
| + spotlight::DOMAIN_COUNT);
|
| +
|
| + if (!itemID) {
|
| + return NO;
|
| + }
|
| + if (domain == spotlight::DOMAIN_ACTIONS) {
|
| + webpageURL =
|
| + [NSURL URLWithString:base::SysUTF8ToNSString(kChromeUINewTabURL)];
|
| + base::scoped_nsobject<AppStartupParameters> startupParams(
|
| + [[AppStartupParameters alloc]
|
| + initWithExternalURL:GURL(kChromeUINewTabURL)]);
|
| + BOOL startupParamsSet = spotlight::SetStartupParametersForSpotlightAction(
|
| + itemID, startupParams);
|
| + if (!startupParamsSet) {
|
| + return NO;
|
| + }
|
| + [startupInformation setStartupParameters:startupParams];
|
| + } else if (!webpageURL && base::ios::IsRunningOnIOS10OrLater()) {
|
| + // spotlight::GetURLForSpotlightItemID uses CSSearchQuery, which is only
|
| + // supported from iOS 10.
|
| + spotlight::GetURLForSpotlightItemID(itemID, ^(NSURL* contentURL) {
|
| + if (!contentURL) {
|
| + return;
|
| + }
|
| + dispatch_async(dispatch_get_main_queue(), ^{
|
| + // Update the isActive flag as it may have changed during the async
|
| + // calls.
|
| + BOOL isActive = [[UIApplication sharedApplication]
|
| + applicationState] == UIApplicationStateActive;
|
| + [self continueUserActivityURL:contentURL
|
| + applicationIsActive:isActive
|
| + tabOpener:tabOpener
|
| + startupInformation:startupInformation];
|
| + });
|
| + });
|
| + return YES;
|
| + }
|
| + } else {
|
| + // Do nothing for unknown activity type.
|
| + return NO;
|
| + }
|
| +
|
| + return [self continueUserActivityURL:webpageURL
|
| + applicationIsActive:applicationIsActive
|
| + tabOpener:tabOpener
|
| + startupInformation:startupInformation];
|
| +}
|
| +
|
| ++ (BOOL)continueUserActivityURL:(NSURL*)webpageURL
|
| + applicationIsActive:(BOOL)applicationIsActive
|
| + tabOpener:(id<TabOpening>)tabOpener
|
| + startupInformation:(id<StartupInformation>)startupInformation {
|
| + if (!webpageURL)
|
| + return NO;
|
| +
|
| + GURL webpageGURL(net::GURLWithNSURL(webpageURL));
|
| + if (!webpageGURL.is_valid())
|
| + return NO;
|
| +
|
| + if (applicationIsActive && ![startupInformation isPresentingFirstRunUI]) {
|
| + // The app is already active so the applicationDidBecomeActive: method will
|
| + // never be called. Open the requested URL immediately.
|
| + ApplicationMode targetMode =
|
| + [[startupInformation startupParameters] launchInIncognito]
|
| + ? ApplicationMode::INCOGNITO
|
| + : ApplicationMode::NORMAL;
|
| + [tabOpener dismissModalsAndOpenSelectedTabInMode:targetMode
|
| + withURL:webpageGURL
|
| + transition:ui::PAGE_TRANSITION_LINK
|
| + completion:^{
|
| + [startupInformation
|
| + setStartupParameters:nil];
|
| + }];
|
| + return YES;
|
| + }
|
| +
|
| + // Don't record the first action as a user action, since it will not be
|
| + // initiated by the user.
|
| + [startupInformation resetFirstUserActionRecorder];
|
| +
|
| + if (![startupInformation startupParameters]) {
|
| + base::scoped_nsobject<AppStartupParameters> startupParams(
|
| + [[AppStartupParameters alloc] initWithExternalURL:webpageGURL]);
|
| + [startupInformation setStartupParameters:startupParams];
|
| + }
|
| + return YES;
|
| +}
|
| +
|
| ++ (void)performActionForShortcutItem:(UIApplicationShortcutItem*)shortcutItem
|
| + completionHandler:(void (^)(BOOL succeeded))completionHandler
|
| + tabOpener:(id<TabOpening>)tabOpener
|
| + startupInformation:(id<StartupInformation>)startupInformation
|
| + browserViewInformation:
|
| + (id<BrowserViewInformation>)browserViewInformation {
|
| + BOOL handledShortcutItem =
|
| + [UserActivityHandler handleShortcutItem:shortcutItem
|
| + startupInformation:startupInformation];
|
| + if (handledShortcutItem) {
|
| + [UserActivityHandler
|
| + handleStartupParametersWithTabOpener:tabOpener
|
| + startupInformation:startupInformation
|
| + browserViewInformation:browserViewInformation];
|
| + }
|
| + completionHandler(handledShortcutItem);
|
| +}
|
| +
|
| ++ (BOOL)willContinueUserActivityWithType:(NSString*)userActivityType {
|
| + return
|
| + [userActivityType isEqualToString:handoff::kChromeHandoffActivityType] ||
|
| + (spotlight::IsSpotlightAvailable() &&
|
| + [userActivityType isEqualToString:CSSearchableItemActionType]);
|
| +}
|
| +
|
| ++ (void)handleStartupParametersWithTabOpener:(id<TabOpening>)tabOpener
|
| + startupInformation:
|
| + (id<StartupInformation>)startupInformation
|
| + browserViewInformation:
|
| + (id<BrowserViewInformation>)browserViewInformation {
|
| + DCHECK([startupInformation startupParameters]);
|
| + // 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 ([startupInformation isPresentingFirstRunUI])
|
| + return;
|
| +
|
| + // Check if it's an U2F call. If so, route it to correct tab.
|
| + // If not, open or reuse tab in main BVC.
|
| + if ([U2FController
|
| + isU2FURL:[[startupInformation startupParameters] externalURL]]) {
|
| + [UserActivityHandler routeU2FURL:[[startupInformation startupParameters]
|
| + externalURL]
|
| + browserViewInformation:browserViewInformation];
|
| + // It's OK to clear startup parameters here because routeU2FURL works
|
| + // synchronously.
|
| + [startupInformation setStartupParameters:nil];
|
| + } else {
|
| + // 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 with requested URL.
|
| + ApplicationMode targetMode =
|
| + [[startupInformation startupParameters] launchInIncognito]
|
| + ? ApplicationMode::INCOGNITO
|
| + : ApplicationMode::NORMAL;
|
| + [tabOpener dismissModalsAndOpenSelectedTabInMode:targetMode
|
| + withURL:[[startupInformation
|
| + startupParameters]
|
| + externalURL]
|
| + transition:ui::PAGE_TRANSITION_LINK
|
| + completion:^{
|
| + [startupInformation
|
| + setStartupParameters:nil];
|
| + }];
|
| + }
|
| +}
|
| +
|
| +#pragma mark - Internal methods.
|
| +
|
| ++ (BOOL)handleShortcutItem:(UIApplicationShortcutItem*)shortcutItem
|
| + startupInformation:(id<StartupInformation>)startupInformation {
|
| + if ([startupInformation isPresentingFirstRunUI])
|
| + return NO;
|
| +
|
| + base::scoped_nsobject<AppStartupParameters> startupParams(
|
| + [[AppStartupParameters alloc]
|
| + initWithExternalURL:GURL(kChromeUINewTabURL)]);
|
| +
|
| + if ([shortcutItem.type isEqualToString:kShortcutNewTab]) {
|
| + base::RecordAction(UserMetricsAction("ApplicationShortcut.NewTabPressed"));
|
| + [startupInformation setStartupParameters:startupParams];
|
| + return YES;
|
| +
|
| + } else if ([shortcutItem.type isEqualToString:kShortcutNewIncognitoTab]) {
|
| + base::RecordAction(
|
| + UserMetricsAction("ApplicationShortcut.NewIncognitoTabPressed"));
|
| + [startupParams setLaunchInIncognito:YES];
|
| + [startupInformation setStartupParameters:startupParams];
|
| + return YES;
|
| +
|
| + } else if ([shortcutItem.type isEqualToString:kShortcutVoiceSearch]) {
|
| + base::RecordAction(
|
| + UserMetricsAction("ApplicationShortcut.VoiceSearchPressed"));
|
| + [startupParams setLaunchVoiceSearch:YES];
|
| + [startupInformation setStartupParameters:startupParams];
|
| + return YES;
|
| +
|
| + } else if ([shortcutItem.type isEqualToString:kShortcutQRScanner]) {
|
| + if (experimental_flags::IsQRCodeReaderEnabled()) {
|
| + base::RecordAction(
|
| + UserMetricsAction("ApplicationShortcut.ScanQRCodePressed"));
|
| + [startupParams setLaunchQRScanner:YES];
|
| + }
|
| + [startupInformation setStartupParameters:startupParams];
|
| + return YES;
|
| + }
|
| +
|
| + NOTREACHED();
|
| + return NO;
|
| +}
|
| +
|
| ++ (void)routeU2FURL:(const GURL&)URL
|
| + browserViewInformation:(id<BrowserViewInformation>)browserViewInformation {
|
| + // Retrieve the designated TabID from U2F URL.
|
| + NSString* tabID = [U2FController tabIDFromResponseURL:URL];
|
| + if (!tabID) {
|
| + return;
|
| + }
|
| +
|
| + // TODO(crbug.com/619598): move this code to BrowserViewInformation to hide
|
| + // implementation details of TabModel.
|
| + // Iterate through mainTabModel and OTRTabModel to find the corresponding tab.
|
| + NSArray* tabModels = @[
|
| + [browserViewInformation mainTabModel], [browserViewInformation otrTabModel]
|
| + ];
|
| + for (TabModel* tabModel in tabModels) {
|
| + for (Tab* tab in tabModel) {
|
| + if ([tab.tabId isEqualToString:tabID]) {
|
| + [tab evaluateU2FResultFromURL:URL];
|
| + return;
|
| + }
|
| + }
|
| + }
|
| +}
|
| +
|
| +@end
|
|
|