Chromium Code Reviews| 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 |