// Copyright 2018 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_UI_VIEWS_RELAUNCH_NOTIFICATION_RELAUNCH_NOTIFICATION_CONTROLLER_H_
#define CHROME_BROWSER_UI_VIEWS_RELAUNCH_NOTIFICATION_RELAUNCH_NOTIFICATION_CONTROLLER_H_

#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "base/timer/wall_clock_timer.h"
#include "chrome/browser/safe_browsing/application_advanced_protection_status_detector.h"
#include "chrome/browser/upgrade_detector/upgrade_detector.h"
#include "chrome/browser/upgrade_detector/upgrade_observer.h"
#include "components/prefs/pref_change_registrar.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ui/views/relaunch_notification/relaunch_notification_controller_platform_impl_chromeos.h"
#else
#include "chrome/browser/ui/views/relaunch_notification/relaunch_notification_controller_platform_impl_desktop.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

namespace base {
class Clock;
class TickClock;
}  // namespace base

// A class that observes changes to the browser.relaunch_notification
// preference (which is backed by the RelaunchNotification policy
// setting) and upgrade notifications from the UpgradeDetector. The two
// values for the RelaunchNotification policy setting are handled as follows:
//
// On Chrome desktop:
// - Recommended (1): The controller displays the relaunch recommended bubble on
//   each change to the UpgradeDetector's upgrade_notification_stage (an
//   "annoyance level" of low, elevated, grace or high). Once the high annoyance
//   level is reached, the controller continually reshows a the bubble on a
//   timer with a period equal to the time delta between the "elevated" and
//   "high" showings.
//
// - Required (2): The controller displays the relaunch required dialog when the
// UpgradeDetector's upgrade_notification_stage changes to an "annoyance level"
// of low, elevated, and grace. The browser is relaunched when the "annoyance
// level" reaches "high".
//
// On Chrome OS both notifications (recommended and required, described above)
// are shown in the unified system tray, overwriting the default "update
// available" notification. It cannot be deferred, so it persists until reboot.
// In certain conditions, the preference value could be overridden by the
// UpgradeDetector which then takes priority over the original value and any
// further changes to the preference have no effect.
class RelaunchNotificationController
    : public UpgradeObserver,
      public safe_browsing::ApplicationAdvancedProtectionStatusDetector::
          StatusObserver {
 public:
  // |upgrade_detector| is expected to be the process-wide detector, and must
  // outlive the controller.
  explicit RelaunchNotificationController(UpgradeDetector* upgrade_detector);

  RelaunchNotificationController(const RelaunchNotificationController&) =
      delete;
  RelaunchNotificationController& operator=(
      const RelaunchNotificationController&) = delete;

  ~RelaunchNotificationController() override;

 protected:
  // The length of the final countdown given to the user before the browser is
  // summarily relaunched on Chrome desktop, or the device is rebooted on
  // Chrome OS.
  static constexpr base::TimeDelta kRelaunchGracePeriod = base::Hours(1);

  RelaunchNotificationController(UpgradeDetector* upgrade_detector,
                                 const base::Clock* clock,
                                 const base::TickClock* tick_clock);

  // The deadline may be extended to ensure that the user has at least the full
  // duration of the grace period to take action.
  base::Time IncreaseRelaunchDeadlineOnShow();

  // UpgradeObserver:
  void OnUpgradeRecommended() override;
  void OnRelaunchOverriddenToRequired(bool overridden) override;

  // ApplicationAdvancedProtectionStatusDetector::StatusObserver:
  void OnApplicationAdvancedProtectionStatusChanged(bool enabled) override;

 private:
  enum class NotificationStyle {
    kNone,         // No notifications are shown.
    kRecommended,  // Relaunches are recommended.
    kRequired,     // Relaunches are required.
  };

  // Adjusts to the current notification style as indicated by the
  // browser.relaunch_notification Local State preference. If the notification
  // style has been overridden, then that value is given priority over the
  // preference value.
  void HandleCurrentStyle();

  // Bring the instance out of or back to dormant mode.
  void StartObservingUpgrades();
  void StopObservingUpgrades();
  // Observe BrowserAdvanceProtectionStatus.
  void StartObservingAPStatus();

  // Shows the proper notification based on the preference setting and starts
  // the timer to either reshow the bubble or restart the browser/device as
  // appropriate. |level| is the current annoyance level reported by the
  // UpgradeDetector, and |high_deadline| is the time at which the
  // UpgradeDetector will reach the high annoyance level; see the class comment
  // for further details.
  void ShowRelaunchNotification(
      UpgradeDetector::UpgradeNotificationAnnoyanceLevel level,
      base::Time high_deadline);

  // Closes any previously-shown notifications. This is safe to call if no
  // notifications have been shown. Notifications may be closed by other means
  // (e.g., by the user), so there is no expectation that a previously-shown
  // notification is still open when this is invoked. The timer to either
  // repeatedly show the relaunch recommended notification or to force a
  // relaunch once the deadline is reached is also stopped.
  void CloseRelaunchNotification();

  // Starts or reschedules a timer to periodically re-show the relaunch
  // recommended bubble.
  void StartReshowTimer();

  // Run on a timer once high annoyance has been reached to re-show the relaunch
  // recommended bubble.
  void OnReshowRelaunchRecommended();

  // Handles a new |level| and/or |high_deadline| by adjusting the runtime of
  // the relaunch timer, updating the deadline displayed in the title of the
  // relaunch required notification (if shown), and showing it if needed.
  void HandleRelaunchRequiredState(
      UpgradeDetector::UpgradeNotificationAnnoyanceLevel level,
      base::Time high_deadline);

  // Update |last_relaunch_notification_time_| before calling
  // DoNotifyRelaunchRecommended. |past_deadline| reflects whether the
  // Recommended deadline was already passed or not.
  void NotifyRelaunchRecommended(bool past_deadline);

  // Calls DoNotifyRelaunchRequired to show the notification.
  void NotifyRelaunchRequired();

  // The following methods, which are invoked by the controller to show or close
  // notifications, are virtual for the sake of testing.

  // Shows the relaunch recommended notification if it is not already open.
  // |past_deadline| reflects whether the Recommended deadline was already
  // passed or not.
  virtual void DoNotifyRelaunchRecommended(bool past_deadline);

  // Shows the relaunch required notification if it is not already open.
  // |on_visible| is a callback to be run when the notification is potentially
  // seen by the user to push back the relaunch deadline if the remaining time
  // is less than the grace period.
  // If |is_notification_style_ap_required|, the relaunch notification is shown
  // with Advanced Protection string and icon.
  virtual void DoNotifyRelaunchRequired(
      bool is_notification_style_ap_required,
      base::Time relaunch_deadline,
      base::OnceCallback<base::Time()> on_visible);

  // Closes bubble or dialog if either is still open on desktop, or sets the
  // default notification on Chrome OS.
  virtual void Close();

  // Run to restart the browser/device once the relaunch deadline is reached
  // when relaunches are required by policy.
  virtual void OnRelaunchDeadlineExpired();

  // The process-wide upgrade detector.
  const raw_ptr<UpgradeDetector> upgrade_detector_;

  // A provider of Time to the controller and its timer for the sake of
  // testability.
  const raw_ptr<const base::Clock> clock_;

  // The platform-specific implementation.
  RelaunchNotificationControllerPlatformImpl platform_impl_;

  // Observes changes to the browser.relaunch_notification Local State pref.
  PrefChangeRegistrar pref_change_registrar_;

  // The last observed notification style. When kNone, the controller is
  // said to be "dormant" as there is no work for it to do aside from watch for
  // changes to browser.relaunch_notification. When any other value, the
  // controller is observing the UpgradeDetector to detect when to show a
  // notification.
  NotificationStyle last_notification_style_;

  // The last observed annoyance level. This member is unconditionally
  // UPGRADE_ANNOYANCE_NONE when the controller is dormant
  // (browser.relaunch_notification is 0).
  UpgradeDetector::UpgradeNotificationAnnoyanceLevel last_level_;

  // The last observed high annoyance deadline.
  base::Time last_high_deadline_;

  // The last time recommended relaunch notification triggered
  base::Time last_relaunch_notification_time_;

  // A timer used either to repeatedly reshow the relaunch recommended bubble
  // once the high annoyance level has been reached, or to trigger browser
  // relaunch once the relaunch required dialog's deadline is reached.
  base::WallClockTimer timer_;

  // A flag to denote that the relaunch notification type policy value has been
  // overridden to required. Changes to the policy value will not affect the
  // notification type.
  bool notification_type_required_overridden_ = false;
  // A flag to denote that relaunch notification should be required for Advanced
  // Protection Program. This is true when there is at least a profile with
  // Advanced Protection and relaunch notification is not already required by
  // other override or the enterprise policy.
  bool notification_style_overridden_for_advanced_protection_ = false;

  // Observes changes to application Advanced Protection status.
  base::ScopedObservation<
      safe_browsing::ApplicationAdvancedProtectionStatusDetector,
      safe_browsing::ApplicationAdvancedProtectionStatusDetector::
          StatusObserver>
      advanced_protection_observation_{this};
};

#endif  // CHROME_BROWSER_UI_VIEWS_RELAUNCH_NOTIFICATION_RELAUNCH_NOTIFICATION_CONTROLLER_H_
