Chromium Code Reviews| 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 action:(NSString*)openLabel; |
|
Eugene But (OOO till 7-30)
2017/05/18 23:40:14
s/action/openLabel ?
pkl (ping after 24h if needed)
2017/05/19 00:59:34
Done.
| |
| 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 action:(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 action: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 |
|
Eugene But (OOO till 7-30)
2017/05/18 23:40:15
Optional nit: Maybe pass GURL to openExternalAppWi
pkl (ping after 24h if needed)
2017/05/19 00:59:34
I'll save this for the next iteration of refactori
| |
| 180 params[kAlertAction] = PromptActionString([URL scheme]); | 156 prompt:[[self class] formatCallArgument:URL] |
| 157 action: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 action: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 |