blob: 3684eb7b45ae0d843ac904da360a9acbb6173d1c [file] [log] [blame]
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_UPGRADE_DETECTOR_UPGRADE_DETECTOR_H_
#define CHROME_BROWSER_UPGRADE_DETECTOR_UPGRADE_DETECTOR_H_
#include <optional>
#include <string>
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/upgrade_detector/upgrade_observer.h"
#include "components/prefs/pref_change_registrar.h"
class PrefRegistrySimple;
class UpgradeObserver;
namespace base {
class Clock;
class TickClock;
}
///////////////////////////////////////////////////////////////////////////////
// UpgradeDetector
//
// This class is a singleton class that monitors when an upgrade happens in the
// background. We basically ask Omaha what it thinks the latest version is and
// if our version is lower we send out a notification upon:
// a) Detecting an upgrade and...
// b) When we think the user should be notified about the upgrade.
// The latter happens much later, since we don't want to be too annoying.
// This class is not thread safe -- all access must take place on the UI thread.
//
class UpgradeDetector {
public:
// The Homeland Security Upgrade Advisory System.
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum UpgradeNotificationAnnoyanceLevel {
UPGRADE_ANNOYANCE_NONE = 0, // What? Me worry?
UPGRADE_ANNOYANCE_LOW = 1, // Green.
UPGRADE_ANNOYANCE_ELEVATED = 2, // Yellow.
UPGRADE_ANNOYANCE_HIGH = 3, // Red.
// UPGRADE_ANNOYANCE_SEVERE = 4, // Removed in 2018-03 for lack of use.
UPGRADE_ANNOYANCE_CRITICAL = 5, // Red exclamation mark.
UPGRADE_ANNOYANCE_VERY_LOW = 6, // Green early warning for canary and dev.
UPGRADE_ANNOYANCE_GRACE = 7, // Red last warning before deadline.
UPGRADE_ANNOYANCE_MAX_VALUE = UPGRADE_ANNOYANCE_GRACE
};
struct RelaunchWindow {
constexpr RelaunchWindow(int start_hour,
int start_minute,
base::TimeDelta duration)
: hour(start_hour), minute(start_minute), duration(duration) {}
bool IsValid() const {
return hour >= 0 && hour <= 23 && minute >= 0 && minute <= 59 &&
duration >= base::Minutes(1) && duration != base::TimeDelta::Max();
}
int hour;
int minute;
base::TimeDelta duration;
};
// Returns the singleton implementation instance.
static UpgradeDetector* GetInstance();
UpgradeDetector(const UpgradeDetector&) = delete;
UpgradeDetector& operator=(const UpgradeDetector&) = delete;
virtual ~UpgradeDetector();
// Returns the default delta from upgrade detection until high annoyance is
// reached.
static base::TimeDelta GetDefaultHighAnnoyanceThreshold();
// Returns the default delta from upgrade detection until elevated annoyance
// is reached.
static base::TimeDelta GetDefaultElevatedAnnoyanceThreshold();
static void RegisterPrefs(PrefRegistrySimple* registry);
virtual void Init();
virtual void Shutdown();
// Returns the time at which an available upgrade was detected.
base::Time upgrade_detected_time() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return upgrade_detected_time_;
}
// Whether the user should be notified about an upgrade.
bool notify_upgrade() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return notify_upgrade_;
}
// Whether the upgrade recommendation is due to Chrome being outdated.
bool is_outdated_install() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return upgrade_available_ == UPGRADE_NEEDED_OUTDATED_INSTALL;
}
// Whether the upgrade recommendation is due to Chrome being outdated AND
// auto-update is turned off.
bool is_outdated_install_no_au() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return upgrade_available_ == UPGRADE_NEEDED_OUTDATED_INSTALL_NO_AU;
}
// Returns true if the detector has found that a newer version of Chrome is
// installed and a relaunch would complete the update.
bool is_upgrade_available() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return upgrade_available_ == UPGRADE_AVAILABLE_REGULAR ||
upgrade_available_ == UPGRADE_AVAILABLE_CRITICAL;
}
// Notify this object that the user has acknowledged the critical update so we
// don't need to complain about it for now.
void acknowledge_critical_update() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
critical_update_acknowledged_ = true;
}
// Whether the user has acknowledged the critical update.
bool critical_update_acknowledged() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return critical_update_acknowledged_;
}
#if BUILDFLAG(IS_CHROMEOS)
bool is_factory_reset_required() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_factory_reset_required_;
}
bool is_rollback() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return is_rollback_;
}
#endif // BUILDFLAG(IS_CHROMEOS)
UpgradeNotificationAnnoyanceLevel upgrade_notification_stage() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return upgrade_notification_stage_;
}
// Returns the time at which `level` annoyance level will be (or was) reached,
// or a null time object if an upgrade has not yet been detected.
virtual base::Time GetAnnoyanceLevelDeadline(
UpgradeNotificationAnnoyanceLevel level) = 0;
// Overrides the "high" annoyance deadline, setting it to |deadline|. On
// Chrome OS, this also sets the "elevated" annoyance deadline to the time at
// which the available update was detected. This has no effect on desktop
// Chrome browsers.
virtual void OverrideHighAnnoyanceDeadline(base::Time deadine) {}
// Resets the overridden deadlines and recalculates them according to the
// thresholds from the Local State. This has no effect on desktop Chrome
// browsers.
virtual void ResetOverriddenDeadline() {}
// Overrides the relaunch notification style to required if |override|; else
// resets the override so that the policy settings take effect.
void OverrideRelaunchNotificationToRequired(bool overridden);
void AddObserver(UpgradeObserver* observer);
void RemoveObserver(UpgradeObserver* observer);
// Notifies that the current install is outdated. No details are expected.
void NotifyOutdatedInstall();
// Notifies that the current install is outdated and auto-update (AU) is
// disabled. No details are expected.
void NotifyOutdatedInstallNoAutoUpdate();
void set_upgrade_notification_stage_for_testing(
UpgradeNotificationAnnoyanceLevel stage) {
set_upgrade_notification_stage(stage);
}
void NotifyUpgradeForTesting();
protected:
enum UpgradeAvailable {
// If no update is available and current install is recent enough.
UPGRADE_AVAILABLE_NONE,
// If a regular update is available.
UPGRADE_AVAILABLE_REGULAR,
// If a critical update to Chrome has been installed, such as a zero-day
// fix.
UPGRADE_AVAILABLE_CRITICAL,
// If no update to Chrome has been installed for more than the recommended
// time.
UPGRADE_NEEDED_OUTDATED_INSTALL,
// If no update to Chrome has been installed for more than the recommended
// time AND auto-update is turned off.
UPGRADE_NEEDED_OUTDATED_INSTALL_NO_AU,
};
UpgradeDetector(const base::Clock* clock, const base::TickClock* tick_clock);
// Starts observing changes to Local State preference `pref`.
void MonitorPrefChanges(const std::string& pref);
// Returns the notification period specified via the
// RelaunchNotificationPeriod policy setting, or a zero delta if unset or out
// of range.
static base::TimeDelta GetRelaunchNotificationPeriod();
static bool IsRelaunchNotificationPolicyEnabled();
// Returns the adjusted deadline to fall within `window`. If the
// `deadline` has already passed the window for the day, it is prolonged for
// the next day within the window. If the `deadline` already falls within the
// window, no change is made.
static base::Time AdjustDeadline(base::Time deadline,
const RelaunchWindow& window);
// Returns the relaunch window specified via the RelaunchWindow policy
// setting, or nullopt if unset or set incorrectly.
static std::optional<RelaunchWindow> GetRelaunchWindowPolicyValue();
// Returns the default relaunch window within which the relaunch should take
// place. It is 2am to 4am from Chrome OS and the whole day for others.
static RelaunchWindow GetDefaultRelaunchWindow();
// Returns the delta between "grace" and "high" annoyance levels using
// `elevated_to_high_delta` which is the delta between "elevated" and "high"
// annoyance levels.
static base::TimeDelta GetGracePeriod(base::TimeDelta elevated_to_high_delta);
// Returns the network time, falling back to system time if unavailable.
// Returns true if it's network time, or false if it's not.
bool GetNetworkTimeWithFallback(base::Time& network_time);
// Returns true if `last_served_date_` is known and older than the number
// of days specified by the RelaunchFastIfOutdated policy.
bool ShouldRelaunchFast();
// Returns true if the last served date should be fetched on update, for
// the RelaunchOutdatedInstall policy.
bool ShouldFetchLastServedDate() const;
// Fetches the last served date via GetLastServedDate(), and updates
// `last_served_date_`.
void FetchLastServedDate();
const base::Clock* clock() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return clock_;
}
const base::TickClock* tick_clock() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return tick_clock_;
}
// Notifies that update is recommended and triggers different actions based
// on the update availability.
void NotifyUpgrade();
// Notifies that update is recommended.
void NotifyUpgradeRecommended();
// Notifies that a critical update has been installed. No details are
// expected.
void NotifyCriticalUpgradeInstalled();
// Notifies that an update is downloaded but deferred. Set `use_notification`
// to true to enable system tray notification.
void NotifyUpdateDeferred(bool use_notification);
// The function that sends out a notification that lets the rest of the UI
// know we should notify the user that a new update is available to download
// over cellular connection.
void NotifyUpdateOverCellularAvailable();
// Notifies that the user's one time permission on update over cellular
// connection has been granted.
void NotifyUpdateOverCellularOneTimePermissionGranted();
// Notifies about a request to override the relaunch notification style to
// required or reset the overridden style.
void NotifyRelaunchOverriddenToRequired(bool overridden);
// Triggers a critical update, which starts a timer that checks the machine
// idle state. Protected and virtual so that it could be overridden by tests.
virtual void TriggerCriticalUpdate();
UpgradeAvailable upgrade_available() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return upgrade_available_;
}
void set_upgrade_available(UpgradeAvailable available) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
upgrade_available_ = available;
}
void set_upgrade_detected_time(base::Time upgrade_detected_time) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
upgrade_detected_time_ = upgrade_detected_time;
}
void set_best_effort_experiment_updates_available(bool available) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
best_effort_experiment_updates_available_ = available;
}
bool critical_experiment_updates_available() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return critical_experiment_updates_available_;
}
void set_critical_experiment_updates_available(bool available) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
critical_experiment_updates_available_ = available;
}
void set_critical_update_acknowledged(bool acknowledged) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
critical_update_acknowledged_ = acknowledged;
}
void set_upgrade_notification_stage(UpgradeNotificationAnnoyanceLevel stage) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
upgrade_notification_stage_ = stage;
}
#if BUILDFLAG(IS_CHROMEOS)
void set_is_factory_reset_required(bool is_factory_reset_required) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
is_factory_reset_required_ = is_factory_reset_required;
}
void set_is_rollback(bool is_rollback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
is_rollback_ = is_rollback;
}
#endif // BUILDFLAG(IS_CHROMEOS)
private:
FRIEND_TEST_ALL_PREFIXES(AppMenuModelTest, Basics);
FRIEND_TEST_ALL_PREFIXES(RelaunchNotificationControllerUiTest,
ReactivateAfterDeadline);
FRIEND_TEST_ALL_PREFIXES(SystemTrayClientTest, UpdateTrayIcon);
friend class RelaunchNotificationControllerUiTest;
friend class UpgradeMetricsProviderTest;
// Called on the UI thread after one or more monitored prefs or
// `last_served_date_` have changed. If an update has been detected,
// subclasses may need to recompute the schedule for advancing through the
// annoyance levels.
virtual void RecomputeSchedule() {}
// Initiates an Idle check. Tells us whether Chrome has received any
// input events since the specified time.
void CheckIdle();
// Handles a change to the relaunch notification related Local State
// preferences. Posts a task to call OnThresholdPrefChanged() if it isn't
// already posted and pending for execution.
void OnRelaunchPrefChanged();
// Handles the result of GetLastServedDate().
void OnGotLastServedDate(std::optional<base::Time> last_served_date);
// A provider of Time to the detector.
const raw_ptr<const base::Clock> clock_;
// A provider of TimeTicks to the detectors' timers.
const raw_ptr<const base::TickClock> tick_clock_;
// Observes changes to the browser.relaunch_notification_period Local State
// preference.
PrefChangeRegistrar pref_change_registrar_;
// Whether any software updates are available (experiment updates are tracked
// separately via additional member variables below).
UpgradeAvailable upgrade_available_;
// The time at which an available upgrade was detected.
base::Time upgrade_detected_time_;
// Whether "best effort" experiment updates are available.
bool best_effort_experiment_updates_available_;
// Whether "critical" experiment updates are available.
bool critical_experiment_updates_available_;
// Whether the user has acknowledged the critical update.
bool critical_update_acknowledged_;
// Whether a task posted on any relaunch preference change is still pending
// for execution.
bool pref_change_task_pending_ = false;
#if BUILDFLAG(IS_CHROMEOS)
// Whether a factory reset is needed to complete an update.
bool is_factory_reset_required_ = false;
// Whether the update is actually an admin-initiated rollback of the device
// to an earlier version of Chrome OS, which results in the device being
// wiped when it's rebooted.
bool is_rollback_ = false;
#endif // BUILDFLAG(IS_CHROMEOS)
// A timer to check to see if we've been idle for long enough to show the
// critical warning. Should only be set if |upgrade_available_| is
// UPGRADE_AVAILABLE_CRITICAL.
base::RepeatingTimer idle_check_timer_;
// The stage at which the annoyance level for upgrade notifications is at.
UpgradeNotificationAnnoyanceLevel upgrade_notification_stage_;
// Whether we have waited long enough after detecting an upgrade (to see
// is we should start nagging about upgrading).
bool notify_upgrade_;
bool fetched_last_served_date_ = false;
std::optional<base::Time> last_served_date_;
SEQUENCE_CHECKER(sequence_checker_);
base::ObserverList<UpgradeObserver>::Unchecked observer_list_;
base::WeakPtrFactory<UpgradeDetector> weak_factory_{this};
};
#endif // CHROME_BROWSER_UPGRADE_DETECTOR_UPGRADE_DETECTOR_H_