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.
5 #include "chrome/browser/upgrade_detector_impl.h"
10 #include "base/build_time.h"
11 #include "base/command_line.h"
13 #include "base/files/file_path.h"
14 #include "base/memory/scoped_ptr.h"
15 #include "base/memory/singleton.h"
16 #include "base/path_service.h"
17 #include "base/prefs/pref_service.h"
18 #include "base/process/launch.h"
19 #include "base/strings/string_number_conversions.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/time/time.h"
23 #include "chrome/browser/browser_process.h"
24 #include "chrome/browser/google/google_brand.h"
25 #include "chrome/common/chrome_switches.h"
26 #include "chrome/common/chrome_version_info.h"
27 #include "chrome/common/pref_names.h"
28 #include "components/network_time/network_time_tracker.h"
29 #include "content/public/browser/browser_thread.h"
30 #include "ui/base/resource/resource_bundle.h"
33 #include "base/win/win_util.h"
34 #include "chrome/installer/util/browser_distribution.h"
35 #include "chrome/installer/util/google_update_settings.h"
36 #include "chrome/installer/util/helper.h"
37 #include "chrome/installer/util/install_util.h"
38 #elif defined(OS_MACOSX)
39 #include "chrome/browser/mac/keystone_glue.h"
42 using content::BrowserThread
;
46 // How long (in milliseconds) to wait (each cycle) before checking whether
47 // Chrome's been upgraded behind our back.
48 const int kCheckForUpgradeMs
= 2 * 60 * 60 * 1000; // 2 hours.
50 // How long to wait (each cycle) before checking which severity level we should
51 // be at. Once we reach the highest severity, the timer will stop.
52 const int kNotifyCycleTimeMs
= 20 * 60 * 1000; // 20 minutes.
54 // Same as kNotifyCycleTimeMs but only used during testing.
55 const int kNotifyCycleTimeForTestingMs
= 500; // Half a second.
57 // The number of days after which we identify a build/install as outdated.
58 const uint64 kOutdatedBuildAgeInDays
= 12 * 7;
60 // Return the string that was passed as a value for the
61 // kCheckForUpdateIntervalSec switch.
62 std::string
CmdLineInterval() {
63 const CommandLine
& cmd_line
= *CommandLine::ForCurrentProcess();
64 return cmd_line
.GetSwitchValueASCII(switches::kCheckForUpdateIntervalSec
);
67 // Check if one of the outdated simulation switches was present on the command
69 bool SimulatingOutdated() {
70 const CommandLine
& cmd_line
= *CommandLine::ForCurrentProcess();
71 return cmd_line
.HasSwitch(switches::kSimulateOutdated
) ||
72 cmd_line
.HasSwitch(switches::kSimulateOutdatedNoAU
);
75 // Check if any of the testing switches was present on the command line.
77 const CommandLine
& cmd_line
= *CommandLine::ForCurrentProcess();
78 return cmd_line
.HasSwitch(switches::kSimulateUpgrade
) ||
79 cmd_line
.HasSwitch(switches::kCheckForUpdateIntervalSec
) ||
80 cmd_line
.HasSwitch(switches::kSimulateCriticalUpdate
) ||
84 // How often to check for an upgrade.
85 int GetCheckForUpgradeEveryMs() {
86 // Check for a value passed via the command line.
88 std::string interval
= CmdLineInterval();
89 if (!interval
.empty() && base::StringToInt(interval
, &interval_ms
))
90 return interval_ms
* 1000; // Command line value is in seconds.
92 return kCheckForUpgradeMs
;
95 // Return true if the current build is one of the unstable channels.
96 bool IsUnstableChannel() {
97 // TODO(mad): Investigate whether we still need to be on the file thread for
98 // this. On Windows, the file thread used to be required for registry access
99 // but no anymore. But other platform may still need the file thread.
101 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
102 chrome::VersionInfo::Channel channel
= chrome::VersionInfo::GetChannel();
103 return channel
== chrome::VersionInfo::CHANNEL_DEV
||
104 channel
== chrome::VersionInfo::CHANNEL_CANARY
;
107 // This task identifies whether we are running an unstable version. And then it
108 // unconditionally calls back the provided task.
109 void CheckForUnstableChannel(const base::Closure
& callback_task
,
110 bool* is_unstable_channel
) {
111 *is_unstable_channel
= IsUnstableChannel();
112 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
, callback_task
);
116 // Return true if the currently running Chrome is a system install.
117 bool IsSystemInstall() {
118 // Get the version of the currently *installed* instance of Chrome,
119 // which might be newer than the *running* instance if we have been
120 // upgraded in the background.
121 base::FilePath exe_path
;
122 if (!PathService::Get(base::DIR_EXE
, &exe_path
)) {
123 NOTREACHED() << "Failed to find executable path";
127 return !InstallUtil::IsPerUserInstall(exe_path
.value().c_str());
130 // Sets |is_unstable_channel| to true if the current chrome is on the dev or
131 // canary channels. Sets |is_auto_update_enabled| to true if Google Update will
132 // update the current chrome. Unconditionally posts |callback_task| to the UI
133 // thread to continue processing.
134 void DetectUpdatability(const base::Closure
& callback_task
,
135 bool* is_unstable_channel
,
136 bool* is_auto_update_enabled
) {
137 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
139 base::string16 app_guid
= installer::GetAppGuidForUpdates(IsSystemInstall());
140 DCHECK(!app_guid
.empty());
141 // Don't try to turn on autoupdate when we failed previously.
142 if (is_auto_update_enabled
) {
143 *is_auto_update_enabled
=
144 GoogleUpdateSettings::AreAutoupdatesEnabled(app_guid
);
146 *is_unstable_channel
= IsUnstableChannel();
147 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
, callback_task
);
149 #endif // defined(OS_WIN)
151 // Gets the currently installed version. On Windows, if |critical_update| is not
152 // NULL, also retrieves the critical update version info if available.
153 base::Version
GetCurrentlyInstalledVersionImpl(Version
* critical_update
) {
154 base::ThreadRestrictions::AssertIOAllowed();
156 Version installed_version
;
158 // Get the version of the currently *installed* instance of Chrome,
159 // which might be newer than the *running* instance if we have been
160 // upgraded in the background.
161 bool system_install
= IsSystemInstall();
163 // TODO(tommi): Check if using the default distribution is always the right
165 BrowserDistribution
* dist
= BrowserDistribution::GetDistribution();
166 InstallUtil::GetChromeVersion(dist
, system_install
, &installed_version
);
167 if (critical_update
&& installed_version
.IsValid()) {
168 InstallUtil::GetCriticalUpdateVersion(dist
, system_install
,
171 #elif defined(OS_MACOSX)
173 Version(base::UTF16ToASCII(keystone_glue::CurrentlyInstalledVersion()));
174 #elif defined(OS_POSIX)
175 // POSIX but not Mac OS X: Linux, etc.
176 CommandLine
command_line(*CommandLine::ForCurrentProcess());
177 command_line
.AppendSwitch(switches::kProductVersion
);
179 if (!base::GetAppOutput(command_line
, &reply
)) {
180 DLOG(ERROR
) << "Failed to get current file version";
181 return installed_version
;
184 installed_version
= Version(reply
);
186 return installed_version
;
191 UpgradeDetectorImpl::UpgradeDetectorImpl()
192 : weak_factory_(this),
193 is_unstable_channel_(false),
194 is_auto_update_enabled_(true),
195 build_date_(base::GetBuildTime()) {
196 CommandLine
command_line(*CommandLine::ForCurrentProcess());
197 // The different command line switches that affect testing can't be used
198 // simultaneously, if they do, here's the precedence order, based on the order
199 // of the if statements below:
200 // - kDisableBackgroundNetworking prevents any of the other command line
201 // switch from being taken into account.
202 // - kSimulateUpgrade supersedes critical or outdated upgrade switches.
203 // - kSimulateCriticalUpdate has precedence over kSimulateOutdated.
204 // - kSimulateOutdatedNoAU has precedence over kSimulateOutdated.
205 // - kSimulateOutdated[NoAu] can work on its own, or with a specified date.
206 if (command_line
.HasSwitch(switches::kDisableBackgroundNetworking
))
208 if (command_line
.HasSwitch(switches::kSimulateUpgrade
)) {
209 UpgradeDetected(UPGRADE_AVAILABLE_REGULAR
);
212 if (command_line
.HasSwitch(switches::kSimulateCriticalUpdate
)) {
213 UpgradeDetected(UPGRADE_AVAILABLE_CRITICAL
);
216 if (SimulatingOutdated()) {
217 // The outdated simulation can work without a value, which means outdated
218 // now, or with a value that must be a well formed date/time string that
219 // overrides the build date.
220 // Also note that to test with a given time/date, until the network time
221 // tracking moves off of the VariationsService, the "variations-server-url"
222 // command line switch must also be specified for the service to be
223 // available on non GOOGLE_CHROME_BUILD.
224 std::string switch_name
;
225 if (command_line
.HasSwitch(switches::kSimulateOutdatedNoAU
)) {
226 is_auto_update_enabled_
= false;
227 switch_name
= switches::kSimulateOutdatedNoAU
;
229 switch_name
= switches::kSimulateOutdated
;
231 std::string build_date
= command_line
.GetSwitchValueASCII(switch_name
);
232 base::Time maybe_build_time
;
233 bool result
= base::Time::FromString(build_date
.c_str(), &maybe_build_time
);
234 if (result
&& !maybe_build_time
.is_null()) {
235 // We got a valid build date simulation so use it and check for upgrades.
236 build_date_
= maybe_build_time
;
237 StartTimerForUpgradeCheck();
239 // Without a valid date, we simulate that we are already outdated...
241 is_auto_update_enabled_
? UPGRADE_NEEDED_OUTDATED_INSTALL
242 : UPGRADE_NEEDED_OUTDATED_INSTALL_NO_AU
);
247 base::Closure start_upgrade_check_timer_task
=
248 base::Bind(&UpgradeDetectorImpl::StartTimerForUpgradeCheck
,
249 weak_factory_
.GetWeakPtr());
252 // Only enable upgrade notifications for official builds. Chromium has no
254 #if defined(GOOGLE_CHROME_BUILD)
255 // On Windows, there might be a policy/enterprise environment preventing
256 // updates, so validate updatability, and then call StartTimerForUpgradeCheck
257 // appropriately. And don't check for autoupdate if we already attempted to
258 // enable it in the past.
259 bool attempted_enabling_autoupdate
= g_browser_process
->local_state() &&
260 g_browser_process
->local_state()->GetBoolean(
261 prefs::kAttemptedToEnableAutoupdate
);
262 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
263 base::Bind(&DetectUpdatability
,
264 start_upgrade_check_timer_task
,
265 &is_unstable_channel_
,
266 attempted_enabling_autoupdate
?
267 NULL
: &is_auto_update_enabled_
));
270 #if defined(OS_MACOSX)
271 // Only enable upgrade notifications if the updater (Keystone) is present.
272 if (!keystone_glue::KeystoneEnabled()) {
273 is_auto_update_enabled_
= false;
276 #elif defined(OS_POSIX)
277 // Always enable upgrade notifications regardless of branding.
281 // Check whether the build is an unstable channel before starting the timer.
282 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
283 base::Bind(&CheckForUnstableChannel
,
284 start_upgrade_check_timer_task
,
285 &is_unstable_channel_
));
289 UpgradeDetectorImpl::~UpgradeDetectorImpl() {
293 base::Version
UpgradeDetectorImpl::GetCurrentlyInstalledVersion() {
294 return GetCurrentlyInstalledVersionImpl(NULL
);
298 // This task checks the currently running version of Chrome against the
299 // installed version. If the installed version is newer, it calls back
300 // UpgradeDetectorImpl::UpgradeDetected using a weak pointer so that it can
301 // be interrupted from the UI thread.
302 void UpgradeDetectorImpl::DetectUpgradeTask(
303 base::WeakPtr
<UpgradeDetectorImpl
> upgrade_detector
) {
304 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
306 Version critical_update
;
307 Version installed_version
=
308 GetCurrentlyInstalledVersionImpl(&critical_update
);
310 // Get the version of the currently *running* instance of Chrome.
311 chrome::VersionInfo version_info
;
312 if (!version_info
.is_valid()) {
313 NOTREACHED() << "Failed to get current file version";
316 Version
running_version(version_info
.Version());
317 if (!running_version
.IsValid()) {
322 // |installed_version| may be NULL when the user downgrades on Linux (by
323 // switching from dev to beta channel, for example). The user needs a
324 // restart in this case as well. See http://crbug.com/46547
325 if (!installed_version
.IsValid() ||
326 (installed_version
.CompareTo(running_version
) > 0)) {
327 // If a more recent version is available, it might be that we are lacking
328 // a critical update, such as a zero-day fix.
329 UpgradeAvailable upgrade_available
= UPGRADE_AVAILABLE_REGULAR
;
330 if (critical_update
.IsValid() &&
331 critical_update
.CompareTo(running_version
) > 0) {
332 upgrade_available
= UPGRADE_AVAILABLE_CRITICAL
;
335 // Fire off the upgrade detected task.
336 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
337 base::Bind(&UpgradeDetectorImpl::UpgradeDetected
,
343 void UpgradeDetectorImpl::StartTimerForUpgradeCheck() {
344 detect_upgrade_timer_
.Start(FROM_HERE
,
345 base::TimeDelta::FromMilliseconds(GetCheckForUpgradeEveryMs()),
346 this, &UpgradeDetectorImpl::CheckForUpgrade
);
349 void UpgradeDetectorImpl::CheckForUpgrade() {
350 // Interrupt any (unlikely) unfinished execution of DetectUpgradeTask, or at
351 // least prevent the callback from being executed, because we will potentially
352 // call it from within DetectOutdatedInstall() or will post
353 // DetectUpgradeTask again below anyway.
354 weak_factory_
.InvalidateWeakPtrs();
356 // No need to look for upgrades if the install is outdated.
357 if (DetectOutdatedInstall())
360 // We use FILE as the thread to run the upgrade detection code on all
361 // platforms. For Linux, this is because we don't want to block the UI thread
362 // while launching a background process and reading its output; on the Mac and
363 // on Windows checking for an upgrade requires reading a file.
364 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
,
365 base::Bind(&UpgradeDetectorImpl::DetectUpgradeTask
,
366 weak_factory_
.GetWeakPtr()));
369 bool UpgradeDetectorImpl::DetectOutdatedInstall() {
370 // Don't show the bubble if we have a brand code that is NOT organic, unless
371 // an outdated build is being simulated by command line switches.
372 static bool simulate_outdated
= SimulatingOutdated();
373 if (!simulate_outdated
) {
375 if (google_brand::GetBrand(&brand
) && !google_brand::IsOrganic(brand
))
379 // Don't show the update bubbles to entreprise users (i.e., on a domain).
380 if (base::win::IsEnrolledToDomain())
383 // On Windows, we don't want to warn about outdated installs when the
384 // machine doesn't support SSE2, it's been deprecated starting with M35.
385 if (!base::CPU().has_sse2())
390 base::Time network_time
;
391 base::TimeDelta uncertainty
;
392 if (!g_browser_process
->network_time_tracker()->GetNetworkTime(
393 base::TimeTicks::Now(), &network_time
, &uncertainty
)) {
394 // When network time has not been initialized yet, simply rely on the
395 // machine's current time.
396 network_time
= base::Time::Now();
399 if (network_time
.is_null() || build_date_
.is_null() ||
400 build_date_
> network_time
) {
405 if (network_time
- build_date_
>
406 base::TimeDelta::FromDays(kOutdatedBuildAgeInDays
)) {
407 UpgradeDetected(is_auto_update_enabled_
?
408 UPGRADE_NEEDED_OUTDATED_INSTALL
:
409 UPGRADE_NEEDED_OUTDATED_INSTALL_NO_AU
);
412 // If we simlated an outdated install with a date, we don't want to keep
413 // checking for version upgrades, which happens on non-official builds.
414 return simulate_outdated
;
417 void UpgradeDetectorImpl::UpgradeDetected(UpgradeAvailable upgrade_available
) {
418 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
419 upgrade_available_
= upgrade_available
;
421 // Stop the recurring timer (that is checking for changes).
422 detect_upgrade_timer_
.Stop();
424 NotifyUpgradeDetected();
426 // Start the repeating timer for notifying the user after a certain period.
427 // The called function will eventually figure out that enough time has passed
428 // and stop the timer.
429 int cycle_time
= IsTesting() ?
430 kNotifyCycleTimeForTestingMs
: kNotifyCycleTimeMs
;
431 upgrade_notification_timer_
.Start(FROM_HERE
,
432 base::TimeDelta::FromMilliseconds(cycle_time
),
433 this, &UpgradeDetectorImpl::NotifyOnUpgrade
);
436 void UpgradeDetectorImpl::NotifyOnUpgrade() {
437 base::TimeDelta delta
= base::Time::Now() - upgrade_detected_time();
439 // We'll make testing more convenient by switching to seconds of waiting
440 // instead of days between flipping severity.
441 bool is_testing
= IsTesting();
442 int64 time_passed
= is_testing
? delta
.InSeconds() : delta
.InHours();
444 bool is_critical_or_outdated
= upgrade_available_
> UPGRADE_AVAILABLE_REGULAR
;
445 if (is_unstable_channel_
) {
446 // There's only one threat level for unstable channels like dev and
447 // canary, and it hits after one hour. During testing, it hits after one
449 const int kUnstableThreshold
= 1;
451 if (is_critical_or_outdated
) {
452 set_upgrade_notification_stage(UPGRADE_ANNOYANCE_CRITICAL
);
453 } else if (time_passed
>= kUnstableThreshold
) {
454 set_upgrade_notification_stage(UPGRADE_ANNOYANCE_LOW
);
456 // That's as high as it goes.
457 upgrade_notification_timer_
.Stop();
459 return; // Not ready to recommend upgrade.
462 const int kMultiplier
= is_testing
? 10 : 24;
463 // 14 days when not testing, otherwise 14 seconds.
464 const int kSevereThreshold
= 14 * kMultiplier
;
465 const int kHighThreshold
= 7 * kMultiplier
;
466 const int kElevatedThreshold
= 4 * kMultiplier
;
467 const int kLowThreshold
= 2 * kMultiplier
;
469 // These if statements must be sorted (highest interval first).
470 if (time_passed
>= kSevereThreshold
|| is_critical_or_outdated
) {
471 set_upgrade_notification_stage(
472 is_critical_or_outdated
? UPGRADE_ANNOYANCE_CRITICAL
:
473 UPGRADE_ANNOYANCE_SEVERE
);
475 // We can't get any higher, baby.
476 upgrade_notification_timer_
.Stop();
477 } else if (time_passed
>= kHighThreshold
) {
478 set_upgrade_notification_stage(UPGRADE_ANNOYANCE_HIGH
);
479 } else if (time_passed
>= kElevatedThreshold
) {
480 set_upgrade_notification_stage(UPGRADE_ANNOYANCE_ELEVATED
);
481 } else if (time_passed
>= kLowThreshold
) {
482 set_upgrade_notification_stage(UPGRADE_ANNOYANCE_LOW
);
484 return; // Not ready to recommend upgrade.
488 NotifyUpgradeRecommended();
492 UpgradeDetectorImpl
* UpgradeDetectorImpl::GetInstance() {
493 return Singleton
<UpgradeDetectorImpl
>::get();
497 UpgradeDetector
* UpgradeDetector::GetInstance() {
498 return UpgradeDetectorImpl::GetInstance();