chromium/ios/chrome/browser/content_notification/model/content_notification_util.mm

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

#import "ios/chrome/browser/content_notification/model/content_notification_util.h"

#import "base/metrics/histogram_functions.h"
#import "base/time/time.h"
#import "components/prefs/pref_service.h"
#import "components/prefs/scoped_user_pref_update.h"
#import "components/search_engines/prepopulated_engines.h"
#import "components/search_engines/template_url.h"
#import "components/search_engines/template_url_prepopulate_data.h"
#import "components/search_engines/template_url_service.h"
#import "ios/chrome/browser/content_notification/model/constants.h"
#import "ios/chrome/browser/metrics/model/constants.h"
#import "ios/chrome/browser/push_notification/model/constants.h"
#import "ios/chrome/browser/push_notification/model/push_notification_client_id.h"
#import "ios/chrome/browser/search_engines/model/template_url_service_factory.h"
#import "ios/chrome/browser/shared/model/prefs/pref_names.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/model/utils/first_run_util.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/browser/signin/model/authentication_service.h"
#import "ios/chrome/browser/signin/model/authentication_service_factory.h"

namespace {

// Enum for content notification promo events UMA metrics. Entries should not
// be renumbered and numeric values should never be reused. This should align
// with the ContentNotificationEligibilityType enum in enums.xml.
//
// LINT.IfChange
enum class ContentNotificationEligibilityType {
  kPromoEnabled = 0,
  kProvisionalEnabled = 1,
  kSetUpListEnabled = 2,
  kPromoRegistered = 3,
  kProvisionalRegistered = 4,
  kSetUpListRegistered = 5,
  kMaxValue = kSetUpListRegistered,
};
// LINT.ThenChange(/tools/metrics/histograms/metadata/content/enums.xml)

bool IsClientEligible(bool user_signed_in, bool default_search_engine) {
  // Only users who signed in and using Google as default search engine are
  // eligible to enroll content notifications.
  return user_signed_in && default_search_engine;
}

// Return true if the user has enabled the price tracking notification.
bool IsPriceTrackingNotificationEnabled(PrefService* pref_service) {
  return pref_service->GetDict(prefs::kFeaturePushNotificationPermissions)
      .FindBool(kCommerceNotificationKey)
      .value_or(false);
}

// Return Feed engagement level.
int GetFeedActivityLevel(PrefService* pref_service) {
  return pref_service->GetInteger(kActivityBucketKey);
}

// Return true if the user installed Chrome less than 4 weeks.
bool IsNewUser() {
  return IsFirstRunRecent(base::Days(28));
}

// Return a dictionary that stores values that impact user enrollment
// eligibility.
const base::Value::Dict& GetUserEnrollmentEligibilityDict(
    PrefService* pref_service) {
  const base::Value::Dict& dict =
      pref_service->GetDict(prefs::kContentNotificationsEnrollmentEligibility);
  if (dict.empty()) {
    ScopedDictPrefUpdate update(
        pref_service, prefs::kContentNotificationsEnrollmentEligibility);
    update->Set(kPriceTrackingNotificationEnabledKey,
                IsPriceTrackingNotificationEnabled(pref_service));
    update->Set(kFeedActivityKey, GetFeedActivityLevel(pref_service));
    update->Set(kNewUserKey, IsNewUser());
  }

  return pref_service->GetDict(
      prefs::kContentNotificationsEnrollmentEligibility);
}

bool IsPromoEligible(bool user_signed_in,
                     bool default_search_engine,
                     PrefService* pref_service) {
  if (!pref_service) {
    return false;
  }

  if (!IsClientEligible(user_signed_in, default_search_engine)) {
    return false;
  }

  const base::Value::Dict& dict =
      GetUserEnrollmentEligibilityDict(pref_service);
  bool isPriceTrackingEnabled =
      dict.FindBool(kPriceTrackingNotificationEnabledKey).value_or(false);
  int activity = dict.FindInt(kFeedActivityKey).value_or(0);
  if (isPriceTrackingEnabled || (activity == 0)) {
    // The enrollment eligibility for notificaions top-of-feed promo are:
    // 1. The Price Tracking Notificaitons are disabled.
    // 2. The user has feed activities.
    return false;
  }
  return true;
}

bool IsProvisionalEligible(bool user_signed_in,
                           bool default_search_engine,
                           PrefService* pref_service) {
  if (!pref_service) {
    return false;
  }

  if (!IsClientEligible(user_signed_in, default_search_engine)) {
    return false;
  }

  const base::Value::Dict& dict =
      GetUserEnrollmentEligibilityDict(pref_service);
  bool isPriceTrackingEnabled =
      dict.FindBool(kPriceTrackingNotificationEnabledKey).value_or(false);
  bool isNewUser = dict.FindBool(kNewUserKey).value_or(false);
  if (isPriceTrackingEnabled || isNewUser) {
    // The enrollment eligibility of provisional notificaions are:
    // 1. The Price Tracking Notificaitons are disabled.
    // 2. The user is not new to Chrome.
    return false;
  }
  return true;
}

bool IsSetUpListEligible(bool user_signed_in,
                         bool default_search_engine,
                         PrefService* pref_service) {
  if (!pref_service) {
    return false;
  }

  if (!IsClientEligible(user_signed_in, default_search_engine)) {
    return false;
  }

  if (!GetUserEnrollmentEligibilityDict(pref_service)
           .FindBool(kNewUserKey)
           .value_or(false)) {
    // The user should be new to Chrome to be eligible to enroll.
    return false;
  }
  return true;
}

void LogHistogramForEligibilityType(ContentNotificationEligibilityType type) {
  base::UmaHistogramEnumeration("ContentNotifications.EligibilityType", type);
}

}  // namespace

bool IsContentNotificationEnabled(ChromeBrowserState* browser_state) {
  if (!browser_state) {
    return false;
  }

  if (!IsContentNotificationExperimentEnabled()) {
    return false;
  }

  AuthenticationService* auth_service =
      AuthenticationServiceFactory::GetForBrowserState(browser_state);
  BOOL user_signed_in = auth_service && auth_service->HasPrimaryIdentity(
                                            signin::ConsentLevel::kSignin);

  const TemplateURL* default_search_url_template =
      ios::TemplateURLServiceFactory::GetForBrowserState(browser_state)
          ->GetDefaultSearchProvider();
  bool default_search_engine = default_search_url_template &&
                               default_search_url_template->prepopulate_id() ==
                                   TemplateURLPrepopulateData::google.id;
  PrefService* pref_service = browser_state->GetPrefs();

  return IsContentNotificationPromoEnabled(
             user_signed_in, default_search_engine, pref_service) ||
         IsContentNotificationProvisionalEnabled(
             user_signed_in, default_search_engine, pref_service) ||
         IsContentNotificationSetUpListEnabled(
             user_signed_in, default_search_engine, pref_service);
}

bool IsContentNotificationRegistered(ChromeBrowserState* browser_state) {
  if (!browser_state) {
    return false;
  }

  if (!IsContentNotificationExperimentEnabled()) {
    return false;
  }

  AuthenticationService* auth_service =
      AuthenticationServiceFactory::GetForBrowserState(browser_state);
  BOOL user_signed_in = auth_service && auth_service->HasPrimaryIdentity(
                                            signin::ConsentLevel::kSignin);

  const TemplateURL* default_search_url_template =
      ios::TemplateURLServiceFactory::GetForBrowserState(browser_state)
          ->GetDefaultSearchProvider();
  bool default_search_engine = default_search_url_template &&
                               default_search_url_template->prepopulate_id() ==
                                   TemplateURLPrepopulateData::google.id;
  PrefService* pref_service = browser_state->GetPrefs();

  return IsContentNotificationPromoRegistered(
             user_signed_in, default_search_engine, pref_service) ||
         IsContentNotificationProvisionalRegistered(
             user_signed_in, default_search_engine, pref_service) ||
         IsContentNotificationSetUpListRegistered(
             user_signed_in, default_search_engine, pref_service);
}

bool IsContentNotificationPromoEnabled(bool user_signed_in,
                                       bool default_search_engine,
                                       PrefService* pref_service) {
  if (IsPromoEligible(user_signed_in, default_search_engine, pref_service) &&
      IsContentPushNotificationsPromoEnabled()) {
    LogHistogramForEligibilityType(
        ContentNotificationEligibilityType::kPromoEnabled);
    return true;
  }
  return false;
}

bool IsContentNotificationProvisionalEnabled(bool user_signed_in,
                                             bool default_search_engine,
                                             PrefService* pref_service) {
  if (user_signed_in && IsContentNotificationProvisionalIgnoreConditions()) {
    return true;
  }

  if (IsProvisionalEligible(user_signed_in, default_search_engine,
                            pref_service) &&
      IsContentPushNotificationsProvisionalEnabled()) {
    LogHistogramForEligibilityType(
        ContentNotificationEligibilityType::kProvisionalEnabled);
    return true;
  }
  return false;
}

bool IsContentNotificationSetUpListEnabled(bool user_signed_in,
                                           bool default_search_engine,
                                           PrefService* pref_service) {
  if (IsSetUpListEligible(user_signed_in, default_search_engine,
                          pref_service) &&
      IsContentPushNotificationsSetUpListEnabled()) {
    LogHistogramForEligibilityType(
        ContentNotificationEligibilityType::kSetUpListEnabled);
    return true;
  }
  return false;
}

bool IsContentNotificationPromoRegistered(bool user_signed_in,
                                          bool default_search_engine,
                                          PrefService* pref_service) {
  if (IsPromoEligible(user_signed_in, default_search_engine, pref_service) &&
      IsContentPushNotificationsPromoRegistrationOnly()) {
    LogHistogramForEligibilityType(
        ContentNotificationEligibilityType::kPromoRegistered);
    return true;
  }
  return false;
}

bool IsContentNotificationProvisionalRegistered(bool user_signed_in,
                                                bool default_search_engine,
                                                PrefService* pref_service) {
  if (IsProvisionalEligible(user_signed_in, default_search_engine,
                            pref_service) &&
      IsContentPushNotificationsProvisionalRegistrationOnly()) {
    LogHistogramForEligibilityType(
        ContentNotificationEligibilityType::kProvisionalRegistered);
    return true;
  }
  return false;
}

bool IsContentNotificationSetUpListRegistered(bool user_signed_in,
                                              bool default_search_engine,
                                              PrefService* pref_service) {
  if (IsSetUpListEligible(user_signed_in, default_search_engine,
                          pref_service) &&
      IsContentPushNotificationsSetUpListRegistrationOnly()) {
    LogHistogramForEligibilityType(
        ContentNotificationEligibilityType::kSetUpListRegistered);
    return true;
  }
  return false;
}