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

// 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.

#include "chrome/browser/ash/input_method/native_input_method_engine.h"

#include <utility>

#include "ash/constants/ash_features.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/ash/input_method/assistive_suggester_client_filter.h"
#include "chrome/browser/ash/input_method/assistive_suggester_switch.h"
#include "chrome/browser/ash/input_method/autocorrect_manager.h"
#include "chrome/browser/ash/input_method/editor_mediator_factory.h"
#include "chrome/browser/ash/input_method/get_current_window_properties.h"
#include "chrome/browser/ash/input_method/grammar_service_client.h"
#include "chrome/browser/ash/input_method/native_input_method_engine_observer.h"
#include "chrome/browser/ash/input_method/suggestions_service_client.h"
#include "chrome/browser/ui/ash/input_method/input_method_menu_manager.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/prefs/pref_service.h"
#include "ui/base/ime/ash/input_method_manager.h"

namespace ash {

namespace input_method {
namespace {
bool ShouldRouteToFirstPartyVietnameseInput(std::string_view engine_id) {
  return base::FeatureList::IsEnabled(features::kFirstPartyVietnameseInput) &&
         (engine_id == "vkd_vi_vni" || engine_id == "vkd_vi_telex");
}
}  // namespace

NativeInputMethodEngine::NativeInputMethodEngine()
    : NativeInputMethodEngine(/*use_ime_service=*/true) {}

NativeInputMethodEngine::NativeInputMethodEngine(bool use_ime_service)
    : use_ime_service_(use_ime_service) {}

// static
std::unique_ptr<NativeInputMethodEngine>
NativeInputMethodEngine::CreateForTesting(bool use_ime_service) {
  return base::WrapUnique<NativeInputMethodEngine>(
      new NativeInputMethodEngine(use_ime_service));
}

NativeInputMethodEngine::~NativeInputMethodEngine() = default;

NativeInputMethodEngine::NativeInputMethodEngine(
    std::unique_ptr<AssistiveSuggesterSwitch> suggester_switch)
    : suggester_switch_(std::move(suggester_switch)) {}

void NativeInputMethodEngine::Initialize(
    std::unique_ptr<InputMethodEngineObserver> observer,
    const char* extension_id,
    Profile* profile) {
  // TODO(crbug.com/40154142): refactor the mix of unique and raw ptr here.
  std::unique_ptr<AssistiveSuggester> assistive_suggester =
      std::make_unique<AssistiveSuggester>(
          this, profile,
          suggester_switch_
              ? std::move(suggester_switch_)
              : std::make_unique<AssistiveSuggesterClientFilter>(
                    base::BindRepeating(&GetFocusedTabUrl),
                    base::BindRepeating(&GetFocusedWindowProperties)));
  assistive_suggester_ = assistive_suggester.get();
  std::unique_ptr<AutocorrectManager> autocorrect_manager =
      std::make_unique<AutocorrectManager>(this, profile);
  autocorrect_manager_ = autocorrect_manager.get();

  auto suggestions_service_client =
      base::FeatureList::IsEnabled(features::kAssistMultiWord)
          ? std::make_unique<SuggestionsServiceClient>()
          : nullptr;

  auto suggestions_collector =
      base::FeatureList::IsEnabled(features::kAssistMultiWord)
          ? std::make_unique<SuggestionsCollector>(
                assistive_suggester_, std::move(suggestions_service_client))
          : nullptr;

  EditorMediator* editor_event_sink =
      chromeos::features::IsOrcaEnabled()
          ? EditorMediatorFactory::GetInstance()->GetForProfile(profile)
          : nullptr;

  chrome_keyboard_controller_client_observer_.Observe(
      ChromeKeyboardControllerClient::Get());

  // Wrap the given observer in our observer that will decide whether to call
  // Mojo directly or forward to the extension.
  auto native_observer = std::make_unique<NativeInputMethodEngineObserver>(
      profile->GetPrefs(), editor_event_sink, std::move(observer),
      std::move(assistive_suggester), std::move(autocorrect_manager),
      std::move(suggestions_collector),
      std::make_unique<GrammarManager>(
          profile, std::make_unique<GrammarServiceClient>(), this),
      use_ime_service_);
  InputMethodEngine::Initialize(std::move(native_observer), extension_id,
                                profile);
}

bool NativeInputMethodEngine::ShouldRouteToNativeMojoEngine(
    const std::string& engine_id) const {
  return use_ime_service_ && CanRouteToNativeMojoEngine(engine_id);
}

void NativeInputMethodEngine::CandidateClicked(uint32_t index) {
  // The parent implementation will try to convert `index` into a candidate ID.
  // The native Mojo engine doesn't use candidate IDs, so we just treat `index`
  // as the ID, without doing a mapping.
  if (ShouldRouteToNativeMojoEngine(GetActiveComponentId())) {
    GetNativeObserver()->OnCandidateClicked(GetActiveComponentId(), index,
                                            MOUSE_BUTTON_LEFT);
  } else {
    InputMethodEngine::CandidateClicked(index);
  }
}

bool NativeInputMethodEngine::IsReadyForTesting() {
  if (ShouldRouteToNativeMojoEngine(GetActiveComponentId())) {
    return GetNativeObserver()->IsReadyForTesting();  // IN-TEST
  }
  return InputMethodEngine::IsReadyForTesting();
}

void NativeInputMethodEngine::OnKeyboardEnabledChanged(bool enabled) {
  // Re-activate the engine whenever the virtual keyboard is enabled or disabled
  // so that the native or extension state is reset correctly.
  Enable(GetActiveComponentId());
}

void NativeInputMethodEngine::OnProfileWillBeDestroyed(Profile* profile) {
  InputMethodEngine::OnProfileWillBeDestroyed(profile);
  GetNativeObserver()->OnProfileWillBeDestroyed();
}

void NativeInputMethodEngine::FlushForTesting() {
  GetNativeObserver()->FlushForTesting();
}

bool NativeInputMethodEngine::IsConnectedForTesting() const {
  return GetNativeObserver()->IsConnectedForTesting();
}

void NativeInputMethodEngine::OnAutocorrect(
    const std::u16string& typed_word,
    const std::u16string& corrected_word,
    int start_index) {
  autocorrect_manager_->HandleAutocorrect(
      gfx::Range(start_index, start_index + corrected_word.length()),
      typed_word, corrected_word);
}

NativeInputMethodEngineObserver* NativeInputMethodEngine::GetNativeObserver()
    const {
  return static_cast<NativeInputMethodEngineObserver*>(observer_.get());
}

bool NativeInputMethodEngine::UpdateMenuItems(
    const std::vector<InputMethodManager::MenuItem>& items,
    std::string* error) {
  // Ignore calls to UpdateMenuItems when the native Mojo engine is active.
  // The menu items are stored in a singleton that is shared between the native
  // Mojo engine and the extension. This method is called when the extension
  // wants to update the menu items.
  // Ignore this if the native Mojo engine is active to prevent the extension
  // from overriding the menu items set by the native Mojo engine.
  if (ShouldRouteToNativeMojoEngine(GetActiveComponentId())) {
    return true;
  }

  return InputMethodEngine::UpdateMenuItems(items, error);
}

void NativeInputMethodEngine::OnInputMethodOptionsChanged() {
  if (ShouldRouteToNativeMojoEngine(GetActiveComponentId()) ||
      ShouldRouteToFirstPartyVietnameseInput(GetActiveComponentId())) {
    Enable(GetActiveComponentId());
  } else {
    InputMethodEngine::OnInputMethodOptionsChanged();
  }
}

}  // namespace input_method
}  // namespace ash