| Index: chrome/browser/upgrade_detector_impl.cc
 | 
| diff --git a/chrome/browser/upgrade_detector_impl.cc b/chrome/browser/upgrade_detector_impl.cc
 | 
| index 91d498625c75d676d08189c606e9dde0f6c78537..3934b9dde7f594f91eca7e93d68d2d23dd5bf3a0 100644
 | 
| --- a/chrome/browser/upgrade_detector_impl.cc
 | 
| +++ b/chrome/browser/upgrade_detector_impl.cc
 | 
| @@ -7,28 +7,34 @@
 | 
|  #include <string>
 | 
|  
 | 
|  #include "base/bind.h"
 | 
| +#include "base/build_time.h"
 | 
|  #include "base/command_line.h"
 | 
|  #include "base/file_path.h"
 | 
|  #include "base/memory/scoped_ptr.h"
 | 
|  #include "base/memory/singleton.h"
 | 
| +#include "base/metrics/field_trial.h"
 | 
|  #include "base/path_service.h"
 | 
|  #include "base/string_util.h"
 | 
|  #include "base/strings/string_number_conversions.h"
 | 
|  #include "base/time.h"
 | 
|  #include "base/utf_string_conversions.h"
 | 
| +#include "base/version.h"
 | 
| +#include "chrome/browser/browser_process.h"
 | 
| +#include "chrome/browser/metrics/variations/variations_service.h"
 | 
|  #include "chrome/common/chrome_switches.h"
 | 
|  #include "chrome/common/chrome_version_info.h"
 | 
| -#include "chrome/installer/util/browser_distribution.h"
 | 
|  #include "content/public/browser/browser_thread.h"
 | 
|  #include "ui/base/resource/resource_bundle.h"
 | 
|  
 | 
|  #if defined(OS_WIN)
 | 
| +#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_util.h"
 | 
| -#include "base/version.h"
 | 
|  #endif
 | 
|  
 | 
|  using content::BrowserThread;
 | 
| @@ -46,6 +52,13 @@ const int kNotifyCycleTimeMs = 20 * 60 * 1000;  // 20 minutes.
 | 
|  // Same as kNotifyCycleTimeMs but only used during testing.
 | 
|  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;
 | 
| +
 | 
| +// Finch Experiment strings to identify if we should check for outdated install.
 | 
| +const char kOutdatedInstallCheckTrialName[] = "OutdatedInstallCheck";
 | 
| +const char kOutdatedInstallCheck12WeeksGroupName[] = "12WeeksOutdatedIntalls";
 | 
| +
 | 
|  std::string CmdLineInterval() {
 | 
|    const CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
 | 
|    return cmd_line.GetSwitchValueASCII(switches::kCheckForUpdateIntervalSec);
 | 
| @@ -54,7 +67,9 @@ std::string CmdLineInterval() {
 | 
|  bool IsTesting() {
 | 
|    const CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
 | 
|    return cmd_line.HasSwitch(switches::kSimulateUpgrade) ||
 | 
| -      cmd_line.HasSwitch(switches::kCheckForUpdateIntervalSec);
 | 
| +      cmd_line.HasSwitch(switches::kCheckForUpdateIntervalSec) ||
 | 
| +      cmd_line.HasSwitch(switches::kSimulateCriticalUpdate) ||
 | 
| +      cmd_line.HasSwitch(switches::kSimulateOutdated);
 | 
|  }
 | 
|  
 | 
|  // How often to check for an upgrade.
 | 
| @@ -68,29 +83,150 @@ int GetCheckForUpgradeEveryMs() {
 | 
|    return kCheckForUpgradeMs;
 | 
|  }
 | 
|  
 | 
| -// This task checks the currently running version of Chrome against the
 | 
| -// installed version. If the installed version is newer, it runs the passed
 | 
| -// callback task. Otherwise it just deletes the task.
 | 
| -void DetectUpgradeTask(const base::Closure& upgrade_detected_task,
 | 
| -                       bool* is_unstable_channel,
 | 
| -                       bool* is_critical_upgrade) {
 | 
| +bool IsUnstableChannel() {
 | 
|    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
 | 
| +  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
 | 
| +  return channel == chrome::VersionInfo::CHANNEL_DEV ||
 | 
| +         channel == chrome::VersionInfo::CHANNEL_CANARY;
 | 
| +}
 | 
|  
 | 
| -  Version installed_version;
 | 
| -  Version critical_update;
 | 
| +// 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)
 | 
| +bool IsSystemInstall() {
 | 
|    // 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.
 | 
|    base::FilePath exe_path;
 | 
|    if (!PathService::Get(base::DIR_EXE, &exe_path)) {
 | 
|      NOTREACHED() << "Failed to find executable path";
 | 
| +    return false;
 | 
| +  }
 | 
| +
 | 
| +  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.
 | 
| +void DetectUpdatability(const base::Closure& callback_task,
 | 
| +                        bool* is_unstable_channel) {
 | 
| +  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
 | 
| +
 | 
| +  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);
 | 
| +  }
 | 
| +}
 | 
| +#endif  // defined(OS_WIN)
 | 
| +
 | 
| +}  // namespace
 | 
| +
 | 
| +UpgradeDetectorImpl::UpgradeDetectorImpl()
 | 
| +    : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
 | 
| +      is_unstable_channel_(false),
 | 
| +      build_date_(base::GetBuildTime()) {
 | 
| +  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
 | 
| +  // of the if statements below:
 | 
| +  // - kDisableBackgroundNetworking prevents any of the other command line
 | 
| +  //   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.
 | 
| +  if (command_line.HasSwitch(switches::kDisableBackgroundNetworking))
 | 
| +    return;
 | 
| +  if (command_line.HasSwitch(switches::kSimulateUpgrade)) {
 | 
| +    UpgradeDetected(UPGRADE_AVAILABLE_REGULAR);
 | 
| +    return;
 | 
| +  }
 | 
| +  if (command_line.HasSwitch(switches::kSimulateCriticalUpdate)) {
 | 
| +    UpgradeDetected(UPGRADE_AVAILABLE_CRITICAL);
 | 
| +    return;
 | 
| +  }
 | 
| +  if (command_line.HasSwitch(switches::kSimulateOutdated)) {
 | 
| +    // 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.
 | 
| +    // Also note that to test with a given time/date, until the network time
 | 
| +    // 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);
 | 
| +    base::Time maybe_build_time;
 | 
| +    bool result = base::Time::FromString(build_date.c_str(), &maybe_build_time);
 | 
| +    if (result && !maybe_build_time.is_null()) {
 | 
| +      // We got a valid build date simulation so use it and check for upgrades.
 | 
| +      build_date_ = maybe_build_time;
 | 
| +      StartTimerForUpgradeCheck();
 | 
| +    } else {
 | 
| +      // Without a valid date, we simulate that we are already outdated...
 | 
| +      UpgradeDetected(UPGRADE_NEEDED_OUTDATED_INSTALL);
 | 
| +    }
 | 
|      return;
 | 
|    }
 | 
|  
 | 
| -  bool system_install =
 | 
| -      !InstallUtil::IsPerUserInstall(exe_path.value().c_str());
 | 
| +  // 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.
 | 
| +  base::Closure start_upgrade_check_timer_task =
 | 
| +      base::Bind(&UpgradeDetectorImpl::StartTimerForUpgradeCheck,
 | 
| +                 weak_factory_.GetWeakPtr());
 | 
| +#if defined(OS_WINDOW) && defined(GOOGLE_CHROME_BUILD)
 | 
| +  // On Windows, there might be a policy preventing updates, so validate
 | 
| +  // updatability, and then call StartTimerForUpgradeCheck appropriately.
 | 
| +  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
 | 
| +                          base::Bind(&DetectUpdatability,
 | 
| +                                     start_upgrade_check_timer_task,
 | 
| +                                     &is_unstable_channel_));
 | 
| +  return;
 | 
| +#elif defined(OS_WINDOW) && !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)
 | 
| +  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_));
 | 
| +}
 | 
| +
 | 
| +UpgradeDetectorImpl::~UpgradeDetectorImpl() {
 | 
| +}
 | 
| +
 | 
| +// 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
 | 
| +// be interrupted from the UI thread.
 | 
| +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.
 | 
| @@ -117,10 +253,6 @@ void DetectUpgradeTask(const base::Closure& upgrade_detected_task,
 | 
|    installed_version = Version(reply);
 | 
|  #endif
 | 
|  
 | 
| -  chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
 | 
| -  *is_unstable_channel = channel == chrome::VersionInfo::CHANNEL_DEV ||
 | 
| -                         channel == chrome::VersionInfo::CHANNEL_CANARY;
 | 
| -
 | 
|    // Get the version of the currently *running* instance of Chrome.
 | 
|    chrome::VersionInfo version_info;
 | 
|    if (!version_info.is_valid()) {
 | 
| @@ -140,64 +272,83 @@ void DetectUpgradeTask(const base::Closure& upgrade_detected_task,
 | 
|        (installed_version.CompareTo(running_version) > 0)) {
 | 
|      // If a more recent version is available, it might be that we are lacking
 | 
|      // a critical update, such as a zero-day fix.
 | 
| -    *is_critical_upgrade =
 | 
| -        critical_update.IsValid() &&
 | 
| -        (critical_update.CompareTo(running_version) > 0);
 | 
| +    UpgradeAvailable upgrade_available = UPGRADE_AVAILABLE_REGULAR;
 | 
| +    if (critical_update.IsValid() &&
 | 
| +        critical_update.CompareTo(running_version) > 0) {
 | 
| +      upgrade_available = UPGRADE_AVAILABLE_CRITICAL;
 | 
| +    }
 | 
|  
 | 
|      // Fire off the upgrade detected task.
 | 
|      BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
 | 
| -                            upgrade_detected_task);
 | 
| -  }
 | 
| -}
 | 
| -
 | 
| -}  // namespace
 | 
| -
 | 
| -UpgradeDetectorImpl::UpgradeDetectorImpl()
 | 
| -    : ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
 | 
| -      is_unstable_channel_(false) {
 | 
| -  CommandLine command_line(*CommandLine::ForCurrentProcess());
 | 
| -  if (command_line.HasSwitch(switches::kDisableBackgroundNetworking))
 | 
| -    return;
 | 
| -  if (command_line.HasSwitch(switches::kSimulateUpgrade)) {
 | 
| -    UpgradeDetected();
 | 
| -    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.
 | 
| -#if (defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)) || defined(OS_POSIX)
 | 
| -#if defined(OS_MACOSX)
 | 
| -  if (keystone_glue::KeystoneEnabled())
 | 
| -#endif
 | 
| -  {
 | 
| -    detect_upgrade_timer_.Start(FROM_HERE,
 | 
| -        base::TimeDelta::FromMilliseconds(GetCheckForUpgradeEveryMs()),
 | 
| -        this, &UpgradeDetectorImpl::CheckForUpgrade);
 | 
| +                            base::Bind(&UpgradeDetectorImpl::UpgradeDetected,
 | 
| +                                       upgrade_detector,
 | 
| +                                       upgrade_available));
 | 
|    }
 | 
| -#endif
 | 
|  }
 | 
|  
 | 
| -UpgradeDetectorImpl::~UpgradeDetectorImpl() {
 | 
| +void UpgradeDetectorImpl::StartTimerForUpgradeCheck() {
 | 
| +  detect_upgrade_timer_.Start(FROM_HERE,
 | 
| +      base::TimeDelta::FromMilliseconds(GetCheckForUpgradeEveryMs()),
 | 
| +      this, &UpgradeDetectorImpl::CheckForUpgrade);
 | 
|  }
 | 
|  
 | 
|  void UpgradeDetectorImpl::CheckForUpgrade() {
 | 
| +  // Interrupt any (unlikely) unfinished execution of DetectUpgradeTask, or at
 | 
| +  // least prevent the callback from being executed, because we will potentially
 | 
| +  // call it from within DetectOutdatedInstall() or will post
 | 
| +  // DetectUpgradeTask again below anyway.
 | 
|    weak_factory_.InvalidateWeakPtrs();
 | 
| -  base::Closure callback_task =
 | 
| -      base::Bind(&UpgradeDetectorImpl::UpgradeDetected,
 | 
| -                 weak_factory_.GetWeakPtr());
 | 
| +
 | 
| +  // No need to look for upgrades if the install is outdated.
 | 
| +  if (DetectOutdatedInstall())
 | 
| +    return;
 | 
| +
 | 
|    // We use FILE as the thread to run the upgrade detection code on all
 | 
|    // platforms. For Linux, this is because we don't want to block the UI thread
 | 
|    // while launching a background process and reading its output; on the Mac and
 | 
|    // on Windows checking for an upgrade requires reading a file.
 | 
|    BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
 | 
| -                          base::Bind(&DetectUpgradeTask,
 | 
| -                                     callback_task,
 | 
| -                                     &is_unstable_channel_,
 | 
| -                                     &is_critical_upgrade_));
 | 
| +                          base::Bind(&UpgradeDetectorImpl::DetectUpgradeTask,
 | 
| +                                     weak_factory_.GetWeakPtr()));
 | 
| +}
 | 
| +
 | 
| +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);
 | 
| +  if (base::FieldTrialList::FindFullName(kOutdatedInstallCheckTrialName) !=
 | 
| +          kOutdatedInstallCheck12WeeksGroupName && !simulate_outdated) {
 | 
| +    return false;
 | 
| +  }
 | 
| +
 | 
| +  base::Time network_time;
 | 
| +  base::TimeDelta uncertainty;
 | 
| +  if (!g_browser_process->variations_service() ||
 | 
| +      !g_browser_process->variations_service()->GetNetworkTime(&network_time,
 | 
| +                                                               &uncertainty)) {
 | 
| +    return false;
 | 
| +  }
 | 
| +
 | 
| +  if (network_time.is_null() || build_date_.is_null() ||
 | 
| +      build_date_ > network_time) {
 | 
| +    NOTREACHED();
 | 
| +    return false;
 | 
| +  }
 | 
| +
 | 
| +  if (network_time - build_date_ >
 | 
| +      base::TimeDelta::FromDays(kOutdatedBuildAgeInDays)) {
 | 
| +    UpgradeDetected(UPGRADE_NEEDED_OUTDATED_INSTALL);
 | 
| +    return true;
 | 
| +  }
 | 
| +  // If we simlated an outdated install with a date, we don't want to keep
 | 
| +  // checking for version upgrades, which happens on non-official builds.
 | 
| +  return simulate_outdated;
 | 
|  }
 | 
|  
 | 
| -void UpgradeDetectorImpl::UpgradeDetected() {
 | 
| +void UpgradeDetectorImpl::UpgradeDetected(UpgradeAvailable upgrade_available) {
 | 
|    DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
 | 
| +  upgrade_available_ = upgrade_available;
 | 
|  
 | 
|    // Stop the recurring timer (that is checking for changes).
 | 
|    detect_upgrade_timer_.Stop();
 | 
| @@ -222,13 +373,14 @@ void UpgradeDetectorImpl::NotifyOnUpgrade() {
 | 
|    bool is_testing = IsTesting();
 | 
|    int64 time_passed = is_testing ? delta.InSeconds() : delta.InHours();
 | 
|  
 | 
| +  bool is_critical_or_outdated = upgrade_available_ > UPGRADE_AVAILABLE_REGULAR;
 | 
|    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
 | 
|      // minute.
 | 
|      const int kUnstableThreshold = 1;
 | 
|  
 | 
| -    if (is_critical_upgrade_)
 | 
| +    if (is_critical_or_outdated)
 | 
|        set_upgrade_notification_stage(UPGRADE_ANNOYANCE_CRITICAL);
 | 
|      else if (time_passed >= kUnstableThreshold) {
 | 
|        set_upgrade_notification_stage(UPGRADE_ANNOYANCE_LOW);
 | 
| @@ -247,10 +399,10 @@ void UpgradeDetectorImpl::NotifyOnUpgrade() {
 | 
|      const int kLowThreshold = 2 * kMultiplier;
 | 
|  
 | 
|      // These if statements must be sorted (highest interval first).
 | 
| -    if (time_passed >= kSevereThreshold || is_critical_upgrade_) {
 | 
| +    if (time_passed >= kSevereThreshold || is_critical_or_outdated) {
 | 
|        set_upgrade_notification_stage(
 | 
| -          is_critical_upgrade_ ? UPGRADE_ANNOYANCE_CRITICAL :
 | 
| -                                 UPGRADE_ANNOYANCE_SEVERE);
 | 
| +          is_critical_or_outdated ? UPGRADE_ANNOYANCE_CRITICAL :
 | 
| +                                    UPGRADE_ANNOYANCE_SEVERE);
 | 
|  
 | 
|        // We can't get any higher, baby.
 | 
|        upgrade_notification_timer_.Stop();
 | 
| 
 |