Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/upgrade_detector_impl.h"
      6 
      7 #include <string>
      8 
      9 #include "base/bind.h"
     10 #include "base/build_time.h"
     11 #include "base/command_line.h"
     12 #include "base/files/file_path.h"
     13 #include "base/memory/scoped_ptr.h"
     14 #include "base/memory/singleton.h"
     15 #include "base/metrics/field_trial.h"
     16 #include "base/path_service.h"
     17 #include "base/process/launch.h"
     18 #include "base/strings/string_number_conversions.h"
     19 #include "base/strings/string_util.h"
     20 #include "base/strings/utf_string_conversions.h"
     21 #include "base/time/time.h"
     22 #include "base/version.h"
     23 #include "chrome/browser/browser_process.h"
     24 #include "chrome/browser/google/google_util.h"
     25 #include "chrome/common/chrome_switches.h"
     26 #include "chrome/common/chrome_version_info.h"
     27 #include "content/public/browser/browser_thread.h"
     28 #include "ui/base/resource/resource_bundle.h"
     29 
     30 #if defined(OS_WIN)
     31 #include "chrome/installer/util/browser_distribution.h"
     32 #include "chrome/installer/util/google_update_settings.h"
     33 #include "chrome/installer/util/helper.h"
     34 #include "chrome/installer/util/install_util.h"
     35 #elif defined(OS_MACOSX)
     36 #include "chrome/browser/mac/keystone_glue.h"
     37 #elif defined(OS_POSIX)
     38 #include "base/process/launch.h"
     39 #endif
     40 
     41 using content::BrowserThread;
     42 
     43 namespace {
     44 
     45 // How long (in milliseconds) to wait (each cycle) before checking whether
     46 // Chrome's been upgraded behind our back.
     47 const int kCheckForUpgradeMs = 2 * 60 * 60 * 1000;  // 2 hours.
     48 
     49 // How long to wait (each cycle) before checking which severity level we should
     50 // be at. Once we reach the highest severity, the timer will stop.
     51 const int kNotifyCycleTimeMs = 20 * 60 * 1000;  // 20 minutes.
     52 
     53 // Same as kNotifyCycleTimeMs but only used during testing.
     54 const int kNotifyCycleTimeForTestingMs = 500;  // Half a second.
     55 
     56 // The number of days after which we identify a build/install as outdated.
     57 const uint64 kOutdatedBuildAgeInDays = 12 * 7;
     58 
     59 // Finch Experiment strings to identify if we should check for outdated install.
     60 const char kOutdatedInstallCheckTrialName[] = "OutdatedInstallCheck";
     61 const char kOutdatedInstallCheck12WeeksGroupName[] = "12WeeksOutdatedInstall";
     62 
     63 std::string CmdLineInterval() {
     64   const CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
     65   return cmd_line.GetSwitchValueASCII(switches::kCheckForUpdateIntervalSec);
     66 }
     67 
     68 bool IsTesting() {
     69   const CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
     70   return cmd_line.HasSwitch(switches::kSimulateUpgrade) ||
     71       cmd_line.HasSwitch(switches::kCheckForUpdateIntervalSec) ||
     72       cmd_line.HasSwitch(switches::kSimulateCriticalUpdate) ||
     73       cmd_line.HasSwitch(switches::kSimulateOutdated);
     74 }
     75 
     76 // How often to check for an upgrade.
     77 int GetCheckForUpgradeEveryMs() {
     78   // Check for a value passed via the command line.
     79   int interval_ms;
     80   std::string interval = CmdLineInterval();
     81   if (!interval.empty() && base::StringToInt(interval, &interval_ms))
     82     return interval_ms * 1000;  // Command line value is in seconds.
     83 
     84   return kCheckForUpgradeMs;
     85 }
     86 
     87 bool IsUnstableChannel() {
     88   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
     89   chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
     90   return channel == chrome::VersionInfo::CHANNEL_DEV ||
     91          channel == chrome::VersionInfo::CHANNEL_CANARY;
     92 }
     93 
     94 // This task identifies whether we are running an unstable version. And then
     95 // it unconditionally calls back the provided task.
     96 void CheckForUnstableChannel(const base::Closure& callback_task,
     97                              bool* is_unstable_channel) {
     98   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
     99   *is_unstable_channel = IsUnstableChannel();
    100   BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, callback_task);
    101 }
    102 
    103 #if defined(OS_WIN)
    104 bool IsSystemInstall() {
    105   // Get the version of the currently *installed* instance of Chrome,
    106   // which might be newer than the *running* instance if we have been
    107   // upgraded in the background.
    108   base::FilePath exe_path;
    109   if (!PathService::Get(base::DIR_EXE, &exe_path)) {
    110     NOTREACHED() << "Failed to find executable path";
    111     return false;
    112   }
    113 
    114   return !InstallUtil::IsPerUserInstall(exe_path.value().c_str());
    115 }
    116 
    117 // This task checks the update policy and calls back the task only if automatic
    118 // updates are allowed. It also identifies whether we are running an unstable
    119 // channel.
    120 void DetectUpdatability(const base::Closure& callback_task,
    121                         bool* is_unstable_channel) {
    122   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    123 
    124   string16 app_guid = installer::GetAppGuidForUpdates(IsSystemInstall());
    125   DCHECK(!app_guid.empty());
    126   if (GoogleUpdateSettings::AUTOMATIC_UPDATES ==
    127       GoogleUpdateSettings::GetAppUpdatePolicy(app_guid, NULL)) {
    128     CheckForUnstableChannel(callback_task, is_unstable_channel);
    129   }
    130 }
    131 #endif  // defined(OS_WIN)
    132 
    133 }  // namespace
    134 
    135 UpgradeDetectorImpl::UpgradeDetectorImpl()
    136     : weak_factory_(this),
    137       is_unstable_channel_(false),
    138       build_date_(base::GetBuildTime()) {
    139   CommandLine command_line(*CommandLine::ForCurrentProcess());
    140   // The different command line switches that affect testing can't be used
    141   // simultaneously, if they do, here's the precedence order, based on the order
    142   // of the if statements below:
    143   // - kDisableBackgroundNetworking prevents any of the other command line
    144   //   switch from being taken into account.
    145   // - kSimulateUpgrade supersedes critical or outdated upgrade switches.
    146   // - kSimulateCriticalUpdate has precedence over kSimulateOutdated.
    147   // - kSimulateOutdated can work on its own, or with a specified date.
    148   if (command_line.HasSwitch(switches::kDisableBackgroundNetworking))
    149     return;
    150   if (command_line.HasSwitch(switches::kSimulateUpgrade)) {
    151     UpgradeDetected(UPGRADE_AVAILABLE_REGULAR);
    152     return;
    153   }
    154   if (command_line.HasSwitch(switches::kSimulateCriticalUpdate)) {
    155     UpgradeDetected(UPGRADE_AVAILABLE_CRITICAL);
    156     return;
    157   }
    158   if (command_line.HasSwitch(switches::kSimulateOutdated)) {
    159     // The outdated simulation can work without a value, which means outdated
    160     // now, or with a value that must be a well formed date/time string that
    161     // overrides the build date.
    162     // Also note that to test with a given time/date, until the network time
    163     // tracking moves off of the VariationsService, the "variations-server-url"
    164     // command line switch must also be specified for the service to be
    165     // available on non GOOGLE_CHROME_BUILD.
    166     std::string build_date = command_line.GetSwitchValueASCII(
    167         switches::kSimulateOutdated);
    168     base::Time maybe_build_time;
    169     bool result = base::Time::FromString(build_date.c_str(), &maybe_build_time);
    170     if (result && !maybe_build_time.is_null()) {
    171       // We got a valid build date simulation so use it and check for upgrades.
    172       build_date_ = maybe_build_time;
    173       StartTimerForUpgradeCheck();
    174     } else {
    175       // Without a valid date, we simulate that we are already outdated...
    176       UpgradeDetected(UPGRADE_NEEDED_OUTDATED_INSTALL);
    177     }
    178     return;
    179   }
    180 
    181   // Windows: only enable upgrade notifications for official builds.
    182   // Mac: only enable them if the updater (Keystone) is present.
    183   // Linux (and other POSIX): always enable regardless of branding.
    184   base::Closure start_upgrade_check_timer_task =
    185       base::Bind(&UpgradeDetectorImpl::StartTimerForUpgradeCheck,
    186                  weak_factory_.GetWeakPtr());
    187 #if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
    188   // On Windows, there might be a policy preventing updates, so validate
    189   // updatability, and then call StartTimerForUpgradeCheck appropriately.
    190   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    191                           base::Bind(&DetectUpdatability,
    192                                      start_upgrade_check_timer_task,
    193                                      &is_unstable_channel_));
    194   return;
    195 #elif defined(OS_WIN) && !defined(GOOGLE_CHROME_BUILD)
    196   return;  // Chromium has no upgrade channel.
    197 #elif defined(OS_MACOSX)
    198   if (!keystone_glue::KeystoneEnabled())
    199     return;  // Keystone updater not enabled.
    200 #elif !defined(OS_POSIX)
    201   return;
    202 #endif
    203 
    204   // Check whether the build is an unstable channel before starting the timer.
    205   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    206                           base::Bind(&CheckForUnstableChannel,
    207                                      start_upgrade_check_timer_task,
    208                                      &is_unstable_channel_));
    209 
    210   // Start tracking network time updates.
    211   network_time_tracker_.Start();
    212 }
    213 
    214 UpgradeDetectorImpl::~UpgradeDetectorImpl() {
    215 }
    216 
    217 // Static
    218 // This task checks the currently running version of Chrome against the
    219 // installed version. If the installed version is newer, it calls back
    220 // UpgradeDetectorImpl::UpgradeDetected using a weak pointer so that it can
    221 // be interrupted from the UI thread.
    222 void UpgradeDetectorImpl::DetectUpgradeTask(
    223     base::WeakPtr<UpgradeDetectorImpl> upgrade_detector) {
    224   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
    225 
    226   Version installed_version;
    227   Version critical_update;
    228 
    229 #if defined(OS_WIN)
    230   // Get the version of the currently *installed* instance of Chrome,
    231   // which might be newer than the *running* instance if we have been
    232   // upgraded in the background.
    233   bool system_install = IsSystemInstall();
    234 
    235   // TODO(tommi): Check if using the default distribution is always the right
    236   // thing to do.
    237   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
    238   InstallUtil::GetChromeVersion(dist, system_install, &installed_version);
    239 
    240   if (installed_version.IsValid()) {
    241     InstallUtil::GetCriticalUpdateVersion(dist, system_install,
    242                                           &critical_update);
    243   }
    244 #elif defined(OS_MACOSX)
    245   installed_version =
    246       Version(UTF16ToASCII(keystone_glue::CurrentlyInstalledVersion()));
    247 #elif defined(OS_POSIX)
    248   // POSIX but not Mac OS X: Linux, etc.
    249   CommandLine command_line(*CommandLine::ForCurrentProcess());
    250   command_line.AppendSwitch(switches::kProductVersion);
    251   std::string reply;
    252   if (!base::GetAppOutput(command_line, &reply)) {
    253     DLOG(ERROR) << "Failed to get current file version";
    254     return;
    255   }
    256 
    257   installed_version = Version(reply);
    258 #endif
    259 
    260   // Get the version of the currently *running* instance of Chrome.
    261   chrome::VersionInfo version_info;
    262   if (!version_info.is_valid()) {
    263     NOTREACHED() << "Failed to get current file version";
    264     return;
    265   }
    266   Version running_version(version_info.Version());
    267   if (!running_version.IsValid()) {
    268     NOTREACHED();
    269     return;
    270   }
    271 
    272   // |installed_version| may be NULL when the user downgrades on Linux (by
    273   // switching from dev to beta channel, for example). The user needs a
    274   // restart in this case as well. See http://crbug.com/46547
    275   if (!installed_version.IsValid() ||
    276       (installed_version.CompareTo(running_version) > 0)) {
    277     // If a more recent version is available, it might be that we are lacking
    278     // a critical update, such as a zero-day fix.
    279     UpgradeAvailable upgrade_available = UPGRADE_AVAILABLE_REGULAR;
    280     if (critical_update.IsValid() &&
    281         critical_update.CompareTo(running_version) > 0) {
    282       upgrade_available = UPGRADE_AVAILABLE_CRITICAL;
    283     }
    284 
    285     // Fire off the upgrade detected task.
    286     BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    287                             base::Bind(&UpgradeDetectorImpl::UpgradeDetected,
    288                                        upgrade_detector,
    289                                        upgrade_available));
    290   }
    291 }
    292 
    293 void UpgradeDetectorImpl::StartTimerForUpgradeCheck() {
    294   detect_upgrade_timer_.Start(FROM_HERE,
    295       base::TimeDelta::FromMilliseconds(GetCheckForUpgradeEveryMs()),
    296       this, &UpgradeDetectorImpl::CheckForUpgrade);
    297 }
    298 
    299 void UpgradeDetectorImpl::CheckForUpgrade() {
    300   // Interrupt any (unlikely) unfinished execution of DetectUpgradeTask, or at
    301   // least prevent the callback from being executed, because we will potentially
    302   // call it from within DetectOutdatedInstall() or will post
    303   // DetectUpgradeTask again below anyway.
    304   weak_factory_.InvalidateWeakPtrs();
    305 
    306   // No need to look for upgrades if the install is outdated.
    307   if (DetectOutdatedInstall())
    308     return;
    309 
    310   // We use FILE as the thread to run the upgrade detection code on all
    311   // platforms. For Linux, this is because we don't want to block the UI thread
    312   // while launching a background process and reading its output; on the Mac and
    313   // on Windows checking for an upgrade requires reading a file.
    314   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    315                           base::Bind(&UpgradeDetectorImpl::DetectUpgradeTask,
    316                                      weak_factory_.GetWeakPtr()));
    317 }
    318 
    319 bool UpgradeDetectorImpl::DetectOutdatedInstall() {
    320   // Only enable the outdated install check if we are running the trial for it,
    321   // unless we are simulating an outdated isntall.
    322   static bool simulate_outdated = CommandLine::ForCurrentProcess()->HasSwitch(
    323       switches::kSimulateOutdated);
    324   if (!simulate_outdated) {
    325     if (base::FieldTrialList::FindFullName(kOutdatedInstallCheckTrialName) !=
    326             kOutdatedInstallCheck12WeeksGroupName) {
    327       return false;
    328     }
    329 
    330     // Also don't show the bubble if we have a brand code that is NOT organic.
    331     std::string brand;
    332     if (google_util::GetBrand(&brand) && !google_util::IsOrganic(brand))
    333       return false;
    334   }
    335 
    336   base::Time network_time;
    337   base::TimeDelta uncertainty;
    338   if (!network_time_tracker_.GetNetworkTime(base::TimeTicks::Now(),
    339                                             &network_time,
    340                                             &uncertainty)) {
    341     return false;
    342   }
    343 
    344   if (network_time.is_null() || build_date_.is_null() ||
    345       build_date_ > network_time) {
    346     NOTREACHED();
    347     return false;
    348   }
    349 
    350   if (network_time - build_date_ >
    351       base::TimeDelta::FromDays(kOutdatedBuildAgeInDays)) {
    352     UpgradeDetected(UPGRADE_NEEDED_OUTDATED_INSTALL);
    353     return true;
    354   }
    355   // If we simlated an outdated install with a date, we don't want to keep
    356   // checking for version upgrades, which happens on non-official builds.
    357   return simulate_outdated;
    358 }
    359 
    360 void UpgradeDetectorImpl::UpgradeDetected(UpgradeAvailable upgrade_available) {
    361   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    362   upgrade_available_ = upgrade_available;
    363 
    364   // Stop the recurring timer (that is checking for changes).
    365   detect_upgrade_timer_.Stop();
    366 
    367   NotifyUpgradeDetected();
    368 
    369   // Start the repeating timer for notifying the user after a certain period.
    370   // The called function will eventually figure out that enough time has passed
    371   // and stop the timer.
    372   int cycle_time = IsTesting() ?
    373       kNotifyCycleTimeForTestingMs : kNotifyCycleTimeMs;
    374   upgrade_notification_timer_.Start(FROM_HERE,
    375       base::TimeDelta::FromMilliseconds(cycle_time),
    376       this, &UpgradeDetectorImpl::NotifyOnUpgrade);
    377 }
    378 
    379 void UpgradeDetectorImpl::NotifyOnUpgrade() {
    380   base::TimeDelta delta = base::Time::Now() - upgrade_detected_time();
    381 
    382   // We'll make testing more convenient by switching to seconds of waiting
    383   // instead of days between flipping severity.
    384   bool is_testing = IsTesting();
    385   int64 time_passed = is_testing ? delta.InSeconds() : delta.InHours();
    386 
    387   bool is_critical_or_outdated = upgrade_available_ > UPGRADE_AVAILABLE_REGULAR;
    388   if (is_unstable_channel_) {
    389     // There's only one threat level for unstable channels like dev and
    390     // canary, and it hits after one hour. During testing, it hits after one
    391     // second.
    392     const int kUnstableThreshold = 1;
    393 
    394     if (is_critical_or_outdated)
    395       set_upgrade_notification_stage(UPGRADE_ANNOYANCE_CRITICAL);
    396     else if (time_passed >= kUnstableThreshold) {
    397       set_upgrade_notification_stage(UPGRADE_ANNOYANCE_LOW);
    398 
    399       // That's as high as it goes.
    400       upgrade_notification_timer_.Stop();
    401     } else {
    402       return;  // Not ready to recommend upgrade.
    403     }
    404   } else {
    405     const int kMultiplier = is_testing ? 10 : 24;
    406     // 14 days when not testing, otherwise 14 seconds.
    407     const int kSevereThreshold = 14 * kMultiplier;
    408     const int kHighThreshold = 7 * kMultiplier;
    409     const int kElevatedThreshold = 4 * kMultiplier;
    410     const int kLowThreshold = 2 * kMultiplier;
    411 
    412     // These if statements must be sorted (highest interval first).
    413     if (time_passed >= kSevereThreshold || is_critical_or_outdated) {
    414       set_upgrade_notification_stage(
    415           is_critical_or_outdated ? UPGRADE_ANNOYANCE_CRITICAL :
    416                                     UPGRADE_ANNOYANCE_SEVERE);
    417 
    418       // We can't get any higher, baby.
    419       upgrade_notification_timer_.Stop();
    420     } else if (time_passed >= kHighThreshold) {
    421       set_upgrade_notification_stage(UPGRADE_ANNOYANCE_HIGH);
    422     } else if (time_passed >= kElevatedThreshold) {
    423       set_upgrade_notification_stage(UPGRADE_ANNOYANCE_ELEVATED);
    424     } else if (time_passed >= kLowThreshold) {
    425       set_upgrade_notification_stage(UPGRADE_ANNOYANCE_LOW);
    426     } else {
    427       return;  // Not ready to recommend upgrade.
    428     }
    429   }
    430 
    431   NotifyUpgradeRecommended();
    432 }
    433 
    434 // static
    435 UpgradeDetectorImpl* UpgradeDetectorImpl::GetInstance() {
    436   return Singleton<UpgradeDetectorImpl>::get();
    437 }
    438 
    439 // static
    440 UpgradeDetector* UpgradeDetector::GetInstance() {
    441   return UpgradeDetectorImpl::GetInstance();
    442 }
    443