OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 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/upgrade/upgrade_center.h" | |
6 | |
7 #include <memory> | |
8 #include <set> | |
9 #include <utility> | |
10 | |
11 #include "base/mac/bundle_locations.h" | |
12 #include "base/mac/scoped_nsobject.h" | |
13 #include "base/scoped_observer.h" | |
14 #include "base/strings/sys_string_conversions.h" | |
15 #include "base/version.h" | |
16 #include "components/infobars/core/confirm_infobar_delegate.h" | |
17 #include "components/infobars/core/infobar.h" | |
18 #include "components/infobars/core/infobar_manager.h" | |
19 #include "components/version_info/version_info.h" | |
20 #import "ios/chrome/browser/open_url_util.h" | |
21 #import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" | |
22 #import "ios/chrome/browser/ui/commands/open_url_command.h" | |
23 #include "ios/chrome/grit/ios_chromium_strings.h" | |
24 #include "ios/chrome/grit/ios_strings.h" | |
25 #import "ios/web/public/url_scheme_util.h" | |
26 #import "net/base/mac/url_conversions.h" | |
27 #include "ui/base/l10n/l10n_util.h" | |
28 #include "ui/gfx/image/image.h" | |
29 #include "url/gurl.h" | |
30 | |
31 @interface UpgradeCenter () | |
32 // Create infobars on all tabs. | |
sdefresne
2016/12/14 11:50:56
nit: Creates?
rohitrao (ping after 24h)
2016/12/14 13:26:33
Done.
| |
33 - (void)showUpgradeInfoBars; | |
34 // Remove all the infobars. | |
sdefresne
2016/12/14 11:50:56
nit: Removes?
rohitrao (ping after 24h)
2016/12/14 13:26:33
Done.
| |
35 - (void)hideUpgradeInfoBars; | |
36 // Callback when an infobar is closed, for any reason. Perform upgrade is set to | |
37 // YES if the user choose to upgrade. | |
38 - (void)dismissedInfoBar:(NSString*)tabId performUpgrade:(BOOL)shouldUpgrade; | |
39 // Return YES if the infobar should be shown. | |
sdefresne
2016/12/14 11:50:56
nit: Returns?
rohitrao (ping after 24h)
2016/12/14 13:26:33
Done.
| |
40 - (BOOL)shouldShowInfoBar; | |
41 // Return YES if the last version signaled by a server side service is more | |
sdefresne
2016/12/14 11:50:56
nit: Returns?
rohitrao (ping after 24h)
2016/12/14 13:26:33
Done.
| |
42 // recent than the current version. | |
43 - (BOOL)isCurrentVersionObsolete; | |
44 // Returns YES if the infobar has already been shown within the allowed display | |
45 // interval. | |
46 - (BOOL)infoBarShownRecently; | |
47 // Called when the application become active again. | |
48 - (void)applicationWillEnterForeground:(NSNotification*)note; | |
49 @end | |
50 | |
51 namespace { | |
52 | |
53 // The user defaults key for the upgrade version. | |
54 NSString* const kNextVersionKey = @"UpdateInfobarNextVersion"; | |
55 // The user defaults key for the upgrade URL. | |
56 NSString* const kUpgradeURLKey = @"UpdateInfobarUpgradeURL"; | |
57 // The user defaults key for the last time the update infobar was shown. | |
58 NSString* const kLastInfobarDisplayTimeKey = @"UpdateInfobarLastDisplayTime"; | |
59 // The amount of time that must elapse before showing the infobar again. | |
60 const NSTimeInterval kInfobarDisplayInterval = 24 * 60 * 60; // One day. | |
61 | |
62 // The class controlling the look of the infobar displayed when an upgrade is | |
63 // available. | |
64 class UpgradeInfoBarDelegate : public ConfirmInfoBarDelegate { | |
65 public: | |
66 UpgradeInfoBarDelegate() : trigger_upgrade_(false) {} | |
67 | |
68 ~UpgradeInfoBarDelegate() override {} | |
69 | |
70 // Returns true is the infobar was closed by pressing the accept button. | |
71 bool AcceptPressed() { return trigger_upgrade_; } | |
72 | |
73 void RemoveSelf() { | |
74 infobars::InfoBar* infobar = this->infobar(); | |
75 if (infobar) | |
76 infobar->RemoveSelf(); | |
77 } | |
78 | |
79 private: | |
80 InfoBarIdentifier GetIdentifier() const override { | |
81 return UPGRADE_INFOBAR_DELEGATE_IOS; | |
82 } | |
83 | |
84 bool ShouldExpire(const NavigationDetails& details) const override { | |
85 return false; | |
86 } | |
87 | |
88 gfx::Image GetIcon() const override { | |
89 if (icon_.IsEmpty()) { | |
90 icon_ = gfx::Image([UIImage imageNamed:@"infobar_update"], | |
91 base::scoped_policy::RETAIN); | |
92 } | |
93 return icon_; | |
94 } | |
95 | |
96 InfoBarDelegate::Type GetInfoBarType() const override { | |
97 return PAGE_ACTION_TYPE; | |
98 } | |
99 | |
100 base::string16 GetMessageText() const override { | |
101 return l10n_util::GetStringUTF16(IDS_IOS_UPGRADE_AVAILABLE); | |
102 } | |
103 | |
104 bool Accept() override { | |
105 trigger_upgrade_ = true; | |
106 return true; | |
107 } | |
108 | |
109 int GetButtons() const override { return BUTTON_OK; } | |
110 | |
111 base::string16 GetButtonLabel(InfoBarButton button) const override { | |
112 DCHECK(button == BUTTON_OK); | |
113 return l10n_util::GetStringUTF16(IDS_IOS_UPGRADE_AVAILABLE_BUTTON); | |
114 } | |
115 | |
116 mutable gfx::Image icon_; | |
117 bool trigger_upgrade_; | |
118 | |
119 DISALLOW_COPY_AND_ASSIGN(UpgradeInfoBarDelegate); | |
120 }; | |
121 | |
122 // The InfoBarDelegate unfortunately is not called at all when an infoBar is | |
123 // simply dismissed. In order to catch that case this object listens to the | |
124 // infobars::InfoBarManager::Observer::OnInfoBarRemoved() which is invoked when | |
125 // an infobar is closed, for any reason. | |
126 class UpgradeInfoBarDismissObserver | |
127 : public infobars::InfoBarManager::Observer { | |
128 public: | |
129 UpgradeInfoBarDismissObserver() | |
130 : infobar_delegate_(nullptr), | |
131 dismiss_delegate_(nil), | |
132 scoped_observer_(this) {} | |
133 | |
134 ~UpgradeInfoBarDismissObserver() override {} | |
135 | |
136 void RegisterObserver(infobars::InfoBarManager* infobar_manager, | |
137 UpgradeInfoBarDelegate* infobar_delegate, | |
138 NSString* tab_id, | |
139 UpgradeCenter* dismiss_delegate) { | |
140 scoped_observer_.Add(infobar_manager); | |
141 infobar_delegate_ = infobar_delegate; | |
142 dismiss_delegate_ = dismiss_delegate; | |
143 tab_id_.reset([tab_id copy]); | |
144 } | |
145 | |
146 UpgradeInfoBarDelegate* infobar_delegate() { return infobar_delegate_; } | |
147 | |
148 private: | |
149 // infobars::InfoBarManager::Observer implementation. | |
150 void OnInfoBarRemoved(infobars::InfoBar* infobar, bool animate) override { | |
151 if (infobar->delegate() == infobar_delegate_) { | |
152 [dismiss_delegate_ dismissedInfoBar:tab_id_.get() | |
153 performUpgrade:infobar_delegate_->AcceptPressed()]; | |
154 } | |
155 } | |
156 | |
157 void OnManagerShuttingDown( | |
158 infobars::InfoBarManager* infobar_manager) override { | |
159 scoped_observer_.Remove(infobar_manager); | |
160 } | |
161 | |
162 UpgradeInfoBarDelegate* infobar_delegate_; | |
163 UpgradeCenter* dismiss_delegate_; | |
164 base::scoped_nsobject<NSString> tab_id_; | |
165 ScopedObserver<infobars::InfoBarManager, infobars::InfoBarManager::Observer> | |
166 scoped_observer_; | |
167 | |
168 DISALLOW_COPY_AND_ASSIGN(UpgradeInfoBarDismissObserver); | |
169 }; | |
170 | |
171 } // namespace | |
172 | |
173 // The delegateHolder is a simple wrapper to be able to store all the | |
174 // infoBarDelegate related information in an object that can be put in | |
175 // an ObjectiveC container. | |
176 @interface DelegateHolder : NSObject { | |
177 UpgradeInfoBarDismissObserver observer_; | |
178 } | |
179 | |
180 - (instancetype)initWithInfoBarManager:(infobars::InfoBarManager*)infoBarManager | |
181 infoBarDelegate:(UpgradeInfoBarDelegate*)infoBarDelegate | |
182 upgradeCenter:(UpgradeCenter*)upgradeCenter | |
183 tabId:(NSString*)tabId; | |
184 | |
185 @property(nonatomic, readonly) UpgradeInfoBarDelegate* infoBarDelegate; | |
186 @end | |
187 | |
188 @implementation DelegateHolder | |
189 | |
190 - (instancetype)initWithInfoBarManager:(infobars::InfoBarManager*)infoBarManager | |
191 infoBarDelegate:(UpgradeInfoBarDelegate*)infoBarDelegate | |
192 upgradeCenter:(UpgradeCenter*)upgradeCenter | |
193 tabId:(NSString*)tabId { | |
194 self = [super init]; | |
195 if (self) { | |
196 observer_.RegisterObserver(infoBarManager, infoBarDelegate, tabId, | |
197 upgradeCenter); | |
198 } | |
199 return self; | |
200 } | |
201 | |
202 - (UpgradeInfoBarDelegate*)infoBarDelegate { | |
203 return observer_.infobar_delegate(); | |
204 } | |
205 | |
206 @end | |
207 | |
208 @implementation UpgradeCenter { | |
209 // YES if the infobars are currently visible. | |
210 BOOL upgradeInfoBarIsVisible_; | |
211 // Used to store the visible upgrade infobars, indexed by tabId. | |
212 base::scoped_nsobject<NSMutableDictionary> upgradeInfoBarDelegates_; | |
213 // Stores the clients of the upgrade center. These objectiveC objects are not | |
214 // retained. | |
215 std::set<id<UpgradeCenterClientProtocol>> clients_; | |
216 #ifndef NDEBUG | |
sdefresne
2016/12/14 11:50:56
nit: I think all "#ifndef NDEBUG" should be change
rohitrao (ping after 24h)
2016/12/14 13:26:33
Noted. Not changing in this CL.
| |
217 bool inCallback_; | |
218 #endif | |
219 } | |
220 | |
221 + (UpgradeCenter*)sharedInstance { | |
222 static UpgradeCenter* obj; | |
223 static dispatch_once_t onceToken; | |
224 dispatch_once(&onceToken, ^{ | |
225 obj = [[self alloc] init]; | |
226 }); | |
227 return obj; | |
228 } | |
229 | |
230 - (instancetype)init { | |
231 self = [super init]; | |
232 if (self) { | |
233 upgradeInfoBarDelegates_.reset([[NSMutableDictionary alloc] init]); | |
234 | |
235 // There is no dealloc and no unregister as this class is a never | |
236 // deallocated singleton. | |
237 [[NSNotificationCenter defaultCenter] | |
238 addObserver:self | |
239 selector:@selector(applicationWillEnterForeground:) | |
240 name:UIApplicationWillEnterForegroundNotification | |
241 object:nil]; | |
242 | |
243 upgradeInfoBarIsVisible_ = [self shouldShowInfoBar]; | |
244 } | |
245 return self; | |
246 } | |
247 | |
248 // Return YES if the infobar should be shown. | |
sdefresne
2016/12/14 11:50:56
Comment is already at the top of the file where th
rohitrao (ping after 24h)
2016/12/14 13:26:33
Done.
| |
249 - (BOOL)shouldShowInfoBar { | |
250 return [self isCurrentVersionObsolete] && ![self infoBarShownRecently]; | |
251 } | |
252 | |
253 - (BOOL)isCurrentVersionObsolete { | |
254 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; | |
255 NSString* nextVersion = [defaults stringForKey:kNextVersionKey]; | |
256 if (nextVersion) { | |
257 base::Version current_version(version_info::GetVersionNumber()); | |
258 const std::string upgrade = base::SysNSStringToUTF8(nextVersion); | |
259 return current_version < base::Version(upgrade); | |
260 } | |
261 return NO; | |
262 } | |
263 | |
264 - (BOOL)infoBarShownRecently { | |
265 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; | |
266 NSDate* lastDisplay = [defaults objectForKey:kLastInfobarDisplayTimeKey]; | |
267 // Absolute value is to ensure the infobar won't be supressed forever if the | |
268 // clock temporarily jumps to the distant future. | |
269 if (lastDisplay && | |
270 fabs([lastDisplay timeIntervalSinceNow]) < kInfobarDisplayInterval) { | |
271 return YES; | |
272 } | |
273 return NO; | |
274 } | |
275 | |
276 - (void)applicationWillEnterForeground:(NSNotification*)note { | |
277 if (upgradeInfoBarIsVisible_) | |
278 return; | |
279 | |
280 // When returning to active if the upgrade notification has been dismissed, | |
281 // bring it back. | |
282 if ([self shouldShowInfoBar]) | |
283 [self showUpgradeInfoBars]; | |
284 } | |
285 | |
286 - (void)registerClient:(id<UpgradeCenterClientProtocol>)client { | |
287 clients_.insert(client); | |
288 if (upgradeInfoBarIsVisible_) | |
289 [client showUpgrade:self]; | |
290 } | |
291 | |
292 - (void)unregisterClient:(id<UpgradeCenterClientProtocol>)client { | |
293 #ifndef NDEBUG | |
294 DCHECK(!inCallback_); | |
295 #endif | |
296 clients_.erase(client); | |
297 } | |
298 | |
299 // For the client to call when -showUpgrade: is called or when a new tab is | |
sdefresne
2016/12/14 11:50:56
The comment is already in the header file, please
rohitrao (ping after 24h)
2016/12/14 13:26:33
Done.
| |
300 // created. The infobar will not be created if is already there or if there is | |
301 // no need to do so. | |
302 - (void)addInfoBarToManager:(infobars::InfoBarManager*)infoBarManager | |
303 forTabId:(NSString*)tabId { | |
304 DCHECK(tabId); | |
305 DCHECK(infoBarManager); | |
306 | |
307 // Nothing to do if the infobar are not visible at this point in time. | |
308 if (!upgradeInfoBarIsVisible_) | |
309 return; | |
310 | |
311 // Nothing to do if the infobar is already there. | |
312 if ([upgradeInfoBarDelegates_ objectForKey:tabId]) | |
313 return; | |
314 | |
315 std::unique_ptr<UpgradeInfoBarDelegate> infobarDelegate( | |
sdefresne
2016/12/14 11:50:56
nit: use base::MakeUnique
auto infobarDelegate
rohitrao (ping after 24h)
2016/12/14 13:26:33
Done.
| |
316 new UpgradeInfoBarDelegate); | |
317 base::scoped_nsobject<DelegateHolder> delegateHolder([[DelegateHolder alloc] | |
318 initWithInfoBarManager:infoBarManager | |
319 infoBarDelegate:infobarDelegate.get() | |
320 upgradeCenter:self | |
321 tabId:tabId]); | |
322 | |
323 [upgradeInfoBarDelegates_ setObject:delegateHolder forKey:tabId]; | |
324 infoBarManager->AddInfoBar( | |
325 infoBarManager->CreateConfirmInfoBar(std::move(infobarDelegate))); | |
326 } | |
327 | |
328 - (void)tabWillClose:(NSString*)tabId { | |
329 [upgradeInfoBarDelegates_ removeObjectForKey:tabId]; | |
330 } | |
331 | |
332 // Callback when an infobar is closed, for any reason. Perform upgrade is set to | |
sdefresne
2016/12/14 11:50:56
Comment is already at the top of the file when the
rohitrao (ping after 24h)
2016/12/14 13:26:33
Done.
| |
333 // YES if the user choose to upgrade. | |
334 - (void)dismissedInfoBar:(NSString*)tabId performUpgrade:(BOOL)shouldUpgrade { | |
335 // If the tabId is not in the upgradeInfoBarDelegates_ just ignore the | |
336 // notification. In all likelyhood it was trigerred by calling | |
337 // -hideUpgradeInfoBars. Or because a tab was closed without dismissing the | |
338 // infobar. | |
339 base::scoped_nsobject<DelegateHolder> delegateHolder( | |
340 [[upgradeInfoBarDelegates_ objectForKey:tabId] retain]); | |
341 if (!delegateHolder.get()) | |
342 return; | |
343 | |
344 // Forget about this dismissed infobar. | |
345 [upgradeInfoBarDelegates_ removeObjectForKey:tabId]; | |
346 | |
347 // Get rid of all the infobars on the other tabs. | |
348 [self hideUpgradeInfoBars]; | |
349 | |
350 if (shouldUpgrade) { | |
351 NSString* urlString = | |
352 [[NSUserDefaults standardUserDefaults] valueForKey:kUpgradeURLKey]; | |
353 if (!urlString) | |
354 return; // Missing URL, no upgrade possible. | |
355 | |
356 GURL url = GURL(base::SysNSStringToUTF8(urlString)); | |
357 if (!url.is_valid()) | |
358 return; | |
359 | |
360 if (web::UrlHasWebScheme(url)) { | |
361 // This URL can be opened in the application, just open in a new tab. | |
362 base::scoped_nsobject<OpenUrlCommand> command( | |
363 [[OpenUrlCommand alloc] initWithURLFromChrome:url]); | |
364 UIWindow* main_window = [[UIApplication sharedApplication] keyWindow]; | |
365 DCHECK(main_window); | |
366 [main_window chromeExecuteCommand:command]; | |
367 } else { | |
368 // This URL scheme is not understood, ask the system to open it. | |
369 NSURL* nsurl = [NSURL URLWithString:urlString]; | |
370 if (nsurl) { | |
371 OpenUrlWithCompletionHandler(nsurl, nil); | |
372 } | |
373 } | |
374 } | |
375 } | |
376 | |
377 - (void)showUpgradeInfoBars { | |
378 // Add an infobar on all the open tabs. | |
379 #ifndef NDEBUG | |
380 inCallback_ = YES; | |
381 #endif | |
382 upgradeInfoBarIsVisible_ = YES; | |
383 std::set<id<UpgradeCenterClientProtocol>>::iterator it; | |
384 for (it = clients_.begin(); it != clients_.end(); ++it) | |
385 [*it showUpgrade:self]; | |
386 #ifndef NDEBUG | |
387 inCallback_ = NO; | |
388 #endif | |
389 | |
390 [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] | |
391 forKey:kLastInfobarDisplayTimeKey]; | |
392 } | |
393 | |
394 - (void)hideUpgradeInfoBars { | |
395 upgradeInfoBarIsVisible_ = NO; | |
396 // It is important to call -allKeys here and not using a fast iteration on the | |
397 // dictionary directly: the dictionary is modified as we go... | |
398 for (NSString* tabId in [upgradeInfoBarDelegates_ allKeys]) { | |
399 // It is important to retain the delegateHolder as otherwise it is | |
400 // deallocated as soon as it is removed from the dictionary. | |
401 base::scoped_nsobject<DelegateHolder> delegateHolder( | |
402 [[upgradeInfoBarDelegates_ objectForKey:tabId] retain]); | |
403 if (delegateHolder.get()) { | |
404 [upgradeInfoBarDelegates_ removeObjectForKey:tabId]; | |
405 UpgradeInfoBarDelegate* delegate = [delegateHolder infoBarDelegate]; | |
406 DCHECK(delegate); | |
407 delegate->RemoveSelf(); | |
408 } | |
409 } | |
410 } | |
411 | |
412 - (void)upgradeNotificationDidOccur:(const UpgradeRecommendedDetails&)details { | |
413 const GURL& upgradeUrl = details.upgrade_url; | |
414 | |
415 if (!upgradeUrl.is_valid()) { | |
416 // The application may crash if the URL is invalid. As the URL is defined | |
417 // externally to the application it needs to bail right away and ignore the | |
418 // upgrade notification. | |
419 NOTREACHED(); | |
420 return; | |
421 } | |
422 | |
423 if (!details.next_version.size() || | |
424 !base::Version(details.next_version).IsValid()) { | |
425 // If the upgrade version is not known or is invalid just ignore the | |
426 // upgrade notification. | |
427 NOTREACHED(); | |
428 return; | |
429 } | |
430 | |
431 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; | |
432 | |
433 // Reset the display clock when the version changes. | |
434 NSString* newVersionString = base::SysUTF8ToNSString(details.next_version); | |
435 NSString* previousVersionString = [defaults stringForKey:kNextVersionKey]; | |
436 if (!previousVersionString || | |
437 ![previousVersionString isEqualToString:newVersionString]) { | |
438 [defaults removeObjectForKey:kLastInfobarDisplayTimeKey]; | |
439 } | |
440 | |
441 [defaults setValue:base::SysUTF8ToNSString(upgradeUrl.spec()) | |
442 forKey:kUpgradeURLKey]; | |
443 [defaults setValue:newVersionString forKey:kNextVersionKey]; | |
444 | |
445 if ([self shouldShowInfoBar]) | |
446 [self showUpgradeInfoBars]; | |
447 } | |
448 | |
449 - (void)resetForTests { | |
450 [[UpgradeCenter sharedInstance] hideUpgradeInfoBars]; | |
451 NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; | |
452 [defaults removeObjectForKey:kNextVersionKey]; | |
453 [defaults removeObjectForKey:kUpgradeURLKey]; | |
454 [defaults removeObjectForKey:kLastInfobarDisplayTimeKey]; | |
455 clients_.clear(); | |
456 } | |
457 | |
458 - (void)setLastDisplayToPast { | |
459 NSDate* pastDate = | |
460 [NSDate dateWithTimeIntervalSinceNow:-(kInfobarDisplayInterval + 1)]; | |
461 [[NSUserDefaults standardUserDefaults] setObject:pastDate | |
462 forKey:kLastInfobarDisplayTimeKey]; | |
463 } | |
464 | |
465 @end | |
OLD | NEW |