chromium/chrome/browser/ash/input_method/editor_metrics_recorder.cc

// Copyright 2023 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/input_method/editor_metrics_recorder.h"

#include <optional>

#include "ash/constants/ash_features.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "chrome/browser/ash/input_method/editor_consent_enums.h"
#include "chrome/browser/ash/input_method/editor_context.h"
#include "chrome/browser/ash/input_method/editor_metrics_enums.h"
#include "chrome/browser/ash/input_method/input_methods_by_language.h"
#include "chromeos/ash/services/orca/public/mojom/orca_service.mojom.h"
#include "chromeos/constants/chromeos_features.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source_id.h"

namespace ash::input_method {

namespace {

constexpr int kMaxNumResponsesFromServer = 20;

std::string GetToneStringFromEnum(EditorTone tone) {
  switch (tone) {
    case EditorTone::kRephrase:
      return "Rephrase";
    case EditorTone::kEmojify:
      return "Emojify";
    case EditorTone::kShorten:
      return "Shorten";
    case EditorTone::kElaborate:
      return "Elaborate";
    case EditorTone::kFormalize:
      return "Formalize";
    case EditorTone::kProofread:
      return "Proofread";
    case EditorTone::kFreeformRewrite:
      return "FreeformRewrite";
    case EditorTone::kUnset:
      return "Unset";
    case EditorTone::kUnknown:
      return "Unknown";
  }
}

EditorTone GetEditorToneFromString(std::string_view tone) {
  if (tone == "REPHRASE") {
    return EditorTone::kRephrase;
  }
  if (tone == "EMOJIFY") {
    return EditorTone::kEmojify;
  }
  if (tone == "SHORTEN") {
    return EditorTone::kShorten;
  }
  if (tone == "ELABORATE") {
    return EditorTone::kElaborate;
  }
  if (tone == "FORMALIZE") {
    return EditorTone::kFormalize;
  }
  if (tone == "PROOFREAD") {
    return EditorTone::kProofread;
  }
  return EditorTone::kUnknown;
}

std::string_view AsString(const EditorOpportunityMode& mode) {
  switch (mode) {
    case EditorOpportunityMode::kWrite:
      return "Write";
    case EditorOpportunityMode::kRewrite:
      return "Rewrite";
    case EditorOpportunityMode::kNotAllowedForUse:
      return "NotAllowed";
    case EditorOpportunityMode::kInvalidInput:
      return "InvalidInput";
  }
}

std::string_view AsString(const LanguageCategory& category) {
  switch (category) {
    case LanguageCategory::kAfrikaans:
      return "Afrikaans";
    case LanguageCategory::kDanish:
      return "Danish";
    case LanguageCategory::kDutch:
      return "Dutch";
    case LanguageCategory::kFinnish:
      return "Finnish";
    case LanguageCategory::kEnglish:
      return "English";
    case LanguageCategory::kFrench:
      return "French";
    case LanguageCategory::kGerman:
      return "German";
    case LanguageCategory::kItalian:
      return "Italian";
    case LanguageCategory::kJapanese:
      return "Japanese";
    case LanguageCategory::kNorwegian:
      return "Norwegian";
    case LanguageCategory::kPolish:
      return "Polish";
    case LanguageCategory::kPortugese:
      return "Portugese";
    case LanguageCategory::kSpanish:
      return "Spanish";
    case LanguageCategory::kSwedish:
      return "Swedish";
    default:
      return "Other";
  }
}

std::optional<EditorCriticalStates> AsCriticalState(const EditorStates& state) {
  switch (state) {
    case EditorStates::kNativeUIShown:
    case EditorStates::kPromoCardImpression:
      return EditorCriticalStates::kShowUI;
    case EditorStates::kNativeRequest:
    case EditorStates::kWebUIRequest:
      return EditorCriticalStates::kRequestTriggered;
    case EditorStates::kInsert:
      return EditorCriticalStates::kTextInserted;
    default:
      return std::nullopt;
  }
}

std::string_view AsEnglishOrOther(const LanguageCategory& category) {
  switch (category) {
    case LanguageCategory::kEnglish:
      return "English";
    default:
      return "Other";
  }
}

bool IsInternationalizedPathEnabled() {
  return base::FeatureList::IsEnabled(features::kOrcaAfrikaans) ||
         base::FeatureList::IsEnabled(features::kOrcaDanish) ||
         base::FeatureList::IsEnabled(features::kOrcaDutch) ||
         base::FeatureList::IsEnabled(features::kOrcaFinnish) ||
         base::FeatureList::IsEnabled(features::kOrcaFrench) ||
         base::FeatureList::IsEnabled(features::kOrcaGerman) ||
         base::FeatureList::IsEnabled(features::kOrcaItalian) ||
         base::FeatureList::IsEnabled(features::kOrcaJapanese) ||
         base::FeatureList::IsEnabled(features::kOrcaNorwegian) ||
         base::FeatureList::IsEnabled(features::kOrcaPolish) ||
         base::FeatureList::IsEnabled(features::kOrcaPortugese) ||
         base::FeatureList::IsEnabled(features::kOrcaSpanish) ||
         base::FeatureList::IsEnabled(features::kOrcaSwedish);
}

}  // namespace

EditorStates ToEditorStatesMetric(EditorBlockedReason reason) {
  switch (reason) {
    case EditorBlockedReason::kBlockedByConsent:
      return EditorStates::kBlockedByConsent;
    case EditorBlockedReason::kBlockedBySetting:
      return EditorStates::kBlockedBySetting;
    case EditorBlockedReason::kBlockedByTextLength:
      return EditorStates::kBlockedByTextLength;
    case EditorBlockedReason::kBlockedByUrl:
      return EditorStates::kBlockedByUrl;
    case EditorBlockedReason::kBlockedByApp:
      return EditorStates::kBlockedByApp;
    case EditorBlockedReason::kBlockedByInputMethod:
      return EditorStates::kBlockedByInputMethod;
    case EditorBlockedReason::kBlockedByInputType:
      return EditorStates::kBlockedByInputType;
    case EditorBlockedReason::kBlockedByAppType:
      return EditorStates::kBlockedByAppType;
    case EditorBlockedReason::kBlockedByInvalidFormFactor:
      return EditorStates::kBlockedByInvalidFormFactor;
    case EditorBlockedReason::kBlockedByNetworkStatus:
      return EditorStates::kBlockedByNetworkStatus;
    case EditorBlockedReason::kBlockedByUnsupportedRegion:
      return EditorStates::kBlockedByUnsupportedRegion;
    case EditorBlockedReason::kBlockedByPolicy:
      return EditorStates::kBlockedByPolicy;
    case EditorBlockedReason::kBlockedByUnknownCapability:
      return EditorStates::kBlockedByUnknownCapability;
    case EditorBlockedReason::kBlockedByUnsupportedCapability:
      return EditorStates::kBlockedByUnsupportedCapability;
  }
}

EditorStates ToEditorStatesMetric(orca::mojom::TextQueryErrorCode error_code) {
  switch (error_code) {
    case orca::mojom::TextQueryErrorCode::kUnknown:
      return EditorStates::kErrorUnknown;
    case orca::mojom::TextQueryErrorCode::kInvalidArgument:
      return EditorStates::kErrorInvalidArgument;
    case orca::mojom::TextQueryErrorCode::kResourceExhausted:
      return EditorStates::kErrorResourceExhausted;
    case orca::mojom::TextQueryErrorCode::kBackendFailure:
      return EditorStates::kErrorBackendFailure;
    case orca::mojom::TextQueryErrorCode::kNoInternetConnection:
      return EditorStates::kErrorNoInternetConnection;
    case orca::mojom::TextQueryErrorCode::kUnsupportedLanguage:
      return EditorStates::kErrorUnsupportedLanguage;
    case orca::mojom::TextQueryErrorCode::kBlockedOutputs:
      return EditorStates::kErrorBlockedOutputs;
    case orca::mojom::TextQueryErrorCode::kRestrictedRegion:
      return EditorStates::kErrorRestrictedRegion;
  }
}

std::optional<EditorStates> ToEditorStatesMetric(
    orca::mojom::MetricEvent metric_event) {
  switch (metric_event) {
    case orca::mojom::MetricEvent::kRefineRequest:
      return EditorStates::kRefineRequest;
    case orca::mojom::MetricEvent::kFeedbackThumbsUp:
      return EditorStates::kThumbsUp;
    case orca::mojom::MetricEvent::kFeedbackThumbsDown:
      return EditorStates::kThumbsDown;
    case orca::mojom::MetricEvent::kReturnToPreviousSuggestions:
      return EditorStates::kReturnToPreviousSuggestions;
    case orca::mojom::MetricEvent::kWebUIRequest:
      return EditorStates::kWebUIRequest;
    case orca::mojom::MetricEvent::kUnknown:
      return std::nullopt;
  }
}

EditorTone ToEditorMetricTone(orca::mojom::TriggerContextPtr trigger_context) {
  if (trigger_context->freeform_selected) {
    return EditorTone::kFreeformRewrite;
  }
  switch (trigger_context->preset_type_selected) {
    case orca::mojom::PresetTextQueryType::kShorten:
      return EditorTone::kShorten;
    case orca::mojom::PresetTextQueryType::kElaborate:
      return EditorTone::kElaborate;
    case orca::mojom::PresetTextQueryType::kRephrase:
      return EditorTone::kRephrase;
    case orca::mojom::PresetTextQueryType::kFormalize:
      return EditorTone::kFormalize;
    case orca::mojom::PresetTextQueryType::kEmojify:
      return EditorTone::kEmojify;
    case orca::mojom::PresetTextQueryType::kProofread:
      return EditorTone::kProofread;
    case orca::mojom::PresetTextQueryType::kUnknown:
      return EditorTone::kUnknown;
  }
}

EditorMetricsRecorder::EditorMetricsRecorder(EditorContext* context,
                                             EditorOpportunityMode mode)
    : context_(context), mode_(mode) {}

void EditorMetricsRecorder::SetMode(EditorOpportunityMode mode) {
  mode_ = mode;
}

void EditorMetricsRecorder::SetTone(
    std::optional<std::string_view> preset_query_id,
    std::optional<std::string_view> freeform_text) {
  if (freeform_text.has_value() && !freeform_text->empty()) {
    tone_ = EditorTone::kFreeformRewrite;
    return;
  }
  if (!preset_query_id.has_value()) {
    return;
  }

  tone_ = GetEditorToneFromString(*preset_query_id);
}

void EditorMetricsRecorder::SetTone(EditorTone tone) {
  tone_ = tone;
}

void EditorMetricsRecorder::LogEditorState(EditorStates state) {
  base::UmaHistogramEnumeration(
      base::StrCat({"InputMethod.Manta.Orca.States.", AsString(mode_)}), state);

  if (IsInternationalizedPathEnabled()) {
    base::UmaHistogramEnumeration(
        base::StrCat({"InputMethod.Manta.Orca.",
                      AsString(InputMethodToLanguageCategory(
                          context_->active_engine_id())),
                      ".States.", AsString(mode_)}),
        state);
  }

  if (std::optional<EditorCriticalStates> critical_state =
          AsCriticalState(state);
      critical_state != std::nullopt) {
    LogEditorCriticalState(*critical_state);
  }

  if (mode_ != EditorOpportunityMode::kRewrite) {
    return;
  }

  base::UmaHistogramEnumeration(base::StrCat({"InputMethod.Manta.Orca.States.",
                                              GetToneStringFromEnum(tone_)}),
                                state);
}

void EditorMetricsRecorder::LogNumberOfCharactersInserted(
    int number_of_characters) {
  if (mode_ == EditorOpportunityMode::kInvalidInput ||
      mode_ == EditorOpportunityMode::kNotAllowedForUse) {
    return;
  }

  base::UmaHistogramCounts100000(
      base::StrCat(
          {"InputMethod.Manta.Orca.CharactersInserted.", AsString(mode_)}),
      number_of_characters);

  if (IsInternationalizedPathEnabled()) {
    base::UmaHistogramCounts100000(
        base::StrCat({"InputMethod.Manta.Orca.",
                      AsEnglishOrOther(InputMethodToLanguageCategory(
                          context_->active_engine_id())),
                      ".CharactersInserted.", AsString(mode_)}),
        number_of_characters);
  }

  if (mode_ != EditorOpportunityMode::kRewrite) {
    return;
  }

  base::UmaHistogramCounts100000("InputMethod.Manta.Orca.CharactersInserted." +
                                     GetToneStringFromEnum(tone_),
                                 number_of_characters);
}

void EditorMetricsRecorder::LogNumberOfCharactersSelectedForInsert(
    int number_of_characters) {
  if (mode_ == EditorOpportunityMode::kInvalidInput ||
      mode_ == EditorOpportunityMode::kNotAllowedForUse) {
    return;
  }

  base::UmaHistogramCounts100000(
      base::StrCat({"InputMethod.Manta.Orca.CharactersSelectedForInsert.",
                    AsString(mode_)}),
      number_of_characters);

  if (IsInternationalizedPathEnabled()) {
    base::UmaHistogramCounts100000(
        base::StrCat({"InputMethod.Manta.Orca.",
                      AsEnglishOrOther(InputMethodToLanguageCategory(
                          context_->active_engine_id())),
                      ".CharactersSelectedForInsert.", AsString(mode_)}),
        number_of_characters);
  }

  if (mode_ != EditorOpportunityMode::kRewrite) {
    return;
  }

  base::UmaHistogramCounts100000(
      "InputMethod.Manta.Orca.CharactersSelectedForInsert." +
          GetToneStringFromEnum(tone_),
      number_of_characters);
}

void EditorMetricsRecorder::LogNumberOfResponsesFromServer(
    int number_of_responses) {
  if (mode_ == EditorOpportunityMode::kInvalidInput ||
      mode_ == EditorOpportunityMode::kNotAllowedForUse) {
    return;
  }

  base::UmaHistogramExactLinear(
      base::StrCat({"InputMethod.Manta.Orca.NumResponses.", AsString(mode_)}),
      number_of_responses, kMaxNumResponsesFromServer);

  if (IsInternationalizedPathEnabled()) {
    base::UmaHistogramExactLinear(
        base::StrCat({"InputMethod.Manta.Orca.",
                      AsEnglishOrOther(InputMethodToLanguageCategory(
                          context_->active_engine_id())),
                      ".NumResponses.", AsString(mode_)}),
        number_of_responses, kMaxNumResponsesFromServer);
  }

  if (mode_ != EditorOpportunityMode::kRewrite) {
    return;
  }

  base::UmaHistogramExactLinear(
      base::StrCat({"InputMethod.Manta.Orca.NumResponses.",
                    GetToneStringFromEnum(tone_)}),
      number_of_responses, kMaxNumResponsesFromServer);
}

void EditorMetricsRecorder::LogLengthOfLongestResponseFromServer(
    int number_of_characters) {
  if (mode_ == EditorOpportunityMode::kInvalidInput ||
      mode_ == EditorOpportunityMode::kNotAllowedForUse) {
    return;
  }

  base::UmaHistogramCounts100000(
      base::StrCat(
          {"InputMethod.Manta.Orca.LengthOfLongestResponse.", AsString(mode_)}),
      number_of_characters);

  if (IsInternationalizedPathEnabled()) {
    base::UmaHistogramCounts100000(
        base::StrCat({"InputMethod.Manta.Orca.",
                      AsEnglishOrOther(InputMethodToLanguageCategory(
                          context_->active_engine_id())),
                      ".LengthOfLongestResponse.", AsString(mode_)}),
        number_of_characters);
  }

  if (mode_ != EditorOpportunityMode::kRewrite) {
    return;
  }

  base::UmaHistogramCounts100000(
      base::StrCat({"InputMethod.Manta.Orca.LengthOfLongestResponse.",
                    GetToneStringFromEnum(tone_)}),
      number_of_characters);
}

void EditorMetricsRecorder::LogEditorCriticalState(
    const EditorCriticalStates& critical_state) {
  if (std::optional<ukm::SourceId> source_id = context_->GetUkmSourceId();
      source_id.has_value()) {
    ukm::builders::InputMethod_Manta_Orca(*source_id)
        .SetEditorCriticalStates(static_cast<int>(critical_state))
        .Record(ukm::UkmRecorder::Get());
  }
}

}  // namespace ash::input_method