chromium/chrome/browser/ash/input_method/autocorrect_manager.h

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_ASH_INPUT_METHOD_AUTOCORRECT_MANAGER_H_
#define CHROME_BROWSER_ASH_INPUT_METHOD_AUTOCORRECT_MANAGER_H_

#include <optional>
#include <string>

#include "ash/system/federated/federated_client_manager.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/ash/input_method/assistive_input_denylist.h"
#include "chrome/browser/ash/input_method/assistive_window_controller.h"
#include "chrome/browser/ash/input_method/autocorrect_enums.h"
#include "chrome/browser/ash/input_method/diacritics_insensitive_string_comparator.h"
#include "chrome/browser/ash/input_method/input_method_engine.h"
#include "chrome/browser/ash/input_method/suggestion_handler_interface.h"
#include "chrome/browser/ash/input_method/text_field_contextual_info_fetcher.h"
#include "chromeos/ash/services/ime/public/cpp/autocorrect.h"

namespace ash {
namespace input_method {

// Implements functionality for chrome.input.ime.autocorrect() extension API.
// This function shows UI to indicate that autocorrect has happened and allows
// it to be undone easily.
class AutocorrectManager {
 public:
  // `profile` and `suggestion_handler` must be alive for the lifetime of this
  // instance.
  explicit AutocorrectManager(SuggestionHandlerInterface* suggestion_handler,
                              Profile* profile);
  ~AutocorrectManager();

  AutocorrectManager(const AutocorrectManager&) = delete;
  AutocorrectManager& operator=(const AutocorrectManager&) = delete;

  // Mark `autocorrect_range` with an underline. `autocorrect_range` is based on
  // the `current_text` contents.
  // NOTE: Technically redundant to require client to supply `current_text` as
  // AutocorrectManager can retrieve it from current text editing state known to
  // IMF. However, due to async situation between browser-process IMF and
  // render-process TextInputClient, it may just get a stale value that way.
  // TODO(crbug/1194424): Remove technically redundant `current_text` param
  // to avoid situation with multiple conflicting sources of truth.
  void HandleAutocorrect(gfx::Range autocorrect_range,
                         const std::u16string& original_text,
                         const std::u16string& current_text);

  // Called when a new input method is activated.
  void OnActivate(const std::string& engine_id);

  // Handles interactions with Undo UI.
  bool OnKeyEvent(const ui::KeyEvent& event);

  // Indicates a new text field is focused, used to save context ID.
  void OnFocus(int context_id);

  // Triggered whenever a connection to the external autocorrect suggestion
  // provider has been initialized successfully.
  void OnConnectedToSuggestionProvider(
      const ime::AutocorrectSuggestionProvider& suggestion_provider);

  // Handles OnBlur event and processes any pending autocorrect range.
  void OnBlur();

  // Processes the state where a user leaves or focuses a text field. At this
  // stage any pending autocorrect range is cleared and relevant metrics are
  // recorded.
  void ProcessTextFieldChange();

  // To show the undo window when cursor is in an autocorrected word, this class
  // is notified of surrounding text changes.
  void OnSurroundingTextChanged(const std::u16string& text,
                                gfx::Range selection_range);

  // Hides undo window if there is any visible.
  void HideUndoWindow();

  void UndoAutocorrect();

  // Whether autocorrect is disabled by some rule.
  bool DisabledByRule();

  // Whether autocorrect is disabled by an "invalid" experiment context. An
  // example of an invalid experiment context could be a provider or decoder
  // parameter set that is not allowed with the currently enabled experiments.
  bool DisabledByInvalidExperimentContext();

  const federated::FederatedClientManager& GetFederatedClientManagerForTest()
      const {
    return federated_manager_;
  }

 private:
  void LogAssistiveAutocorrectAction(AutocorrectActions action);
  void LogRejectionInteractions(AutocorrectActions action);
  void MeasureAndLogAssistiveAutocorrectQualityBreakdown(
      AutocorrectActions action);
  void LogAssistiveAutocorrectInternalState(
      AutocorrectInternalStates internal_state);
  bool AutoCorrectPrefIsPkEnabledByDefault();
  void LogAssistiveAutocorrectQualityBreakdown(
      AutocorrectQualityBreakdown quality_breakdown,
      bool suggestion_accepted,
      bool virtual_keyboard_visible);

  void OnTextFieldContextualInfoChanged(const TextFieldContextualInfo& info);

  // Forces to accept or clear a pending autocorrect suggestion if any. If the
  // autocorrect range is empty, it means the user interacted with the
  // pending autocorrect suggestion and made it invalid, so it considers
  // the autocorrect suggestion as "cleared". Otherwise, it considers the
  // autocorrect suggestion as "accepted". For the both cases, relevant
  // metrics are recorded, state variables are reset and autocorrect range is
  // set to empty.
  void AcceptOrClearPendingAutocorrect();

  // Shows undo window and record the relevant metric if undo window is
  // not already visible.
  void ShowUndoWindow(gfx::Range range, const std::u16string& text);

  // Highlights the appropriate undo or learn more buttons in the undo window
  void HighlightButtons(bool should_highlight_undo,
                        bool should_highlight_learn_more);

  // Processes the result of a set autocorrect range call. An unsuccessful
  // result could mean that autocorrect was not supported by the text input
  // client, so the autocorrect suggestion can be ignored. Otherwise, the
  // autocorrect suggestion will be set as pending and its relevant
  // interactions and metrics will be managed here.
  void ProcessSetAutocorrectRangeDone(const gfx::Range& autocorrect_range,
                                      const std::u16string& original_text,
                                      const std::u16string& current_text,
                                      bool set_range_success);

  // Records any pending metrics that are awaiting a key press from the user.
  void RecordPendingMetricsAwaitingKeyPress();

  struct PendingAutocorrectState {
    explicit PendingAutocorrectState(const std::u16string& original_text,
                                     const std::u16string& suggested_text,
                                     const base::TimeTicks& start_time,
                                     bool virtual_keyboard_visible = false,
                                     bool learn_more_button_visible = false);
    PendingAutocorrectState(const PendingAutocorrectState& other);
    ~PendingAutocorrectState();

    // Original text that is now corrected by autocorrect.
    std::u16string original_text;

    // Autocorrect suggestion that replaced original text.
    std::u16string suggested_text;

    // Specifies if the suggestion is validated in the surrounding text.
    bool is_validated = false;

    // Number of times that validation of autocorrect suggestion in the
    // surrounding text failed.
    int validation_tries = 0;

    // Number of characters inserted anytime after setting the pending
    // autocorrect range. Negative means no autocorrect range is pending or a
    // range has just been set to pending with no OnSurroundingTextChanged
    // called yet.
    int num_inserted_chars = -1;

    // Last known text length from OnSurroundingTextChanged after setting
    // the pending autocorrect range. Negative means no autocorrect range is
    // pending or a range has just been set to pending with no
    // OnSurroundingTextChanged called yet.
    int text_length = -1;

    // Specifies if undo window is visible or not.
    bool undo_window_visible = false;

    // Specifies if undo button is highlighted or not.
    bool undo_button_highlighted = false;

    // Specifies if learn more button is highlighted or not.
    bool learn_more_button_highlighted = false;

    // Specifies if window_shown metric is already incremented for the pending
    // autocorrect or not.
    bool window_shown_logged = false;

    // The time of setting the pending range.
    base::TimeTicks start_time;

    // Specifies if virtual keyboard was visible when suggesting the pending
    // autocorrect or not.
    bool virtual_keyboard_visible = false;

    // Specifies if learn more button is visible or not.
    bool learn_more_button_visible = false;

    // Records the most recent keypress and if control was down for use in
    // metrics.
    std::optional<ui::KeyEvent> last_key_event;

    // The range of the current pending autocorrect.
    gfx::Range last_autocorrect_range = gfx::Range();

    // The range of the selected text or (cursor_pos, cursor_pos] if no text is
    // selected.
    gfx::Range last_selection_range = gfx::Range();

    // Records the difference in length between the previous text and the
    // current |current text| - |prev text|.
    int text_length_diff = 0;
  };

  struct PendingPhysicalKeyboardUserPrefMetric {
    // The currently active engine id.
    std::string engine_id;
  };

  struct PendingSuggestionProviderMetric {
    // Suggestion provider that has been connected.
    ime::AutocorrectSuggestionProvider provider;
  };

  // State variable for pending autocorrect, nullopt means no autocorrect
  // suggestion is pending. The state is kept to avoid issue where
  // InputContext returns stale autocorrect range.
  std::optional<PendingAutocorrectState> pending_autocorrect_;

  // Specifies if the last try for hiding undo window failed. This means
  // undo window is possibly visible while it must not be.
  bool error_on_hiding_undo_window_ = false;

  // The number of autocorrect suggestions that have been handled since
  // focusing on the text field.
  int num_handled_autocorrect_in_text_field_ = 0;

  // Holds the currently active engine_id. There are cases where this could be
  // a nullopt, for example, when the object has been constructed and the
  // OnActivate method has not been invoked.
  std::optional<std::string> active_engine_id_;

  // Holds a pending physical keyboard user preference metric ready to be
  // recorded. This metric should be recorded once per input focused, and only
  // if the user is currently using the physical keyboard.
  std::optional<PendingPhysicalKeyboardUserPrefMetric>
      pending_user_pref_metric_;

  // Holds a pending suggestion provider metric. This metric should be recorded
  // only once per input, and only if the user is currently using the physical
  // keyboard.
  std::optional<PendingSuggestionProviderMetric>
      pending_suggestion_provider_metric_;

  // Holds the suggestion provider enabled for the current input method.
  std::optional<ime::AutocorrectSuggestionProvider> suggestion_provider_;

  // Used to determine if autocorrect should be enabled for a particular input.
  AssistiveInputDenylist denylist_;

  // Holds the identifier of the currently focused input field.
  int context_id_ = 0;

  // Not owned by this class.
  raw_ptr<SuggestionHandlerInterface> suggestion_handler_;
  raw_ptr<Profile> profile_;

  // For logging examples to the CrOS Federated Service.
  federated::FederatedClientManager federated_manager_;

  DiacriticsInsensitiveStringComparator
      diacritics_insensitive_string_comparator_;

  ui::ime::AssistiveWindowButton undo_button_;
  ui::ime::AssistiveWindowButton learn_more_button_;

  bool disabled_by_rule_ = false;

  base::WeakPtrFactory<AutocorrectManager> weak_ptr_factory_{this};
};

}  // namespace input_method
}  // namespace ash

#endif  // CHROME_BROWSER_ASH_INPUT_METHOD_AUTOCORRECT_MANAGER_H_:w