chromium/chromeos/components/magic_boost/public/cpp/magic_boost_state.h

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROMEOS_COMPONENTS_MAGIC_BOOST_PUBLIC_CPP_MAGIC_BOOST_STATE_H_
#define CHROMEOS_COMPONENTS_MAGIC_BOOST_PUBLIC_CPP_MAGIC_BOOST_STATE_H_

#include "base/component_export.h"
#include "base/functional/callback_forward.h"
#include "base/observer_list.h"
#include "base/types/expected.h"

namespace chromeos {

// HMR consent is two phases. These are flows and state transitions.
//
// Flow A (Mini Card):
// 1. Mini card is shown (kUnset -> kPendingDisclaimer | kDeclined)
// 2. Disclaimer dialog is shown (kPendingDisclaimer -> kApproved | kDeclined)
//
// *: If a user has pressed [No Thanks] in the mini card, kDeclined is set.
//
// Flow B (Settings):
// 1. A user toggles HMR settings in Settings UI
//    (kUnset | kDeclined -> kPendingDisclaimer)
// 2. Disclaimer dialog is shown (kPendingDisclaimer -> kApproved | kDeclined)
enum class HMRConsentStatus : int {
  // User has agreed to consent by pressing the accept button on the disclaimer
  // UI.
  kApproved = 0,
  // User has disagreed to consent by pressing the decline button on the
  // disclaimer UI or the opt-in card.
  kDeclined = 1,
  // This state is being used when the feature is turned on through the Settings
  // app or a mini card and consent status is unset. In this case, we will show
  // the disclaimer UI when users try to access the Mahi feature through the
  // Mahi menu card.
  kPendingDisclaimer = 2,
  // Users hasn't accept nor decline the consent.
  kUnset = 3,
};

// A class that holds MagicBoost related prefs and states.
class COMPONENT_EXPORT(MAGIC_BOOST) MagicBoostState {
 public:
  // A checked observer which receives MagicBoost state changes.
  class Observer : public base::CheckedObserver {
   public:
    virtual void OnMagicBoostEnabledUpdated(bool enabled) {}
    virtual void OnHMREnabledUpdated(bool enabled) {}
    virtual void OnHMRConsentStatusUpdated(HMRConsentStatus status) {}

    // `MagicBoostState` is being deleted. All `ScopedObservation`s MUST get
    // reset. `ScopedObservation::Reset` accesses source (i.e., magic boost
    // state pointer). This is intentionally defined as a pure virtual function
    // as all observers care this.
    virtual void OnIsDeleting() = 0;
  };

  enum class Error {
    kUninitialized,
  };

  static MagicBoostState* Get();

  MagicBoostState();

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

  virtual ~MagicBoostState();

  void AddObserver(Observer* observer);
  void RemoveObserver(Observer* observer);

  // Increments HMRWindowDismissCount count and returns an incremented value.
  // Note that this method is not thread safe, i.e., this increment does NOT
  // operate as an atomic operation. Reading HMRWindowDismissCount immediately
  // after the write can read a stale value.
  virtual int32_t AsyncIncrementHMRConsentWindowDismissCount() = 0;

  // Writes consent status and a respective enabled state to the pref. Note that
  // this method returns BEFORE a write is completed. Reading consent status
  // and/or enabled state immediately after the write can read a stale value.
  virtual void AsyncWriteConsentStatus(HMRConsentStatus consent_status) = 0;

  // Writes HMR enabled value to the pref. Note that this method returns BEFORE
  // a write is completed. Reading consent status and/or enabled state
  // immediately after the write can read a stale value.
  virtual void AsyncWriteHMREnabled(bool enabled) = 0;

  // Marks Orca consent status as rejected and disable the feature.
  virtual void DisableOrcaFeature() = 0;

  base::expected<bool, Error> magic_boost_enabled() const {
    return magic_boost_enabled_;
  }

  base::expected<bool, Error> hmr_enabled() const { return hmr_enabled_; }

  base::expected<HMRConsentStatus, Error> hmr_consent_status() const {
    return hmr_consent_status_;
  }

  int hmr_consent_window_dismiss_count() const {
    return hmr_consent_window_dismiss_count_;
  }

 protected:
  void UpdateMagicBoostEnabled(bool enabled);
  void UpdateHMREnabled(bool enabled);
  void UpdateHMRConsentStatus(HMRConsentStatus status);
  void UpdateHMRConsentWindowDismissCount(int32_t count);

 private:
  void NotifyOnIsDeleting();

  // Use `base::expected` instead of `std::optional` to avoid implicit bool
  // conversion: https://abseil.io/tips/141.
  base::expected<bool, Error> magic_boost_enabled_ =
      base::unexpected(Error::kUninitialized);
  base::expected<bool, Error> hmr_enabled_ =
      base::unexpected(Error::kUninitialized);
  base::expected<HMRConsentStatus, Error> hmr_consent_status_ =
      base::unexpected(Error::kUninitialized);
  int32_t hmr_consent_window_dismiss_count_ = 0;

  base::ObserverList<Observer> observers_;
};

}  // namespace chromeos

#endif  // CHROMEOS_COMPONENTS_MAGIC_BOOST_PUBLIC_CPP_MAGIC_BOOST_STATE_H_