OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #import "ios/chrome/browser/native_app_launcher/native_app_navigation_controller
.h" | |
6 | |
7 #import <StoreKit/StoreKit.h> | |
8 | |
9 #include "base/memory/ptr_util.h" | |
10 #include "base/metrics/user_metrics.h" | |
11 #include "base/metrics/user_metrics_action.h" | |
12 #include "base/threading/sequenced_worker_pool.h" | |
13 #include "components/image_fetcher/ios/ios_image_data_fetcher_wrapper.h" | |
14 #include "components/infobars/core/infobar_manager.h" | |
15 #include "ios/chrome/browser/infobars/infobar_manager_impl.h" | |
16 #import "ios/chrome/browser/installation_notifier.h" | |
17 #include "ios/chrome/browser/native_app_launcher/native_app_infobar_delegate.h" | |
18 #import "ios/chrome/browser/native_app_launcher/native_app_navigation_controller
_protocol.h" | |
19 #include "ios/chrome/browser/native_app_launcher/native_app_navigation_util.h" | |
20 #import "ios/chrome/browser/open_url_util.h" | |
21 #import "ios/chrome/browser/store_kit/store_kit_tab_helper.h" | |
22 #import "ios/chrome/browser/tabs/legacy_tab_helper.h" | |
23 #import "ios/chrome/browser/tabs/tab.h" | |
24 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h" | |
25 #import "ios/public/provider/chrome/browser/native_app_launcher/native_app_metad
ata.h" | |
26 #import "ios/public/provider/chrome/browser/native_app_launcher/native_app_types
.h" | |
27 #import "ios/public/provider/chrome/browser/native_app_launcher/native_app_white
list_manager.h" | |
28 #include "ios/web/public/browser_state.h" | |
29 #include "ios/web/public/web_state/web_state.h" | |
30 #import "ios/web/public/web_state/web_state_observer_bridge.h" | |
31 #include "ios/web/public/web_thread.h" | |
32 #import "net/base/mac/url_conversions.h" | |
33 | |
34 #if !defined(__has_feature) || !__has_feature(objc_arc) | |
35 #error "This file requires ARC support." | |
36 #endif | |
37 | |
38 using base::UserMetricsAction; | |
39 | |
40 @interface NativeAppNavigationController ()< | |
41 CRWWebStateObserver, | |
42 NativeAppNavigationControllerProtocol> | |
43 // Shows a native app infobar by looking at the page's URL and by checking | |
44 // wheter that infobar should be bypassed or not. | |
45 - (void)showInfoBarIfNecessary; | |
46 | |
47 // Returns a pointer to the NSMutableSet of |_appsPossiblyBeingInstalled| | |
48 - (NSMutableSet*)appsPossiblyBeingInstalled; | |
49 | |
50 // Records what type of infobar was opened. | |
51 - (void)recordInfobarDisplayedOfType:(NativeAppControllerType)type | |
52 onLinkNavigation:(BOOL)isLinkNavigation; | |
53 | |
54 @end | |
55 | |
56 @implementation NativeAppNavigationController { | |
57 // WebState provides access to the *TabHelper objects. | |
58 web::WebState* _webState; | |
59 // ImageFetcher needed to fetch the icons. | |
60 std::unique_ptr<image_fetcher::IOSImageDataFetcherWrapper> _imageFetcher; | |
61 id<NativeAppMetadata> _metadata; | |
62 // A set of appIds encoded as NSStrings. | |
63 NSMutableSet* _appsPossiblyBeingInstalled; | |
64 // Allows this class to subscribe for CRWWebStateObserver callbacks. | |
65 std::unique_ptr<web::WebStateObserverBridge> _webStateObserver; | |
66 } | |
67 | |
68 // Designated initializer. Use this instead of -init. | |
69 - (instancetype)initWithWebState:(web::WebState*)webState { | |
70 self = [super init]; | |
71 if (self) { | |
72 DCHECK(webState); | |
73 _webState = webState; | |
74 _imageFetcher = base::MakeUnique<image_fetcher::IOSImageDataFetcherWrapper>( | |
75 _webState->GetBrowserState()->GetRequestContext(), | |
76 web::WebThread::GetBlockingPool()); | |
77 _appsPossiblyBeingInstalled = [[NSMutableSet alloc] init]; | |
78 _webStateObserver = | |
79 base::MakeUnique<web::WebStateObserverBridge>(webState, self); | |
80 } | |
81 return self; | |
82 } | |
83 | |
84 - (void)copyStateFrom:(NativeAppNavigationController*)controller { | |
85 DCHECK(controller); | |
86 _appsPossiblyBeingInstalled = [[NSMutableSet alloc] | |
87 initWithSet:[controller appsPossiblyBeingInstalled]]; | |
88 for (NSString* appIdString in _appsPossiblyBeingInstalled) { | |
89 DCHECK([appIdString isKindOfClass:[NSString class]]); | |
90 NSURL* appURL = | |
91 [ios::GetChromeBrowserProvider()->GetNativeAppWhitelistManager() | |
92 schemeForAppId:appIdString]; | |
93 [[InstallationNotifier sharedInstance] | |
94 registerForInstallationNotifications:self | |
95 withSelector:@selector(appDidInstall:) | |
96 forScheme:[appURL scheme]]; | |
97 } | |
98 [self showInfoBarIfNecessary]; | |
99 } | |
100 | |
101 - (void)dealloc { | |
102 [[InstallationNotifier sharedInstance] unregisterForNotifications:self]; | |
103 } | |
104 | |
105 - (NSMutableSet*)appsPossiblyBeingInstalled { | |
106 return _appsPossiblyBeingInstalled; | |
107 } | |
108 | |
109 - (void)showInfoBarIfNecessary { | |
110 // Find a potential matching native app. | |
111 GURL pageURL = _webState->GetLastCommittedURL(); | |
112 _metadata = [ios::GetChromeBrowserProvider()->GetNativeAppWhitelistManager() | |
113 nativeAppForURL:pageURL]; | |
114 if (!_metadata || [_metadata shouldBypassInfoBars]) | |
115 return; | |
116 | |
117 // Select the infobar type. | |
118 NativeAppControllerType type; | |
119 bool isLinkNavigation = native_app_launcher::IsLinkNavigation(_webState); | |
120 if ([_metadata canOpenURL:pageURL]) { // App is installed. | |
121 type = isLinkNavigation && ![_metadata shouldAutoOpenLinks] | |
122 ? NATIVE_APP_OPEN_POLICY_CONTROLLER | |
123 : NATIVE_APP_LAUNCHER_CONTROLLER; | |
124 } else { // App is not installed. | |
125 // Check if the user already opened the store for this app. | |
126 if ([_appsPossiblyBeingInstalled containsObject:[_metadata appId]]) | |
127 return; | |
128 type = NATIVE_APP_INSTALLER_CONTROLLER; | |
129 } | |
130 // Inform the metadata that an infobar of |type| will be shown so that metrics | |
131 // and ignored behavior can be handled. | |
132 [_metadata willBeShownInInfobarOfType:type]; | |
133 // Display the proper infobar. | |
134 infobars::InfoBarManager* infoBarManager = | |
135 InfoBarManagerImpl::FromWebState(_webState); | |
136 NativeAppInfoBarDelegate::Create(infoBarManager, self, pageURL, type); | |
137 [self recordInfobarDisplayedOfType:type onLinkNavigation:isLinkNavigation]; | |
138 } | |
139 | |
140 - (void)recordInfobarDisplayedOfType:(NativeAppControllerType)type | |
141 onLinkNavigation:(BOOL)isLinkNavigation { | |
142 switch (type) { | |
143 case NATIVE_APP_INSTALLER_CONTROLLER: | |
144 base::RecordAction( | |
145 isLinkNavigation | |
146 ? UserMetricsAction("MobileGALInstallInfoBarLinkNavigation") | |
147 : UserMetricsAction("MobileGALInstallInfoBarDirectNavigation")); | |
148 break; | |
149 case NATIVE_APP_LAUNCHER_CONTROLLER: | |
150 base::RecordAction(UserMetricsAction("MobileGALLaunchInfoBar")); | |
151 break; | |
152 case NATIVE_APP_OPEN_POLICY_CONTROLLER: | |
153 base::RecordAction(UserMetricsAction("MobileGALOpenPolicyInfoBar")); | |
154 break; | |
155 default: | |
156 NOTREACHED(); | |
157 break; | |
158 } | |
159 } | |
160 | |
161 #pragma mark - NativeAppNavigationControllerProtocol methods | |
162 | |
163 - (NSString*)appId { | |
164 return [_metadata appId]; | |
165 } | |
166 | |
167 - (NSString*)appName { | |
168 return [_metadata appName]; | |
169 } | |
170 | |
171 - (void)fetchSmallIconWithCompletionBlock:(void (^)(UIImage*))block { | |
172 [_metadata fetchSmallIconWithImageFetcher:_imageFetcher.get() | |
173 completionBlock:block]; | |
174 } | |
175 | |
176 - (void)openStore { | |
177 // Register to get a notification when the app is installed. | |
178 [[InstallationNotifier sharedInstance] | |
179 registerForInstallationNotifications:self | |
180 withSelector:@selector(appDidInstall:) | |
181 forScheme:[_metadata anyScheme]]; | |
182 NSString* appIdString = [self appId]; | |
183 // Defensively early return if native app metadata returns an nil string for | |
184 // appId which can cause subsequent -addObject: to throw exceptions. | |
185 if (![appIdString length]) | |
186 return; | |
187 DCHECK(![_appsPossiblyBeingInstalled containsObject:appIdString]); | |
188 [_appsPossiblyBeingInstalled addObject:appIdString]; | |
189 StoreKitTabHelper* tabHelper = StoreKitTabHelper::FromWebState(_webState); | |
190 if (tabHelper) | |
191 tabHelper->OpenAppStore(appIdString); | |
192 } | |
193 | |
194 - (void)launchApp:(const GURL&)URL { | |
195 // TODO(crbug.com/353957): Pass the ChromeIdentity to | |
196 // -launchURLWithURL:identity: | |
197 GURL launchURL([_metadata launchURLWithURL:URL identity:nil]); | |
198 if (launchURL.is_valid()) { | |
199 OpenUrlWithCompletionHandler(net::NSURLWithGURL(launchURL), nil); | |
200 } | |
201 } | |
202 | |
203 - (void)updateMetadataWithUserAction:(NativeAppActionType)userAction { | |
204 [_metadata updateWithUserAction:userAction]; | |
205 } | |
206 | |
207 #pragma mark - CRWWebStateObserver methods | |
208 | |
209 - (void)webState:(web::WebState*)webState didLoadPageWithSuccess:(BOOL)success { | |
210 Tab* tab = LegacyTabHelper::GetTabForWebState(_webState); | |
211 if (success && ![tab isPrerenderTab]) | |
212 [self showInfoBarIfNecessary]; | |
213 } | |
214 | |
215 - (void)webStateDestroyed:(web::WebState*)webState { | |
216 _webState = nullptr; | |
217 _webStateObserver.reset(); | |
218 } | |
219 | |
220 #pragma mark - Private methods | |
221 | |
222 - (void)appDidInstall:(NSNotification*)notification { | |
223 [self removeAppFromNotification:notification]; | |
224 [self showInfoBarIfNecessary]; | |
225 } | |
226 | |
227 - (void)removeAppFromNotification:(NSNotification*)notification { | |
228 DCHECK([[notification object] isKindOfClass:[InstallationNotifier class]]); | |
229 NSString* schemeOfInstalledApp = [notification name]; | |
230 __block NSString* appIDToRemove = nil; | |
231 [_appsPossiblyBeingInstalled | |
232 enumerateObjectsUsingBlock:^(id appID, BOOL* stop) { | |
233 NSURL* appURL = | |
234 [ios::GetChromeBrowserProvider()->GetNativeAppWhitelistManager() | |
235 schemeForAppId:appID]; | |
236 if ([[appURL scheme] isEqualToString:schemeOfInstalledApp]) { | |
237 appIDToRemove = appID; | |
238 *stop = YES; | |
239 } | |
240 }]; | |
241 DCHECK(appIDToRemove); | |
242 [_appsPossiblyBeingInstalled removeObject:appIDToRemove]; | |
243 } | |
244 | |
245 @end | |
OLD | NEW |