Index: ios/chrome/browser/upgrade/upgrade_center.mm |
diff --git a/ios/chrome/browser/upgrade/upgrade_center.mm b/ios/chrome/browser/upgrade/upgrade_center.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2d12bda2560d18fcba218f30bc2376fff4293a5c |
--- /dev/null |
+++ b/ios/chrome/browser/upgrade/upgrade_center.mm |
@@ -0,0 +1,465 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "ios/chrome/browser/upgrade/upgrade_center.h" |
+ |
+#include <memory> |
+#include <set> |
+#include <utility> |
+ |
+#include "base/mac/bundle_locations.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/scoped_observer.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/version.h" |
+#include "components/infobars/core/confirm_infobar_delegate.h" |
+#include "components/infobars/core/infobar.h" |
+#include "components/infobars/core/infobar_manager.h" |
+#include "components/version_info/version_info.h" |
+#import "ios/chrome/browser/open_url_util.h" |
+#import "ios/chrome/browser/ui/commands/UIKit+ChromeExecuteCommand.h" |
+#import "ios/chrome/browser/ui/commands/open_url_command.h" |
+#include "ios/chrome/grit/ios_chromium_strings.h" |
+#include "ios/chrome/grit/ios_strings.h" |
+#import "ios/web/public/url_scheme_util.h" |
+#import "net/base/mac/url_conversions.h" |
+#include "ui/base/l10n/l10n_util.h" |
+#include "ui/gfx/image/image.h" |
+#include "url/gurl.h" |
+ |
+@interface UpgradeCenter () |
+// 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.
|
+- (void)showUpgradeInfoBars; |
+// Remove all the infobars. |
sdefresne
2016/12/14 11:50:56
nit: Removes?
rohitrao (ping after 24h)
2016/12/14 13:26:33
Done.
|
+- (void)hideUpgradeInfoBars; |
+// Callback when an infobar is closed, for any reason. Perform upgrade is set to |
+// YES if the user choose to upgrade. |
+- (void)dismissedInfoBar:(NSString*)tabId performUpgrade:(BOOL)shouldUpgrade; |
+// 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.
|
+- (BOOL)shouldShowInfoBar; |
+// 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.
|
+// recent than the current version. |
+- (BOOL)isCurrentVersionObsolete; |
+// Returns YES if the infobar has already been shown within the allowed display |
+// interval. |
+- (BOOL)infoBarShownRecently; |
+// Called when the application become active again. |
+- (void)applicationWillEnterForeground:(NSNotification*)note; |
+@end |
+ |
+namespace { |
+ |
+// The user defaults key for the upgrade version. |
+NSString* const kNextVersionKey = @"UpdateInfobarNextVersion"; |
+// The user defaults key for the upgrade URL. |
+NSString* const kUpgradeURLKey = @"UpdateInfobarUpgradeURL"; |
+// The user defaults key for the last time the update infobar was shown. |
+NSString* const kLastInfobarDisplayTimeKey = @"UpdateInfobarLastDisplayTime"; |
+// The amount of time that must elapse before showing the infobar again. |
+const NSTimeInterval kInfobarDisplayInterval = 24 * 60 * 60; // One day. |
+ |
+// The class controlling the look of the infobar displayed when an upgrade is |
+// available. |
+class UpgradeInfoBarDelegate : public ConfirmInfoBarDelegate { |
+ public: |
+ UpgradeInfoBarDelegate() : trigger_upgrade_(false) {} |
+ |
+ ~UpgradeInfoBarDelegate() override {} |
+ |
+ // Returns true is the infobar was closed by pressing the accept button. |
+ bool AcceptPressed() { return trigger_upgrade_; } |
+ |
+ void RemoveSelf() { |
+ infobars::InfoBar* infobar = this->infobar(); |
+ if (infobar) |
+ infobar->RemoveSelf(); |
+ } |
+ |
+ private: |
+ InfoBarIdentifier GetIdentifier() const override { |
+ return UPGRADE_INFOBAR_DELEGATE_IOS; |
+ } |
+ |
+ bool ShouldExpire(const NavigationDetails& details) const override { |
+ return false; |
+ } |
+ |
+ gfx::Image GetIcon() const override { |
+ if (icon_.IsEmpty()) { |
+ icon_ = gfx::Image([UIImage imageNamed:@"infobar_update"], |
+ base::scoped_policy::RETAIN); |
+ } |
+ return icon_; |
+ } |
+ |
+ InfoBarDelegate::Type GetInfoBarType() const override { |
+ return PAGE_ACTION_TYPE; |
+ } |
+ |
+ base::string16 GetMessageText() const override { |
+ return l10n_util::GetStringUTF16(IDS_IOS_UPGRADE_AVAILABLE); |
+ } |
+ |
+ bool Accept() override { |
+ trigger_upgrade_ = true; |
+ return true; |
+ } |
+ |
+ int GetButtons() const override { return BUTTON_OK; } |
+ |
+ base::string16 GetButtonLabel(InfoBarButton button) const override { |
+ DCHECK(button == BUTTON_OK); |
+ return l10n_util::GetStringUTF16(IDS_IOS_UPGRADE_AVAILABLE_BUTTON); |
+ } |
+ |
+ mutable gfx::Image icon_; |
+ bool trigger_upgrade_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(UpgradeInfoBarDelegate); |
+}; |
+ |
+// The InfoBarDelegate unfortunately is not called at all when an infoBar is |
+// simply dismissed. In order to catch that case this object listens to the |
+// infobars::InfoBarManager::Observer::OnInfoBarRemoved() which is invoked when |
+// an infobar is closed, for any reason. |
+class UpgradeInfoBarDismissObserver |
+ : public infobars::InfoBarManager::Observer { |
+ public: |
+ UpgradeInfoBarDismissObserver() |
+ : infobar_delegate_(nullptr), |
+ dismiss_delegate_(nil), |
+ scoped_observer_(this) {} |
+ |
+ ~UpgradeInfoBarDismissObserver() override {} |
+ |
+ void RegisterObserver(infobars::InfoBarManager* infobar_manager, |
+ UpgradeInfoBarDelegate* infobar_delegate, |
+ NSString* tab_id, |
+ UpgradeCenter* dismiss_delegate) { |
+ scoped_observer_.Add(infobar_manager); |
+ infobar_delegate_ = infobar_delegate; |
+ dismiss_delegate_ = dismiss_delegate; |
+ tab_id_.reset([tab_id copy]); |
+ } |
+ |
+ UpgradeInfoBarDelegate* infobar_delegate() { return infobar_delegate_; } |
+ |
+ private: |
+ // infobars::InfoBarManager::Observer implementation. |
+ void OnInfoBarRemoved(infobars::InfoBar* infobar, bool animate) override { |
+ if (infobar->delegate() == infobar_delegate_) { |
+ [dismiss_delegate_ dismissedInfoBar:tab_id_.get() |
+ performUpgrade:infobar_delegate_->AcceptPressed()]; |
+ } |
+ } |
+ |
+ void OnManagerShuttingDown( |
+ infobars::InfoBarManager* infobar_manager) override { |
+ scoped_observer_.Remove(infobar_manager); |
+ } |
+ |
+ UpgradeInfoBarDelegate* infobar_delegate_; |
+ UpgradeCenter* dismiss_delegate_; |
+ base::scoped_nsobject<NSString> tab_id_; |
+ ScopedObserver<infobars::InfoBarManager, infobars::InfoBarManager::Observer> |
+ scoped_observer_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(UpgradeInfoBarDismissObserver); |
+}; |
+ |
+} // namespace |
+ |
+// The delegateHolder is a simple wrapper to be able to store all the |
+// infoBarDelegate related information in an object that can be put in |
+// an ObjectiveC container. |
+@interface DelegateHolder : NSObject { |
+ UpgradeInfoBarDismissObserver observer_; |
+} |
+ |
+- (instancetype)initWithInfoBarManager:(infobars::InfoBarManager*)infoBarManager |
+ infoBarDelegate:(UpgradeInfoBarDelegate*)infoBarDelegate |
+ upgradeCenter:(UpgradeCenter*)upgradeCenter |
+ tabId:(NSString*)tabId; |
+ |
+@property(nonatomic, readonly) UpgradeInfoBarDelegate* infoBarDelegate; |
+@end |
+ |
+@implementation DelegateHolder |
+ |
+- (instancetype)initWithInfoBarManager:(infobars::InfoBarManager*)infoBarManager |
+ infoBarDelegate:(UpgradeInfoBarDelegate*)infoBarDelegate |
+ upgradeCenter:(UpgradeCenter*)upgradeCenter |
+ tabId:(NSString*)tabId { |
+ self = [super init]; |
+ if (self) { |
+ observer_.RegisterObserver(infoBarManager, infoBarDelegate, tabId, |
+ upgradeCenter); |
+ } |
+ return self; |
+} |
+ |
+- (UpgradeInfoBarDelegate*)infoBarDelegate { |
+ return observer_.infobar_delegate(); |
+} |
+ |
+@end |
+ |
+@implementation UpgradeCenter { |
+ // YES if the infobars are currently visible. |
+ BOOL upgradeInfoBarIsVisible_; |
+ // Used to store the visible upgrade infobars, indexed by tabId. |
+ base::scoped_nsobject<NSMutableDictionary> upgradeInfoBarDelegates_; |
+ // Stores the clients of the upgrade center. These objectiveC objects are not |
+ // retained. |
+ std::set<id<UpgradeCenterClientProtocol>> clients_; |
+#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.
|
+ bool inCallback_; |
+#endif |
+} |
+ |
++ (UpgradeCenter*)sharedInstance { |
+ static UpgradeCenter* obj; |
+ static dispatch_once_t onceToken; |
+ dispatch_once(&onceToken, ^{ |
+ obj = [[self alloc] init]; |
+ }); |
+ return obj; |
+} |
+ |
+- (instancetype)init { |
+ self = [super init]; |
+ if (self) { |
+ upgradeInfoBarDelegates_.reset([[NSMutableDictionary alloc] init]); |
+ |
+ // There is no dealloc and no unregister as this class is a never |
+ // deallocated singleton. |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(applicationWillEnterForeground:) |
+ name:UIApplicationWillEnterForegroundNotification |
+ object:nil]; |
+ |
+ upgradeInfoBarIsVisible_ = [self shouldShowInfoBar]; |
+ } |
+ return self; |
+} |
+ |
+// 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.
|
+- (BOOL)shouldShowInfoBar { |
+ return [self isCurrentVersionObsolete] && ![self infoBarShownRecently]; |
+} |
+ |
+- (BOOL)isCurrentVersionObsolete { |
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
+ NSString* nextVersion = [defaults stringForKey:kNextVersionKey]; |
+ if (nextVersion) { |
+ base::Version current_version(version_info::GetVersionNumber()); |
+ const std::string upgrade = base::SysNSStringToUTF8(nextVersion); |
+ return current_version < base::Version(upgrade); |
+ } |
+ return NO; |
+} |
+ |
+- (BOOL)infoBarShownRecently { |
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
+ NSDate* lastDisplay = [defaults objectForKey:kLastInfobarDisplayTimeKey]; |
+ // Absolute value is to ensure the infobar won't be supressed forever if the |
+ // clock temporarily jumps to the distant future. |
+ if (lastDisplay && |
+ fabs([lastDisplay timeIntervalSinceNow]) < kInfobarDisplayInterval) { |
+ return YES; |
+ } |
+ return NO; |
+} |
+ |
+- (void)applicationWillEnterForeground:(NSNotification*)note { |
+ if (upgradeInfoBarIsVisible_) |
+ return; |
+ |
+ // When returning to active if the upgrade notification has been dismissed, |
+ // bring it back. |
+ if ([self shouldShowInfoBar]) |
+ [self showUpgradeInfoBars]; |
+} |
+ |
+- (void)registerClient:(id<UpgradeCenterClientProtocol>)client { |
+ clients_.insert(client); |
+ if (upgradeInfoBarIsVisible_) |
+ [client showUpgrade:self]; |
+} |
+ |
+- (void)unregisterClient:(id<UpgradeCenterClientProtocol>)client { |
+#ifndef NDEBUG |
+ DCHECK(!inCallback_); |
+#endif |
+ clients_.erase(client); |
+} |
+ |
+// 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.
|
+// created. The infobar will not be created if is already there or if there is |
+// no need to do so. |
+- (void)addInfoBarToManager:(infobars::InfoBarManager*)infoBarManager |
+ forTabId:(NSString*)tabId { |
+ DCHECK(tabId); |
+ DCHECK(infoBarManager); |
+ |
+ // Nothing to do if the infobar are not visible at this point in time. |
+ if (!upgradeInfoBarIsVisible_) |
+ return; |
+ |
+ // Nothing to do if the infobar is already there. |
+ if ([upgradeInfoBarDelegates_ objectForKey:tabId]) |
+ return; |
+ |
+ 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.
|
+ new UpgradeInfoBarDelegate); |
+ base::scoped_nsobject<DelegateHolder> delegateHolder([[DelegateHolder alloc] |
+ initWithInfoBarManager:infoBarManager |
+ infoBarDelegate:infobarDelegate.get() |
+ upgradeCenter:self |
+ tabId:tabId]); |
+ |
+ [upgradeInfoBarDelegates_ setObject:delegateHolder forKey:tabId]; |
+ infoBarManager->AddInfoBar( |
+ infoBarManager->CreateConfirmInfoBar(std::move(infobarDelegate))); |
+} |
+ |
+- (void)tabWillClose:(NSString*)tabId { |
+ [upgradeInfoBarDelegates_ removeObjectForKey:tabId]; |
+} |
+ |
+// 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.
|
+// YES if the user choose to upgrade. |
+- (void)dismissedInfoBar:(NSString*)tabId performUpgrade:(BOOL)shouldUpgrade { |
+ // If the tabId is not in the upgradeInfoBarDelegates_ just ignore the |
+ // notification. In all likelyhood it was trigerred by calling |
+ // -hideUpgradeInfoBars. Or because a tab was closed without dismissing the |
+ // infobar. |
+ base::scoped_nsobject<DelegateHolder> delegateHolder( |
+ [[upgradeInfoBarDelegates_ objectForKey:tabId] retain]); |
+ if (!delegateHolder.get()) |
+ return; |
+ |
+ // Forget about this dismissed infobar. |
+ [upgradeInfoBarDelegates_ removeObjectForKey:tabId]; |
+ |
+ // Get rid of all the infobars on the other tabs. |
+ [self hideUpgradeInfoBars]; |
+ |
+ if (shouldUpgrade) { |
+ NSString* urlString = |
+ [[NSUserDefaults standardUserDefaults] valueForKey:kUpgradeURLKey]; |
+ if (!urlString) |
+ return; // Missing URL, no upgrade possible. |
+ |
+ GURL url = GURL(base::SysNSStringToUTF8(urlString)); |
+ if (!url.is_valid()) |
+ return; |
+ |
+ if (web::UrlHasWebScheme(url)) { |
+ // This URL can be opened in the application, just open in a new tab. |
+ base::scoped_nsobject<OpenUrlCommand> command( |
+ [[OpenUrlCommand alloc] initWithURLFromChrome:url]); |
+ UIWindow* main_window = [[UIApplication sharedApplication] keyWindow]; |
+ DCHECK(main_window); |
+ [main_window chromeExecuteCommand:command]; |
+ } else { |
+ // This URL scheme is not understood, ask the system to open it. |
+ NSURL* nsurl = [NSURL URLWithString:urlString]; |
+ if (nsurl) { |
+ OpenUrlWithCompletionHandler(nsurl, nil); |
+ } |
+ } |
+ } |
+} |
+ |
+- (void)showUpgradeInfoBars { |
+// Add an infobar on all the open tabs. |
+#ifndef NDEBUG |
+ inCallback_ = YES; |
+#endif |
+ upgradeInfoBarIsVisible_ = YES; |
+ std::set<id<UpgradeCenterClientProtocol>>::iterator it; |
+ for (it = clients_.begin(); it != clients_.end(); ++it) |
+ [*it showUpgrade:self]; |
+#ifndef NDEBUG |
+ inCallback_ = NO; |
+#endif |
+ |
+ [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] |
+ forKey:kLastInfobarDisplayTimeKey]; |
+} |
+ |
+- (void)hideUpgradeInfoBars { |
+ upgradeInfoBarIsVisible_ = NO; |
+ // It is important to call -allKeys here and not using a fast iteration on the |
+ // dictionary directly: the dictionary is modified as we go... |
+ for (NSString* tabId in [upgradeInfoBarDelegates_ allKeys]) { |
+ // It is important to retain the delegateHolder as otherwise it is |
+ // deallocated as soon as it is removed from the dictionary. |
+ base::scoped_nsobject<DelegateHolder> delegateHolder( |
+ [[upgradeInfoBarDelegates_ objectForKey:tabId] retain]); |
+ if (delegateHolder.get()) { |
+ [upgradeInfoBarDelegates_ removeObjectForKey:tabId]; |
+ UpgradeInfoBarDelegate* delegate = [delegateHolder infoBarDelegate]; |
+ DCHECK(delegate); |
+ delegate->RemoveSelf(); |
+ } |
+ } |
+} |
+ |
+- (void)upgradeNotificationDidOccur:(const UpgradeRecommendedDetails&)details { |
+ const GURL& upgradeUrl = details.upgrade_url; |
+ |
+ if (!upgradeUrl.is_valid()) { |
+ // The application may crash if the URL is invalid. As the URL is defined |
+ // externally to the application it needs to bail right away and ignore the |
+ // upgrade notification. |
+ NOTREACHED(); |
+ return; |
+ } |
+ |
+ if (!details.next_version.size() || |
+ !base::Version(details.next_version).IsValid()) { |
+ // If the upgrade version is not known or is invalid just ignore the |
+ // upgrade notification. |
+ NOTREACHED(); |
+ return; |
+ } |
+ |
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
+ |
+ // Reset the display clock when the version changes. |
+ NSString* newVersionString = base::SysUTF8ToNSString(details.next_version); |
+ NSString* previousVersionString = [defaults stringForKey:kNextVersionKey]; |
+ if (!previousVersionString || |
+ ![previousVersionString isEqualToString:newVersionString]) { |
+ [defaults removeObjectForKey:kLastInfobarDisplayTimeKey]; |
+ } |
+ |
+ [defaults setValue:base::SysUTF8ToNSString(upgradeUrl.spec()) |
+ forKey:kUpgradeURLKey]; |
+ [defaults setValue:newVersionString forKey:kNextVersionKey]; |
+ |
+ if ([self shouldShowInfoBar]) |
+ [self showUpgradeInfoBars]; |
+} |
+ |
+- (void)resetForTests { |
+ [[UpgradeCenter sharedInstance] hideUpgradeInfoBars]; |
+ NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults]; |
+ [defaults removeObjectForKey:kNextVersionKey]; |
+ [defaults removeObjectForKey:kUpgradeURLKey]; |
+ [defaults removeObjectForKey:kLastInfobarDisplayTimeKey]; |
+ clients_.clear(); |
+} |
+ |
+- (void)setLastDisplayToPast { |
+ NSDate* pastDate = |
+ [NSDate dateWithTimeIntervalSinceNow:-(kInfobarDisplayInterval + 1)]; |
+ [[NSUserDefaults standardUserDefaults] setObject:pastDate |
+ forKey:kLastInfobarDisplayTimeKey]; |
+} |
+ |
+@end |