• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/user_demographics.h"
6 
7 #include <optional>
8 #include <utility>
9 
10 #include "base/check.h"
11 #include "base/rand_util.h"
12 #include "base/values.h"
13 #include "build/build_config.h"
14 #include "components/pref_registry/pref_registry_syncable.h"
15 #include "components/prefs/pref_service.h"
16 
17 namespace metrics {
18 
19 #if !BUILDFLAG(IS_CHROMEOS_ASH)
20 constexpr auto kSyncDemographicsPrefFlags =
21     user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF;
22 #else
23 constexpr auto kSyncOsDemographicsPrefFlags =
24     user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PRIORITY_PREF;
25 // TODO(crbug.com/40240008): Make this non-syncable (on Ash only) after full
26 // rollout of the syncable os priority pref; then delete it locally from Ash
27 // devices.
28 constexpr auto kSyncDemographicsPrefFlags =
29     user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF;
30 #endif
31 
32 constexpr auto kUserDemographicsBirthYearOffsetPrefFlags =
33     PrefRegistry::NO_REGISTRATION_FLAGS;
34 constexpr auto kDeprecatedDemographicsBirthYearOffsetPrefFlags =
35     PrefRegistry::NO_REGISTRATION_FLAGS;
36 
37 namespace {
38 
GetDemographicsDict(PrefService * profile_prefs)39 const base::Value::Dict& GetDemographicsDict(PrefService* profile_prefs) {
40 #if BUILDFLAG(IS_CHROMEOS_ASH)
41   // TODO(crbug.com/40240008): On Ash only, clear sync demographics pref once
42   // os-level syncable pref is fully rolled out and Ash drops support for
43   // non-os-level syncable prefs.
44   if (profile_prefs->HasPrefPath(kSyncOsDemographicsPrefName)) {
45     return profile_prefs->GetDict(kSyncOsDemographicsPrefName);
46   }
47 #endif
48   return profile_prefs->GetDict(kSyncDemographicsPrefName);
49 }
50 
MigrateBirthYearOffset(PrefService * to_local_state,PrefService * from_profile_prefs)51 void MigrateBirthYearOffset(PrefService* to_local_state,
52                             PrefService* from_profile_prefs) {
53   const int profile_offset = from_profile_prefs->GetInteger(
54       kDeprecatedDemographicsBirthYearOffsetPrefName);
55   if (profile_offset == kUserDemographicsBirthYearNoiseOffsetDefaultValue)
56     return;
57 
58   // TODO(crbug.com/40240008): clear/remove deprecated pref after 2023/09
59 
60   const int local_offset =
61       to_local_state->GetInteger(kUserDemographicsBirthYearOffsetPrefName);
62   if (local_offset == kUserDemographicsBirthYearNoiseOffsetDefaultValue) {
63     to_local_state->SetInteger(kUserDemographicsBirthYearOffsetPrefName,
64                                profile_offset);
65   }
66 }
67 
68 // Returns the noise offset for the birth year. If not found in |local_state|,
69 // the offset will be randomly generated within the offset range and cached in
70 // |local_state|.
GetBirthYearOffset(PrefService * local_state)71 int GetBirthYearOffset(PrefService* local_state) {
72   int offset =
73       local_state->GetInteger(kUserDemographicsBirthYearOffsetPrefName);
74   if (offset == kUserDemographicsBirthYearNoiseOffsetDefaultValue) {
75     // Generate a new random offset when not already cached.
76     offset = base::RandInt(-kUserDemographicsBirthYearNoiseOffsetRange,
77                            kUserDemographicsBirthYearNoiseOffsetRange);
78     local_state->SetInteger(kUserDemographicsBirthYearOffsetPrefName, offset);
79   }
80   return offset;
81 }
82 
83 // Determines whether the synced user has provided a birth year to Google which
84 // is eligible, once aggregated and anonymized, to measure usage of Chrome
85 // features by age groups. See doc of DemographicMetricsProvider in
86 // demographic_metrics_provider.h for more details.
HasEligibleBirthYear(base::Time now,int user_birth_year,int offset)87 bool HasEligibleBirthYear(base::Time now, int user_birth_year, int offset) {
88   // Compute user age.
89   base::Time::Exploded exploded_now_time;
90   now.LocalExplode(&exploded_now_time);
91   int user_age = exploded_now_time.year - (user_birth_year + offset);
92 
93   // Verify if the synced user's age has a population size in the age
94   // distribution of the society that is big enough to not raise the entropy of
95   // the demographics too much. At a certain point, as the age increase, the
96   // size of the population starts declining sharply as you can see in this
97   // approximate representation of the age distribution:
98   // |       ________         max age
99   // |______/        \_________ |
100   // |                          |\
101   // |                          | \
102   // +--------------------------|---------
103   //  0 10 20 30 40 50 60 70 80 90 100+
104   if (user_age > kUserDemographicsMaxAgeInYears)
105     return false;
106 
107   // Verify if the synced user is old enough. Use > rather than >= because we
108   // want to be sure that the user is at least |kUserDemographicsMinAgeInYears|
109   // without disclosing their birth date, which requires to add an extra year
110   // margin to the minimal age to be safe. For example, if we are in 2019-07-10
111   // (now) and the user was born in 1999-08-10, the user is not yet 20 years old
112   // (minimal age) but we cannot know that because we only have access to the
113   // year of the dates (2019 and 1999 respectively). If we make sure that the
114   // minimal age (computed at year granularity) is at least 21, we are 100% sure
115   // that the user will be at least 20 years old when providing the user’s birth
116   // year and gender.
117   return user_age > kUserDemographicsMinAgeInYears;
118 }
119 
120 // Gets the synced user's birth year from synced prefs, see doc of
121 // DemographicMetricsProvider in demographic_metrics_provider.h for more
122 // details.
GetUserBirthYear(const base::Value::Dict & demographics)123 std::optional<int> GetUserBirthYear(const base::Value::Dict& demographics) {
124   return demographics.FindInt(kSyncDemographicsBirthYearPath);
125 }
126 
127 // Gets the synced user's gender from synced prefs, see doc of
128 // DemographicMetricsProvider in demographic_metrics_provider.h for more
129 // details.
GetUserGender(const base::Value::Dict & demographics)130 std::optional<UserDemographicsProto_Gender> GetUserGender(
131     const base::Value::Dict& demographics) {
132   const std::optional<int> gender_int =
133       demographics.FindInt(kSyncDemographicsGenderPath);
134 
135   // Verify that the gender is unset.
136   if (!gender_int)
137     return std::nullopt;
138 
139   // Verify that the gender number is a valid UserDemographicsProto_Gender
140   // encoding.
141   if (!UserDemographicsProto_Gender_IsValid(*gender_int))
142     return std::nullopt;
143 
144   const auto gender = UserDemographicsProto_Gender(*gender_int);
145 
146   // Verify that the gender is in a large enough population set to preserve
147   // anonymity.
148   if (gender != UserDemographicsProto::GENDER_FEMALE &&
149       gender != UserDemographicsProto::GENDER_MALE) {
150     return std::nullopt;
151   }
152 
153   return gender;
154 }
155 
156 }  // namespace
157 
158 // static
ForValue(UserDemographics value)159 UserDemographicsResult UserDemographicsResult::ForValue(
160     UserDemographics value) {
161   return UserDemographicsResult(std::move(value),
162                                 UserDemographicsStatus::kSuccess);
163 }
164 
165 // static
ForStatus(UserDemographicsStatus status)166 UserDemographicsResult UserDemographicsResult::ForStatus(
167     UserDemographicsStatus status) {
168   DCHECK(status != UserDemographicsStatus::kSuccess);
169   return UserDemographicsResult(UserDemographics(), status);
170 }
171 
IsSuccess() const172 bool UserDemographicsResult::IsSuccess() const {
173   return status_ == UserDemographicsStatus::kSuccess;
174 }
175 
status() const176 UserDemographicsStatus UserDemographicsResult::status() const {
177   return status_;
178 }
179 
value() const180 const UserDemographics& UserDemographicsResult::value() const {
181   return value_;
182 }
183 
UserDemographicsResult(UserDemographics value,UserDemographicsStatus status)184 UserDemographicsResult::UserDemographicsResult(UserDemographics value,
185                                                UserDemographicsStatus status)
186     : value_(std::move(value)), status_(status) {}
187 
RegisterDemographicsLocalStatePrefs(PrefRegistrySimple * registry)188 void RegisterDemographicsLocalStatePrefs(PrefRegistrySimple* registry) {
189   registry->RegisterIntegerPref(
190       kUserDemographicsBirthYearOffsetPrefName,
191       kUserDemographicsBirthYearNoiseOffsetDefaultValue,
192       kUserDemographicsBirthYearOffsetPrefFlags);
193 }
194 
RegisterDemographicsProfilePrefs(PrefRegistrySimple * registry)195 void RegisterDemographicsProfilePrefs(PrefRegistrySimple* registry) {
196 #if BUILDFLAG(IS_CHROMEOS_ASH)
197   registry->RegisterDictionaryPref(kSyncOsDemographicsPrefName,
198                                    kSyncOsDemographicsPrefFlags);
199 #endif
200   registry->RegisterDictionaryPref(kSyncDemographicsPrefName,
201                                    kSyncDemographicsPrefFlags);
202   registry->RegisterIntegerPref(
203       kDeprecatedDemographicsBirthYearOffsetPrefName,
204       kUserDemographicsBirthYearNoiseOffsetDefaultValue,
205       kDeprecatedDemographicsBirthYearOffsetPrefFlags);
206 }
207 
ClearDemographicsPrefs(PrefService * profile_prefs)208 void ClearDemographicsPrefs(PrefService* profile_prefs) {
209   // Clear the dict holding the user's birth year and gender.
210   //
211   // Note: We never clear kUserDemographicsBirthYearOffset from local state.
212   // The device should continue to use the *same* noise value as long as the
213   // device's UMA client id remains the same. If the noise value were allowed
214   // to change for a given user + client id, then the min/max noisy birth year
215   // values could both be reported, revealing the true value in the middle.
216   profile_prefs->ClearPref(kSyncDemographicsPrefName);
217 #if BUILDFLAG(IS_CHROMEOS_ASH)
218   profile_prefs->ClearPref(kSyncOsDemographicsPrefName);
219 #endif
220 }
221 
GetUserNoisedBirthYearAndGenderFromPrefs(base::Time now,PrefService * local_state,PrefService * profile_prefs)222 UserDemographicsResult GetUserNoisedBirthYearAndGenderFromPrefs(
223     base::Time now,
224     PrefService* local_state,
225     PrefService* profile_prefs) {
226   // Verify that the now time is available. There are situations where the now
227   // time cannot be provided.
228   if (now.is_null()) {
229     return UserDemographicsResult::ForStatus(
230         UserDemographicsStatus::kCannotGetTime);
231   }
232 
233   // Get the synced user’s noised birth year and gender from synced profile
234   // prefs. Only one error status code should be used to represent the case
235   // where demographics are ineligible, see doc of UserDemographicsStatus in
236   // user_demographics.h for more details.
237 
238   // Get the pref that contains the user's birth year and gender.
239   const base::Value::Dict& demographics = GetDemographicsDict(profile_prefs);
240 
241   // Get the user's birth year.
242   std::optional<int> birth_year = GetUserBirthYear(demographics);
243   if (!birth_year.has_value()) {
244     return UserDemographicsResult::ForStatus(
245         UserDemographicsStatus::kIneligibleDemographicsData);
246   }
247 
248   // Get the user's gender.
249   std::optional<UserDemographicsProto_Gender> gender =
250       GetUserGender(demographics);
251   if (!gender.has_value()) {
252     return UserDemographicsResult::ForStatus(
253         UserDemographicsStatus::kIneligibleDemographicsData);
254   }
255 
256   // Get the offset from local_state/profile_prefs and do one last check that
257   // the birth year is eligible.
258   // TODO(crbug.com/40240008): remove profile_prefs after 2023/09
259   MigrateBirthYearOffset(local_state, profile_prefs);
260   int offset = GetBirthYearOffset(local_state);
261   if (!HasEligibleBirthYear(now, *birth_year, offset)) {
262     return UserDemographicsResult::ForStatus(
263         UserDemographicsStatus::kIneligibleDemographicsData);
264   }
265 
266   // Set gender and noised birth year in demographics.
267   UserDemographics user_demographics;
268   user_demographics.gender = *gender;
269   user_demographics.birth_year = *birth_year + offset;
270 
271   return UserDemographicsResult::ForValue(std::move(user_demographics));
272 }
273 
274 }  // namespace metrics
275