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

Unified 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 side-by-side diff with in-line comments
Download patch
« 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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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..3566c5316daa239357be0693194e098b7d1cd494
--- /dev/null
+++ b/ios/chrome/browser/upgrade/upgrade_center.mm
@@ -0,0 +1,459 @@
+// 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/memory/ptr_util.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 ()
+// Creates infobars on all tabs.
+- (void)showUpgradeInfoBars;
+// Removes all the infobars.
+- (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;
+// Returns YES if the infobar should be shown.
+- (BOOL)shouldShowInfoBar;
+// Returns YES if the last version signaled by a server side service is more
+// 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
+ 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;
+}
+
+- (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);
+}
+
+- (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;
+
+ auto infobarDelegate = base::MakeUnique<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];
+}
+
+- (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
« 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