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

// 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.

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

#include <memory>

#include "base/containers/contains.h"
#include "base/logging.h"
#include "base/values.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chromeos/ash/components/growth/campaigns_logger.h"
#include "chromeos/ash/components/growth/growth_metrics.h"
#include "components/prefs/pref_service.h"

namespace {

constexpr char kName[] = "name";
constexpr char kType[] = "type";
constexpr char kValue[] = "value";

enum class UpdateType {
  kSet = 0,
  kClear,
  kAppend,
  kRemove,
  kMaxUpdateType = kRemove,
};

// Return nullptr if the pref service is not available or the pref is invalid or
// the pref type is invalid.
// Use type NONE as target type to skip type checking.
PrefService* GetPrefServiceAndCheckUserPrefType(
    const std::string& pref_name,
    const base::Value::Type& target_type) {
  PrefService* pref_service =
      ProfileManager::GetActiveUserProfile()->GetPrefs();
  if (!pref_service) {
    growth::RecordCampaignsManagerError(
        growth::CampaignsManagerError::kUserPrefServiceNotAvailable);
    CAMPAIGNS_LOG(ERROR) << "User pref service not available.";
    return nullptr;
  }

  const PrefService::Preference* pref = pref_service->FindPreference(pref_name);
  if (!pref) {
    growth::RecordCampaignsManagerError(
        growth::CampaignsManagerError::kUserPrefNotFound);
    CAMPAIGNS_LOG(ERROR) << "User pref action: " << pref_name << " not found";
    return nullptr;
  }

  // Skip type checking if the target_type is type::NONE.
  if (target_type != base::Value::Type::NONE &&
      pref->GetType() != target_type) {
    growth::RecordCampaignsManagerError(
        growth::CampaignsManagerError::kUserPrefValueTypeMismatch);
    CAMPAIGNS_LOG(ERROR) << "User pref action: " << pref_name
                         << " type mismatched";
    return nullptr;
  }
  return pref_service;
}

bool SetUserPrefValue(const std::string& pref_name, const base::Value* value) {
  PrefService* pref_service =
      GetPrefServiceAndCheckUserPrefType(pref_name, value->type());
  if (!pref_service) {
    return false;
  }

  pref_service->Set(pref_name, *value);
  return true;
}

bool ClearUserPrefValue(const std::string& pref_name) {
  PrefService* pref_service =
      GetPrefServiceAndCheckUserPrefType(pref_name, base::Value::Type::NONE);
  if (!pref_service) {
    return false;
  }
  pref_service->ClearPref(pref_name);
  return true;
}

bool AppendValueToUserPref(const std::string& pref_name,
                           const base::Value* value) {
  PrefService* pref_service =
      GetPrefServiceAndCheckUserPrefType(pref_name, base::Value::Type::LIST);
  if (!pref_service) {
    return false;
  }

  if (base::Contains(pref_service->GetList(pref_name), *value)) {
    CAMPAIGNS_LOG(ERROR) << "Pref value is already in the list.";
    return false;
  }

  auto& values = pref_service->GetList(pref_name);
  auto cached_values = values.Clone();
  cached_values.Append(value->Clone());
  pref_service->SetList(pref_name, std::move(cached_values));
  return true;
}

bool RemoveValueFromUserPref(const std::string& pref_name,
                             const base::Value* value) {
  PrefService* pref_service =
      GetPrefServiceAndCheckUserPrefType(pref_name, base::Value::Type::LIST);
  if (!pref_service) {
    return false;
  }

  if (!base::Contains(pref_service->GetList(pref_name), *value)) {
    CAMPAIGNS_LOG(ERROR)
        << "Unable to remove: Pref value not in user preference.";
    return false;
  }

  auto& values = pref_service->GetList(pref_name);
  auto cached_values = values.Clone();
  cached_values.EraseValue(*value);
  pref_service->SetList(pref_name, std::move(cached_values));
  return true;
}

bool UpdateUserPrefValue(const std::string& pref_name,
                         UpdateType type,
                         const base::Value* value) {
  switch (type) {
    case UpdateType::kSet:
      return SetUserPrefValue(pref_name, value);
    case UpdateType::kClear:
      return ClearUserPrefValue(pref_name);
    case UpdateType::kAppend:
      return AppendValueToUserPref(pref_name, value);
    case UpdateType::kRemove:
      return RemoveValueFromUserPref(pref_name, value);
  }
}

}  // namespace

UpdateUserPrefActionPerformer::UpdateUserPrefActionPerformer() = default;
UpdateUserPrefActionPerformer::~UpdateUserPrefActionPerformer() = default;

void UpdateUserPrefActionPerformer::Run(
    int campaign_id,
    std::optional<int> group_id,
    const base::Value::Dict* params,
    growth::ActionPerformer::Callback callback) {
  if (!params) {
    std::move(callback).Run(growth::ActionResult::kFailure,
                            growth::ActionResultReason::kParsingActionFailed);
    CAMPAIGNS_LOG(ERROR) << "Update User Pref params not found.";
    return;
  }

  auto* pref_name = params->FindString(kName);
  if (!pref_name) {
    std::move(callback).Run(growth::ActionResult::kFailure,
                            growth::ActionResultReason::kParsingActionFailed);
    CAMPAIGNS_LOG(ERROR) << kName << " parameter not found.";
    return;
  }

  auto type = params->FindInt(kType);
  if (!type) {
    std::move(callback).Run(growth::ActionResult::kFailure,
                            growth::ActionResultReason::kParsingActionFailed);
    CAMPAIGNS_LOG(ERROR) << kType << " parameter not found.";
    return;
  }

  if (type.value() < 0 ||
      type.value() > static_cast<int>(UpdateType::kMaxUpdateType)) {
    std::move(callback).Run(growth::ActionResult::kFailure,
                            growth::ActionResultReason::kParsingActionFailed);
    CAMPAIGNS_LOG(ERROR) << kType << " invalid value: " << type.value();
    return;
  }

  auto update_type = static_cast<UpdateType>(type.value());
  auto* value = params->Find(kValue);

  // Value is required for all the actions except clear action.
  if (update_type != UpdateType::kClear && !value) {
    std::move(callback).Run(growth::ActionResult::kFailure,
                            growth::ActionResultReason::kParsingActionFailed);
    CAMPAIGNS_LOG(ERROR) << kValue << " parameter not found.";
    return;
  }

  if (!UpdateUserPrefValue(*pref_name, update_type, value)) {
    std::move(callback).Run(growth::ActionResult::kFailure,
                            growth::ActionResultReason::kUpdateUserPrefFailed);
    return;
  }
  std::move(callback).Run(growth::ActionResult::kSuccess,
                          /*action_result_reason=*/std::nullopt);
}

growth::ActionType UpdateUserPrefActionPerformer::ActionType() const {
  return growth::ActionType::kUpdateUserPref;
}