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