// Copyright 2016 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_ASH_HATS_HATS_NOTIFICATION_CONTROLLER_H_
#define CHROME_BROWSER_ASH_HATS_HATS_NOTIFICATION_CONTROLLER_H_
#include "base/containers/flat_map.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ref.h"
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "base/time/time.h"
#include "chrome/browser/profiles/profile_observer.h"
#include "chromeos/ash/components/network/network_state_handler_observer.h"
#include "ui/message_center/public/cpp/notification_delegate.h"
namespace message_center {
class Notification;
} // namespace message_center
class Profile;
class NetworkState;
namespace ash {
struct HatsConfig;
// TODO(jackshira): Extract non-notification specific code into a manager class.
// Happiness tracking survey (HaTS) notification controller is responsible for
// managing the HaTS notification that is displayed to the user.
// This class lives on the UI thread.
class HatsNotificationController : public message_center::NotificationDelegate,
public NetworkStateHandlerObserver,
public ProfileObserver {
public:
static const char kNotificationId[];
// Minimum amount of time before the notification is displayed again after a
// user has interacted with it.
static constexpr base::TimeDelta kHatsThreshold = base::Days(60);
// The threshold for a Googler is less.
static constexpr base::TimeDelta kHatsGooglerThreshold = base::Days(30);
// Prioritized HaTS has much shorter threshold.
static constexpr base::TimeDelta kPrioritizedHatsThreshold = base::Days(10);
// There are multiple pool/quota of cooldowns: normal and prioritized,
// when user was selected for one pool, there need to be another cooldown
// to ensure the user would not be selected for the other pool immediately
// after.
static constexpr base::TimeDelta kMinimumHatsThreshold = base::Days(1);
// HaTS threshold should be configured correctly.
static_assert(kHatsThreshold > kPrioritizedHatsThreshold);
static_assert(kHatsThreshold > kHatsGooglerThreshold);
static_assert(kPrioritizedHatsThreshold > kMinimumHatsThreshold);
static_assert(kHatsGooglerThreshold > kMinimumHatsThreshold);
HatsNotificationController(
Profile* profile,
const HatsConfig& config,
const base::flat_map<std::string, std::string>& product_specific_data,
const std::u16string title,
const std::u16string body);
// |product_specific_data| is meant to allow attaching extra runtime data that
// is specific to the survey, e.g. a survey about the log-in experience might
// include the last used authentication method.
HatsNotificationController(
Profile* profile,
const HatsConfig& config,
const base::flat_map<std::string, std::string>& product_specific_data);
HatsNotificationController(Profile* profile, const HatsConfig& config);
HatsNotificationController(const HatsNotificationController&) = delete;
HatsNotificationController& operator=(const HatsNotificationController&) =
delete;
// Returns true if the survey needs to be displayed for the given |profile|.
static bool ShouldShowSurveyToProfile(Profile* profile,
const HatsConfig& config);
private:
friend class HatsNotificationControllerTest;
FRIEND_TEST_ALL_PREFIXES(HatsNotificationControllerTest,
GetFormattedSiteContext);
FRIEND_TEST_ALL_PREFIXES(HatsNotificationControllerTest,
NewDevice_ShouldNotShowNotification);
FRIEND_TEST_ALL_PREFIXES(HatsNotificationControllerTest,
OldDevice_ShouldShowNotification);
FRIEND_TEST_ALL_PREFIXES(HatsNotificationControllerTest,
NoInternet_DoNotShowNotification);
FRIEND_TEST_ALL_PREFIXES(HatsNotificationControllerTest,
InternetConnected_ShowNotification);
FRIEND_TEST_ALL_PREFIXES(HatsNotificationControllerTest,
DismissNotification_ShouldUpdatePref);
FRIEND_TEST_ALL_PREFIXES(
HatsNotificationControllerTest,
Disconnected_RemoveNotification_Connected_AddNotification);
FRIEND_TEST_ALL_PREFIXES(HatsNotificationControllerTest,
DismissNotification_PrioritizedShouldUpdatePref);
~HatsNotificationController() override;
enum class HatsState {
kDeviceSelected = 0, // Device was selected in roll of dice.
kSurveyShownRecently = 1, // A survey was shown recently on device.
kNewDevice = 2, // Device is too new to show the survey.
kNotificationDisplayed = 3, // Pop up for survey was presented to user.
kNotificationDismissed = 4, // Notification was dismissed by user.
kNotificationClicked = 5, // User clicked on notification to open the
// survey.
kMaxValue = kNotificationClicked
};
void Initialize(bool is_new_device);
// NotificationDelegate overrides:
void Close(bool by_user) override;
void Click(const std::optional<int>& button_index,
const std::optional<std::u16string>& reply) override;
// NetworkStateHandlerObserver override:
void PortalStateChanged(const NetworkState* default_network,
NetworkState::PortalState portal_state) override;
void OnShuttingDown() override;
// ProfileObserver:
void OnProfileWillBeDestroyed(Profile* profile) override;
// Must be run on a blocking thread pool.
// Gathers the browser version info, firmware info and platform info and
// returns them in a single encoded string, in the format
// "<key>=<value>&<key>=<value>&<key>=<value>" where the keys and values are
// url-escaped. Any key-value pairs in |product_specific_data| are also
// encoded and appended to the string, unless the keys collide with existing
// device info keys.
static std::string GetFormattedSiteContext(
const std::string& user_locale,
const base::flat_map<std::string, std::string>& product_specific_data);
void UpdateLastInteractionTime();
void UpdateLastSurveyInteractionTime();
void ShowDialog(const std::string& site_context);
raw_ptr<Profile> profile_;
const raw_ref<const HatsConfig> hats_config_;
base::flat_map<std::string, std::string> product_specific_data_;
std::unique_ptr<message_center::Notification> notification_;
const std::u16string title_;
const std::u16string body_;
HatsState state_ = HatsState::kDeviceSelected;
base::ScopedObservation<Profile, ProfileObserver> profile_observation_{this};
base::WeakPtrFactory<HatsNotificationController> weak_pointer_factory_{this};
};
} // namespace ash
#endif // CHROME_BROWSER_ASH_HATS_HATS_NOTIFICATION_CONTROLLER_H_