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

Side by Side Diff: ios/chrome/browser/upgrade/upgrade_center.mm

Issue 2568003005: [ios] Adds code for Omaha and the upgrade center. (Closed)
Patch Set: Created 4 years 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
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698