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