| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/autofill_assistant/browser/metrics.h" |
| |
| #include "base/containers/flat_map.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/no_destructor.h" |
| #include "components/autofill_assistant/browser/features.h" |
| #include "components/autofill_assistant/browser/intent_strings.h" |
| #include "components/ukm/content/source_url_recorder.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| |
| namespace autofill_assistant { |
| namespace { |
| int64_t ToEntryCountBucket(int entry_count) { |
| DCHECK_GE(entry_count, 0); |
| if (entry_count < 5) |
| return static_cast<int64_t>(entry_count); |
| |
| return static_cast<int64_t>(Metrics::UserDataEntryCount::FIVE_OR_MORE); |
| } |
| } // namespace |
| |
| // Intent not set constant. |
| const char* const kIntentNotSet = "NotSet"; |
| |
| namespace { |
| const char kDropOut[] = "Android.AutofillAssistant.DropOutReason"; |
| const char kTtsButtonAction[] = |
| "Android.AutofillAssistant.TextToSpeech.ButtonAction"; |
| const char kTtsEngineEvent[] = |
| "Android.AutofillAssistant.TextToSpeech.EngineEvent"; |
| const char kFeatureModuleInstallation[] = |
| "Android.AutofillAssistant.FeatureModuleInstallation"; |
| const char kPaymentRequestPrefilled[] = |
| "Android.AutofillAssistant.PaymentRequest.Prefilled"; |
| const char kPaymentRequestAutofillInfoChanged[] = |
| "Android.AutofillAssistant.PaymentRequest.AutofillChanged"; |
| const char kPaymentRequestFirstNameOnly[] = |
| "Android.AutofillAssistant.PaymentRequest.FirstNameOnly"; |
| const char kDependenciesInvalidated[] = |
| "Android.AutofillAssistant.DependenciesInvalidated"; |
| const char kOnboardingFetcherResultStatus[] = |
| "Android.AutofillAssistant.OnboardingFetcher.ResultStatus"; |
| static bool DROPOUT_RECORDED = false; |
| |
| std::string GetSuffixForIntent(const std::string& intent) { |
| base::flat_map<std::string, std::string> histogramsSuffixes = { |
| {kBuyMovieTicket, ".BuyMovieTicket"}, |
| {kFlightsCheckin, ".FlightsCheckin"}, |
| {kFoodOrdering, ".FoodOrdering"}, |
| {kFoodOrderingDelivery, ".FoodOrderingDelivery"}, |
| {kFoodOrderingPickup, ".FoodOrderingPickup"}, |
| {kPasswordChange, ".PasswordChange"}, |
| {kRentCar, ".RentCar"}, |
| {kShopping, ".Shopping"}, |
| {kShoppingAssistedCheckout, ".ShoppingAssistedCheckout"}, |
| {kTeleport, ".Teleport"}, |
| {kIntentNotSet, ".NotSet"}}; |
| |
| // Check if histogram exists for given intent. |
| if (histogramsSuffixes.count(intent) == 0) { |
| DVLOG(2) << "Unknown intent " << intent; |
| return ".UnknownIntent"; |
| } |
| return histogramsSuffixes[intent]; |
| } |
| |
| // Extracts the enum value corresponding to the intent specified in |
| // |script_parameters|. |
| Metrics::AutofillAssistantIntent ExtractIntentFromScriptParameters( |
| const ScriptParameters& script_parameters) { |
| auto intent = script_parameters.GetIntent(); |
| if (!intent) { |
| return Metrics::AutofillAssistantIntent::UNDEFINED_INTENT; |
| } |
| // The list of intents that is known at compile-time. Intents not in this list |
| // will be recorded as UNDEFINED_INTENT. |
| static const base::NoDestructor< |
| base::flat_map<std::string, Metrics::AutofillAssistantIntent>> |
| intents( |
| {{"BUY_MOVIE_TICKET", |
| Metrics::AutofillAssistantIntent::BUY_MOVIE_TICKET}, |
| {"RENT_CAR", Metrics::AutofillAssistantIntent::RENT_CAR}, |
| {"SHOPPING", Metrics::AutofillAssistantIntent::SHOPPING}, |
| {"TELEPORT", Metrics::AutofillAssistantIntent::TELEPORT}, |
| {"SHOPPING_ASSISTED_CHECKOUT", |
| Metrics::AutofillAssistantIntent::SHOPPING_ASSISTED_CHECKOUT}, |
| {"FLIGHTS_CHECKIN", |
| Metrics::AutofillAssistantIntent::FLIGHTS_CHECKIN}, |
| {"FOOD_ORDERING", Metrics::AutofillAssistantIntent::FOOD_ORDERING}, |
| {"PASSWORD_CHANGE", |
| Metrics::AutofillAssistantIntent::PASSWORD_CHANGE}, |
| {"FOOD_ORDERING_PICKUP", |
| Metrics::AutofillAssistantIntent::FOOD_ORDERING_PICKUP}, |
| {"FOOD_ORDERING_DELIVERY", |
| Metrics::AutofillAssistantIntent::FOOD_ORDERING_DELIVERY}, |
| {"UNLAUNCHED_VERTICAL_1", |
| Metrics::AutofillAssistantIntent::UNLAUNCHED_VERTICAL_1}, |
| {"FIND_COUPONS", Metrics::AutofillAssistantIntent::FIND_COUPONS}}); |
| |
| auto enum_value_iter = intents->find(*intent); |
| if (enum_value_iter == intents->end()) { |
| return Metrics::AutofillAssistantIntent::UNDEFINED_INTENT; |
| } |
| return enum_value_iter->second; |
| } // namespace |
| |
| // Extracts the enum value corresponding to the caller specified in |
| // |script_parameters|. |
| Metrics::AutofillAssistantCaller ExtractCallerFromScriptParameters( |
| const ScriptParameters& script_parameters) { |
| auto caller = script_parameters.GetCaller(); |
| if (!caller || |
| *caller > |
| static_cast<int64_t>(Metrics::AutofillAssistantCaller::kMaxValue) || |
| *caller < 0) { |
| return Metrics::AutofillAssistantCaller::UNKNOWN_CALLER; |
| } |
| |
| return static_cast<Metrics::AutofillAssistantCaller>(*caller); |
| } |
| |
| // Extracts the enum value corresponding to the source specified in |
| // |script_parameters|. |
| Metrics::AutofillAssistantSource ExtractSourceFromScriptParameters( |
| const ScriptParameters& script_parameters) { |
| auto source = script_parameters.GetSource(); |
| if (!source || |
| *source > |
| static_cast<int64_t>(Metrics::AutofillAssistantSource::kMaxValue) || |
| *source < 0) { |
| return Metrics::AutofillAssistantSource::UNKNOWN_SOURCE; |
| } |
| |
| return static_cast<Metrics::AutofillAssistantSource>(*source); |
| } |
| |
| // Extracts the list of experiments specified in |script_parameters|, if any. |
| // Returns a bit-wise OR of the running experiments. |
| int64_t ExtractExperimentsFromScriptParameters( |
| const ScriptParameters& script_parameters) { |
| std::vector<std::string> experiments = script_parameters.GetExperiments(); |
| if (experiments.empty()) { |
| return static_cast<int64_t>( |
| Metrics::AutofillAssistantExperiment::NO_EXPERIMENT); |
| } |
| |
| // This will be bit-wise OR of running experiments. Currently, there are no |
| // known experiments. |
| return static_cast<int64_t>( |
| Metrics::AutofillAssistantExperiment::UNKNOWN_EXPERIMENT); |
| } |
| |
| Metrics::AutofillAssistantStarted ToAutofillAssistantStarted( |
| StartupUtil::StartupMode event) { |
| switch (event) { |
| case StartupUtil::StartupMode::FEATURE_DISABLED: |
| return Metrics::AutofillAssistantStarted::FAILED_FEATURE_DISABLED; |
| case StartupUtil::StartupMode::MANDATORY_PARAMETERS_MISSING: |
| return Metrics::AutofillAssistantStarted:: |
| FAILED_MANDATORY_PARAMETER_MISSING; |
| case StartupUtil::StartupMode::SETTING_DISABLED: |
| return Metrics::AutofillAssistantStarted::FAILED_SETTING_DISABLED; |
| case StartupUtil::StartupMode::NO_INITIAL_URL: |
| return Metrics::AutofillAssistantStarted::FAILED_NO_INITIAL_URL; |
| case StartupUtil::StartupMode::START_REGULAR: |
| return Metrics::AutofillAssistantStarted::OK_IMMEDIATE_START; |
| case StartupUtil::StartupMode::START_BASE64_TRIGGER_SCRIPT: |
| case StartupUtil::StartupMode::START_RPC_TRIGGER_SCRIPT: |
| return Metrics::AutofillAssistantStarted::OK_DELAYED_START; |
| } |
| } |
| |
| } // namespace |
| |
| // static |
| void Metrics::RecordDropOut(DropOutReason reason, const std::string& intent) { |
| // TODO(arbesser): use an RAII token instead of a static variable to ensure |
| // that dropout recording happens exactly once per startup attempt. |
| DCHECK_LE(reason, DropOutReason::kMaxValue); |
| if (DROPOUT_RECORDED) { |
| return; |
| } |
| |
| auto suffix = GetSuffixForIntent(intent.empty() ? kIntentNotSet : intent); |
| base::UmaHistogramEnumeration(kDropOut + suffix, reason); |
| base::UmaHistogramEnumeration(kDropOut, reason); |
| if (reason != DropOutReason::AA_START) { |
| DVLOG(3) << "Drop out with reason: " << reason; |
| DROPOUT_RECORDED = true; |
| } |
| } |
| |
| // static |
| void Metrics::RecordPaymentRequestPrefilledSuccess(bool initially_complete, |
| bool success) { |
| if (initially_complete && success) { |
| base::UmaHistogramEnumeration(kPaymentRequestPrefilled, |
| PaymentRequestPrefilled::PREFILLED_SUCCESS); |
| } else if (initially_complete && !success) { |
| base::UmaHistogramEnumeration(kPaymentRequestPrefilled, |
| PaymentRequestPrefilled::PREFILLED_FAILURE); |
| } else if (!initially_complete && success) { |
| base::UmaHistogramEnumeration( |
| kPaymentRequestPrefilled, |
| PaymentRequestPrefilled::NOTPREFILLED_SUCCESS); |
| } else if (!initially_complete && !success) { |
| base::UmaHistogramEnumeration( |
| kPaymentRequestPrefilled, |
| PaymentRequestPrefilled::NOTPREFILLED_FAILURE); |
| } |
| } |
| |
| // static |
| void Metrics::RecordPaymentRequestAutofillChanged(bool changed, bool success) { |
| if (changed && success) { |
| base::UmaHistogramEnumeration( |
| kPaymentRequestAutofillInfoChanged, |
| PaymentRequestAutofillInfoChanged::CHANGED_SUCCESS); |
| } else if (changed && !success) { |
| base::UmaHistogramEnumeration( |
| kPaymentRequestAutofillInfoChanged, |
| PaymentRequestAutofillInfoChanged::CHANGED_FAILURE); |
| } else if (!changed && success) { |
| base::UmaHistogramEnumeration( |
| kPaymentRequestAutofillInfoChanged, |
| PaymentRequestAutofillInfoChanged::NOTCHANGED_SUCCESS); |
| } else if (!changed && !success) { |
| base::UmaHistogramEnumeration( |
| kPaymentRequestAutofillInfoChanged, |
| PaymentRequestAutofillInfoChanged::NOTCHANGED_FAILURE); |
| } |
| } |
| |
| // static |
| void Metrics::RecordPaymentRequestFirstNameOnly(bool first_name_only) { |
| base::UmaHistogramBoolean(kPaymentRequestFirstNameOnly, first_name_only); |
| } |
| |
| // static |
| void Metrics::RecordTriggerScriptStarted(ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| TriggerScriptStarted event) { |
| ukm::builders::AutofillAssistant_LiteScriptStarted(source_id) |
| .SetLiteScriptStarted(static_cast<int64_t>(event)) |
| .Record(ukm_recorder); |
| } |
| |
| // static |
| void Metrics::RecordTriggerScriptStarted(ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| StartupUtil::StartupMode startup_mode, |
| bool feature_module_installed, |
| bool is_first_time_user) { |
| TriggerScriptStarted event; |
| switch (startup_mode) { |
| case StartupUtil::StartupMode::FEATURE_DISABLED: |
| if (base::FeatureList::IsEnabled( |
| features::kAutofillAssistantProactiveHelp) && |
| !feature_module_installed) { |
| event = TriggerScriptStarted::DFM_UNAVAILABLE; |
| } else { |
| event = TriggerScriptStarted::FEATURE_DISABLED; |
| } |
| break; |
| case StartupUtil::StartupMode::SETTING_DISABLED: |
| event = TriggerScriptStarted::PROACTIVE_TRIGGERING_DISABLED; |
| break; |
| case StartupUtil::StartupMode::NO_INITIAL_URL: |
| event = TriggerScriptStarted::NO_INITIAL_URL; |
| break; |
| case StartupUtil::StartupMode::MANDATORY_PARAMETERS_MISSING: |
| event = TriggerScriptStarted::MANDATORY_PARAMETER_MISSING; |
| break; |
| case StartupUtil::StartupMode::START_BASE64_TRIGGER_SCRIPT: |
| case StartupUtil::StartupMode::START_RPC_TRIGGER_SCRIPT: |
| event = is_first_time_user ? TriggerScriptStarted::FIRST_TIME_USER |
| : TriggerScriptStarted::RETURNING_USER; |
| break; |
| case StartupUtil::StartupMode::START_REGULAR: |
| // Regular starts do not record impressions for |TriggerScriptStarted|. |
| return; |
| } |
| |
| RecordTriggerScriptStarted(ukm_recorder, source_id, event); |
| } |
| |
| // static |
| void Metrics::RecordTriggerScriptFinished( |
| ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| TriggerScriptProto::TriggerUIType trigger_ui_type, |
| TriggerScriptFinishedState event) { |
| ukm::builders::AutofillAssistant_LiteScriptFinished(source_id) |
| .SetTriggerUIType(static_cast<int64_t>(trigger_ui_type)) |
| .SetLiteScriptFinished(static_cast<int64_t>(event)) |
| .Record(ukm_recorder); |
| } |
| |
| // static |
| void Metrics::RecordTriggerScriptShownToUser( |
| ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| TriggerScriptProto::TriggerUIType trigger_ui_type, |
| TriggerScriptShownToUser event) { |
| ukm::builders::AutofillAssistant_LiteScriptShownToUser(source_id) |
| .SetTriggerUIType(static_cast<int64_t>(trigger_ui_type)) |
| .SetLiteScriptShownToUser(static_cast<int64_t>(event)) |
| .Record(ukm_recorder); |
| } |
| |
| // static |
| void Metrics::RecordTriggerScriptOnboarding( |
| ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| TriggerScriptProto::TriggerUIType trigger_ui_type, |
| TriggerScriptOnboarding event) { |
| ukm::builders::AutofillAssistant_LiteScriptOnboarding(source_id) |
| .SetTriggerUIType(static_cast<int64_t>(trigger_ui_type)) |
| .SetLiteScriptOnboarding(static_cast<int64_t>(event)) |
| .Record(ukm_recorder); |
| } |
| |
| // static |
| void Metrics::RecordRegularScriptOnboarding(ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| Metrics::Onboarding event) { |
| ukm::builders::AutofillAssistant_RegularScriptOnboarding(source_id) |
| .SetOnboarding(static_cast<int64_t>(event)) |
| .Record(ukm_recorder); |
| } |
| |
| // static |
| void Metrics::RecordInChromeTriggerAction(ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| InChromeTriggerAction event) { |
| ukm::builders::AutofillAssistant_InChromeTriggering(source_id) |
| .SetInChromeTriggerAction(static_cast<int64_t>(event)) |
| .Record(ukm_recorder); |
| } |
| |
| // static |
| void Metrics::RecordTtsButtonAction(TtsButtonAction action) { |
| DCHECK_LE(action, TtsButtonAction::kMaxValue); |
| base::UmaHistogramEnumeration(kTtsButtonAction, action); |
| } |
| |
| // static |
| void Metrics::RecordTtsEngineEvent(TtsEngineEvent event) { |
| DCHECK_LE(event, TtsEngineEvent::kMaxValue); |
| base::UmaHistogramEnumeration(kTtsEngineEvent, event); |
| } |
| |
| // static |
| void Metrics::RecordFeatureModuleInstallation(FeatureModuleInstallation event) { |
| DCHECK_LE(event, FeatureModuleInstallation::kMaxValue); |
| base::UmaHistogramEnumeration(kFeatureModuleInstallation, event); |
| } |
| |
| // static |
| void Metrics::RecordTriggerConditionEvaluationTime( |
| ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| base::TimeDelta evaluation_time) { |
| ukm::builders::AutofillAssistant_Timing(source_id) |
| .SetTriggerConditionEvaluationMs(evaluation_time.InMilliseconds()) |
| .Record(ukm_recorder); |
| } |
| |
| // static |
| void Metrics::RecordDependenciesInvalidated( |
| DependenciesInvalidated dependencies_invalidated) { |
| DCHECK_LE(dependencies_invalidated, DependenciesInvalidated::kMaxValue); |
| base::UmaHistogramEnumeration(kDependenciesInvalidated, |
| dependencies_invalidated); |
| } |
| |
| // static |
| void Metrics::RecordStartRequest(ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| const ScriptParameters& script_parameters, |
| StartupUtil::StartupMode event) { |
| ukm::builders::AutofillAssistant_StartRequest(source_id) |
| .SetCaller(static_cast<int64_t>( |
| ExtractCallerFromScriptParameters(script_parameters))) |
| .SetSource(static_cast<int64_t>( |
| ExtractSourceFromScriptParameters(script_parameters))) |
| .SetIntent(static_cast<int64_t>( |
| ExtractIntentFromScriptParameters(script_parameters))) |
| .SetExperiments(ExtractExperimentsFromScriptParameters(script_parameters)) |
| .SetStarted(static_cast<int64_t>(ToAutofillAssistantStarted(event))) |
| .Record(ukm_recorder); |
| } |
| |
| // static |
| void Metrics::RecordContactMetrics(ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| int complete_count, |
| int incomplete_count, |
| int initially_selected_field_bitmask, |
| UserDataSelectionState selection_state) { |
| ukm::builders::AutofillAssistant_CollectContact(source_id) |
| .SetCompleteContactProfilesCount(ToEntryCountBucket(complete_count)) |
| .SetIncompleteContactProfilesCount(ToEntryCountBucket(incomplete_count)) |
| .SetInitialContactFieldsStatus(initially_selected_field_bitmask) |
| .SetContactModified(static_cast<int64_t>(selection_state)) |
| .Record(ukm_recorder); |
| } |
| |
| void Metrics::RecordCreditCardMetrics( |
| ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| int complete_count, |
| int incomplete_count, |
| int initially_selected_card_field_bitmask, |
| int initially_selected_billing_address_field_bitmask, |
| UserDataSelectionState selection_state) { |
| ukm::builders::AutofillAssistant_CollectPayment(source_id) |
| .SetCompleteCreditCardsCount(ToEntryCountBucket(complete_count)) |
| .SetIncompleteCreditCardsCount(ToEntryCountBucket(incomplete_count)) |
| .SetInitialCreditCardFieldsStatus(initially_selected_card_field_bitmask) |
| .SetInitialBillingAddressFieldsStatus( |
| initially_selected_billing_address_field_bitmask) |
| .SetCreditCardModified(static_cast<int64_t>(selection_state)) |
| .Record(ukm_recorder); |
| } |
| |
| // static |
| void Metrics::RecordShippingMetrics(ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| int complete_count, |
| int incomplete_count, |
| int initially_selected_field_bitmask, |
| UserDataSelectionState selection_state) { |
| ukm::builders::AutofillAssistant_CollectShippingAddress(source_id) |
| .SetCompleteShippingProfilesCount(ToEntryCountBucket(complete_count)) |
| .SetIncompleteShippingProfilesCount(ToEntryCountBucket(incomplete_count)) |
| .SetInitialShippingFieldsStatus(initially_selected_field_bitmask) |
| .SetShippingModified(static_cast<int64_t>(selection_state)) |
| .Record(ukm_recorder); |
| } |
| |
| // static |
| void Metrics::RecordCollectUserDataSuccess(ukm::UkmRecorder* ukm_recorder, |
| ukm::SourceId source_id, |
| bool success, |
| int64_t time_taken_ms, |
| UserDataSource source) { |
| ukm::builders::AutofillAssistant_CollectUserDataResult(source_id) |
| .SetResult(static_cast<int64_t>( |
| success ? Metrics::CollectUserDataResult::SUCCESS |
| : Metrics::CollectUserDataResult::FAILURE)) |
| .SetTimeTakenMs(time_taken_ms) |
| .SetUserDataSource(static_cast<int64_t>(source)) |
| .Record(ukm_recorder); |
| } |
| |
| // static |
| void Metrics::RecordOnboardingFetcherResult( |
| OnboardingFetcherResultStatus status) { |
| DCHECK_LE(status, OnboardingFetcherResultStatus::kMaxValue); |
| base::UmaHistogramEnumeration(kOnboardingFetcherResultStatus, status); |
| } |
| |
| } // namespace autofill_assistant |