Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / upgrade_detector_impl.cc
index 74a1388..e61d2f8 100644 (file)
 #include "base/memory/scoped_ptr.h"
 #include "base/memory/singleton.h"
 #include "base/path_service.h"
+#include "base/prefs/pref_service.h"
 #include "base/process/launch.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/time/time.h"
-#include "base/version.h"
 #include "chrome/browser/browser_process.h"
-#include "chrome/browser/google/google_util.h"
+#include "chrome/browser/google/google_brand.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/chrome_version_info.h"
+#include "chrome/common/pref_names.h"
+#include "components/network_time/network_time_tracker.h"
 #include "content/public/browser/browser_thread.h"
-#include "ui/base/resource/resource_bundle.h"
 
 #if defined(OS_WIN)
+#include "base/win/win_util.h"
 #include "chrome/installer/util/browser_distribution.h"
 #include "chrome/installer/util/google_update_settings.h"
 #include "chrome/installer/util/helper.h"
 #include "chrome/installer/util/install_util.h"
 #elif defined(OS_MACOSX)
 #include "chrome/browser/mac/keystone_glue.h"
-#elif defined(OS_POSIX)
-#include "base/process/launch.h"
 #endif
 
 using content::BrowserThread;
@@ -55,17 +55,28 @@ const int kNotifyCycleTimeForTestingMs = 500;  // Half a second.
 // The number of days after which we identify a build/install as outdated.
 const uint64 kOutdatedBuildAgeInDays = 12 * 7;
 
+// Return the string that was passed as a value for the
+// kCheckForUpdateIntervalSec switch.
 std::string CmdLineInterval() {
   const CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
   return cmd_line.GetSwitchValueASCII(switches::kCheckForUpdateIntervalSec);
 }
 
+// Check if one of the outdated simulation switches was present on the command
+// line.
+bool SimulatingOutdated() {
+  const CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
+  return cmd_line.HasSwitch(switches::kSimulateOutdated) ||
+      cmd_line.HasSwitch(switches::kSimulateOutdatedNoAU);
+}
+
+// Check if any of the testing switches was present on the command line.
 bool IsTesting() {
   const CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
   return cmd_line.HasSwitch(switches::kSimulateUpgrade) ||
       cmd_line.HasSwitch(switches::kCheckForUpdateIntervalSec) ||
       cmd_line.HasSwitch(switches::kSimulateCriticalUpdate) ||
-      cmd_line.HasSwitch(switches::kSimulateOutdated);
+      SimulatingOutdated();
 }
 
 // How often to check for an upgrade.
@@ -79,23 +90,28 @@ int GetCheckForUpgradeEveryMs() {
   return kCheckForUpgradeMs;
 }
 
+// Return true if the current build is one of the unstable channels.
 bool IsUnstableChannel() {
+  // TODO(mad): Investigate whether we still need to be on the file thread for
+  // this. On Windows, the file thread used to be required for registry access
+  // but no anymore. But other platform may still need the file thread.
+  // crbug.com/366647.
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
   chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
   return channel == chrome::VersionInfo::CHANNEL_DEV ||
          channel == chrome::VersionInfo::CHANNEL_CANARY;
 }
 
-// This task identifies whether we are running an unstable version. And then
-// it unconditionally calls back the provided task.
+// This task identifies whether we are running an unstable version. And then it
+// unconditionally calls back the provided task.
 void CheckForUnstableChannel(const base::Closure& callback_task,
                              bool* is_unstable_channel) {
-  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
   *is_unstable_channel = IsUnstableChannel();
   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback_task);
 }
 
 #if defined(OS_WIN)
+// Return true if the currently running Chrome is a system install.
 bool IsSystemInstall() {
   // Get the version of the currently *installed* instance of Chrome,
   // which might be newer than the *running* instance if we have been
@@ -109,28 +125,72 @@ bool IsSystemInstall() {
   return !InstallUtil::IsPerUserInstall(exe_path.value().c_str());
 }
 
-// This task checks the update policy and calls back the task only if automatic
-// updates are allowed. It also identifies whether we are running an unstable
-// channel.
+// Sets |is_unstable_channel| to true if the current chrome is on the dev or
+// canary channels. Sets |is_auto_update_enabled| to true if Google Update will
+// update the current chrome. Unconditionally posts |callback_task| to the UI
+// thread to continue processing.
 void DetectUpdatability(const base::Closure& callback_task,
-                        bool* is_unstable_channel) {
+                        bool* is_unstable_channel,
+                        bool* is_auto_update_enabled) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
 
   base::string16 app_guid = installer::GetAppGuidForUpdates(IsSystemInstall());
   DCHECK(!app_guid.empty());
-  if (GoogleUpdateSettings::AUTOMATIC_UPDATES ==
-      GoogleUpdateSettings::GetAppUpdatePolicy(app_guid, NULL)) {
-    CheckForUnstableChannel(callback_task, is_unstable_channel);
+  // Don't try to turn on autoupdate when we failed previously.
+  if (is_auto_update_enabled) {
+    *is_auto_update_enabled =
+        GoogleUpdateSettings::AreAutoupdatesEnabled(app_guid);
   }
+  *is_unstable_channel = IsUnstableChannel();
+  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback_task);
 }
 #endif  // defined(OS_WIN)
 
+// Gets the currently installed version. On Windows, if |critical_update| is not
+// NULL, also retrieves the critical update version info if available.
+base::Version GetCurrentlyInstalledVersionImpl(Version* critical_update) {
+  base::ThreadRestrictions::AssertIOAllowed();
+
+  Version installed_version;
+#if defined(OS_WIN)
+  // Get the version of the currently *installed* instance of Chrome,
+  // which might be newer than the *running* instance if we have been
+  // upgraded in the background.
+  bool system_install = IsSystemInstall();
+
+  // TODO(tommi): Check if using the default distribution is always the right
+  // thing to do.
+  BrowserDistribution* dist = BrowserDistribution::GetDistribution();
+  InstallUtil::GetChromeVersion(dist, system_install, &installed_version);
+  if (critical_update && installed_version.IsValid()) {
+    InstallUtil::GetCriticalUpdateVersion(dist, system_install,
+                                          critical_update);
+  }
+#elif defined(OS_MACOSX)
+  installed_version =
+      Version(base::UTF16ToASCII(keystone_glue::CurrentlyInstalledVersion()));
+#elif defined(OS_POSIX)
+  // POSIX but not Mac OS X: Linux, etc.
+  CommandLine command_line(*CommandLine::ForCurrentProcess());
+  command_line.AppendSwitch(switches::kProductVersion);
+  std::string reply;
+  if (!base::GetAppOutput(command_line, &reply)) {
+    DLOG(ERROR) << "Failed to get current file version";
+    return installed_version;
+  }
+
+  installed_version = Version(reply);
+#endif
+  return installed_version;
+}
+
 }  // namespace
 
 UpgradeDetectorImpl::UpgradeDetectorImpl()
-    : weak_factory_(this),
-      is_unstable_channel_(false),
-      build_date_(base::GetBuildTime()) {
+    : is_unstable_channel_(false),
+      is_auto_update_enabled_(true),
+      build_date_(base::GetBuildTime()),
+      weak_factory_(this) {
   CommandLine command_line(*CommandLine::ForCurrentProcess());
   // The different command line switches that affect testing can't be used
   // simultaneously, if they do, here's the precedence order, based on the order
@@ -139,7 +199,8 @@ UpgradeDetectorImpl::UpgradeDetectorImpl()
   //   switch from being taken into account.
   // - kSimulateUpgrade supersedes critical or outdated upgrade switches.
   // - kSimulateCriticalUpdate has precedence over kSimulateOutdated.
-  // - kSimulateOutdated can work on its own, or with a specified date.
+  // - kSimulateOutdatedNoAU has precedence over kSimulateOutdated.
+  // - kSimulateOutdated[NoAu] can work on its own, or with a specified date.
   if (command_line.HasSwitch(switches::kDisableBackgroundNetworking))
     return;
   if (command_line.HasSwitch(switches::kSimulateUpgrade)) {
@@ -150,7 +211,7 @@ UpgradeDetectorImpl::UpgradeDetectorImpl()
     UpgradeDetected(UPGRADE_AVAILABLE_CRITICAL);
     return;
   }
-  if (command_line.HasSwitch(switches::kSimulateOutdated)) {
+  if (SimulatingOutdated()) {
     // The outdated simulation can work without a value, which means outdated
     // now, or with a value that must be a well formed date/time string that
     // overrides the build date.
@@ -158,8 +219,14 @@ UpgradeDetectorImpl::UpgradeDetectorImpl()
     // tracking moves off of the VariationsService, the "variations-server-url"
     // command line switch must also be specified for the service to be
     // available on non GOOGLE_CHROME_BUILD.
-    std::string build_date = command_line.GetSwitchValueASCII(
-        switches::kSimulateOutdated);
+    std::string switch_name;
+    if (command_line.HasSwitch(switches::kSimulateOutdatedNoAU)) {
+      is_auto_update_enabled_ = false;
+      switch_name = switches::kSimulateOutdatedNoAU;
+    } else {
+      switch_name = switches::kSimulateOutdated;
+    }
+    std::string build_date = command_line.GetSwitchValueASCII(switch_name);
     base::Time maybe_build_time;
     bool result = base::Time::FromString(build_date.c_str(), &maybe_build_time);
     if (result && !maybe_build_time.is_null()) {
@@ -168,48 +235,72 @@ UpgradeDetectorImpl::UpgradeDetectorImpl()
       StartTimerForUpgradeCheck();
     } else {
       // Without a valid date, we simulate that we are already outdated...
-      UpgradeDetected(UPGRADE_NEEDED_OUTDATED_INSTALL);
+      UpgradeDetected(
+          is_auto_update_enabled_ ? UPGRADE_NEEDED_OUTDATED_INSTALL
+                                  : UPGRADE_NEEDED_OUTDATED_INSTALL_NO_AU);
     }
     return;
   }
 
-  // Windows: only enable upgrade notifications for official builds.
-  // Mac: only enable them if the updater (Keystone) is present.
-  // Linux (and other POSIX): always enable regardless of branding.
+  // Register for experiment notifications. Note that since this class is a
+  // singleton, it does not need to unregister for notifications when destroyed,
+  // since it outlives the VariationsService.
+  chrome_variations::VariationsService* variations_service =
+      g_browser_process->variations_service();
+  if (variations_service)
+    variations_service->AddObserver(this);
+
   base::Closure start_upgrade_check_timer_task =
       base::Bind(&UpgradeDetectorImpl::StartTimerForUpgradeCheck,
                  weak_factory_.GetWeakPtr());
-#if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
-  // On Windows, there might be a policy preventing updates, so validate
-  // updatability, and then call StartTimerForUpgradeCheck appropriately.
+
+#if defined(OS_WIN)
+  // Only enable upgrade notifications for official builds.  Chromium has no
+  // upgrade channel.
+#if defined(GOOGLE_CHROME_BUILD)
+  // On Windows, there might be a policy/enterprise environment preventing
+  // updates, so validate updatability, and then call StartTimerForUpgradeCheck
+  // appropriately. And don't check for autoupdate if we already attempted to
+  // enable it in the past.
+  bool attempted_enabling_autoupdate = g_browser_process->local_state() &&
+      g_browser_process->local_state()->GetBoolean(
+          prefs::kAttemptedToEnableAutoupdate);
   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
                           base::Bind(&DetectUpdatability,
                                      start_upgrade_check_timer_task,
-                                     &is_unstable_channel_));
-  return;
-#elif defined(OS_WIN) && !defined(GOOGLE_CHROME_BUILD)
-  return;  // Chromium has no upgrade channel.
-#elif defined(OS_MACOSX)
-  if (!keystone_glue::KeystoneEnabled())
-    return;  // Keystone updater not enabled.
-#elif !defined(OS_POSIX)
+                                     &is_unstable_channel_,
+                                     attempted_enabling_autoupdate ?
+                                         NULL : &is_auto_update_enabled_));
+#endif
+#else
+#if defined(OS_MACOSX)
+  // Only enable upgrade notifications if the updater (Keystone) is present.
+  if (!keystone_glue::KeystoneEnabled()) {
+    is_auto_update_enabled_ = false;
+    return;
+  }
+#elif defined(OS_POSIX)
+  // Always enable upgrade notifications regardless of branding.
+#else
   return;
 #endif
-
   // Check whether the build is an unstable channel before starting the timer.
   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
                           base::Bind(&CheckForUnstableChannel,
                                      start_upgrade_check_timer_task,
                                      &is_unstable_channel_));
-
-  // Start tracking network time updates.
-  network_time_tracker_.Start();
+#endif
 }
 
 UpgradeDetectorImpl::~UpgradeDetectorImpl() {
 }
 
-// Static
+// static
+base::Version UpgradeDetectorImpl::GetCurrentlyInstalledVersion() {
+  return GetCurrentlyInstalledVersionImpl(NULL);
+}
+
+// static
 // This task checks the currently running version of Chrome against the
 // installed version. If the installed version is newer, it calls back
 // UpgradeDetectorImpl::UpgradeDetected using a weak pointer so that it can
@@ -218,39 +309,9 @@ void UpgradeDetectorImpl::DetectUpgradeTask(
     base::WeakPtr<UpgradeDetectorImpl> upgrade_detector) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
 
-  Version installed_version;
   Version critical_update;
-
-#if defined(OS_WIN)
-  // Get the version of the currently *installed* instance of Chrome,
-  // which might be newer than the *running* instance if we have been
-  // upgraded in the background.
-  bool system_install = IsSystemInstall();
-
-  // TODO(tommi): Check if using the default distribution is always the right
-  // thing to do.
-  BrowserDistribution* dist = BrowserDistribution::GetDistribution();
-  InstallUtil::GetChromeVersion(dist, system_install, &installed_version);
-
-  if (installed_version.IsValid()) {
-    InstallUtil::GetCriticalUpdateVersion(dist, system_install,
-                                          &critical_update);
-  }
-#elif defined(OS_MACOSX)
-  installed_version =
-      Version(UTF16ToASCII(keystone_glue::CurrentlyInstalledVersion()));
-#elif defined(OS_POSIX)
-  // POSIX but not Mac OS X: Linux, etc.
-  CommandLine command_line(*CommandLine::ForCurrentProcess());
-  command_line.AppendSwitch(switches::kProductVersion);
-  std::string reply;
-  if (!base::GetAppOutput(command_line, &reply)) {
-    DLOG(ERROR) << "Failed to get current file version";
-    return;
-  }
-
-  installed_version = Version(reply);
-#endif
+  Version installed_version =
+      GetCurrentlyInstalledVersionImpl(&critical_update);
 
   // Get the version of the currently *running* instance of Chrome.
   chrome::VersionInfo version_info;
@@ -291,6 +352,24 @@ void UpgradeDetectorImpl::StartTimerForUpgradeCheck() {
       this, &UpgradeDetectorImpl::CheckForUpgrade);
 }
 
+void UpgradeDetectorImpl::StartUpgradeNotificationTimer() {
+  // The timer may already be running (e.g. due to both a software upgrade and
+  // experiment updates being available).
+  if (upgrade_notification_timer_.IsRunning())
+    return;
+
+  upgrade_detected_time_ = base::TimeTicks::Now();
+
+  // Start the repeating timer for notifying the user after a certain period.
+  // The called function will eventually figure out that enough time has passed
+  // and stop the timer.
+  const int cycle_time_ms = IsTesting() ?
+      kNotifyCycleTimeForTestingMs : kNotifyCycleTimeMs;
+  upgrade_notification_timer_.Start(FROM_HERE,
+      base::TimeDelta::FromMilliseconds(cycle_time_ms),
+      this, &UpgradeDetectorImpl::NotifyOnUpgrade);
+}
+
 void UpgradeDetectorImpl::CheckForUpgrade() {
   // Interrupt any (unlikely) unfinished execution of DetectUpgradeTask, or at
   // least prevent the callback from being executed, because we will potentially
@@ -312,23 +391,28 @@ void UpgradeDetectorImpl::CheckForUpgrade() {
 }
 
 bool UpgradeDetectorImpl::DetectOutdatedInstall() {
-  // Only enable the outdated install check if we are running the trial for it,
-  // unless we are simulating an outdated isntall.
-  static bool simulate_outdated = CommandLine::ForCurrentProcess()->HasSwitch(
-      switches::kSimulateOutdated);
+  // Don't show the bubble if we have a brand code that is NOT organic, unless
+  // an outdated build is being simulated by command line switches.
+  static bool simulate_outdated = SimulatingOutdated();
   if (!simulate_outdated) {
-    // Also don't show the bubble if we have a brand code that is NOT organic.
     std::string brand;
-    if (google_util::GetBrand(&brand) && !google_util::IsOrganic(brand))
+    if (google_brand::GetBrand(&brand) && !google_brand::IsOrganic(brand))
       return false;
+
+#if defined(OS_WIN)
+    // Don't show the update bubbles to enterprise users (i.e., on a domain).
+    if (base::win::IsEnrolledToDomain())
+      return false;
+#endif
   }
 
   base::Time network_time;
   base::TimeDelta uncertainty;
-  if (!network_time_tracker_.GetNetworkTime(base::TimeTicks::Now(),
-                                            &network_time,
-                                            &uncertainty)) {
-    return false;
+  if (!g_browser_process->network_time_tracker()->GetNetworkTime(
+          base::TimeTicks::Now(), &network_time, &uncertainty)) {
+    // When network time has not been initialized yet, simply rely on the
+    // machine's current time.
+    network_time = base::Time::Now();
   }
 
   if (network_time.is_null() || build_date_.is_null() ||
@@ -339,7 +423,9 @@ bool UpgradeDetectorImpl::DetectOutdatedInstall() {
 
   if (network_time - build_date_ >
       base::TimeDelta::FromDays(kOutdatedBuildAgeInDays)) {
-    UpgradeDetected(UPGRADE_NEEDED_OUTDATED_INSTALL);
+    UpgradeDetected(is_auto_update_enabled_ ?
+        UPGRADE_NEEDED_OUTDATED_INSTALL :
+        UPGRADE_NEEDED_OUTDATED_INSTALL_NO_AU);
     return true;
   }
   // If we simlated an outdated install with a date, we don't want to keep
@@ -347,43 +433,38 @@ bool UpgradeDetectorImpl::DetectOutdatedInstall() {
   return simulate_outdated;
 }
 
+void UpgradeDetectorImpl::OnExperimentChangesDetected(Severity severity) {
+  set_best_effort_experiment_updates_available(severity == BEST_EFFORT);
+  set_critical_experiment_updates_available(severity == CRITICAL);
+  StartUpgradeNotificationTimer();
+}
+
 void UpgradeDetectorImpl::UpgradeDetected(UpgradeAvailable upgrade_available) {
   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
-  upgrade_available_ = upgrade_available;
+  set_upgrade_available(upgrade_available);
 
   // Stop the recurring timer (that is checking for changes).
   detect_upgrade_timer_.Stop();
+  set_critical_update_acknowledged(false);
 
-  NotifyUpgradeDetected();
-
-  // Start the repeating timer for notifying the user after a certain period.
-  // The called function will eventually figure out that enough time has passed
-  // and stop the timer.
-  int cycle_time = IsTesting() ?
-      kNotifyCycleTimeForTestingMs : kNotifyCycleTimeMs;
-  upgrade_notification_timer_.Start(FROM_HERE,
-      base::TimeDelta::FromMilliseconds(cycle_time),
-      this, &UpgradeDetectorImpl::NotifyOnUpgrade);
+  StartUpgradeNotificationTimer();
 }
 
-void UpgradeDetectorImpl::NotifyOnUpgrade() {
-  base::TimeDelta delta = base::Time::Now() - upgrade_detected_time();
-
-  // We'll make testing more convenient by switching to seconds of waiting
-  // instead of days between flipping severity.
-  bool is_testing = IsTesting();
-  int64 time_passed = is_testing ? delta.InSeconds() : delta.InHours();
-
-  bool is_critical_or_outdated = upgrade_available_ > UPGRADE_AVAILABLE_REGULAR;
+void UpgradeDetectorImpl::NotifyOnUpgradeWithTimePassed(
+    base::TimeDelta time_passed) {
+  const bool is_critical_or_outdated =
+      upgrade_available() > UPGRADE_AVAILABLE_REGULAR ||
+      critical_experiment_updates_available();
   if (is_unstable_channel_) {
     // There's only one threat level for unstable channels like dev and
     // canary, and it hits after one hour. During testing, it hits after one
     // second.
-    const int kUnstableThreshold = 1;
+    const base::TimeDelta unstable_threshold = IsTesting() ?
+        base::TimeDelta::FromSeconds(1) : base::TimeDelta::FromHours(1);
 
-    if (is_critical_or_outdated)
+    if (is_critical_or_outdated) {
       set_upgrade_notification_stage(UPGRADE_ANNOYANCE_CRITICAL);
-    else if (time_passed >= kUnstableThreshold) {
+    } else if (time_passed >= unstable_threshold) {
       set_upgrade_notification_stage(UPGRADE_ANNOYANCE_LOW);
 
       // That's as high as it goes.
@@ -392,26 +473,28 @@ void UpgradeDetectorImpl::NotifyOnUpgrade() {
       return;  // Not ready to recommend upgrade.
     }
   } else {
-    const int kMultiplier = is_testing ? 10 : 24;
-    // 14 days when not testing, otherwise 14 seconds.
-    const int kSevereThreshold = 14 * kMultiplier;
-    const int kHighThreshold = 7 * kMultiplier;
-    const int kElevatedThreshold = 4 * kMultiplier;
-    const int kLowThreshold = 2 * kMultiplier;
+    const base::TimeDelta multiplier = IsTesting() ?
+        base::TimeDelta::FromSeconds(10) : base::TimeDelta::FromDays(1);
+
+    // 14 days when not testing, otherwise 140 seconds.
+    const base::TimeDelta severe_threshold = 14 * multiplier;
+    const base::TimeDelta high_threshold = 7 * multiplier;
+    const base::TimeDelta elevated_threshold = 4 * multiplier;
+    const base::TimeDelta low_threshold = 2 * multiplier;
 
     // These if statements must be sorted (highest interval first).
-    if (time_passed >= kSevereThreshold || is_critical_or_outdated) {
+    if (time_passed >= severe_threshold || is_critical_or_outdated) {
       set_upgrade_notification_stage(
           is_critical_or_outdated ? UPGRADE_ANNOYANCE_CRITICAL :
                                     UPGRADE_ANNOYANCE_SEVERE);
 
       // We can't get any higher, baby.
       upgrade_notification_timer_.Stop();
-    } else if (time_passed >= kHighThreshold) {
+    } else if (time_passed >= high_threshold) {
       set_upgrade_notification_stage(UPGRADE_ANNOYANCE_HIGH);
-    } else if (time_passed >= kElevatedThreshold) {
+    } else if (time_passed >= elevated_threshold) {
       set_upgrade_notification_stage(UPGRADE_ANNOYANCE_ELEVATED);
-    } else if (time_passed >= kLowThreshold) {
+    } else if (time_passed >= low_threshold) {
       set_upgrade_notification_stage(UPGRADE_ANNOYANCE_LOW);
     } else {
       return;  // Not ready to recommend upgrade.
@@ -421,6 +504,12 @@ void UpgradeDetectorImpl::NotifyOnUpgrade() {
   NotifyUpgradeRecommended();
 }
 
+void UpgradeDetectorImpl::NotifyOnUpgrade() {
+  const base::TimeDelta time_passed =
+      base::TimeTicks::Now() - upgrade_detected_time_;
+  NotifyOnUpgradeWithTimePassed(time_passed);
+}
+
 // static
 UpgradeDetectorImpl* UpgradeDetectorImpl::GetInstance() {
   return Singleton<UpgradeDetectorImpl>::get();