OLD | NEW |
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/mac/foundation_util.h" |
10 #include "base/metrics/histogram_macros.h" | 10 #include "base/metrics/histogram_macros.h" |
11 #include "base/strings/sys_string_conversions.h" | 11 #include "base/strings/sys_string_conversions.h" |
12 #include "components/strings/grit/components_strings.h" | 12 #include "components/strings/grit/components_strings.h" |
13 #include "ios/chrome/browser/experimental_flags.h" | 13 #include "ios/chrome/browser/experimental_flags.h" |
14 #import "ios/chrome/browser/open_url_util.h" | 14 #import "ios/chrome/browser/open_url_util.h" |
15 #import "ios/chrome/browser/web/mailto_url_rewriter.h" | 15 #import "ios/chrome/browser/web/mailto_url_rewriter.h" |
16 #include "ios/chrome/grit/ios_strings.h" | 16 #include "ios/chrome/grit/ios_strings.h" |
17 #import "net/base/mac/url_conversions.h" | 17 #import "net/base/mac/url_conversions.h" |
18 #include "ui/base/l10n/l10n_util.h" | 18 #include "ui/base/l10n/l10n_util.h" |
19 #include "url/gurl.h" | 19 #include "url/gurl.h" |
20 #include "url/url_constants.h" | 20 #include "url/url_constants.h" |
21 | 21 |
22 #if !defined(__has_feature) || !__has_feature(objc_arc) | 22 #if !defined(__has_feature) || !__has_feature(objc_arc) |
23 #error "This file requires ARC support." | 23 #error "This file requires ARC support." |
24 #endif | 24 #endif |
25 | 25 |
26 namespace { | 26 namespace { |
27 | 27 |
28 typedef void (^AlertHandler)(UIAlertAction* action); | |
29 | |
30 // Returns a set of NSStrings that are URL schemes for iTunes Stores. | 28 // Returns a set of NSStrings that are URL schemes for iTunes Stores. |
31 NSSet<NSString*>* ITMSSchemes() { | 29 NSSet<NSString*>* ITMSSchemes() { |
32 static NSSet<NSString*>* schemes; | 30 static NSSet<NSString*>* schemes; |
33 static dispatch_once_t once; | 31 static dispatch_once_t once; |
34 dispatch_once(&once, ^{ | 32 dispatch_once(&once, ^{ |
35 schemes = [NSSet<NSString*> | 33 schemes = [NSSet<NSString*> |
36 setWithObjects:@"itms", @"itmss", @"itms-apps", @"itms-appss", | 34 setWithObjects:@"itms", @"itmss", @"itms-apps", @"itms-appss", |
37 // There's no evidence that itms-bookss is actually | 35 // There's no evidence that itms-bookss is actually |
38 // supported, but over-inclusion costs less than | 36 // supported, but over-inclusion costs less than |
39 // under-inclusion. | 37 // under-inclusion. |
(...skipping 24 matching lines...) Expand all Loading... |
64 NSString* PromptActionString(NSString* scheme) { | 62 NSString* PromptActionString(NSString* scheme) { |
65 if ([scheme isEqualToString:@"facetime"]) | 63 if ([scheme isEqualToString:@"facetime"]) |
66 return l10n_util::GetNSString(IDS_IOS_FACETIME_BUTTON); | 64 return l10n_util::GetNSString(IDS_IOS_FACETIME_BUTTON); |
67 else if ([scheme isEqualToString:@"tel"] || | 65 else if ([scheme isEqualToString:@"tel"] || |
68 [scheme isEqualToString:@"facetime-audio"]) | 66 [scheme isEqualToString:@"facetime-audio"]) |
69 return l10n_util::GetNSString(IDS_IOS_PHONE_CALL_BUTTON); | 67 return l10n_util::GetNSString(IDS_IOS_PHONE_CALL_BUTTON); |
70 NOTREACHED(); | 68 NOTREACHED(); |
71 return @""; | 69 return @""; |
72 } | 70 } |
73 | 71 |
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 | |
80 } // namespace | 72 } // namespace |
81 | 73 |
82 @interface ExternalAppLauncher () | 74 @interface ExternalAppLauncher () |
83 // Returns the Phone/FaceTime call argument from |URL|. | 75 // Returns the Phone/FaceTime call argument from |URL|. |
84 + (NSString*)formatCallArgument:(NSURL*)URL; | 76 + (NSString*)formatCallArgument:(NSURL*)URL; |
85 // Asks user for confirmation before moving to external app. |params| is a | 77 // Presents an alert controller with |prompt| and |openLabel| as button label |
86 // dictionary keyed with values of kAlert* above. | 78 // on the root view controller before launching an external app identified by |
87 - (void)openExternalAppWithPromptWithParams: | 79 // |URL|. |
88 (NSDictionary<NSString*, id>*)params; | 80 - (void)openExternalAppWithURL:(NSURL*)URL |
89 // Presents a configured alert controller on the root view controller. | 81 prompt:(NSString*)prompt |
90 - (void)presentAlertControllerWithMessage:(NSString*)message | 82 openLabel:(NSString*)openLabel; |
91 openTitle:(NSString*)openTitle | |
92 openHandler:(AlertHandler)openHandler | |
93 cancelHandler:(AlertHandler)cancelHandler; | |
94 @end | 83 @end |
95 | 84 |
96 @implementation ExternalAppLauncher | 85 @implementation ExternalAppLauncher |
97 | 86 |
98 + (NSString*)formatCallArgument:(NSURL*)URL { | 87 + (NSString*)formatCallArgument:(NSURL*)URL { |
99 NSCharacterSet* charSet = | 88 NSCharacterSet* charSet = |
100 [NSCharacterSet characterSetWithCharactersInString:@"/"]; | 89 [NSCharacterSet characterSetWithCharactersInString:@"/"]; |
101 NSString* scheme = [NSString stringWithFormat:@"%@:", [URL scheme]]; | 90 NSString* scheme = [NSString stringWithFormat:@"%@:", [URL scheme]]; |
102 NSString* URLString = [URL absoluteString]; | 91 NSString* URLString = [URL absoluteString]; |
103 if ([URLString length] <= [scheme length]) | 92 if ([URLString length] <= [scheme length]) |
104 return URLString; | 93 return URLString; |
105 NSString* prompt = [[[[URL absoluteString] substringFromIndex:[scheme length]] | 94 NSString* prompt = [[[[URL absoluteString] substringFromIndex:[scheme length]] |
106 stringByTrimmingCharactersInSet:charSet] stringByRemovingPercentEncoding]; | 95 stringByTrimmingCharactersInSet:charSet] stringByRemovingPercentEncoding]; |
107 // Returns original URL string if there's nothing interesting to display | 96 // Returns original URL string if there's nothing interesting to display |
108 // other than the scheme itself. | 97 // other than the scheme itself. |
109 if (![prompt length]) | 98 if (![prompt length]) |
110 return URLString; | 99 return URLString; |
111 return prompt; | 100 return prompt; |
112 } | 101 } |
113 | 102 |
114 - (void)openExternalAppWithPromptWithParams: | 103 - (void)openExternalAppWithURL:(NSURL*)URL |
115 (NSDictionary<NSString*, id>*)params { | 104 prompt:(NSString*)prompt |
116 NSURL* URL = base::mac::ObjCCastStrict<NSURL>(params[kAlertURL]); | 105 openLabel:(NSString*)openLabel { |
117 NSString* prompt = base::mac::ObjCCastStrict<NSString>(params[kAlertPrompt]); | |
118 NSString* actionTitle = | |
119 base::mac::ObjCCastStrict<NSString>(params[kAlertAction]); | |
120 [self presentAlertControllerWithMessage:prompt | |
121 openTitle:actionTitle | |
122 openHandler:^(UIAlertAction* action) { | |
123 RecordExternalApplicationOpened(true); | |
124 OpenUrlWithCompletionHandler(URL, nil); | |
125 } | |
126 cancelHandler:^(UIAlertAction* action) { | |
127 RecordExternalApplicationOpened(false); | |
128 }]; | |
129 } | |
130 | |
131 - (void)presentAlertControllerWithMessage:(NSString*)message | |
132 openTitle:(NSString*)openTitle | |
133 openHandler:(AlertHandler)openHandler | |
134 cancelHandler:(AlertHandler)cancelHandler { | |
135 UIAlertController* alertController = | 106 UIAlertController* alertController = |
136 [UIAlertController alertControllerWithTitle:nil | 107 [UIAlertController alertControllerWithTitle:nil |
137 message:message | 108 message:prompt |
138 preferredStyle:UIAlertControllerStyleAlert]; | 109 preferredStyle:UIAlertControllerStyleAlert]; |
139 UIAlertAction* openAction = | 110 UIAlertAction* openAction = |
140 [UIAlertAction actionWithTitle:openTitle | 111 [UIAlertAction actionWithTitle:openLabel |
141 style:UIAlertActionStyleDefault | 112 style:UIAlertActionStyleDefault |
142 handler:openHandler]; | 113 handler:^(UIAlertAction* action) { |
| 114 RecordExternalApplicationOpened(true); |
| 115 OpenUrlWithCompletionHandler(URL, nil); |
| 116 }]; |
143 UIAlertAction* cancelAction = | 117 UIAlertAction* cancelAction = |
144 [UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_CANCEL) | 118 [UIAlertAction actionWithTitle:l10n_util::GetNSString(IDS_CANCEL) |
145 style:UIAlertActionStyleCancel | 119 style:UIAlertActionStyleCancel |
146 handler:cancelHandler]; | 120 handler:^(UIAlertAction* action) { |
| 121 RecordExternalApplicationOpened(false); |
| 122 }]; |
147 [alertController addAction:cancelAction]; | 123 [alertController addAction:cancelAction]; |
148 [alertController addAction:openAction]; | 124 [alertController addAction:openAction]; |
149 | 125 |
150 [[[[UIApplication sharedApplication] keyWindow] rootViewController] | 126 [[[[UIApplication sharedApplication] keyWindow] rootViewController] |
151 presentViewController:alertController | 127 presentViewController:alertController |
152 animated:YES | 128 animated:YES |
153 completion:nil]; | 129 completion:nil]; |
154 } | 130 } |
155 | 131 |
156 - (BOOL)openURL:(const GURL&)gURL linkClicked:(BOOL)linkClicked { | 132 - (BOOL)openURL:(const GURL&)gURL linkClicked:(BOOL)linkClicked { |
157 if (!gURL.is_valid() || !gURL.has_scheme()) | 133 if (!gURL.is_valid() || !gURL.has_scheme()) |
158 return NO; | 134 return NO; |
159 | 135 |
160 // Don't open external application if chrome is not active. | 136 // Don't open external application if chrome is not active. |
161 if ([[UIApplication sharedApplication] applicationState] != | 137 if ([[UIApplication sharedApplication] applicationState] != |
162 UIApplicationStateActive) | 138 UIApplicationStateActive) |
163 return NO; | 139 return NO; |
164 | 140 |
165 NSURL* URL = net::NSURLWithGURL(gURL); | 141 NSURL* URL = net::NSURLWithGURL(gURL); |
166 NSMutableDictionary<NSString*, id>* params = [NSMutableDictionary dictionary]; | |
167 if (base::ios::IsRunningOnOrLater(10, 3, 0)) { | 142 if (base::ios::IsRunningOnOrLater(10, 3, 0)) { |
168 if (UrlHasAppStoreScheme(gURL)) { | 143 if (UrlHasAppStoreScheme(gURL)) { |
169 params[kAlertPrompt] = | 144 NSString* prompt = l10n_util::GetNSString(IDS_IOS_OPEN_IN_ANOTHER_APP); |
170 l10n_util::GetNSString(IDS_IOS_OPEN_IN_ANOTHER_APP); | 145 NSString* openLabel = |
171 params[kAlertAction] = | |
172 l10n_util::GetNSString(IDS_IOS_APP_LAUNCHER_OPEN_APP_BUTTON_LABEL); | 146 l10n_util::GetNSString(IDS_IOS_APP_LAUNCHER_OPEN_APP_BUTTON_LABEL); |
| 147 [self openExternalAppWithURL:URL prompt:prompt openLabel:openLabel]; |
| 148 return YES; |
173 } | 149 } |
174 } else { | 150 } else { |
175 // Prior to iOS 10.3, iOS does not prompt user when facetime: and | 151 // 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 | 152 // facetime-audio: URL schemes are opened, so Chrome needs to present an |
177 // alert before placing a phone call. | 153 // alert before placing a phone call. |
178 if (UrlHasPhoneCallScheme(gURL)) { | 154 if (UrlHasPhoneCallScheme(gURL)) { |
179 params[kAlertPrompt] = [[self class] formatCallArgument:URL]; | 155 [self openExternalAppWithURL:URL |
180 params[kAlertAction] = PromptActionString([URL scheme]); | 156 prompt:[[self class] formatCallArgument:URL] |
| 157 openLabel:PromptActionString([URL scheme])]; |
| 158 return YES; |
181 } | 159 } |
182 // Prior to iOS 10.3, Chrome prompts user with an alert before opening | 160 // 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 | 161 // 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. | 162 // opened. This maintains parity with Safari in pre-10.3 environment. |
185 if (!linkClicked && UrlHasAppStoreScheme(gURL)) { | 163 if (!linkClicked && UrlHasAppStoreScheme(gURL)) { |
186 params[kAlertPrompt] = | 164 NSString* prompt = l10n_util::GetNSString(IDS_IOS_OPEN_IN_ANOTHER_APP); |
187 l10n_util::GetNSString(IDS_IOS_OPEN_IN_ANOTHER_APP); | 165 NSString* openLabel = |
188 params[kAlertAction] = | |
189 l10n_util::GetNSString(IDS_IOS_APP_LAUNCHER_OPEN_APP_BUTTON_LABEL); | 166 l10n_util::GetNSString(IDS_IOS_APP_LAUNCHER_OPEN_APP_BUTTON_LABEL); |
| 167 [self openExternalAppWithURL:URL prompt:prompt openLabel:openLabel]; |
| 168 return YES; |
190 } | 169 } |
191 } | 170 } |
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 | |
200 afterDelay:0.0]; | |
201 return YES; | |
202 } | |
203 | 171 |
204 // Replaces |URL| with a rewritten URL if it is of mailto: scheme. | 172 // Replaces |URL| with a rewritten URL if it is of mailto: scheme. |
205 if (!experimental_flags::IsNativeAppLauncherEnabled() && | 173 if (!experimental_flags::IsNativeAppLauncherEnabled() && |
206 gURL.SchemeIs(url::kMailToScheme)) { | 174 gURL.SchemeIs(url::kMailToScheme)) { |
207 MailtoURLRewriter* rewriter = | 175 MailtoURLRewriter* rewriter = |
208 [[MailtoURLRewriter alloc] initWithStandardHandlers]; | 176 [[MailtoURLRewriter alloc] initWithStandardHandlers]; |
209 NSString* launchURL = [rewriter rewriteMailtoURL:gURL]; | 177 NSString* launchURL = [rewriter rewriteMailtoURL:gURL]; |
210 if (launchURL) { | 178 if (launchURL) |
211 URL = [NSURL URLWithString:launchURL]; | 179 URL = [NSURL URLWithString:launchURL]; |
212 } | |
213 UMA_HISTOGRAM_BOOLEAN("IOS.MailtoURLRewritten", launchURL != nil); | 180 UMA_HISTOGRAM_BOOLEAN("IOS.MailtoURLRewritten", launchURL != nil); |
214 } | 181 } |
215 | 182 |
216 // If the following call returns YES, an external application is about to be | 183 // If the following call returns YES, an external application is about to be |
217 // launched and Chrome will go into the background now. | 184 // launched and Chrome will go into the background now. |
218 // TODO(crbug.com/622735): This call still needs to be updated. | 185 // TODO(crbug.com/622735): This call still needs to be updated. |
219 // It's heavily nested so some refactoring is needed. | 186 // It's heavily nested so some refactoring is needed. |
220 return [[UIApplication sharedApplication] openURL:URL]; | 187 return [[UIApplication sharedApplication] openURL:URL]; |
221 } | 188 } |
222 | 189 |
223 @end | 190 @end |
OLD | NEW |