Home | History | Annotate | Download | only in browser
      1 // Copyright (c) 2011 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.h"
      6 
      7 #include <string>
      8 
      9 #include "base/command_line.h"
     10 #include "base/memory/scoped_ptr.h"
     11 #include "base/memory/singleton.h"
     12 #include "base/string_number_conversions.h"
     13 #include "base/string_util.h"
     14 #include "base/task.h"
     15 #include "base/time.h"
     16 #include "base/utf_string_conversions.h"
     17 #include "chrome/browser/platform_util.h"
     18 #include "chrome/browser/prefs/pref_service.h"
     19 #include "chrome/common/chrome_switches.h"
     20 #include "chrome/common/chrome_version_info.h"
     21 #include "chrome/common/pref_names.h"
     22 #include "chrome/installer/util/browser_distribution.h"
     23 #include "content/browser/browser_thread.h"
     24 #include "content/common/notification_service.h"
     25 #include "content/common/notification_type.h"
     26 
     27 #if defined(OS_WIN)
     28 #include "chrome/installer/util/install_util.h"
     29 #elif defined(OS_MACOSX)
     30 #include "chrome/browser/cocoa/keystone_glue.h"
     31 #elif defined(OS_POSIX)
     32 #include "base/process_util.h"
     33 #include "base/version.h"
     34 #endif
     35 
     36 namespace {
     37 
     38 // How long (in milliseconds) to wait (each cycle) before checking whether
     39 // Chrome's been upgraded behind our back.
     40 const int kCheckForUpgradeMs = 2 * 60 * 60 * 1000;  // 2 hours.
     41 
     42 // How long to wait (each cycle) before checking which severity level we should
     43 // be at. Once we reach the highest severity, the timer will stop.
     44 const int kNotifyCycleTimeMs = 20 * 60 * 1000;  // 20 minutes.
     45 
     46 // Same as kNotifyCycleTimeMs but only used during testing.
     47 const int kNotifyCycleTimeForTestingMs = 5000;  // 5 seconds.
     48 
     49 std::string CmdLineInterval() {
     50   const CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
     51   return cmd_line.GetSwitchValueASCII(switches::kCheckForUpdateIntervalSec);
     52 }
     53 
     54 // How often to check for an upgrade.
     55 int GetCheckForUpgradeEveryMs() {
     56   // Check for a value passed via the command line.
     57   int interval_ms;
     58   std::string interval = CmdLineInterval();
     59   if (!interval.empty() && base::StringToInt(interval, &interval_ms))
     60     return interval_ms * 1000;  // Command line value is in seconds.
     61 
     62   return kCheckForUpgradeMs;
     63 }
     64 
     65 // This task checks the currently running version of Chrome against the
     66 // installed version. If the installed version is newer, it runs the passed
     67 // callback task. Otherwise it just deletes the task.
     68 class DetectUpgradeTask : public Task {
     69  public:
     70   explicit DetectUpgradeTask(Task* upgrade_detected_task,
     71                              bool* is_dev_channel)
     72       : upgrade_detected_task_(upgrade_detected_task),
     73         is_dev_channel_(is_dev_channel) {
     74   }
     75 
     76   virtual ~DetectUpgradeTask() {
     77     if (upgrade_detected_task_) {
     78       // This has to get deleted on the same thread it was created.
     79       BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
     80                               new DeleteTask<Task>(upgrade_detected_task_));
     81     }
     82   }
     83 
     84   virtual void Run() {
     85     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
     86 
     87     scoped_ptr<Version> installed_version;
     88 
     89 #if defined(OS_WIN)
     90     // Get the version of the currently *installed* instance of Chrome,
     91     // which might be newer than the *running* instance if we have been
     92     // upgraded in the background.
     93     // TODO(tommi): Check if using the default distribution is always the right
     94     // thing to do.
     95     BrowserDistribution* dist = BrowserDistribution::GetDistribution();
     96     installed_version.reset(InstallUtil::GetChromeVersion(dist, false));
     97     if (!installed_version.get()) {
     98       // User level Chrome is not installed, check system level.
     99       installed_version.reset(InstallUtil::GetChromeVersion(dist, true));
    100     }
    101 #elif defined(OS_MACOSX)
    102     installed_version.reset(
    103         Version::GetVersionFromString(UTF16ToASCII(
    104             keystone_glue::CurrentlyInstalledVersion())));
    105 #elif defined(OS_POSIX)
    106     // POSIX but not Mac OS X: Linux, etc.
    107     CommandLine command_line(*CommandLine::ForCurrentProcess());
    108     command_line.AppendSwitch(switches::kProductVersion);
    109     std::string reply;
    110     if (!base::GetAppOutput(command_line, &reply)) {
    111       DLOG(ERROR) << "Failed to get current file version";
    112       return;
    113     }
    114 
    115     installed_version.reset(Version::GetVersionFromString(reply));
    116 #endif
    117 
    118     const std::string channel = platform_util::GetVersionStringModifier();
    119     *is_dev_channel_ = channel == "dev";
    120 
    121     // Get the version of the currently *running* instance of Chrome.
    122     chrome::VersionInfo version_info;
    123     if (!version_info.is_valid()) {
    124       NOTREACHED() << "Failed to get current file version";
    125       return;
    126     }
    127     scoped_ptr<Version> running_version(
    128         Version::GetVersionFromString(version_info.Version()));
    129     if (running_version.get() == NULL) {
    130       NOTREACHED() << "Failed to parse version info";
    131       return;
    132     }
    133 
    134     // |installed_version| may be NULL when the user downgrades on Linux (by
    135     // switching from dev to beta channel, for example). The user needs a
    136     // restart in this case as well. See http://crbug.com/46547
    137     if (!installed_version.get() ||
    138         (installed_version->CompareTo(*running_version) > 0)) {
    139       BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
    140                               upgrade_detected_task_);
    141       upgrade_detected_task_ = NULL;
    142     }
    143   }
    144 
    145  private:
    146   Task* upgrade_detected_task_;
    147   bool* is_dev_channel_;
    148 };
    149 
    150 }  // namespace
    151 
    152 // static
    153 void UpgradeDetector::RegisterPrefs(PrefService* prefs) {
    154   prefs->RegisterBooleanPref(prefs::kRestartLastSessionOnShutdown, false);
    155 }
    156 
    157 UpgradeDetector::UpgradeDetector()
    158     : ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)),
    159       is_dev_channel_(false),
    160       upgrade_notification_stage_(UPGRADE_ANNOYANCE_NONE),
    161       notify_upgrade_(false) {
    162   CommandLine command_line(*CommandLine::ForCurrentProcess());
    163   if (command_line.HasSwitch(switches::kDisableBackgroundNetworking))
    164     return;
    165   // Windows: only enable upgrade notifications for official builds.
    166   // Mac: only enable them if the updater (Keystone) is present.
    167   // Linux (and other POSIX): always enable regardless of branding.
    168 #if (defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)) || defined(OS_POSIX)
    169 #if defined(OS_MACOSX)
    170   if (keystone_glue::KeystoneEnabled())
    171 #endif
    172   {
    173     detect_upgrade_timer_.Start(
    174         base::TimeDelta::FromMilliseconds(GetCheckForUpgradeEveryMs()),
    175         this, &UpgradeDetector::CheckForUpgrade);
    176   }
    177 #endif
    178 }
    179 
    180 UpgradeDetector::~UpgradeDetector() {
    181 }
    182 
    183 // static
    184 UpgradeDetector* UpgradeDetector::GetInstance() {
    185   return Singleton<UpgradeDetector>::get();
    186 }
    187 
    188 void UpgradeDetector::CheckForUpgrade() {
    189   method_factory_.RevokeAll();
    190   Task* callback_task =
    191       method_factory_.NewRunnableMethod(&UpgradeDetector::UpgradeDetected);
    192   // We use FILE as the thread to run the upgrade detection code on all
    193   // platforms. For Linux, this is because we don't want to block the UI thread
    194   // while launching a background process and reading its output; on the Mac and
    195   // on Windows checking for an upgrade requires reading a file.
    196   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
    197                           new DetectUpgradeTask(callback_task,
    198                                                 &is_dev_channel_));
    199 }
    200 
    201 void UpgradeDetector::UpgradeDetected() {
    202   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    203 
    204   // Stop the recurring timer (that is checking for changes).
    205   detect_upgrade_timer_.Stop();
    206 
    207   upgrade_detected_time_ = base::Time::Now();
    208 
    209   NotificationService::current()->Notify(
    210       NotificationType::UPGRADE_DETECTED,
    211       Source<UpgradeDetector>(this),
    212       NotificationService::NoDetails());
    213 
    214   // Start the repeating timer for notifying the user after a certain period.
    215   // The called function will eventually figure out that enough time has passed
    216   // and stop the timer.
    217   int cycle_time = CmdLineInterval().empty() ? kNotifyCycleTimeMs :
    218                                                kNotifyCycleTimeForTestingMs;
    219   upgrade_notification_timer_.Start(
    220       base::TimeDelta::FromMilliseconds(cycle_time),
    221       this, &UpgradeDetector::NotifyOnUpgrade);
    222 }
    223 
    224 void UpgradeDetector::NotifyOnUpgrade() {
    225   base::TimeDelta delta = base::Time::Now() - upgrade_detected_time_;
    226   std::string interval = CmdLineInterval();
    227   // A command line interval implies testing, which we'll make more convenient
    228   // by switching to minutes of waiting instead of hours between flipping
    229   // severity.
    230   int time_passed = interval.empty() ? delta.InHours() : delta.InMinutes();
    231   const int kSevereThreshold = 14 * (interval.empty() ? 24 : 1);
    232   const int kHighThreshold = 7 * (interval.empty() ? 24 : 1);
    233   const int kElevatedThreshold = 4  * (interval.empty() ? 24 : 1);
    234   // Dev channel is fixed at lowest severity after 1 hour. For other channels
    235   // it is after 2 hours. And, as before, if a command line is passed in we
    236   // drastically reduce the wait time.
    237   const int multiplier = is_dev_channel_ ? 1 : 2;
    238   const int kLowThreshold = multiplier * (interval.empty() ? 24 : 1);
    239 
    240   // These if statements (except for the first one) must be sorted (highest
    241   // interval first).
    242   if (time_passed >= kSevereThreshold)
    243     upgrade_notification_stage_ = UPGRADE_ANNOYANCE_SEVERE;
    244   else if (time_passed >= kHighThreshold)
    245     upgrade_notification_stage_ = UPGRADE_ANNOYANCE_HIGH;
    246   else if (time_passed >= kElevatedThreshold)
    247     upgrade_notification_stage_ = UPGRADE_ANNOYANCE_ELEVATED;
    248   else if (time_passed >= kLowThreshold)
    249     upgrade_notification_stage_ = UPGRADE_ANNOYANCE_LOW;
    250   else
    251     return;  // Not ready to recommend upgrade.
    252 
    253   if (is_dev_channel_ ||
    254       upgrade_notification_stage_ == UPGRADE_ANNOYANCE_SEVERE) {
    255     // We can't get any higher, baby.
    256     upgrade_notification_timer_.Stop();
    257   }
    258 
    259   notify_upgrade_ = true;
    260 
    261   NotificationService::current()->Notify(
    262       NotificationType::UPGRADE_RECOMMENDED,
    263       Source<UpgradeDetector>(this),
    264       NotificationService::NoDetails());
    265 }
    266