• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 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/metrics_state_manager.h"
6 
7 #include <cstddef>
8 #include <cstdint>
9 #include <limits>
10 #include <memory>
11 #include <random>
12 #include <string>
13 #include <string_view>
14 #include <tuple>
15 #include <utility>
16 
17 #include "base/base_switches.h"
18 #include "base/check.h"
19 #include "base/command_line.h"
20 #include "base/debug/leak_annotations.h"
21 #include "base/functional/callback_helpers.h"
22 #include "base/memory/raw_ptr.h"
23 #include "base/memory/raw_ref.h"
24 #include "base/metrics/histogram_functions.h"
25 #include "base/metrics/histogram_macros.h"
26 #include "base/numerics/safe_conversions.h"
27 #include "base/rand_util.h"
28 #include "base/strings/string_number_conversions.h"
29 #include "base/strings/stringprintf.h"
30 #include "base/threading/thread_restrictions.h"
31 #include "base/time/time.h"
32 #include "base/uuid.h"
33 #include "build/branding_buildflags.h"
34 #include "build/build_config.h"
35 #include "components/metrics/cloned_install_detector.h"
36 #include "components/metrics/enabled_state_provider.h"
37 #include "components/metrics/entropy_state.h"
38 #include "components/metrics/metrics_data_validation.h"
39 #include "components/metrics/metrics_log.h"
40 #include "components/metrics/metrics_pref_names.h"
41 #include "components/metrics/metrics_provider.h"
42 #include "components/metrics/metrics_switches.h"
43 #include "components/prefs/pref_registry_simple.h"
44 #include "components/prefs/pref_service.h"
45 #include "components/variations/entropy_provider.h"
46 #include "components/variations/field_trial_config/field_trial_util.h"
47 #include "components/variations/pref_names.h"
48 #include "components/variations/variations_switches.h"
49 #include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
50 #include "third_party/metrics_proto/system_profile.pb.h"
51 
52 namespace metrics {
53 namespace {
54 
ReadEnabledDate(PrefService * local_state)55 int64_t ReadEnabledDate(PrefService* local_state) {
56   return local_state->GetInt64(prefs::kMetricsReportingEnabledTimestamp);
57 }
58 
ReadInstallDate(PrefService * local_state)59 int64_t ReadInstallDate(PrefService* local_state) {
60   return local_state->GetInt64(prefs::kInstallDate);
61 }
62 
ReadClientId(PrefService * local_state)63 std::string ReadClientId(PrefService* local_state) {
64   return local_state->GetString(prefs::kMetricsClientID);
65 }
66 
67 // Round a timestamp measured in seconds since epoch to one with a granularity
68 // of an hour. This can be used before uploaded potentially sensitive
69 // timestamps.
RoundSecondsToHour(int64_t time_in_seconds)70 int64_t RoundSecondsToHour(int64_t time_in_seconds) {
71   return 3600 * (time_in_seconds / 3600);
72 }
73 
74 // Records the cloned install histogram.
LogClonedInstall()75 void LogClonedInstall() {
76   // Equivalent to UMA_HISTOGRAM_BOOLEAN with the stability flag set.
77   UMA_STABILITY_HISTOGRAM_ENUMERATION("UMA.IsClonedInstall", 1, 2);
78 }
79 
80 // No-op function used to create a MetricsStateManager.
NoOpLoadClientInfoBackup()81 std::unique_ptr<metrics::ClientInfo> NoOpLoadClientInfoBackup() {
82   return nullptr;
83 }
84 
85 // Exits the browser with a helpful error message if an invalid,
86 // field-trial-related command-line flag was specified.
ExitWithMessage(const std::string & message)87 void ExitWithMessage(const std::string& message) {
88   puts(message.c_str());
89   exit(1);
90 }
91 
92 // Returns a log normal distribution based on the feature params of
93 // |kNonUniformityValidationFeature|.
GetLogNormalDist()94 std::lognormal_distribution<double> GetLogNormalDist() {
95   double mean = kLogNormalMean.Get();
96   double delta = kLogNormalDelta.Get();
97   double std_dev = kLogNormalStdDev.Get();
98   return std::lognormal_distribution<double>(mean + std::log(1.0 + delta),
99                                              std_dev);
100 }
101 
102 // Used to draw a data point from a log normal distribution.
103 struct LogNormalMetricState {
LogNormalMetricStatemetrics::__anon13ddfdc70111::LogNormalMetricState104   LogNormalMetricState()
105       : dist(GetLogNormalDist()), gen(std::mt19937(base::RandUint64())) {}
106 
107   // Records the artificial non-uniformity histogram for data validation.
LogArtificialNonUniformitymetrics::__anon13ddfdc70111::LogNormalMetricState108   void LogArtificialNonUniformity() {
109     double rand = dist(gen);
110     // We pick 10k as the upper bound for this histogram so as to avoid losing
111     // precision. See comments for |kLogNormalMean|.
112     base::UmaHistogramCounts10000("UMA.DataValidation.LogNormal",
113                                   base::saturated_cast<int>(rand));
114   }
115 
116   // A log normal distribution generator generated by the `GetLogNormalDist()`
117   // function.
118   std::lognormal_distribution<double> dist;
119   // The pseudo-random generator used to generate a data point from |dist|.
120   std::mt19937 gen;
121 };
122 
123 class MetricsStateMetricsProvider : public MetricsProvider {
124  public:
MetricsStateMetricsProvider(PrefService * local_state,bool metrics_ids_were_reset,std::string previous_client_id,std::string initial_client_id,ClonedInstallDetector const & cloned_install_detector)125   MetricsStateMetricsProvider(
126       PrefService* local_state,
127       bool metrics_ids_were_reset,
128       std::string previous_client_id,
129       std::string initial_client_id,
130       ClonedInstallDetector const& cloned_install_detector)
131       : local_state_(local_state),
132         metrics_ids_were_reset_(metrics_ids_were_reset),
133         previous_client_id_(std::move(previous_client_id)),
134         initial_client_id_(std::move(initial_client_id)),
135         cloned_install_detector_(cloned_install_detector) {}
136 
137   MetricsStateMetricsProvider(const MetricsStateMetricsProvider&) = delete;
138   MetricsStateMetricsProvider& operator=(const MetricsStateMetricsProvider&) =
139       delete;
140 
141   // MetricsProvider:
ProvideSystemProfileMetrics(SystemProfileProto * system_profile)142   void ProvideSystemProfileMetrics(
143       SystemProfileProto* system_profile) override {
144     system_profile->set_uma_enabled_date(
145         RoundSecondsToHour(ReadEnabledDate(local_state_)));
146     system_profile->set_install_date(
147         RoundSecondsToHour(ReadInstallDate(local_state_)));
148 
149     // Client id in the log shouldn't be different than the |local_state_| one
150     // except when the client disabled UMA before we populate this field to the
151     // log. If that's the case, the client id in the |local_state_| should be
152     // empty and we should set |client_id_was_used_for_trial_assignment| to
153     // false.
154     std::string client_id = ReadClientId(local_state_);
155     system_profile->set_client_id_was_used_for_trial_assignment(
156         !client_id.empty() && client_id == initial_client_id_);
157 
158     ClonedInstallInfo cloned =
159         ClonedInstallDetector::ReadClonedInstallInfo(local_state_);
160     if (cloned.reset_count == 0)
161       return;
162     auto* cloned_install_info = system_profile->mutable_cloned_install_info();
163     if (metrics_ids_were_reset_) {
164       // Only report the cloned from client_id in the resetting session.
165       if (!previous_client_id_.empty()) {
166         cloned_install_info->set_cloned_from_client_id(
167             MetricsLog::Hash(previous_client_id_));
168       }
169     }
170     cloned_install_info->set_last_timestamp(
171         RoundSecondsToHour(cloned.last_reset_timestamp));
172     cloned_install_info->set_first_timestamp(
173         RoundSecondsToHour(cloned.first_reset_timestamp));
174     cloned_install_info->set_count(cloned.reset_count);
175   }
176 
ProvidePreviousSessionData(ChromeUserMetricsExtension * uma_proto)177   void ProvidePreviousSessionData(
178       ChromeUserMetricsExtension* uma_proto) override {
179     if (metrics_ids_were_reset_) {
180       LogClonedInstall();
181       if (!previous_client_id_.empty()) {
182         // NOTE: If you are adding anything here, consider also changing
183         // FileMetricsProvider::ProvideIndependentMetricsOnTaskRunner().
184 
185         // If we know the previous client id, overwrite the client id for the
186         // previous session log so the log contains the client id at the time
187         // of the previous session. This allows better attribution of crashes
188         // to earlier behavior. If the previous client id is unknown, leave
189         // the current client id.
190         uma_proto->set_client_id(MetricsLog::Hash(previous_client_id_));
191       }
192     }
193   }
194 
ProvideCurrentSessionData(ChromeUserMetricsExtension * uma_proto)195   void ProvideCurrentSessionData(
196       ChromeUserMetricsExtension* uma_proto) override {
197     if (cloned_install_detector_->ClonedInstallDetectedInCurrentSession()) {
198       LogClonedInstall();
199     }
200     log_normal_metric_state_.LogArtificialNonUniformity();
201   }
202 
203   // Set a random seed for the random number generator.
SetRandomSeedForTesting(int64_t seed)204   void SetRandomSeedForTesting(int64_t seed) {
205     log_normal_metric_state_.gen = std::mt19937(seed);
206   }
207 
208  private:
209   const raw_ptr<PrefService> local_state_;
210   const bool metrics_ids_were_reset_;
211   // |previous_client_id_| is set only (if known) when
212   // |metrics_ids_were_reset_|
213   const std::string previous_client_id_;
214   // The client id that was used to randomize field trials. An empty string if
215   // the low entropy source was used to do randomization.
216   const std::string initial_client_id_;
217   const raw_ref<const ClonedInstallDetector> cloned_install_detector_;
218   LogNormalMetricState log_normal_metric_state_;
219 };
220 
ShouldEnableBenchmarking(bool force_benchmarking_mode)221 bool ShouldEnableBenchmarking(bool force_benchmarking_mode) {
222   // TODO(crbug.com/40792683): See whether it's possible to consolidate the
223   // switches.
224   return force_benchmarking_mode ||
225          base::CommandLine::ForCurrentProcess()->HasSwitch(
226              variations::switches::kEnableBenchmarking);
227 }
228 
229 }  // namespace
230 
231 // static
232 bool MetricsStateManager::instance_exists_ = false;
233 
234 // static
235 bool MetricsStateManager::enable_provisional_client_id_for_testing_ = false;
236 
MetricsStateManager(PrefService * local_state,EnabledStateProvider * enabled_state_provider,const std::wstring & backup_registry_key,const base::FilePath & user_data_dir,EntropyParams entropy_params,StartupVisibility startup_visibility,StoreClientInfoCallback store_client_info,LoadClientInfoCallback retrieve_client_info,std::string_view external_client_id)237 MetricsStateManager::MetricsStateManager(
238     PrefService* local_state,
239     EnabledStateProvider* enabled_state_provider,
240     const std::wstring& backup_registry_key,
241     const base::FilePath& user_data_dir,
242     EntropyParams entropy_params,
243     StartupVisibility startup_visibility,
244     StoreClientInfoCallback store_client_info,
245     LoadClientInfoCallback retrieve_client_info,
246     std::string_view external_client_id)
247     : local_state_(local_state),
248       enabled_state_provider_(enabled_state_provider),
249       entropy_params_(entropy_params),
250       store_client_info_(std::move(store_client_info)),
251       load_client_info_(std::move(retrieve_client_info)),
252       clean_exit_beacon_(backup_registry_key, user_data_dir, local_state),
253       external_client_id_(external_client_id),
254       entropy_state_(local_state),
255       entropy_source_returned_(ENTROPY_SOURCE_NONE),
256       metrics_ids_were_reset_(false),
257       startup_visibility_(startup_visibility) {
258   DCHECK(!store_client_info_.is_null());
259   DCHECK(!load_client_info_.is_null());
260   ResetMetricsIDsIfNecessary();
261 
262   [[maybe_unused]] bool is_first_run = false;
263   int64_t install_date = local_state_->GetInt64(prefs::kInstallDate);
264 
265   // Set the install date if this is our first run.
266   if (install_date == 0) {
267     local_state_->SetInt64(prefs::kInstallDate, base::Time::Now().ToTimeT());
268     is_first_run = true;
269   }
270 
271   if (enabled_state_provider_->IsConsentGiven()) {
272     ForceClientIdCreation();
273   } else {
274 #if BUILDFLAG(IS_ANDROID)
275     // If on start up we determine that the client has not given their consent
276     // to report their metrics, the new sampling trial should be used to
277     // determine whether the client is sampled in or out (if the user ever
278     // enables metrics reporting). This covers users that are going through
279     // the first run, as well as users that have metrics reporting disabled.
280     //
281     // See crbug/1306481 and the comment above |kUsePostFREFixSamplingTrial| in
282     // components/metrics/metrics_pref_names.cc for more details.
283     local_state_->SetBoolean(metrics::prefs::kUsePostFREFixSamplingTrial, true);
284 #endif  // BUILDFLAG(IS_ANDROID)
285   }
286 
287   // Generate and store a provisional client ID if necessary. This ID will be
288   // used for field trial randomization on first run (and possibly in future
289   // runs if the user closes Chrome during the FRE) and will be promoted to
290   // become the client ID if UMA is enabled during this session, via the logic
291   // in ForceClientIdCreation(). If UMA is disabled (refused), we discard it.
292   //
293   // Note: This means that if a provisional client ID is used for this session,
294   // and the user disables (refuses) UMA, then starting from the next run, the
295   // field trial randomization (group assignment) will be different.
296   if (ShouldGenerateProvisionalClientId(is_first_run)) {
297     local_state_->SetString(prefs::kMetricsProvisionalClientID,
298                             base::Uuid::GenerateRandomV4().AsLowercaseString());
299   }
300 
301   // `initial_client_id_` will only be set in the following cases:
302   // 1. UMA is enabled
303   // 2. there is a provisional client id (due to this being a first run)
304   // 3. there is an externally provided client ID (e.g. in Lacros, from Ash)
305   if (!client_id_.empty()) {
306     initial_client_id_ = client_id_;
307   } else if (!external_client_id_.empty()) {
308     // Typically, `client_id_` should have been set to the external client ID in
309     // the call to ForceClientIdCreation() above. However, that call is gated,
310     // and may not always happen, for example if this is a first run and the
311     // consent state is not yet known (although we know it is soon going to be
312     // set to true, since an external client ID was provided).
313     initial_client_id_ = external_client_id_;
314   } else {
315     // Note that there is possibly no provisional client ID.
316     initial_client_id_ =
317         local_state_->GetString(prefs::kMetricsProvisionalClientID);
318   }
319   CHECK(!instance_exists_);
320   instance_exists_ = true;
321 }
322 
~MetricsStateManager()323 MetricsStateManager::~MetricsStateManager() {
324   CHECK(instance_exists_);
325   instance_exists_ = false;
326 }
327 
GetProvider()328 std::unique_ptr<MetricsProvider> MetricsStateManager::GetProvider() {
329   return std::make_unique<MetricsStateMetricsProvider>(
330       local_state_, metrics_ids_were_reset_, previous_client_id_,
331       initial_client_id_, cloned_install_detector_);
332 }
333 
334 std::unique_ptr<MetricsProvider>
GetProviderAndSetRandomSeedForTesting(int64_t seed)335 MetricsStateManager::GetProviderAndSetRandomSeedForTesting(int64_t seed) {
336   auto provider = std::make_unique<MetricsStateMetricsProvider>(
337       local_state_, metrics_ids_were_reset_, previous_client_id_,
338       initial_client_id_, cloned_install_detector_);
339   provider->SetRandomSeedForTesting(seed);  // IN-TEST
340   return provider;
341 }
342 
IsMetricsReportingEnabled()343 bool MetricsStateManager::IsMetricsReportingEnabled() {
344   return enabled_state_provider_->IsReportingEnabled();
345 }
346 
IsExtendedSafeModeSupported() const347 bool MetricsStateManager::IsExtendedSafeModeSupported() const {
348   return clean_exit_beacon_.IsExtendedSafeModeSupported();
349 }
350 
GetLowEntropySource()351 int MetricsStateManager::GetLowEntropySource() {
352   return entropy_state_.GetLowEntropySource();
353 }
354 
GetOldLowEntropySource()355 int MetricsStateManager::GetOldLowEntropySource() {
356   return entropy_state_.GetOldLowEntropySource();
357 }
358 
GetPseudoLowEntropySource()359 int MetricsStateManager::GetPseudoLowEntropySource() {
360   return entropy_state_.GetPseudoLowEntropySource();
361 }
362 
InstantiateFieldTrialList()363 void MetricsStateManager::InstantiateFieldTrialList() {
364   // Instantiate the FieldTrialList to support field trials. If an instance
365   // already exists, this is likely a test scenario with a ScopedFeatureList, so
366   // use the existing instance so that any overrides are still applied.
367   if (!base::FieldTrialList::GetInstance()) {
368     // This is intentionally leaked since it needs to live for the duration of
369     // the browser process and there's no benefit in cleaning it up at exit.
370     base::FieldTrialList* leaked_field_trial_list = new base::FieldTrialList();
371     ANNOTATE_LEAKING_OBJECT_PTR(leaked_field_trial_list);
372     std::ignore = leaked_field_trial_list;
373   }
374 
375   // When benchmarking is enabled, field trials' default groups are chosen, so
376   // see whether benchmarking needs to be enabled here, before any field trials
377   // are created.
378   // TODO(crbug.com/40796250): Some FieldTrial-setup-related code is here and
379   // some is in VariationsFieldTrialCreator::SetUpFieldTrials(). It's not ideal
380   // that it's in two places.
381   if (ShouldEnableBenchmarking(entropy_params_.force_benchmarking_mode))
382     base::FieldTrial::EnableBenchmarking();
383 
384   const base::CommandLine* command_line =
385       base::CommandLine::ForCurrentProcess();
386   if (command_line->HasSwitch(variations::switches::kForceFieldTrialParams)) {
387     bool result =
388         variations::AssociateParamsFromString(command_line->GetSwitchValueASCII(
389             variations::switches::kForceFieldTrialParams));
390     if (!result) {
391       // Some field trial params implement things like csv or json with a
392       // particular param. If some control characters are not %-encoded, it can
393       // lead to confusing error messages, so add a hint here.
394       ExitWithMessage(base::StringPrintf(
395           "Invalid --%s list specified. Make sure you %%-"
396           "encode the following characters in param values: %%:/.,",
397           variations::switches::kForceFieldTrialParams));
398     }
399   }
400 
401   // Ensure any field trials specified on the command line are initialized.
402   if (command_line->HasSwitch(::switches::kForceFieldTrials)) {
403     // Create field trials without activating them, so that this behaves in a
404     // consistent manner with field trials created from the server.
405     bool result = base::FieldTrialList::CreateTrialsFromString(
406         command_line->GetSwitchValueASCII(::switches::kForceFieldTrials));
407     if (!result) {
408       ExitWithMessage(base::StringPrintf("Invalid --%s list specified.",
409                                          ::switches::kForceFieldTrials));
410     }
411   }
412 
413   // Initializing the CleanExitBeacon is done after FieldTrialList instantiation
414   // to allow experimentation on the CleanExitBeacon.
415   clean_exit_beacon_.Initialize();
416 }
417 
LogHasSessionShutdownCleanly(bool has_session_shutdown_cleanly,bool is_extended_safe_mode)418 void MetricsStateManager::LogHasSessionShutdownCleanly(
419     bool has_session_shutdown_cleanly,
420     bool is_extended_safe_mode) {
421   clean_exit_beacon_.WriteBeaconValue(has_session_shutdown_cleanly,
422                                       is_extended_safe_mode);
423 }
424 
ForceClientIdCreation()425 void MetricsStateManager::ForceClientIdCreation() {
426   // TODO(asvitkine): Ideally, all tests would actually set up consent properly,
427   // so the command-line checks wouldn't be needed here.
428   // Currently, kForceEnableMetricsReporting is used by Java UkmTest and
429   // kMetricsRecordingOnly is used by Chromedriver tests.
430   DCHECK(enabled_state_provider_->IsConsentGiven() ||
431          IsMetricsReportingForceEnabled() || IsMetricsRecordingOnlyEnabled());
432   if (!external_client_id_.empty()) {
433     client_id_ = external_client_id_;
434     base::UmaHistogramEnumeration("UMA.ClientIdSource",
435                                   ClientIdSource::kClientIdFromExternal);
436     local_state_->SetString(prefs::kMetricsClientID, client_id_);
437     return;
438   }
439 #if BUILDFLAG(IS_CHROMEOS_ASH)
440   std::string previous_client_id = client_id_;
441 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
442   {
443     std::string client_id_from_prefs = ReadClientId(local_state_);
444     // If client id in prefs matches the cached copy, return early.
445     if (!client_id_from_prefs.empty() && client_id_from_prefs == client_id_) {
446       base::UmaHistogramEnumeration("UMA.ClientIdSource",
447                                     ClientIdSource::kClientIdMatches);
448       return;
449     }
450     client_id_.swap(client_id_from_prefs);
451   }
452 
453   if (!client_id_.empty()) {
454     base::UmaHistogramEnumeration("UMA.ClientIdSource",
455                                   ClientIdSource::kClientIdFromLocalState);
456     return;
457   }
458 
459   const std::unique_ptr<ClientInfo> client_info_backup = LoadClientInfo();
460   if (client_info_backup) {
461     client_id_ = client_info_backup->client_id;
462 
463     const base::Time now = base::Time::Now();
464 
465     // Save the recovered client id and also try to reinstantiate the backup
466     // values for the dates corresponding with that client id in order to avoid
467     // weird scenarios where we could report an old client id with a recent
468     // install date.
469     local_state_->SetString(prefs::kMetricsClientID, client_id_);
470     local_state_->SetInt64(prefs::kInstallDate,
471                            client_info_backup->installation_date != 0
472                                ? client_info_backup->installation_date
473                                : now.ToTimeT());
474     local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp,
475                            client_info_backup->reporting_enabled_date != 0
476                                ? client_info_backup->reporting_enabled_date
477                                : now.ToTimeT());
478 
479     base::TimeDelta recovered_installation_age;
480     if (client_info_backup->installation_date != 0) {
481       recovered_installation_age =
482           now - base::Time::FromTimeT(client_info_backup->installation_date);
483     }
484     base::UmaHistogramEnumeration("UMA.ClientIdSource",
485                                   ClientIdSource::kClientIdBackupRecovered);
486     base::UmaHistogramCounts10000("UMA.ClientIdBackupRecoveredWithAge",
487                                   recovered_installation_age.InHours());
488 
489     // Flush the backup back to persistent storage in case we re-generated
490     // missing data above.
491     BackUpCurrentClientInfo();
492     return;
493   }
494 
495   // If we're here, there was no client ID yet (either in prefs or backup),
496   // so generate a new one. If there's a provisional client id (e.g. UMA
497   // was enabled as part of first run), promote that to the client id,
498   // otherwise (e.g. UMA enabled in a future session), generate a new one.
499   std::string provisional_client_id =
500       local_state_->GetString(prefs::kMetricsProvisionalClientID);
501   if (provisional_client_id.empty()) {
502     client_id_ = base::Uuid::GenerateRandomV4().AsLowercaseString();
503     base::UmaHistogramEnumeration("UMA.ClientIdSource",
504                                   ClientIdSource::kClientIdNew);
505   } else {
506     client_id_ = provisional_client_id;
507     local_state_->ClearPref(prefs::kMetricsProvisionalClientID);
508     base::UmaHistogramEnumeration("UMA.ClientIdSource",
509                                   ClientIdSource::kClientIdFromProvisionalId);
510   }
511   local_state_->SetString(prefs::kMetricsClientID, client_id_);
512 
513   // Record the timestamp of when the user opted in to UMA.
514   local_state_->SetInt64(prefs::kMetricsReportingEnabledTimestamp,
515                          base::Time::Now().ToTimeT());
516 
517   BackUpCurrentClientInfo();
518 }
519 
SetExternalClientId(const std::string & id)520 void MetricsStateManager::SetExternalClientId(const std::string& id) {
521   external_client_id_ = id;
522 }
523 
CheckForClonedInstall()524 void MetricsStateManager::CheckForClonedInstall() {
525   cloned_install_detector_.CheckForClonedInstall(local_state_);
526 }
527 
ShouldResetClientIdsOnClonedInstall()528 bool MetricsStateManager::ShouldResetClientIdsOnClonedInstall() {
529   return cloned_install_detector_.ShouldResetClientIds(local_state_);
530 }
531 
532 base::CallbackListSubscription
AddOnClonedInstallDetectedCallback(base::OnceClosure callback)533 MetricsStateManager::AddOnClonedInstallDetectedCallback(
534     base::OnceClosure callback) {
535   return cloned_install_detector_.AddOnClonedInstallDetectedCallback(
536       std::move(callback));
537 }
538 
539 std::unique_ptr<const variations::EntropyProviders>
CreateEntropyProviders(bool enable_limited_entropy_mode)540 MetricsStateManager::CreateEntropyProviders(bool enable_limited_entropy_mode) {
541   // TODO(crbug.com/40948861): remove `enable_limited_entropy_mode` when it's
542   // true for all callers.
543   auto limited_entropy_randomization_source =
544       enable_limited_entropy_mode ? GetLimitedEntropyRandomizationSource()
545                                   : std::string_view();
546   return std::make_unique<variations::EntropyProviders>(
547       GetHighEntropySource(),
548       variations::ValueInRange{
549           .value = base::checked_cast<uint32_t>(GetLowEntropySource()),
550           .range = EntropyState::kMaxLowEntropySize},
551       limited_entropy_randomization_source,
552       ShouldEnableBenchmarking(entropy_params_.force_benchmarking_mode));
553 }
554 
555 // static
Create(PrefService * local_state,EnabledStateProvider * enabled_state_provider,const std::wstring & backup_registry_key,const base::FilePath & user_data_dir,StartupVisibility startup_visibility,EntropyParams entropy_params,StoreClientInfoCallback store_client_info,LoadClientInfoCallback retrieve_client_info,std::string_view external_client_id)556 std::unique_ptr<MetricsStateManager> MetricsStateManager::Create(
557     PrefService* local_state,
558     EnabledStateProvider* enabled_state_provider,
559     const std::wstring& backup_registry_key,
560     const base::FilePath& user_data_dir,
561     StartupVisibility startup_visibility,
562     EntropyParams entropy_params,
563     StoreClientInfoCallback store_client_info,
564     LoadClientInfoCallback retrieve_client_info,
565     std::string_view external_client_id) {
566   std::unique_ptr<MetricsStateManager> result;
567   // Note: |instance_exists_| is updated in the constructor and destructor.
568   if (!instance_exists_) {
569     result.reset(new MetricsStateManager(
570         local_state, enabled_state_provider, backup_registry_key, user_data_dir,
571         entropy_params, startup_visibility,
572         store_client_info.is_null() ? base::DoNothing()
573                                     : std::move(store_client_info),
574         retrieve_client_info.is_null()
575             ? base::BindRepeating(&NoOpLoadClientInfoBackup)
576             : std::move(retrieve_client_info),
577         external_client_id));
578   }
579   return result;
580 }
581 
582 // static
RegisterPrefs(PrefRegistrySimple * registry)583 void MetricsStateManager::RegisterPrefs(PrefRegistrySimple* registry) {
584   registry->RegisterStringPref(prefs::kMetricsProvisionalClientID,
585                                std::string());
586   registry->RegisterStringPref(prefs::kMetricsClientID, std::string());
587   registry->RegisterInt64Pref(prefs::kMetricsReportingEnabledTimestamp, 0);
588   registry->RegisterInt64Pref(prefs::kInstallDate, 0);
589 #if BUILDFLAG(IS_ANDROID)
590   registry->RegisterBooleanPref(prefs::kUsePostFREFixSamplingTrial, false);
591 #endif  // BUILDFLAG(IS_ANDROID)
592 
593   EntropyState::RegisterPrefs(registry);
594   ClonedInstallDetector::RegisterPrefs(registry);
595 }
596 
BackUpCurrentClientInfo()597 void MetricsStateManager::BackUpCurrentClientInfo() {
598   ClientInfo client_info;
599   client_info.client_id = client_id_;
600   client_info.installation_date = ReadInstallDate(local_state_);
601   client_info.reporting_enabled_date = ReadEnabledDate(local_state_);
602   store_client_info_.Run(client_info);
603 }
604 
LoadClientInfo()605 std::unique_ptr<ClientInfo> MetricsStateManager::LoadClientInfo() {
606   // If a cloned install was detected, loading ClientInfo from backup will be
607   // a race condition with clearing the backup. Skip all backup reads for this
608   // session.
609   if (metrics_ids_were_reset_)
610     return nullptr;
611 
612   std::unique_ptr<ClientInfo> client_info = load_client_info_.Run();
613 
614   // The GUID retrieved should be valid unless retrieval failed.
615   // If not, return nullptr. This will result in a new GUID being generated by
616   // the calling function ForceClientIdCreation().
617   if (client_info &&
618       !base::Uuid::ParseCaseInsensitive(client_info->client_id).is_valid()) {
619     return nullptr;
620   }
621 
622   return client_info;
623 }
624 
GetLimitedEntropyRandomizationSource()625 std::string_view MetricsStateManager::GetLimitedEntropyRandomizationSource() {
626   // No limited entropy randomization source will be generated if limited
627   // entropy randomization is not supported in this context (e.g. in Android
628   // Webview).
629   if (entropy_params_.default_entropy_provider_type ==
630       EntropyProviderType::kLow) {
631     return std::string_view();
632   }
633   return entropy_state_.GetLimitedEntropyRandomizationSource();
634 }
635 
GetHighEntropySource()636 std::string MetricsStateManager::GetHighEntropySource() {
637   // If high entropy randomization is not supported in this context (e.g. in
638   // Android Webview), or if UMA is not enabled (so there is no client id), then
639   // high entropy randomization is disabled.
640   if (entropy_params_.default_entropy_provider_type ==
641           EntropyProviderType::kLow ||
642       initial_client_id_.empty()) {
643     UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_LOW);
644     return "";
645   }
646   UpdateEntropySourceReturnedValue(ENTROPY_SOURCE_HIGH);
647   return entropy_state_.GetHighEntropySource(initial_client_id_);
648 }
649 
UpdateEntropySourceReturnedValue(EntropySourceType type)650 void MetricsStateManager::UpdateEntropySourceReturnedValue(
651     EntropySourceType type) {
652   if (entropy_source_returned_ != ENTROPY_SOURCE_NONE)
653     return;
654 
655   entropy_source_returned_ = type;
656   base::UmaHistogramEnumeration("UMA.EntropySourceType", type,
657                                 ENTROPY_SOURCE_ENUM_SIZE);
658 }
659 
ResetMetricsIDsIfNecessary()660 void MetricsStateManager::ResetMetricsIDsIfNecessary() {
661   if (!ShouldResetClientIdsOnClonedInstall())
662     return;
663   metrics_ids_were_reset_ = true;
664   previous_client_id_ = ReadClientId(local_state_);
665 
666   base::UmaHistogramBoolean("UMA.MetricsIDsReset", true);
667 
668   DCHECK(client_id_.empty());
669 
670   local_state_->ClearPref(prefs::kMetricsClientID);
671   local_state_->ClearPref(prefs::kMetricsLogRecordId);
672   EntropyState::ClearPrefs(local_state_);
673 
674   ClonedInstallDetector::RecordClonedInstallInfo(local_state_);
675 
676   // Also clear the backed up client info. This is asynchronus; any reads
677   // shortly after may retrieve the old ClientInfo from the backup.
678   store_client_info_.Run(ClientInfo());
679 }
680 
ShouldGenerateProvisionalClientId(bool is_first_run)681 bool MetricsStateManager::ShouldGenerateProvisionalClientId(bool is_first_run) {
682 #if BUILDFLAG(IS_WIN)
683   // We do not want to generate a provisional client ID on Windows because
684   // there's no UMA checkbox on first run. Instead it comes from the install
685   // page. So if UMA is not enabled at this point, it's unlikely it will be
686   // enabled in the same session since that requires the user to manually do
687   // that via settings page after they unchecked it on the download page.
688   //
689   // Note: Windows first run is covered by browser tests
690   // FirstRunMasterPrefsVariationsSeedTest.PRE_SecondRun and
691   // FirstRunMasterPrefsVariationsSeedTest.SecondRun. If the platform ifdef
692   // for this logic changes, the tests should be updated as well.
693   return false;
694 #else
695   // We should only generate a provisional client ID on the first run. If for
696   // some reason there is already a client ID, we do not generate one either.
697   // This can happen if metrics reporting is managed by a policy.
698   if (!is_first_run || !client_id_.empty())
699     return false;
700 
701   // Return false if |kMetricsReportingEnabled| is managed by a policy. For
702   // example, if metrics reporting is disabled by a policy, then
703   // |kMetricsReportingEnabled| will always be set to false, so there is no
704   // reason to generate a provisional client ID. If metrics reporting is enabled
705   // by a policy, then the default value of |kMetricsReportingEnabled| will be
706   // true, and so a client ID will have already been generated (we would have
707   // returned false already because of the previous check).
708   if (local_state_->IsManagedPreference(prefs::kMetricsReportingEnabled))
709     return false;
710 
711   // If this is a non-Google-Chrome-branded build, we do not want to generate a
712   // provisional client ID because metrics reporting is not enabled on those
713   // builds. This would be problematic because we store the provisional client
714   // ID in the Local State, and clear it when either 1) we enable UMA (the
715   // provisional client ID becomes the client ID), or 2) we disable UMA. Since
716   // in non-Google-Chrome-branded builds we never actually go through the code
717   // paths to either enable or disable UMA, the pref storing the provisional
718   // client ID would never be cleared. However, for test consistency between
719   // the different builds, we do not return false here if
720   // |enable_provisional_client_id_for_testing_| is set to true.
721   if (!BUILDFLAG(GOOGLE_CHROME_BRANDING) &&
722       !enable_provisional_client_id_for_testing_) {
723     return false;
724   }
725 
726   return true;
727 #endif  // BUILDFLAG(IS_WIN)
728 }
729 
730 }  // namespace metrics
731