// Copyright 2019 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "components/metrics/demographics/demographic_metrics_provider.h" #include #include "base/feature_list.h" #include "base/metrics/histogram_functions.h" #include "base/notreached.h" #include "build/chromeos_buildflags.h" #include "components/sync/base/features.h" #include "components/sync/service/sync_service.h" #include "components/sync/service/sync_service_utils.h" #include "third_party/metrics_proto/ukm/report.pb.h" namespace metrics { namespace { bool IsValidUploadState(syncer::UploadState upload_state) { switch (upload_state) { case syncer::UploadState::NOT_ACTIVE: return false; case syncer::UploadState::INITIALIZING: // Note that INITIALIZING is considered good enough, because sync is known // to be enabled, and transient errors don't really matter here. case syncer::UploadState::ACTIVE: return true; } NOTREACHED_NORETURN(); } bool CanUploadDemographicsToGoogle(syncer::SyncService* sync_service) { CHECK(sync_service); // PRIORITY_PREFERENCES is the sync datatype used to propagate demographics // information to the client. In its absence, demographics info is unavailable // thus cannot be uploaded. if (!IsValidUploadState(syncer::GetUploadToGoogleState( sync_service, syncer::PRIORITY_PREFERENCES))) { return false; } // Even if GetUploadToGoogleState() reports to be active, the user may be in // transport mode or full-sync (aka sync-the-feature enabled) mode. // If `kReplaceSyncPromosWithSignInPromos` is enabled, then // PRIORITY_PREFERENCES being enabled (which implies the user is signed in) is // enough, and the sync mode doesn't matter. if (base::FeatureList::IsEnabled( syncer::kReplaceSyncPromosWithSignInPromos)) { return true; } // If `kReplaceSyncPromosWithSignInPromos` is NOT enabled, then demographics // may only be uploaded for users who have opted in to Sync. // TODO(crbug.com/40066949): Simplify once IsSyncFeatureEnabled() is deleted // from the codebase. if (sync_service->IsSyncFeatureEnabled()) { return true; } return false; } } // namespace // static BASE_FEATURE(kDemographicMetricsReporting, "DemographicMetricsReporting", base::FEATURE_ENABLED_BY_DEFAULT); DemographicMetricsProvider::DemographicMetricsProvider( std::unique_ptr profile_client, MetricsLogUploader::MetricServiceType metrics_service_type) : profile_client_(std::move(profile_client)), metrics_service_type_(metrics_service_type) { DCHECK(profile_client_); } DemographicMetricsProvider::~DemographicMetricsProvider() {} std::optional DemographicMetricsProvider::ProvideSyncedUserNoisedBirthYearAndGender() { // Skip if feature disabled. if (!base::FeatureList::IsEnabled(kDemographicMetricsReporting)) return std::nullopt; #if !BUILDFLAG(IS_CHROMEOS_ASH) // Skip if not exactly one Profile on disk. Having more than one Profile that // is using the browser can make demographics less relevant. This approach // cannot determine if there is more than 1 distinct user using the Profile. // ChromeOS almost always has more than one profile on disk, so this check // doesn't work. We have a profile selection strategy for ChromeOS, so skip // this check for ChromeOS. // TODO(crbug/1145655): LaCros will behave similarly to desktop Chrome and // reduce the number of profiles on disk to one, so remove these #if guards // after LaCros release. if (profile_client_->GetNumberOfProfilesOnDisk() != 1) { LogUserDemographicsStatusInHistogram( UserDemographicsStatus::kMoreThanOneProfile); return std::nullopt; } #endif // !BUILDFLAG(IS_CHROMEOS_ASH) syncer::SyncService* sync_service = profile_client_->GetSyncService(); // Skip if no sync service. if (!sync_service) { LogUserDemographicsStatusInHistogram( UserDemographicsStatus::kNoSyncService); return std::nullopt; } if (!CanUploadDemographicsToGoogle(sync_service)) { LogUserDemographicsStatusInHistogram( UserDemographicsStatus::kSyncNotEnabled); return std::nullopt; } UserDemographicsResult demographics_result = GetUserNoisedBirthYearAndGenderFromPrefs( profile_client_->GetNetworkTime(), profile_client_->GetLocalState(), profile_client_->GetProfilePrefs()); LogUserDemographicsStatusInHistogram(demographics_result.status()); if (demographics_result.IsSuccess()) return demographics_result.value(); return std::nullopt; } void DemographicMetricsProvider::ProvideCurrentSessionData( ChromeUserMetricsExtension* uma_proto) { ProvideSyncedUserNoisedBirthYearAndGender(uma_proto); } void DemographicMetricsProvider:: ProvideSyncedUserNoisedBirthYearAndGenderToReport(ukm::Report* report) { ProvideSyncedUserNoisedBirthYearAndGender(report); } void DemographicMetricsProvider::LogUserDemographicsStatusInHistogram( UserDemographicsStatus status) { switch (metrics_service_type_) { case MetricsLogUploader::MetricServiceType::UMA: base::UmaHistogramEnumeration("UMA.UserDemographics.Status", status); // If the user demographics data was retrieved successfully, then the user // must be between the ages of |kUserDemographicsMinAgeInYears|+1=21 and // |kUserDemographicsMaxAgeInYears|=85, so the user is not a minor. base::UmaHistogramBoolean("UMA.UserDemographics.IsNoisedAgeOver21Under85", status == UserDemographicsStatus::kSuccess); return; case MetricsLogUploader::MetricServiceType::UKM: // UKM Metrics doesn't have demographic metrics. return; case MetricsLogUploader::MetricServiceType::STRUCTURED_METRICS: // Structured Metrics doesn't have demographic metrics. return; } NOTREACHED(); } } // namespace metrics