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