1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/metrics/demographics/demographic_metrics_provider.h"
6
7 #include <optional>
8
9 #include "base/feature_list.h"
10 #include "base/metrics/histogram_functions.h"
11 #include "base/notreached.h"
12 #include "build/chromeos_buildflags.h"
13 #include "components/sync/base/features.h"
14 #include "components/sync/service/sync_service.h"
15 #include "components/sync/service/sync_service_utils.h"
16 #include "third_party/metrics_proto/ukm/report.pb.h"
17
18 namespace metrics {
19
20 namespace {
21
IsValidUploadState(syncer::UploadState upload_state)22 bool IsValidUploadState(syncer::UploadState upload_state) {
23 switch (upload_state) {
24 case syncer::UploadState::NOT_ACTIVE:
25 return false;
26 case syncer::UploadState::INITIALIZING:
27 // Note that INITIALIZING is considered good enough, because sync is known
28 // to be enabled, and transient errors don't really matter here.
29 case syncer::UploadState::ACTIVE:
30 return true;
31 }
32 NOTREACHED_NORETURN();
33 }
34
CanUploadDemographicsToGoogle(syncer::SyncService * sync_service)35 bool CanUploadDemographicsToGoogle(syncer::SyncService* sync_service) {
36 CHECK(sync_service);
37
38 // PRIORITY_PREFERENCES is the sync datatype used to propagate demographics
39 // information to the client. In its absence, demographics info is unavailable
40 // thus cannot be uploaded.
41 if (!IsValidUploadState(syncer::GetUploadToGoogleState(
42 sync_service, syncer::PRIORITY_PREFERENCES))) {
43 return false;
44 }
45
46 // Even if GetUploadToGoogleState() reports to be active, the user may be in
47 // transport mode or full-sync (aka sync-the-feature enabled) mode.
48 // If `kReplaceSyncPromosWithSignInPromos` is enabled, then
49 // PRIORITY_PREFERENCES being enabled (which implies the user is signed in) is
50 // enough, and the sync mode doesn't matter.
51 if (base::FeatureList::IsEnabled(
52 syncer::kReplaceSyncPromosWithSignInPromos)) {
53 return true;
54 }
55
56 // If `kReplaceSyncPromosWithSignInPromos` is NOT enabled, then demographics
57 // may only be uploaded for users who have opted in to Sync.
58 // TODO(crbug.com/40066949): Simplify once IsSyncFeatureEnabled() is deleted
59 // from the codebase.
60 if (sync_service->IsSyncFeatureEnabled()) {
61 return true;
62 }
63
64 return false;
65 }
66
67 } // namespace
68
69 // static
70 BASE_FEATURE(kDemographicMetricsReporting,
71 "DemographicMetricsReporting",
72 base::FEATURE_ENABLED_BY_DEFAULT);
73
DemographicMetricsProvider(std::unique_ptr<ProfileClient> profile_client,MetricsLogUploader::MetricServiceType metrics_service_type)74 DemographicMetricsProvider::DemographicMetricsProvider(
75 std::unique_ptr<ProfileClient> profile_client,
76 MetricsLogUploader::MetricServiceType metrics_service_type)
77 : profile_client_(std::move(profile_client)),
78 metrics_service_type_(metrics_service_type) {
79 DCHECK(profile_client_);
80 }
81
~DemographicMetricsProvider()82 DemographicMetricsProvider::~DemographicMetricsProvider() {}
83
84 std::optional<UserDemographics>
ProvideSyncedUserNoisedBirthYearAndGender()85 DemographicMetricsProvider::ProvideSyncedUserNoisedBirthYearAndGender() {
86 // Skip if feature disabled.
87 if (!base::FeatureList::IsEnabled(kDemographicMetricsReporting))
88 return std::nullopt;
89
90 #if !BUILDFLAG(IS_CHROMEOS_ASH)
91 // Skip if not exactly one Profile on disk. Having more than one Profile that
92 // is using the browser can make demographics less relevant. This approach
93 // cannot determine if there is more than 1 distinct user using the Profile.
94
95 // ChromeOS almost always has more than one profile on disk, so this check
96 // doesn't work. We have a profile selection strategy for ChromeOS, so skip
97 // this check for ChromeOS.
98 // TODO(crbug/1145655): LaCros will behave similarly to desktop Chrome and
99 // reduce the number of profiles on disk to one, so remove these #if guards
100 // after LaCros release.
101 if (profile_client_->GetNumberOfProfilesOnDisk() != 1) {
102 LogUserDemographicsStatusInHistogram(
103 UserDemographicsStatus::kMoreThanOneProfile);
104 return std::nullopt;
105 }
106 #endif // !BUILDFLAG(IS_CHROMEOS_ASH)
107
108 syncer::SyncService* sync_service = profile_client_->GetSyncService();
109 // Skip if no sync service.
110 if (!sync_service) {
111 LogUserDemographicsStatusInHistogram(
112 UserDemographicsStatus::kNoSyncService);
113 return std::nullopt;
114 }
115
116 if (!CanUploadDemographicsToGoogle(sync_service)) {
117 LogUserDemographicsStatusInHistogram(
118 UserDemographicsStatus::kSyncNotEnabled);
119 return std::nullopt;
120 }
121
122 UserDemographicsResult demographics_result =
123 GetUserNoisedBirthYearAndGenderFromPrefs(
124 profile_client_->GetNetworkTime(), profile_client_->GetLocalState(),
125 profile_client_->GetProfilePrefs());
126 LogUserDemographicsStatusInHistogram(demographics_result.status());
127
128 if (demographics_result.IsSuccess())
129 return demographics_result.value();
130
131 return std::nullopt;
132 }
133
ProvideCurrentSessionData(ChromeUserMetricsExtension * uma_proto)134 void DemographicMetricsProvider::ProvideCurrentSessionData(
135 ChromeUserMetricsExtension* uma_proto) {
136 ProvideSyncedUserNoisedBirthYearAndGender(uma_proto);
137 }
138
139 void DemographicMetricsProvider::
ProvideSyncedUserNoisedBirthYearAndGenderToReport(ukm::Report * report)140 ProvideSyncedUserNoisedBirthYearAndGenderToReport(ukm::Report* report) {
141 ProvideSyncedUserNoisedBirthYearAndGender(report);
142 }
143
LogUserDemographicsStatusInHistogram(UserDemographicsStatus status)144 void DemographicMetricsProvider::LogUserDemographicsStatusInHistogram(
145 UserDemographicsStatus status) {
146 switch (metrics_service_type_) {
147 case MetricsLogUploader::MetricServiceType::UMA:
148 base::UmaHistogramEnumeration("UMA.UserDemographics.Status", status);
149 // If the user demographics data was retrieved successfully, then the user
150 // must be between the ages of |kUserDemographicsMinAgeInYears|+1=21 and
151 // |kUserDemographicsMaxAgeInYears|=85, so the user is not a minor.
152 base::UmaHistogramBoolean("UMA.UserDemographics.IsNoisedAgeOver21Under85",
153 status == UserDemographicsStatus::kSuccess);
154 return;
155 case MetricsLogUploader::MetricServiceType::UKM:
156 // UKM Metrics doesn't have demographic metrics.
157 return;
158 case MetricsLogUploader::MetricServiceType::STRUCTURED_METRICS:
159 // Structured Metrics doesn't have demographic metrics.
160 return;
161 }
162 NOTREACHED();
163 }
164
165 } // namespace metrics
166