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

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

Issue 2878813003: Prompts user before transferring to App Store (Closed)
Patch Set: comments 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 -openExternalAppWithPromptForURL:.
75 NSString* const kAlertPrompt = @"prompt";
76 NSString* const kAlertAction = @"action";
77 NSString* const kAlertURL = @"url";
78
73 } // namespace 79 } // namespace
74 80
75 @interface ExternalAppLauncher () 81 @interface ExternalAppLauncher ()
76 // Returns the Phone/FaceTime call argument from |URL|. 82 // Returns the Phone/FaceTime call argument from |URL|.
77 + (NSString*)formatCallArgument:(NSURL*)URL; 83 + (NSString*)formatCallArgument:(NSURL*)URL;
78 // Ask user for confirmation before dialing Phone or FaceTime destinations. 84 // Asks user for confirmation before moving to external app. |params| is a
79 - (void)openPromptForURL:(NSURL*)URL; 85 // dictionary keyed with values of kAlert* above.
80 // Ask user for confirmation before moving to external app. 86 - (void)openExternalAppWithPromptForURL:(NSDictionary<NSString*, id>*)params;
Eugene But (OOO till 7-30) 2017/05/12 22:53:51 Do you use NSDictionary for passing the arguments,
pkl (ping after 24h if needed) 2017/05/12 23:16:11 As the first pass, I'm just adding the alert promp
Eugene But (OOO till 7-30) 2017/05/13 00:27:52 SG. In case if using GCD will not be possible, ple
pkl (ping after 24h if needed) 2017/05/17 22:23:02 Renamed now.
81 - (void)openExternalAppWithPromptForURL:(NSURL*)URL;
82 // Presents a configured alert controller on the root view controller. 87 // Presents a configured alert controller on the root view controller.
83 - (void)presentAlertControllerWithMessage:(NSString*)message 88 - (void)presentAlertControllerWithMessage:(NSString*)message
84 openTitle:(NSString*)openTitle 89 openTitle:(NSString*)openTitle
85 openHandler:(AlertHandler)openHandler 90 openHandler:(AlertHandler)openHandler
86 cancelHandler:(AlertHandler)cancelHandler; 91 cancelHandler:(AlertHandler)cancelHandler;
87 @end 92 @end
88 93
89 @implementation ExternalAppLauncher 94 @implementation ExternalAppLauncher
90 95
91 + (NSString*)formatCallArgument:(NSURL*)URL { 96 + (NSString*)formatCallArgument:(NSURL*)URL {
92 NSCharacterSet* charSet = 97 NSCharacterSet* charSet =
93 [NSCharacterSet characterSetWithCharactersInString:@"/"]; 98 [NSCharacterSet characterSetWithCharactersInString:@"/"];
94 NSString* scheme = [NSString stringWithFormat:@"%@:", [URL scheme]]; 99 NSString* scheme = [NSString stringWithFormat:@"%@:", [URL scheme]];
95 NSString* URLString = [URL absoluteString]; 100 NSString* URLString = [URL absoluteString];
96 if ([URLString length] <= [scheme length]) 101 if ([URLString length] <= [scheme length])
97 return URLString; 102 return URLString;
98 NSString* prompt = [[[[URL absoluteString] substringFromIndex:[scheme length]] 103 NSString* prompt = [[[[URL absoluteString] substringFromIndex:[scheme length]]
99 stringByTrimmingCharactersInSet:charSet] stringByRemovingPercentEncoding]; 104 stringByTrimmingCharactersInSet:charSet] stringByRemovingPercentEncoding];
100 // Returns original URL string if there's nothing interesting to display 105 // Returns original URL string if there's nothing interesting to display
101 // other than the scheme itself. 106 // other than the scheme itself.
102 if (![prompt length]) 107 if (![prompt length])
103 return URLString; 108 return URLString;
104 return prompt; 109 return prompt;
105 } 110 }
106 111
107 - (void)openExternalAppWithPromptForURL:(NSURL*)URL { 112 - (void)openExternalAppWithPromptForURL:(NSDictionary<NSString*, id>*)params {
108 NSString* message = l10n_util::GetNSString(IDS_IOS_OPEN_IN_ANOTHER_APP); 113 NSURL* URL = base::mac::ObjCCastStrict<NSURL>(params[kAlertURL]);
109 NSString* openTitle = 114 NSString* prompt = base::mac::ObjCCastStrict<NSString>(params[kAlertPrompt]);
110 l10n_util::GetNSString(IDS_IOS_APP_LAUNCHER_OPEN_APP_BUTTON_LABEL); 115 NSString* actionTitle =
111 [self presentAlertControllerWithMessage:message 116 base::mac::ObjCCastStrict<NSString>(params[kAlertAction]);
112 openTitle:openTitle 117 [self presentAlertControllerWithMessage:prompt
118 openTitle:actionTitle
113 openHandler:^(UIAlertAction* action) { 119 openHandler:^(UIAlertAction* action) {
114 RecordExternalApplicationOpened(true); 120 RecordExternalApplicationOpened(true);
115 OpenUrlWithCompletionHandler(URL, nil); 121 OpenUrlWithCompletionHandler(URL, nil);
116 } 122 }
117 cancelHandler:^(UIAlertAction* action) { 123 cancelHandler:^(UIAlertAction* action) {
118 RecordExternalApplicationOpened(false); 124 RecordExternalApplicationOpened(false);
119 }]; 125 }];
120 } 126 }
121 127
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 128 - (void)presentAlertControllerWithMessage:(NSString*)message
132 openTitle:(NSString*)openTitle 129 openTitle:(NSString*)openTitle
133 openHandler:(AlertHandler)openHandler 130 openHandler:(AlertHandler)openHandler
134 cancelHandler:(AlertHandler)cancelHandler { 131 cancelHandler:(AlertHandler)cancelHandler {
135 UIAlertController* alertController = 132 UIAlertController* alertController =
136 [UIAlertController alertControllerWithTitle:nil 133 [UIAlertController alertControllerWithTitle:nil
137 message:message 134 message:message
138 preferredStyle:UIAlertControllerStyleAlert]; 135 preferredStyle:UIAlertControllerStyleAlert];
139 UIAlertAction* openAction = 136 UIAlertAction* openAction =
140 [UIAlertAction actionWithTitle:openTitle 137 [UIAlertAction actionWithTitle:openTitle
141 style:UIAlertActionStyleDefault 138 style:UIAlertActionStyleDefault
142 handler:openHandler]; 139 handler:openHandler];
143 UIAlertAction* cancelAction = 140 UIAlertAction* cancelAction =
144 [UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_CANCEL) 141 [UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_CANCEL)
145 style:UIAlertActionStyleCancel 142 style:UIAlertActionStyleCancel
146 handler:cancelHandler]; 143 handler:cancelHandler];
147 [alertController addAction:cancelAction]; 144 [alertController addAction:cancelAction];
148 [alertController addAction:openAction]; 145 [alertController addAction:openAction];
149 146
150 [[[[UIApplication sharedApplication] keyWindow] rootViewController] 147 [[[[UIApplication sharedApplication] keyWindow] rootViewController]
151 presentViewController:alertController 148 presentViewController:alertController
152 animated:YES 149 animated:YES
153 completion:nil]; 150 completion:nil];
154 } 151 }
155 152
156 - (BOOL)openURL:(const GURL&)gURL linkClicked:(BOOL)linkClicked { 153 - (BOOL)openURL:(const GURL&)gURL linkClicked:(BOOL)linkClicked {
157 if (!gURL.is_valid() || !gURL.has_scheme()) 154 if (!gURL.is_valid() || !gURL.has_scheme())
158 return NO; 155 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 156
175 // Don't open external application if chrome is not active. 157 // Don't open external application if chrome is not active.
176 if ([[UIApplication sharedApplication] applicationState] != 158 if ([[UIApplication sharedApplication] applicationState] !=
177 UIApplicationStateActive) 159 UIApplicationStateActive)
178 return NO; 160 return NO;
179 161
180 // Prompt user to open itunes when opening it is not a result of a link 162 NSURL* URL = net::NSURLWithGURL(gURL);
181 // click. 163 NSMutableDictionary<NSString*, id>* params = [NSMutableDictionary dictionary];
182 if (!linkClicked && UrlHasAppStoreScheme(gURL)) { 164 if (base::ios::IsRunningOnOrLater(10, 3, 0)) {
165 if (UrlHasAppStoreScheme(gURL)) {
166 params[kAlertPrompt] =
167 l10n_util::GetNSString(IDS_IOS_OPEN_IN_ANOTHER_APP);
168 params[kAlertAction] =
169 l10n_util::GetNSString(IDS_IOS_APP_LAUNCHER_OPEN_APP_BUTTON_LABEL);
170 }
171 } else {
rohitrao (ping after 24h) 2017/05/15 12:07:48 My understanding of this block is that: 1) On 10.3
pkl (ping after 24h if needed) 2017/05/17 22:23:02 Thank you! I've broken up the two if-statements so
172 // iOS 10.3 prompts user for permission when facetime: and facetime-audio:
173 // URL schemes are opened. For pre-10.3 users, Chrome needs to present an
174 // alert for phone calls. Prior iOS 10.3, Safari does not prompt when user
175 // taps on an App Store links, so Chrome wouldn't prompt either.
rohitrao (ping after 24h) 2017/05/15 12:07:48 Feature request: should we consider always prompti
pkl (ping after 24h if needed) 2017/05/17 22:23:02 I don't think it would matter, but I'd rather not
176 // This section can be deleted once iOS 10.3 support is dropped.
177 if (UrlHasPhoneCallScheme(gURL)) {
178 params[kAlertPrompt] = [[self class] formatCallArgument:URL];
179 params[kAlertAction] = PromptActionString([URL scheme]);
180 } else if (!linkClicked && UrlHasAppStoreScheme(gURL)) {
181 params[kAlertPrompt] =
182 l10n_util::GetNSString(IDS_IOS_OPEN_IN_ANOTHER_APP);
183 params[kAlertAction] =
184 l10n_util::GetNSString(IDS_IOS_APP_LAUNCHER_OPEN_APP_BUTTON_LABEL);
185 }
186 }
187 if ([params count] > 0) {
188 params[kAlertURL] = URL;
189 // Note: Showing an alert view immediately has a side-effect where focus is
190 // taken from the UIWebView so quickly that mouseup events are lost and
191 // buttons get 'stuck' in the on position. The solution is to defer
192 // showing the view using -performSelector:withObject:afterDelay:.
183 [self performSelector:@selector(openExternalAppWithPromptForURL:) 193 [self performSelector:@selector(openExternalAppWithPromptForURL:)
184 withObject:URL 194 withObject:params
185 afterDelay:0.0]; 195 afterDelay:0.0];
186 return YES; 196 return YES;
187 } 197 }
188 198
189 // Replaces |URL| with a rewritten URL if it is of mailto: scheme. 199 // Replaces |URL| with a rewritten URL if it is of mailto: scheme.
190 if (!experimental_flags::IsNativeAppLauncherEnabled() && 200 if (!experimental_flags::IsNativeAppLauncherEnabled() &&
191 gURL.SchemeIs(url::kMailToScheme)) { 201 gURL.SchemeIs(url::kMailToScheme)) {
192 MailtoURLRewriter* rewriter = 202 MailtoURLRewriter* rewriter =
193 [[MailtoURLRewriter alloc] initWithStandardHandlers]; 203 [[MailtoURLRewriter alloc] initWithStandardHandlers];
194 NSString* launchURL = [rewriter rewriteMailtoURL:gURL]; 204 NSString* launchURL = [rewriter rewriteMailtoURL:gURL];
195 if (launchURL) { 205 if (launchURL) {
196 URL = [NSURL URLWithString:launchURL]; 206 URL = [NSURL URLWithString:launchURL];
197 } 207 }
198 UMA_HISTOGRAM_BOOLEAN("IOS.MailtoURLRewritten", launchURL != nil); 208 UMA_HISTOGRAM_BOOLEAN("IOS.MailtoURLRewritten", launchURL != nil);
199 } 209 }
200 210
201 // If the following call returns YES, an external application is about to be 211 // If the following call returns YES, an external application is about to be
202 // launched and Chrome will go into the background now. 212 // launched and Chrome will go into the background now.
203 // TODO(crbug.com/622735): This call still needs to be updated. 213 // TODO(crbug.com/622735): This call still needs to be updated.
204 // It's heavily nested so some refactoring is needed. 214 // It's heavily nested so some refactoring is needed.
205 return [[UIApplication sharedApplication] openURL:URL]; 215 return [[UIApplication sharedApplication] openURL:URL];
206 } 216 }
207 217
208 @end 218 @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