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

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

Powered by Google App Engine
This is Rietveld 408576698