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