Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(183)

Side by Side Diff: ios/chrome/browser/web/external_app_launcher.mm

Issue 2878813003: Prompts user before transferring to App Store (Closed)
Patch Set: clarified comments and renamed method. Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2012 The Chromium Authors. All rights reserved. 1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #import "ios/chrome/browser/web/external_app_launcher.h" 5 #import "ios/chrome/browser/web/external_app_launcher.h"
6 6
7 #include "base/ios/ios_util.h" 7 #include "base/ios/ios_util.h"
8 #include "base/logging.h" 8 #include "base/logging.h"
9 #include "base/mac/foundation_util.h"
9 #include "base/metrics/histogram_macros.h" 10 #include "base/metrics/histogram_macros.h"
10 #include "base/strings/sys_string_conversions.h" 11 #include "base/strings/sys_string_conversions.h"
11 #include "components/strings/grit/components_strings.h" 12 #include "components/strings/grit/components_strings.h"
12 #include "ios/chrome/browser/experimental_flags.h" 13 #include "ios/chrome/browser/experimental_flags.h"
13 #import "ios/chrome/browser/open_url_util.h" 14 #import "ios/chrome/browser/open_url_util.h"
14 #import "ios/chrome/browser/web/mailto_url_rewriter.h" 15 #import "ios/chrome/browser/web/mailto_url_rewriter.h"
15 #include "ios/chrome/grit/ios_strings.h" 16 #include "ios/chrome/grit/ios_strings.h"
16 #import "net/base/mac/url_conversions.h" 17 #import "net/base/mac/url_conversions.h"
17 #include "ui/base/l10n/l10n_util.h" 18 #include "ui/base/l10n/l10n_util.h"
18 #include "url/gurl.h" 19 #include "url/gurl.h"
19 #include "url/url_constants.h" 20 #include "url/url_constants.h"
20 21
21 #if !defined(__has_feature) || !__has_feature(objc_arc) 22 #if !defined(__has_feature) || !__has_feature(objc_arc)
22 #error "This file requires ARC support." 23 #error "This file requires ARC support."
23 #endif 24 #endif
24 25
25 namespace { 26 namespace {
26 27
27 typedef void (^AlertHandler)(UIAlertAction* action); 28 typedef void (^AlertHandler)(UIAlertAction* action);
28 29
29 // Returns a set of NSStrings that are URL schemes for iTunes Stores. 30 // Returns a set of NSStrings that are URL schemes for iTunes Stores.
30 NSSet<NSString*>* ITMSSchemes() { 31 NSSet<NSString*>* ITMSSchemes() {
31 static NSSet<NSString*>* schemes; 32 static NSSet<NSString*>* schemes;
32 static dispatch_once_t once; 33 static dispatch_once_t once;
33 dispatch_once(&once, ^{ 34 dispatch_once(&once, ^{
34 schemes = 35 schemes = [NSSet<NSString*>
35 [NSSet setWithObjects:@"itms", @"itmss", @"itms-apps", @"itms-appss", 36 setWithObjects:@"itms", @"itmss", @"itms-apps", @"itms-appss",
36 // There's no evidence that itms-bookss is 37 // There's no evidence that itms-bookss is actually
37 // actually supported, but over-inclusion 38 // supported, but over-inclusion costs less than
38 // costs less than under-inclusion. 39 // under-inclusion.
39 @"itms-books", @"itms-bookss", nil]; 40 @"itms-books", @"itms-bookss", nil];
40 }); 41 });
41 return schemes; 42 return schemes;
42 } 43 }
43 44
44 bool UrlHasAppStoreScheme(const GURL& gURL) { 45 bool UrlHasAppStoreScheme(const GURL& gURL) {
45 std::string scheme = gURL.scheme(); 46 std::string scheme = gURL.scheme();
46 return [ITMSSchemes() containsObject:base::SysUTF8ToNSString(scheme)]; 47 return [ITMSSchemes() containsObject:base::SysUTF8ToNSString(scheme)];
47 } 48 }
48 49
49 // Logs an entry for |Tab.ExternalApplicationOpened|. If the user decided to 50 // Logs an entry for |Tab.ExternalApplicationOpened|. If the user decided to
(...skipping 13 matching lines...) Expand all
63 NSString* PromptActionString(NSString* scheme) { 64 NSString* PromptActionString(NSString* scheme) {
64 if ([scheme isEqualToString:@"facetime"]) 65 if ([scheme isEqualToString:@"facetime"])
65 return l10n_util::GetNSString(IDS_IOS_FACETIME_BUTTON); 66 return l10n_util::GetNSString(IDS_IOS_FACETIME_BUTTON);
66 else if ([scheme isEqualToString:@"tel"] || 67 else if ([scheme isEqualToString:@"tel"] ||
67 [scheme isEqualToString:@"facetime-audio"]) 68 [scheme isEqualToString:@"facetime-audio"])
68 return l10n_util::GetNSString(IDS_IOS_PHONE_CALL_BUTTON); 69 return l10n_util::GetNSString(IDS_IOS_PHONE_CALL_BUTTON);
69 NOTREACHED(); 70 NOTREACHED();
70 return @""; 71 return @"";
71 } 72 }
72 73
74 // Keys for dictionary parameters passed into
75 // -openExternalAppWithPromptWithParams:.
76 NSString* const kAlertPrompt = @"prompt";
77 NSString* const kAlertAction = @"action";
78 NSString* const kAlertURL = @"url";
79
73 } // namespace 80 } // namespace
74 81
75 @interface ExternalAppLauncher () 82 @interface ExternalAppLauncher ()
76 // Returns the Phone/FaceTime call argument from |URL|. 83 // Returns the Phone/FaceTime call argument from |URL|.
77 + (NSString*)formatCallArgument:(NSURL*)URL; 84 + (NSString*)formatCallArgument:(NSURL*)URL;
78 // Ask user for confirmation before dialing Phone or FaceTime destinations. 85 // Asks user for confirmation before moving to external app. |params| is a
79 - (void)openPromptForURL:(NSURL*)URL; 86 // dictionary keyed with values of kAlert* above.
80 // Ask user for confirmation before moving to external app. 87 - (void)openExternalAppWithPromptWithParams:
81 - (void)openExternalAppWithPromptForURL:(NSURL*)URL; 88 (NSDictionary<NSString*, id>*)params;
82 // Presents a configured alert controller on the root view controller. 89 // Presents a configured alert controller on the root view controller.
83 - (void)presentAlertControllerWithMessage:(NSString*)message 90 - (void)presentAlertControllerWithMessage:(NSString*)message
84 openTitle:(NSString*)openTitle 91 openTitle:(NSString*)openTitle
85 openHandler:(AlertHandler)openHandler 92 openHandler:(AlertHandler)openHandler
86 cancelHandler:(AlertHandler)cancelHandler; 93 cancelHandler:(AlertHandler)cancelHandler;
87 @end 94 @end
88 95
89 @implementation ExternalAppLauncher 96 @implementation ExternalAppLauncher
90 97
91 + (NSString*)formatCallArgument:(NSURL*)URL { 98 + (NSString*)formatCallArgument:(NSURL*)URL {
92 NSCharacterSet* charSet = 99 NSCharacterSet* charSet =
93 [NSCharacterSet characterSetWithCharactersInString:@"/"]; 100 [NSCharacterSet characterSetWithCharactersInString:@"/"];
94 NSString* scheme = [NSString stringWithFormat:@"%@:", [URL scheme]]; 101 NSString* scheme = [NSString stringWithFormat:@"%@:", [URL scheme]];
95 NSString* URLString = [URL absoluteString]; 102 NSString* URLString = [URL absoluteString];
96 if ([URLString length] <= [scheme length]) 103 if ([URLString length] <= [scheme length])
97 return URLString; 104 return URLString;
98 NSString* prompt = [[[[URL absoluteString] substringFromIndex:[scheme length]] 105 NSString* prompt = [[[[URL absoluteString] substringFromIndex:[scheme length]]
99 stringByTrimmingCharactersInSet:charSet] stringByRemovingPercentEncoding]; 106 stringByTrimmingCharactersInSet:charSet] stringByRemovingPercentEncoding];
100 // Returns original URL string if there's nothing interesting to display 107 // Returns original URL string if there's nothing interesting to display
101 // other than the scheme itself. 108 // other than the scheme itself.
102 if (![prompt length]) 109 if (![prompt length])
103 return URLString; 110 return URLString;
104 return prompt; 111 return prompt;
105 } 112 }
106 113
107 - (void)openExternalAppWithPromptForURL:(NSURL*)URL { 114 - (void)openExternalAppWithPromptWithParams:
108 NSString* message = l10n_util::GetNSString(IDS_IOS_OPEN_IN_ANOTHER_APP); 115 (NSDictionary<NSString*, id>*)params {
109 NSString* openTitle = 116 NSURL* URL = base::mac::ObjCCastStrict<NSURL>(params[kAlertURL]);
110 l10n_util::GetNSString(IDS_IOS_APP_LAUNCHER_OPEN_APP_BUTTON_LABEL); 117 NSString* prompt = base::mac::ObjCCastStrict<NSString>(params[kAlertPrompt]);
111 [self presentAlertControllerWithMessage:message 118 NSString* actionTitle =
112 openTitle:openTitle 119 base::mac::ObjCCastStrict<NSString>(params[kAlertAction]);
120 [self presentAlertControllerWithMessage:prompt
121 openTitle:actionTitle
113 openHandler:^(UIAlertAction* action) { 122 openHandler:^(UIAlertAction* action) {
114 RecordExternalApplicationOpened(true); 123 RecordExternalApplicationOpened(true);
115 OpenUrlWithCompletionHandler(URL, nil); 124 OpenUrlWithCompletionHandler(URL, nil);
116 } 125 }
117 cancelHandler:^(UIAlertAction* action) { 126 cancelHandler:^(UIAlertAction* action) {
118 RecordExternalApplicationOpened(false); 127 RecordExternalApplicationOpened(false);
119 }]; 128 }];
120 } 129 }
121 130
122 - (void)openPromptForURL:(NSURL*)URL {
123 [self presentAlertControllerWithMessage:[[self class] formatCallArgument:URL]
124 openTitle:PromptActionString(URL.scheme)
125 openHandler:^(UIAlertAction* action) {
126 OpenUrlWithCompletionHandler(URL, nil);
127 }
128 cancelHandler:nil];
129 }
130
131 - (void)presentAlertControllerWithMessage:(NSString*)message 131 - (void)presentAlertControllerWithMessage:(NSString*)message
132 openTitle:(NSString*)openTitle 132 openTitle:(NSString*)openTitle
133 openHandler:(AlertHandler)openHandler 133 openHandler:(AlertHandler)openHandler
134 cancelHandler:(AlertHandler)cancelHandler { 134 cancelHandler:(AlertHandler)cancelHandler {
135 UIAlertController* alertController = 135 UIAlertController* alertController =
136 [UIAlertController alertControllerWithTitle:nil 136 [UIAlertController alertControllerWithTitle:nil
137 message:message 137 message:message
138 preferredStyle:UIAlertControllerStyleAlert]; 138 preferredStyle:UIAlertControllerStyleAlert];
139 UIAlertAction* openAction = 139 UIAlertAction* openAction =
140 [UIAlertAction actionWithTitle:openTitle 140 [UIAlertAction actionWithTitle:openTitle
141 style:UIAlertActionStyleDefault 141 style:UIAlertActionStyleDefault
142 handler:openHandler]; 142 handler:openHandler];
143 UIAlertAction* cancelAction = 143 UIAlertAction* cancelAction =
144 [UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_CANCEL) 144 [UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_CANCEL)
145 style:UIAlertActionStyleCancel 145 style:UIAlertActionStyleCancel
146 handler:cancelHandler]; 146 handler:cancelHandler];
147 [alertController addAction:cancelAction]; 147 [alertController addAction:cancelAction];
148 [alertController addAction:openAction]; 148 [alertController addAction:openAction];
149 149
150 [[[[UIApplication sharedApplication] keyWindow] rootViewController] 150 [[[[UIApplication sharedApplication] keyWindow] rootViewController]
151 presentViewController:alertController 151 presentViewController:alertController
152 animated:YES 152 animated:YES
153 completion:nil]; 153 completion:nil];
154 } 154 }
155 155
156 - (BOOL)openURL:(const GURL&)gURL linkClicked:(BOOL)linkClicked { 156 - (BOOL)openURL:(const GURL&)gURL linkClicked:(BOOL)linkClicked {
157 if (!gURL.is_valid() || !gURL.has_scheme()) 157 if (!gURL.is_valid() || !gURL.has_scheme())
158 return NO; 158 return NO;
159 NSURL* URL = net::NSURLWithGURL(gURL);
160
161 // iOS 10.3 introduced new prompts when facetime: and facetime-audio:
162 // URL schemes are opened. It is no longer necessary for Chrome to present
163 // another prompt before the system-provided prompt.
164 if (!base::ios::IsRunningOnOrLater(10, 3, 0) && UrlHasPhoneCallScheme(gURL)) {
165 // Showing an alert view immediately has a side-effect where focus is
166 // taken from the UIWebView so quickly that mouseup events are lost and
167 // buttons get 'stuck' in the on position. The solution is to defer
168 // showing the view.
169 [self performSelector:@selector(openPromptForURL:)
170 withObject:URL
171 afterDelay:0.0];
172 return YES;
173 }
174 159
175 // Don't open external application if chrome is not active. 160 // Don't open external application if chrome is not active.
176 if ([[UIApplication sharedApplication] applicationState] != 161 if ([[UIApplication sharedApplication] applicationState] !=
177 UIApplicationStateActive) 162 UIApplicationStateActive)
178 return NO; 163 return NO;
179 164
180 // Prompt user to open itunes when opening it is not a result of a link 165 NSURL* URL = net::NSURLWithGURL(gURL);
181 // click. 166 NSMutableDictionary<NSString*, id>* params = [NSMutableDictionary dictionary];
182 if (!linkClicked && UrlHasAppStoreScheme(gURL)) { 167 if (base::ios::IsRunningOnOrLater(10, 3, 0)) {
183 [self performSelector:@selector(openExternalAppWithPromptForURL:) 168 if (UrlHasAppStoreScheme(gURL)) {
184 withObject:URL 169 params[kAlertPrompt] =
170 l10n_util::GetNSString(IDS_IOS_OPEN_IN_ANOTHER_APP);
171 params[kAlertAction] =
172 l10n_util::GetNSString(IDS_IOS_APP_LAUNCHER_OPEN_APP_BUTTON_LABEL);
173 }
174 } else {
175 // Prior to iOS 10.3, iOS does not prompt user when facetime: and
176 // facetime-audio: URL schemes are opened, so Chrome needs to present an
177 // alert before placing a phone call.
178 if (UrlHasPhoneCallScheme(gURL)) {
179 params[kAlertPrompt] = [[self class] formatCallArgument:URL];
180 params[kAlertAction] = PromptActionString([URL scheme]);
181 }
182 // Prior to iOS 10.3, Chrome prompts user with an alert before opening
183 // App Store when user did not tap on any links and an iTunes app URL is
184 // opened. This maintains parity with Safari in pre-10.3 environment.
185 if (!linkClicked && UrlHasAppStoreScheme(gURL)) {
186 params[kAlertPrompt] =
187 l10n_util::GetNSString(IDS_IOS_OPEN_IN_ANOTHER_APP);
188 params[kAlertAction] =
189 l10n_util::GetNSString(IDS_IOS_APP_LAUNCHER_OPEN_APP_BUTTON_LABEL);
190 }
191 }
192 if ([params count] > 0) {
193 params[kAlertURL] = URL;
194 // Note: Showing an alert view immediately has a side-effect where focus is
195 // taken from the UIWebView so quickly that mouseup events are lost and
196 // buttons get 'stuck' in the on position. The solution is to defer
197 // showing the view using -performSelector:withObject:afterDelay:.
198 [self performSelector:@selector(openExternalAppWithPromptWithParams:)
199 withObject:params
185 afterDelay:0.0]; 200 afterDelay:0.0];
186 return YES; 201 return YES;
187 } 202 }
188 203
189 // Replaces |URL| with a rewritten URL if it is of mailto: scheme. 204 // Replaces |URL| with a rewritten URL if it is of mailto: scheme.
190 if (!experimental_flags::IsNativeAppLauncherEnabled() && 205 if (!experimental_flags::IsNativeAppLauncherEnabled() &&
191 gURL.SchemeIs(url::kMailToScheme)) { 206 gURL.SchemeIs(url::kMailToScheme)) {
192 MailtoURLRewriter* rewriter = 207 MailtoURLRewriter* rewriter =
193 [[MailtoURLRewriter alloc] initWithStandardHandlers]; 208 [[MailtoURLRewriter alloc] initWithStandardHandlers];
194 NSString* launchURL = [rewriter rewriteMailtoURL:gURL]; 209 NSString* launchURL = [rewriter rewriteMailtoURL:gURL];
195 if (launchURL) { 210 if (launchURL) {
196 URL = [NSURL URLWithString:launchURL]; 211 URL = [NSURL URLWithString:launchURL];
197 } 212 }
198 UMA_HISTOGRAM_BOOLEAN("IOS.MailtoURLRewritten", launchURL != nil); 213 UMA_HISTOGRAM_BOOLEAN("IOS.MailtoURLRewritten", launchURL != nil);
199 } 214 }
200 215
201 // If the following call returns YES, an external application is about to be 216 // If the following call returns YES, an external application is about to be
202 // launched and Chrome will go into the background now. 217 // launched and Chrome will go into the background now.
203 // TODO(crbug.com/622735): This call still needs to be updated. 218 // TODO(crbug.com/622735): This call still needs to be updated.
204 // It's heavily nested so some refactoring is needed. 219 // It's heavily nested so some refactoring is needed.
205 return [[UIApplication sharedApplication] openURL:URL]; 220 return [[UIApplication sharedApplication] openURL:URL];
206 } 221 }
207 222
208 @end 223 @end
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698