chromium/chrome/browser/ash/growth/campaigns_manager_client_impl.cc

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

#include "chrome/browser/ash/growth/campaigns_manager_client_impl.h"

#include <map>
#include <memory>
#include <optional>
#include <string>
#include <variant>

#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/version.h"
#include "chrome/browser/ash/growth/install_web_app_action_performer.h"
#include "chrome/browser/ash/growth/metrics.h"
#include "chrome/browser/ash/growth/open_url_action_performer.h"
#include "chrome/browser/ash/growth/show_notification_action_performer.h"
#include "chrome/browser/ash/growth/show_nudge_action_performer.h"
#include "chrome/browser/ash/growth/update_user_pref_action_performer.h"
#include "chrome/browser/ash/login/demo_mode/demo_components.h"
#include "chrome/browser/ash/login/demo_mode/demo_mode_dimensions.h"
#include "chrome/browser/ash/login/demo_mode/demo_session.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/feature_engagement/tracker_factory.h"
#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chromeos/ash/components/growth/campaigns_constants.h"
#include "chromeos/ash/components/growth/campaigns_logger.h"
#include "chromeos/ash/components/growth/campaigns_manager.h"
#include "chromeos/ash/components/growth/growth_metrics.h"
#include "components/component_updater/ash/component_manager_ash.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/variations/service/variations_service.h"
#include "components/variations/synthetic_trials.h"

namespace {

inline constexpr char kCampaignComponentName[] = "growth-campaigns";

Profile* GetProfile() {
  return ProfileManager::GetActiveUserProfile();
}

}  // namespace

CampaignsManagerClientImpl::CampaignsManagerClientImpl() {
  // `show_nudge_performer_observation_` is used in `campaigns_manager_` ctor,
  // so it needs to be initialized first.
  campaigns_manager_ = std::make_unique<growth::CampaignsManager>(
      /*client=*/this, g_browser_process->local_state());
}

CampaignsManagerClientImpl::~CampaignsManagerClientImpl() = default;

void CampaignsManagerClientImpl::LoadCampaignsComponent(
    growth::CampaignComponentLoadedCallback callback) {
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(ash::switches::kGrowthCampaignsPath)) {
    std::move(callback).Run(base::FilePath(command_line->GetSwitchValueASCII(
        ash::switches::kGrowthCampaignsPath)));
    return;
  }

  // Loads campaigns component.
  auto component_manager_ash =
      g_browser_process->platform_part()->component_manager_ash();
  CHECK(component_manager_ash);

  component_manager_ash->Load(
      kCampaignComponentName,
      component_updater::ComponentManagerAsh::MountPolicy::kMount,
      component_updater::ComponentManagerAsh::UpdatePolicy::kDontForce,
      base::BindOnce(&CampaignsManagerClientImpl::OnComponentDownloaded,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

void CampaignsManagerClientImpl::AddOnTrackerInitializedCallback(
    growth::OnTrackerInitializedCallback callback) {
  auto* tracker =
      feature_engagement::TrackerFactory::GetForBrowserContext(GetProfile());
  if (!tracker) {
    CAMPAIGNS_LOG(ERROR) << "Feature Engagement tracer is not available";
    std::move(callback).Run(false);
  }

  tracker->AddOnInitializedCallback(
      base::BindOnce(&CampaignsManagerClientImpl::OnTrackerInitialized,
                     weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}

bool CampaignsManagerClientImpl::IsDeviceInDemoMode() const {
  return ash::DemoSession::IsDeviceInDemoMode();
}

bool CampaignsManagerClientImpl::IsCloudGamingDevice() const {
  return ash::demo_mode::IsCloudGamingDevice();
}

bool CampaignsManagerClientImpl::IsFeatureAwareDevice() const {
  return ash::demo_mode::IsFeatureAwareDevice();
}

const std::string& CampaignsManagerClientImpl::GetApplicationLocale() const {
  // User selected locale, then resolved using
  // `l10n_util::CheckAndResolveLocale` to a platform locale.
  // For example: `en-IN` will be resolved to `en-GB`.
  return g_browser_process->GetApplicationLocale();
}

const std::string& CampaignsManagerClientImpl::GetUserLocale() const {
  // The locale as selected by the user, such as "en-IN". This is different
  // from `GetApplication` locale which is actually platform locale that
  // resolved using `l10n_util::CheckAndResolveLocale`.
  return GetProfile()->GetPrefs()->GetString(
      language::prefs::kApplicationLocale);
}

const std::string CampaignsManagerClientImpl::GetCountryCode() const {
  return g_browser_process->variations_service()->GetStoredPermanentCountry();
}

const base::Version& CampaignsManagerClientImpl::GetDemoModeAppVersion() const {
  auto* demo_session = ash::DemoSession::Get();
  CHECK(demo_session);

  const auto& version = demo_session->components()->app_component_version();
  if (!version.has_value()) {
    growth::RecordCampaignsManagerError(
        growth::CampaignsManagerError::kDemoModeAppVersionUnavailable);
    static const base::NoDestructor<base::Version> empty_version;
    return *empty_version;
  }

  return version.value();
}

growth::ActionMap CampaignsManagerClientImpl::GetCampaignsActions() {
  growth::ActionMap action_map;
  action_map.emplace(
      make_pair(growth::ActionType::kInstallWebApp,
                std::make_unique<InstallWebAppActionPerformer>()));
  action_map.emplace(make_pair(growth::ActionType::kOpenUrl,
                               std::make_unique<OpenUrlActionPerformer>()));

  std::unique_ptr<ShowNudgeActionPerformer> show_nudge_performer =
      std::make_unique<ShowNudgeActionPerformer>();
  show_nudge_performer_observation_.Observe(show_nudge_performer.get());
  action_map.emplace(make_pair(growth::ActionType::kShowNudge,
                               std::move(show_nudge_performer)));
  std::unique_ptr<ShowNotificationActionPerformer> show_notification_performer =
      std::make_unique<ShowNotificationActionPerformer>();
  show_notification_performer_observation_.Observe(
      show_notification_performer.get());
  action_map.emplace(make_pair(growth::ActionType::kShowNotification,
                               std::move(show_notification_performer)));
  action_map.emplace(
      make_pair(growth::ActionType::kUpdateUserPref,
                std::make_unique<UpdateUserPrefActionPerformer>()));
  return action_map;
}

void CampaignsManagerClientImpl::RegisterSyntheticFieldTrial(
    const std::string& trial_name,
    const std::string& group_name) const {
  ChromeMetricsServiceAccessor::RegisterSyntheticFieldTrial(trial_name,
                                                            group_name);
}

void CampaignsManagerClientImpl::RecordEvent(const std::string& event_name) {
  auto* tracker =
      feature_engagement::TrackerFactory::GetForBrowserContext(GetProfile());
  if (!tracker || !tracker->IsInitialized()) {
    CAMPAIGNS_LOG(ERROR) << "Feature Engagement tracer is not available";
    growth::RecordCampaignsManagerError(
        growth::CampaignsManagerError::kTrackerNotAvailableInSession);
    return;
  }

  tracker->NotifyEvent(event_name);
}

void CampaignsManagerClientImpl::ClearConfig(
    const std::map<std::string, std::string>& params) {
  auto* tracker =
      feature_engagement::TrackerFactory::GetForBrowserContext(GetProfile());
  if (!tracker || !tracker->IsInitialized()) {
    CAMPAIGNS_LOG(ERROR) << "Feature Engagement tracer is not available";
    growth::RecordCampaignsManagerError(
        growth::CampaignsManagerError::kTrackerNotAvailableInSession);
    return;
  }

  UpdateConfig(params);
  tracker->ClearEventData(feature_engagement::kIPHGrowthFramework);
}

bool CampaignsManagerClientImpl::WouldTriggerHelpUI(
    const std::map<std::string, std::string>& params) {
  auto* tracker =
      feature_engagement::TrackerFactory::GetForBrowserContext(GetProfile());
  if (!tracker || !tracker->IsInitialized()) {
    CAMPAIGNS_LOG(ERROR) << "Feature Engagement tracer is not available";
    growth::RecordCampaignsManagerError(
        growth::CampaignsManagerError::kTrackerNotAvailableInSession);
    return false;
  }

  UpdateConfig(params);
  return tracker->WouldTriggerHelpUI(feature_engagement::kIPHGrowthFramework);
}

signin::IdentityManager* CampaignsManagerClientImpl::GetIdentityManager()
    const {
  return IdentityManagerFactory::GetForProfile(GetProfile());
}

void CampaignsManagerClientImpl::OnReadyToLogImpression(
    int campaign_id,
    std::optional<int> group_id,
    bool should_log_cros_events) {
  // Records impression UMA metrics.
  // TODO: b/348495965 - Verify group metrics when ready.
  RecordImpression(campaign_id, should_log_cros_events);
  RecordImpressionEvents(campaign_id, group_id);
}

void CampaignsManagerClientImpl::OnDismissed(int campaign_id,
                                             std::optional<int> group_id,
                                             bool should_mark_dismissed,
                                             bool should_log_cros_events) {
  // Records dismissal UMA metrics.
  // TODO: b/348495965 - Verify group metrics when ready.
  RecordDismissed(campaign_id, should_log_cros_events);

  if (!should_mark_dismissed) {
    return;
  }

  RecordDismissalEvents(campaign_id, group_id);
}

void CampaignsManagerClientImpl::OnButtonPressed(int campaign_id,
                                                 std::optional<int> group_id,
                                                 CampaignButtonId button_id,
                                                 bool should_mark_dismissed,
                                                 bool should_log_cros_events) {
  // TODO: b/348495965 - Verify group metrics when ready.
  RecordButtonPressed(campaign_id, button_id, should_log_cros_events);

  if (!should_mark_dismissed) {
    return;
  }

  // Notify `kDismissed` event to the Feature Engagement framework. This event
  // will be stored and could be used later.
  switch (button_id) {
    case CampaignButtonId::kPrimary:
    case CampaignButtonId::kSecondary:
    case CampaignButtonId::kClose:
      // Primary, Secondary and close button press will treated as user
      // dismissal.
      RecordDismissalEvents(campaign_id, group_id);
      break;
    case CampaignButtonId::kOthers:
      break;
  }
}

void CampaignsManagerClientImpl::OnComponentDownloaded(
    growth::CampaignComponentLoadedCallback loaded_callback,
    component_updater::ComponentManagerAsh::Error error,
    const base::FilePath& path) {
  if (error != component_updater::ComponentManagerAsh::Error::NONE) {
    std::move(loaded_callback).Run(std::nullopt);
    return;
  }

  std::move(loaded_callback).Run(path);
}

void CampaignsManagerClientImpl::OnTrackerInitialized(
    growth::OnTrackerInitializedCallback callback,
    bool init_success) {
  std::move(callback).Run(init_success);
}

void CampaignsManagerClientImpl::UpdateConfig(
    const std::map<std::string, std::string>& params) {
  auto* tracker =
      feature_engagement::TrackerFactory::GetForBrowserContext(GetProfile());
  if (!tracker || !tracker->IsInitialized()) {
    CAMPAIGNS_LOG(ERROR) << "Feature Engagement tracer is not available";
    growth::RecordCampaignsManagerError(
        growth::CampaignsManagerError::kTrackerNotAvailableInSession);
    return;
  }

  config_provider_.SetConfig(params);
  tracker->UpdateConfig(feature_engagement::kIPHGrowthFramework,
                        &config_provider_);
}

void CampaignsManagerClientImpl::RecordImpressionEvents(
    int campaign_id,
    std::optional<int> group_id) {
  campaigns_manager_->RecordEventForTargeting(
      growth::CampaignEvent::kImpression, base::NumberToString(campaign_id));

  if (group_id) {
    campaigns_manager_->RecordEventForTargeting(
        growth::CampaignEvent::kGroupImpression,
        base::NumberToString(group_id.value()));
  }
}

void CampaignsManagerClientImpl::RecordDismissalEvents(
    int campaign_id,
    std::optional<int> group_id) {
  campaigns_manager_->RecordEventForTargeting(
      growth::CampaignEvent::kDismissed, base::NumberToString(campaign_id));

  if (group_id) {
    campaigns_manager_->RecordEventForTargeting(
        growth::CampaignEvent::kGroupDismissed,
        base::NumberToString(group_id.value()));
  }
}