| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/hats/hats_finch_helper.h" |
| |
| #include "base/metrics/field_trial_params.h" |
| #include "base/rand_util.h" |
| #include "base/strings/string_util.h" |
| #include "chrome/browser/ash/hats/hats_config.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/prefs/pref_service.h" |
| |
| namespace ash { |
| |
| // These values should match the param key values in the finch config file. |
| // static |
| const char HatsFinchHelper::kEnabledForGooglersParam[] = "enabled_for_googlers"; |
| // static |
| const char HatsFinchHelper::kCustomClientDataParam[] = "custom_client_data"; |
| // static |
| const char HatsFinchHelper::kProbabilityParam[] = "prob"; |
| // static |
| const char HatsFinchHelper::kResetAllParam[] = "reset_all"; |
| // static |
| const char HatsFinchHelper::kResetSurveyCycleParam[] = "reset_survey_cycle"; |
| // static |
| const char HatsFinchHelper::kSurveyCycleLengthParam[] = "survey_cycle_length"; |
| // static |
| const char HatsFinchHelper::kSurveyStartDateMsParam[] = "survey_start_date_ms"; |
| // static |
| const char HatsFinchHelper::kTriggerIdParam[] = "trigger_id"; |
| |
| const char HatsFinchHelper::kHistogramNameParam[] = "histogram_name"; |
| |
| std::string HatsFinchHelper::GetTriggerID(const HatsConfig& hats_config) { |
| DCHECK(base::FeatureList::IsEnabled(hats_config.feature)); |
| return base::GetFieldTrialParamValueByFeature(hats_config.feature, |
| kTriggerIdParam); |
| } |
| |
| // To enable UMA collection for a specific survey, the Finch configuration |
| // file for this survey should be updated to include a `histogram_name` |
| // parameter along side the `trigger_id` parameter. Without this |
| // `histogram_name` parameter specified, no survey-specific UMA data will be |
| // collected. |
| std::string HatsFinchHelper::GetHistogramName(const HatsConfig& hats_config) { |
| DCHECK(base::FeatureList::IsEnabled(hats_config.feature)); |
| // Fetch the histogram name from the feature parameters, if it is assigned. |
| // An empty string will be returned if the parameter is not set in Finch. |
| // This value should be a valid histogram that has been registered in the |
| // histograms.xml file, otherwise it will not be ingested by UMA. |
| std::string histogram_name = base::GetFieldTrialParamValueByFeature( |
| hats_config.feature, kHistogramNameParam); |
| |
| // Valid histogram names for HaTS/UMA integration must start with the |
| // prefix "ChromeOS.HaTS.". This corresponds to the histogram definition |
| // in the histograms.xml file. |
| if (!base::StartsWith(histogram_name, "ChromeOS.HaTS.")) { |
| LOG(ERROR) << "Invalid HaTS histogram name: " << histogram_name; |
| return std::string(); |
| } |
| |
| return histogram_name; |
| } |
| |
| std::string HatsFinchHelper::GetCustomClientDataAsString( |
| const HatsConfig& hats_config) { |
| DCHECK(base::FeatureList::IsEnabled(hats_config.feature)); |
| return base::GetFieldTrialParamValueByFeature(hats_config.feature, |
| kCustomClientDataParam); |
| } |
| |
| bool HatsFinchHelper::IsEnabledForGooglers(const HatsConfig& hats_config) { |
| DCHECK(base::FeatureList::IsEnabled(hats_config.feature)); |
| return base::GetFieldTrialParamByFeatureAsBool( |
| hats_config.feature, kEnabledForGooglersParam, false); |
| } |
| |
| HatsFinchHelper::HatsFinchHelper(Profile* profile, |
| const HatsConfig& hats_config) |
| : profile_(profile), hats_config_(hats_config) { |
| LoadFinchParamValues(hats_config); |
| |
| // Reset prefs related to survey cycle if the finch seed has the reset param |
| // set. Do no futher op until a new finch seed with the reset flags unset is |
| // received. |
| // Warning: |reset_hats_| applies to all surveys. |
| if (reset_survey_cycle_ || reset_hats_) { |
| profile_->GetPrefs()->ClearPref(hats_config.cycle_end_timestamp_pref_name); |
| profile_->GetPrefs()->ClearPref(hats_config.is_selected_pref_name); |
| if (reset_hats_) |
| profile_->GetPrefs()->ClearPref(prefs::kHatsLastInteractionTimestamp); |
| return; |
| } |
| |
| CheckForDeviceSelection(); |
| } |
| |
| HatsFinchHelper::~HatsFinchHelper() = default; |
| |
| void HatsFinchHelper::LoadFinchParamValues(const HatsConfig& hats_config) { |
| if (!base::FeatureList::IsEnabled(hats_config.feature)) |
| return; |
| |
| probability_of_pick_ = base::GetFieldTrialParamByFeatureAsDouble( |
| hats_config.feature, kProbabilityParam, -1.0); |
| |
| if (probability_of_pick_ < 0.0 || probability_of_pick_ > 1.0) { |
| LOG(ERROR) << "Invalid value for probability: " << probability_of_pick_; |
| probability_of_pick_ = 0; |
| } |
| |
| survey_cycle_length_ = base::GetFieldTrialParamByFeatureAsInt( |
| hats_config.feature, kSurveyCycleLengthParam, 0); |
| |
| if (survey_cycle_length_ <= 0) { |
| LOG(ERROR) << "Invalid value for survey cycle length: " |
| << survey_cycle_length_; |
| survey_cycle_length_ = INT_MAX; |
| } |
| |
| double first_survey_start_date_ms = base::GetFieldTrialParamByFeatureAsDouble( |
| hats_config.feature, kSurveyStartDateMsParam, -1.0); |
| if (first_survey_start_date_ms < 0) { |
| LOG(ERROR) << "Invalid timestamp for survey start date: " |
| << first_survey_start_date_ms; |
| // Set a random date in the distant future so that the survey never starts |
| // until a new finch seed is received with the correct start date. |
| first_survey_start_date_ms = |
| 2 * base::Time::Now().InMillisecondsFSinceUnixEpoch(); |
| } |
| first_survey_start_date_ = |
| base::Time().FromMillisecondsSinceUnixEpoch(first_survey_start_date_ms); |
| |
| trigger_id_ = GetTriggerID(hats_config); |
| |
| reset_survey_cycle_ = base::GetFieldTrialParamByFeatureAsBool( |
| hats_config.feature, kResetSurveyCycleParam, false); |
| |
| reset_hats_ = base::GetFieldTrialParamByFeatureAsBool(hats_config.feature, |
| kResetAllParam, false); |
| |
| // Set every property to no op values if this is a reset finch seed. |
| if (reset_survey_cycle_ || reset_hats_) { |
| probability_of_pick_ = 0; |
| survey_cycle_length_ = INT_MAX; |
| first_survey_start_date_ = base::Time().FromMillisecondsSinceUnixEpoch( |
| 2 * base::Time::Now().InMillisecondsFSinceUnixEpoch()); |
| } |
| } |
| |
| bool HatsFinchHelper::HasPreviousCycleEnded() { |
| int64_t serialized_timestamp = profile_->GetPrefs()->GetInt64( |
| hats_config_->cycle_end_timestamp_pref_name); |
| base::Time recent_survey_cycle_end_time = |
| base::Time::FromInternalValue(serialized_timestamp); |
| return recent_survey_cycle_end_time < base::Time::Now(); |
| } |
| |
| base::Time HatsFinchHelper::ComputeNextEndDate() { |
| base::Time start_date = first_survey_start_date_; |
| base::TimeDelta delta = base::Days(survey_cycle_length_); |
| do { |
| start_date += delta; |
| } while (start_date < base::Time::Now()); |
| return start_date; |
| } |
| |
| void HatsFinchHelper::CheckForDeviceSelection() { |
| device_is_selected_for_cycle_ = false; |
| |
| // The dice is rolled only once per survey cycle. If it has already been done |
| // for the current cycle, then return the stored value of the result. |
| if (!HasPreviousCycleEnded()) { |
| device_is_selected_for_cycle_ = |
| profile_->GetPrefs()->GetBoolean(hats_config_->is_selected_pref_name); |
| return; |
| } |
| |
| // If the start date for the survey is in the future, do nothing. |
| if (first_survey_start_date_ > base::Time::Now()) |
| return; |
| |
| // Start a new survey cycle and compute its end date. |
| base::Time survey_cycle_end_date = ComputeNextEndDate(); |
| |
| PrefService* pref_service = profile_->GetPrefs(); |
| pref_service->SetInt64(hats_config_->cycle_end_timestamp_pref_name, |
| survey_cycle_end_date.ToInternalValue()); |
| |
| double rand_double = base::RandDouble(); |
| bool is_selected = false; |
| if (rand_double < probability_of_pick_) |
| is_selected = true; |
| |
| // Check if the trigger id is a valid string. Trigger IDs are a hash strings |
| // of around 26 characters. |
| is_selected = is_selected && (trigger_id_.length() > 15); |
| |
| pref_service->SetBoolean(hats_config_->is_selected_pref_name, is_selected); |
| device_is_selected_for_cycle_ = is_selected; |
| } |
| |
| } // namespace ash |