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

// Copyright 2019 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_ASSISTIVE_SUGGESTER_H_
#define CHROME_BROWSER_ASH_INPUT_METHOD_ASSISTIVE_SUGGESTER_H_

#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/timer/timer.h"
#include "chrome/browser/ash/input_method/assistive_suggester_switch.h"
#include "chrome/browser/ash/input_method/emoji_suggester.h"
#include "chrome/browser/ash/input_method/longpress_control_v_suggester.h"
#include "chrome/browser/ash/input_method/longpress_diacritics_suggester.h"
#include "chrome/browser/ash/input_method/multi_word_suggester.h"
#include "chrome/browser/ash/input_method/suggester.h"
#include "chrome/browser/ash/input_method/suggestion_enums.h"
#include "chrome/browser/ash/input_method/suggestion_handler_interface.h"
#include "chrome/browser/ash/input_method/suggestions_source.h"
#include "chromeos/ash/services/ime/public/cpp/assistive_suggestions.h"

namespace ash::input_method {

enum class AssistiveSuggesterKeyResult {
  // The key event was not handled by the assistive suggester.
  // The key event should be handled via normal key event flow.
  kNotHandled,
  // The key event was handled by the assistive suggester.
  // The key event should not be propagated as-is. Instead, it should be
  // dispatched as a PROCESS key to prevent the client from triggering the
  // default behaviour for the key.
  kHandled,
  // Same as not kNotHandled, except the key event should not trigger
  // autorepeat.
  kNotHandledSuppressAutoRepeat,
};

// An agent to suggest assistive information when the user types, and adopt or
// dismiss the suggestion according to the user action.
class AssistiveSuggester : public SuggestionsSource {
 public:
  // Features handled by assistive suggester.
  enum class AssistiveFeature {
    kUnknown,  // Includes features not handled by assistive suggester.
    kEmojiSuggestion,
    kMultiWordSuggestion,
  };

  AssistiveSuggester(
      SuggestionHandlerInterface* suggestion_handler,
      Profile* profile,
      std::unique_ptr<AssistiveSuggesterSwitch> suggester_switch);

  ~AssistiveSuggester() override;

  bool IsAssistiveFeatureEnabled();

  // Fetches enabled suggestions in the current browser context then run
  // callback.
  void FetchEnabledSuggestionsFromBrowserContextThen(
      AssistiveSuggesterSwitch::FetchEnabledSuggestionsCallback callback);

  // SuggestionsSource overrides
  std::vector<ime::AssistiveSuggestion> GetSuggestions() override;

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

  // Called when a text field gains focus, and suggester starts working.
  void OnFocus(int context_id, const TextInputMethod::InputContext& context);

  // Called when a text field loses focus, and suggester stops working.
  void OnBlur();

  // Called when a surrounding text is changed.
  // Returns true if it changes the surrounding text, e.g. a suggestion is
  // generated or dismissed.
  void OnSurroundingTextChanged(const std::u16string& text,
                                gfx::Range selection_range);

  // Called when the user pressed a key.
  AssistiveSuggesterKeyResult OnKeyEvent(const ui::KeyEvent& event);

  // Called when suggestions are generated outside of the assistive framework.
  void OnExternalSuggestionsUpdated(
      const std::vector<ime::AssistiveSuggestion>& suggestions,
      const std::optional<ime::SuggestionsTextContext>& context);

  // Accepts the suggestion at a given index if a suggester is currently
  // active.
  void AcceptSuggestion(size_t index);

  // Check if suggestion is being shown.
  bool IsSuggestionShown();

  EmojiSuggester* get_emoji_suggester_for_testing() {
    return &emoji_suggester_;
  }

  std::optional<AssistiveSuggesterSwitch::EnabledSuggestions>
  get_enabled_suggestion_from_last_onfocus_for_testing() {
    return enabled_suggestions_from_last_onfocus_;
  }

 private:
  // Callback that is run after enabled_suggestions is received.
  void ProcessOnSurroundingTextChanged(
      const std::u16string& text,
      gfx::Range selection_range,
      const AssistiveSuggesterSwitch::EnabledSuggestions& enabled_suggestions);

  // Returns if any suggestion text should be displayed according to the
  // surrounding text information.
  bool TrySuggestWithSurroundingText(
      const std::u16string& text,
      gfx::Range selection_range,
      const AssistiveSuggesterSwitch::EnabledSuggestions& enabled_suggestions);

  void DismissSuggestion();

  bool IsEmojiSuggestAdditionEnabled();

  bool IsEnhancedEmojiSuggestEnabled();

  bool IsMultiWordSuggestEnabled();

  bool IsExpandedMultiWordSuggestEnabled();

  bool IsDiacriticsOnPhysicalKeyboardLongpressEnabled();

  // Checks the text before cursor, emits metric if any assistive prefix is
  // matched.
  void RecordAssistiveMatchMetrics(
      const std::u16string& text,
      gfx::Range selection_range,
      const AssistiveSuggesterSwitch::EnabledSuggestions& enabled_suggestions);

  void RecordAssistiveMatchMetricsForAssistiveType(
      AssistiveType type,
      const AssistiveSuggesterSwitch::EnabledSuggestions& enabled_suggestions);

  // Only the first applicable reason in DisabledReason enum is returned.
  DisabledReason GetDisabledReasonForEmoji(
      const AssistiveSuggesterSwitch::EnabledSuggestions& enabled_suggestions);

  // Only the first applicable reason in DisabledReason enum is returned.
  DisabledReason GetDisabledReasonForMultiWord(
      const AssistiveSuggesterSwitch::EnabledSuggestions& enabled_suggestions);

  AssistiveFeature GetAssistiveFeatureForType(AssistiveType type);

  bool IsAssistiveTypeEnabled(AssistiveType type);

  bool IsAssistiveTypeAllowedInBrowserContext(
      AssistiveType type,
      const AssistiveSuggesterSwitch::EnabledSuggestions& enabled_suggestions);

  bool WithinGrammarFragment();

  void ProcessExternalSuggestions(
      const std::vector<ime::AssistiveSuggestion>& suggestions,
      const std::optional<ime::SuggestionsTextContext>& context,
      const AssistiveSuggesterSwitch::EnabledSuggestions& enabled_suggestions);

  // This records any text input state metrics for each relevant assistive
  // feature. It is called once when a text field gains focus.
  void RecordTextInputStateMetrics(
      const AssistiveSuggesterSwitch::EnabledSuggestions& enabled_suggestions);

  // Does longpress related processing (if enabled).
  // Returns true if we block the keyevent from passing to IME, and stop
  // dispatch.
  // Returns false, if we want IME to process the event and dispatch it.
  AssistiveSuggesterKeyResult HandleLongpressEnabledKeyEvent(
      const ui::KeyEvent& key_character);

  void HandleEnabledSuggestionsOnFocus(
      const AssistiveSuggesterSwitch::EnabledSuggestions& enabled_suggestions);

  void OnLongpressDetected();

  // Accepts or dismisses a Ctrl+V long-press suggestion based on the exit
  // status of the clipboard history menu, as indicated by `will_paste_item`.
  void OnClipboardHistoryMenuClosing(bool will_paste_item);

  raw_ptr<Profile> profile_;
  EmojiSuggester emoji_suggester_;
  MultiWordSuggester multi_word_suggester_;
  LongpressDiacriticsSuggester longpress_diacritics_suggester_;
  LongpressControlVSuggester longpress_control_v_suggester_;
  std::unique_ptr<AssistiveSuggesterSwitch> suggester_switch_;

  // The id of the currently active input engine.
  std::string active_engine_id_;

  // ID of the focused text field, nullopt if none focused.
  std::optional<int> focused_context_id_;

  // KeyEvent of the held down key at key down. nullopt if no longpress in
  // progress.
  std::optional<ui::KeyEvent> current_longpress_keydown_;

  // Timer for longpress. Starts when key is held down. Fires when successfully
  // held down for a specified longpress duration.
  base::OneShotTimer longpress_timer_;

  // The current suggester in use, nullptr means no suggestion is shown.
  raw_ptr<Suggester> current_suggester_ = nullptr;

  std::optional<AssistiveSuggesterSwitch::EnabledSuggestions>
      enabled_suggestions_from_last_onfocus_;

  std::u16string last_surrounding_text_ = u"";

  // Keeps track if there is a key being held down currently which was already
  // recorded for the auto repeat suppressed metric.
  bool auto_repeat_suppress_metric_emitted_ = false;

  int last_cursor_pos_ = 0;

  TextInputMethod::InputContext context_;

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

}  // namespace ash::input_method

#endif  // CHROME_BROWSER_ASH_INPUT_METHOD_ASSISTIVE_SUGGESTER_H_