| // 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 "chrome/browser/chromeos/input_method/native_input_method_engine.h" |
| |
| #include "ash/constants/ash_features.h" |
| #include "base/feature_list.h" |
| #include "base/i18n/i18n_constants.h" |
| #include "base/i18n/icu_string_conversions.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_offset_string_conversions.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/chromeos/input_method/autocorrect_manager.h" |
| #include "chrome/browser/chromeos/input_method/grammar_service_client.h" |
| #include "chrome/browser/chromeos/input_method/suggestions_service_client.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/settings_window_manager_chromeos.h" |
| #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "ui/base/ime/chromeos/ime_bridge.h" |
| #include "ui/base/ime/chromeos/input_method_manager.h" |
| #include "ui/base/ime/chromeos/input_method_ukm.h" |
| #include "ui/events/keycodes/dom/keycode_converter.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| // Returns the current input context. This may change during the session, even |
| // if the IME engine does not change. |
| ui::IMEInputContextHandlerInterface* GetInputContext() { |
| return ui::IMEBridge::Get()->GetInputContextHandler(); |
| } |
| |
| // TODO(https://crbug/1166082): Use a separate InputMethodEngine for rule-based. |
| bool ShouldRouteToRuleBasedEngine(const std::string& engine_id) { |
| return base::StartsWith(engine_id, "vkd_", base::CompareCase::SENSITIVE); |
| } |
| |
| bool ShouldRouteToFstMojoEngine(const std::string& engine_id) { |
| // To avoid handling tricky cases where the user types with both the virtual |
| // and the physical keyboard, only run the native code path if the virtual |
| // keyboard is disabled. Otherwise, just let the extension handle any physical |
| // key events. |
| return features::IsSystemLatinPhysicalTypingEnabled() && |
| base::StartsWith(engine_id, "xkb:", base::CompareCase::SENSITIVE) && |
| !ChromeKeyboardControllerClient::Get()->GetKeyboardEnabled(); |
| } |
| |
| bool IsPhysicalKeyboardAutocorrectEnabled(PrefService* prefs, |
| const std::string& engine_id) { |
| if (!prefs) { |
| return false; |
| } |
| |
| // The FST Mojo engine is only needed if autocorrect is enabled. |
| const base::DictionaryValue* input_method_settings = |
| prefs->GetDictionary(prefs::kLanguageInputMethodSpecificSettings); |
| const base::Value* autocorrect_setting = input_method_settings->FindPath( |
| engine_id + ".physicalKeyboardAutoCorrectionLevel"); |
| return autocorrect_setting && autocorrect_setting->GetIfInt().value_or(0) > 0; |
| } |
| |
| std::string NormalizeRuleBasedEngineId(const std::string engine_id) { |
| // For legacy reasons, |engine_id| starts with "vkd_" in the input method |
| // manifest, but the InputEngineManager expects the prefix "m17n:". |
| // TODO(https://crbug.com/1012490): Migrate to m17n prefix and remove this. |
| if (base::StartsWith(engine_id, "vkd_", base::CompareCase::SENSITIVE)) { |
| return "m17n:" + engine_id.substr(4); |
| } |
| return engine_id; |
| } |
| |
| std::u16string ConvertToUtf16AndNormalize(const std::string& str) { |
| // TODO(https://crbug.com/1185629): Add a new helper in |
| // base/i18n/icu_string_conversions.h that does the conversion directly |
| // without a redundant UTF16->UTF8 conversion. |
| std::string normalized_str; |
| base::ConvertToUtf8AndNormalize(str, base::kCodepageUTF8, &normalized_str); |
| return base::UTF8ToUTF16(normalized_str); |
| } |
| |
| ime::mojom::ModifierStatePtr ModifierStateFromEvent(const ui::KeyEvent& event) { |
| auto modifier_state = ime::mojom::ModifierState::New(); |
| modifier_state->alt = event.flags() & ui::EF_ALT_DOWN; |
| modifier_state->alt_graph = event.flags() & ui::EF_ALTGR_DOWN; |
| modifier_state->caps_lock = event.flags() & ui::EF_CAPS_LOCK_ON; |
| modifier_state->control = event.flags() & ui::EF_CONTROL_DOWN; |
| modifier_state->shift = event.flags() & ui::EF_SHIFT_DOWN; |
| return modifier_state; |
| } |
| |
| ime::mojom::InputFieldType TextInputTypeToMojoType(ui::TextInputType type) { |
| using ime::mojom::InputFieldType; |
| switch (type) { |
| case ui::TEXT_INPUT_TYPE_PASSWORD: |
| return InputFieldType::kPassword; |
| case ui::TEXT_INPUT_TYPE_SEARCH: |
| return InputFieldType::kSearch; |
| case ui::TEXT_INPUT_TYPE_EMAIL: |
| return InputFieldType::kEmail; |
| case ui::TEXT_INPUT_TYPE_TELEPHONE: |
| return InputFieldType::kTelephone; |
| case ui::TEXT_INPUT_TYPE_URL: |
| return InputFieldType::kURL; |
| case ui::TEXT_INPUT_TYPE_NUMBER: |
| return InputFieldType::kNumber; |
| case ui::TEXT_INPUT_TYPE_NULL: |
| return InputFieldType::kNoIME; |
| case ui::TEXT_INPUT_TYPE_TEXT: |
| return InputFieldType::kText; |
| default: |
| return InputFieldType::kText; |
| } |
| } |
| |
| ime::mojom::AutocorrectMode AutocorrectFlagsToMojoType(int flags) { |
| if ((flags & ui::TEXT_INPUT_FLAG_AUTOCORRECT_OFF) || |
| (flags & ui::TEXT_INPUT_FLAG_SPELLCHECK_OFF)) { |
| return ime::mojom::AutocorrectMode::kDisabled; |
| } |
| return ime::mojom::AutocorrectMode::kEnabled; |
| } |
| |
| enum class ImeServiceEvent { |
| kUnknown = 0, |
| kInitSuccess = 1, |
| kInitFailed = 2, |
| kActivateImeSuccess = 3, |
| kActivateImeFailed = 4, |
| kServiceDisconnected = 5, |
| kMaxValue = kServiceDisconnected |
| }; |
| |
| void LogEvent(ImeServiceEvent event) { |
| UMA_HISTOGRAM_ENUMERATION("InputMethod.Mojo.Extension.Event", event); |
| } |
| |
| ime::mojom::PhysicalKeyEventPtr CreatePhysicalKeyEventFromKeyEvent( |
| const ui::KeyEvent& event) { |
| return ime::mojom::PhysicalKeyEvent::New( |
| event.type() == ui::ET_KEY_PRESSED ? ime::mojom::KeyEventType::kKeyDown |
| : ime::mojom::KeyEventType::kKeyUp, |
| ui::KeycodeConverter::DomCodeToCodeString(event.code()), |
| ui::KeycodeConverter::DomKeyToKeyString(event.GetDomKey()), |
| ModifierStateFromEvent(event)); |
| } |
| |
| } // namespace |
| |
| NativeInputMethodEngine::NativeInputMethodEngine() = default; |
| |
| NativeInputMethodEngine::~NativeInputMethodEngine() = default; |
| |
| void NativeInputMethodEngine::Initialize( |
| std::unique_ptr<InputMethodEngineBase::Observer> observer, |
| const char* extension_id, |
| Profile* profile) { |
| // TODO(crbug/1141231): refactor the mix of unique and raw ptr here. |
| std::unique_ptr<AssistiveSuggester> assistive_suggester = |
| std::make_unique<AssistiveSuggester>(this, profile); |
| assistive_suggester_ = assistive_suggester.get(); |
| std::unique_ptr<AutocorrectManager> autocorrect_manager = |
| std::make_unique<AutocorrectManager>(this); |
| autocorrect_manager_ = autocorrect_manager.get(); |
| |
| auto suggestions_service_client = |
| base::FeatureList::IsEnabled(chromeos::features::kAssistMultiWord) |
| ? std::make_unique<SuggestionsServiceClient>() |
| : nullptr; |
| |
| auto suggestions_collector = |
| base::FeatureList::IsEnabled(chromeos::features::kAssistMultiWord) |
| ? std::make_unique<SuggestionsCollector>( |
| assistive_suggester_, std::move(suggestions_service_client)) |
| : 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<chromeos::NativeInputMethodEngine::ImeObserver>( |
| profile->GetPrefs(), std::move(observer), |
| std::move(assistive_suggester), std::move(autocorrect_manager), |
| std::move(suggestions_collector), |
| std::make_unique<GrammarManager>( |
| profile, std::make_unique<GrammarServiceClient>())); |
| InputMethodEngine::Initialize(std::move(native_observer), extension_id, |
| profile); |
| } |
| |
| 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); |
| } |
| |
| NativeInputMethodEngine::ImeObserver* |
| NativeInputMethodEngine::GetNativeObserver() const { |
| return static_cast<ImeObserver*>(observer_.get()); |
| } |
| |
| NativeInputMethodEngine::ImeObserver::ImeObserver( |
| PrefService* prefs, |
| std::unique_ptr<InputMethodEngineBase::Observer> ime_base_observer, |
| std::unique_ptr<AssistiveSuggester> assistive_suggester, |
| std::unique_ptr<AutocorrectManager> autocorrect_manager, |
| std::unique_ptr<SuggestionsCollector> suggestions_collector, |
| std::unique_ptr<GrammarManager> grammar_manager) |
| : prefs_(prefs), |
| ime_base_observer_(std::move(ime_base_observer)), |
| receiver_from_engine_(this), |
| assistive_suggester_(std::move(assistive_suggester)), |
| autocorrect_manager_(std::move(autocorrect_manager)), |
| suggestions_collector_(std::move(suggestions_collector)), |
| grammar_manager_(std::move(grammar_manager)) {} |
| |
| NativeInputMethodEngine::ImeObserver::~ImeObserver() = default; |
| |
| void NativeInputMethodEngine::ImeObserver::OnActivate( |
| const std::string& engine_id) { |
| // TODO(b/181077907): Always launch the IME service and let IME service decide |
| // whether it should shutdown or not. |
| if (ShouldRouteToFstMojoEngine(engine_id) && |
| !IsPhysicalKeyboardAutocorrectEnabled(prefs_, engine_id)) { |
| remote_manager_.reset(); |
| remote_to_engine_.reset(); |
| receiver_from_engine_.reset(); |
| return; |
| } |
| |
| if (ShouldRouteToRuleBasedEngine(engine_id)) { |
| if (!remote_manager_.is_bound()) { |
| auto* ime_manager = input_method::InputMethodManager::Get(); |
| ime_manager->ConnectInputEngineManager( |
| remote_manager_.BindNewPipeAndPassReceiver()); |
| remote_manager_.set_disconnect_handler(base::BindOnce( |
| &ImeObserver::OnError, base::Unretained(this), base::Time::Now())); |
| LogEvent(ImeServiceEvent::kInitSuccess); |
| } |
| |
| const auto new_engine_id = NormalizeRuleBasedEngineId(engine_id); |
| |
| // Deactivate any existing engine. |
| remote_to_engine_.reset(); |
| receiver_from_engine_.reset(); |
| |
| remote_manager_->ConnectToImeEngine( |
| new_engine_id, remote_to_engine_.BindNewPipeAndPassReceiver(), |
| receiver_from_engine_.BindNewPipeAndPassRemote(), {}, |
| base::BindOnce(&ImeObserver::OnConnected, base::Unretained(this), |
| base::Time::Now(), new_engine_id)); |
| |
| // Notify the virtual keyboard extension that the IME has changed. |
| ime_base_observer_->OnActivate(engine_id); |
| } else if (ShouldRouteToFstMojoEngine(engine_id)) { |
| if (!remote_manager_.is_bound()) { |
| auto* ime_manager = input_method::InputMethodManager::Get(); |
| ime_manager->ConnectInputEngineManager( |
| remote_manager_.BindNewPipeAndPassReceiver()); |
| remote_manager_.set_disconnect_handler(base::BindOnce( |
| &ImeObserver::OnError, base::Unretained(this), base::Time::Now())); |
| LogEvent(ImeServiceEvent::kInitSuccess); |
| } |
| |
| // Deactivate any existing engine. |
| remote_to_engine_.reset(); |
| receiver_from_engine_.reset(); |
| |
| remote_manager_->ConnectToImeEngine( |
| engine_id, remote_to_engine_.BindNewPipeAndPassReceiver(), |
| receiver_from_engine_.BindNewPipeAndPassRemote(), {}, |
| base::BindOnce(&ImeObserver::OnConnected, base::Unretained(this), |
| base::Time::Now(), engine_id)); |
| |
| // `ConnectToImeEngine` doesn't actually activate the IME in the IME |
| // service. We must call `OnInputMethodChanged` as well. |
| remote_to_engine_->OnInputMethodChanged(engine_id); |
| } else { |
| // Release the IME service. |
| // TODO(b/147709499): A better way to cleanup all. |
| remote_manager_.reset(); |
| remote_to_engine_.reset(); |
| receiver_from_engine_.reset(); |
| |
| ime_base_observer_->OnActivate(engine_id); |
| } |
| } |
| void NativeInputMethodEngine::ImeObserver::ProcessMessage( |
| const std::vector<uint8_t>& message, |
| ProcessMessageCallback callback) { |
| // NativeInputMethodEngine doesn't use binary messages, but it must run the |
| // callback to avoid dropping the connection. |
| std::move(callback).Run(std::vector<uint8_t>()); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnFocus( |
| const std::string& engine_id, |
| int context_id, |
| const IMEEngineHandlerInterface::InputContext& context) { |
| if (assistive_suggester_->IsAssistiveFeatureEnabled()) { |
| assistive_suggester_->OnFocus(context_id); |
| } |
| autocorrect_manager_->OnFocus(context_id); |
| if (grammar_manager_->IsOnDeviceGrammarEnabled()) { |
| grammar_manager_->OnFocus(context_id); |
| } |
| if (ShouldRouteToFstMojoEngine(engine_id)) { |
| if (remote_to_engine_.is_bound()) { |
| remote_to_engine_->OnFocus(ime::mojom::InputFieldInfo::New( |
| TextInputTypeToMojoType(context.type), |
| AutocorrectFlagsToMojoType(context.flags), |
| context.should_do_learning |
| ? ime::mojom::PersonalizationMode::kEnabled |
| : ime::mojom::PersonalizationMode::kDisabled)); |
| } |
| } else { |
| ime_base_observer_->OnFocus(engine_id, context_id, context); |
| } |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnBlur(const std::string& engine_id, |
| int context_id) { |
| if (assistive_suggester_->IsAssistiveFeatureEnabled()) |
| assistive_suggester_->OnBlur(); |
| |
| if (ShouldRouteToFstMojoEngine(engine_id)) { |
| if (remote_to_engine_.is_bound()) { |
| remote_to_engine_->OnBlur(); |
| } |
| } else { |
| ime_base_observer_->OnBlur(engine_id, context_id); |
| } |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnKeyEvent( |
| const std::string& engine_id, |
| const ui::KeyEvent& event, |
| ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback) { |
| if (assistive_suggester_->IsAssistiveFeatureEnabled()) { |
| if (assistive_suggester_->OnKeyEvent(event)) { |
| std::move(callback).Run(true); |
| return; |
| } |
| } |
| if (autocorrect_manager_->OnKeyEvent(event)) { |
| std::move(callback).Run(true); |
| return; |
| } |
| if (grammar_manager_->IsOnDeviceGrammarEnabled() && |
| grammar_manager_->OnKeyEvent(event)) { |
| std::move(callback).Run(true); |
| return; |
| } |
| |
| if (ShouldRouteToRuleBasedEngine(engine_id) && remote_to_engine_.is_bound()) { |
| remote_to_engine_->ProcessKeypressForRulebased( |
| CreatePhysicalKeyEventFromKeyEvent(event), |
| base::BindOnce(&ImeObserver::OnRuleBasedKeyEventResponse, |
| base::Unretained(this), base::Time::Now(), |
| std::move(callback))); |
| } else if (ShouldRouteToFstMojoEngine(engine_id)) { |
| if (remote_to_engine_.is_bound()) { |
| // CharacterComposer only takes KEY_PRESSED events. |
| const bool filtered = event.type() == ui::ET_KEY_PRESSED && |
| character_composer_.FilterKeyPress(event); |
| |
| // Don't send dead keys to the system IME. Dead keys should be handled at |
| // the OS level and not exposed to IMEs. |
| if (event.GetDomKey().IsDeadKey()) { |
| std::move(callback).Run(true); |
| return; |
| } |
| |
| auto key_event = CreatePhysicalKeyEventFromKeyEvent(event); |
| if (filtered) { |
| // TODO(b/174612548): Transform the corresponding KEY_RELEASED event to |
| // use the composed character as well. |
| key_event->key = |
| base::UTF16ToUTF8(character_composer_.composed_character()); |
| } |
| |
| remote_to_engine_->OnKeyEvent(std::move(key_event), std::move(callback)); |
| } else { |
| std::move(callback).Run(false); |
| } |
| } else { |
| ime_base_observer_->OnKeyEvent(engine_id, event, std::move(callback)); |
| } |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnReset( |
| const std::string& engine_id) { |
| if (remote_to_engine_.is_bound() && ShouldRouteToRuleBasedEngine(engine_id)) { |
| remote_to_engine_->ResetForRulebased(); |
| } else if (ShouldRouteToFstMojoEngine(engine_id)) { |
| if (remote_to_engine_.is_bound()) { |
| remote_to_engine_->OnCompositionCanceled(); |
| } |
| } else { |
| ime_base_observer_->OnReset(engine_id); |
| } |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnDeactivated( |
| const std::string& engine_id) { |
| if (ShouldRouteToRuleBasedEngine(engine_id)) { |
| remote_to_engine_.reset(); |
| } |
| ime_base_observer_->OnDeactivated(engine_id); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnCompositionBoundsChanged( |
| const std::vector<gfx::Rect>& bounds) { |
| ime_base_observer_->OnCompositionBoundsChanged(bounds); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnSurroundingTextChanged( |
| const std::string& engine_id, |
| const std::u16string& text, |
| int cursor_pos, |
| int anchor_pos, |
| int offset_pos) { |
| assistive_suggester_->RecordAssistiveMatchMetrics(text, cursor_pos, |
| anchor_pos); |
| if (assistive_suggester_->IsAssistiveFeatureEnabled()) { |
| assistive_suggester_->OnSurroundingTextChanged(text, cursor_pos, |
| anchor_pos); |
| } |
| autocorrect_manager_->OnSurroundingTextChanged(text, cursor_pos, anchor_pos); |
| if (grammar_manager_->IsOnDeviceGrammarEnabled()) { |
| grammar_manager_->OnSurroundingTextChanged(text, cursor_pos, anchor_pos); |
| } |
| if (ShouldRouteToFstMojoEngine(engine_id)) { |
| if (remote_to_engine_.is_bound()) { |
| std::vector<size_t> selection_indices = {anchor_pos, cursor_pos}; |
| std::string utf8_text = |
| base::UTF16ToUTF8AndAdjustOffsets(text, &selection_indices); |
| |
| auto selection = ime::mojom::SelectionRange::New(); |
| selection->anchor = selection_indices[0]; |
| selection->focus = selection_indices[1]; |
| |
| remote_to_engine_->OnSurroundingTextChanged( |
| std::move(utf8_text), offset_pos, std::move(selection)); |
| } |
| } else { |
| ime_base_observer_->OnSurroundingTextChanged(engine_id, text, cursor_pos, |
| anchor_pos, offset_pos); |
| } |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnCandidateClicked( |
| const std::string& component_id, |
| int candidate_id, |
| InputMethodEngineBase::MouseButtonEvent button) { |
| ime_base_observer_->OnCandidateClicked(component_id, candidate_id, button); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnAssistiveWindowButtonClicked( |
| const ui::ime::AssistiveWindowButton& button) { |
| switch (button.id) { |
| case ui::ime::ButtonId::kSmartInputsSettingLink: |
| base::RecordAction(base::UserMetricsAction( |
| "ChromeOS.Settings.SmartInputs.PersonalInfoSuggestions.Open")); |
| // TODO(crbug/1101689): Add subpath for personal info suggestions |
| // settings. |
| chrome::SettingsWindowManager::GetInstance()->ShowOSSettings( |
| ProfileManager::GetActiveUserProfile(), |
| chromeos::settings::mojom::kSmartInputsSubpagePath); |
| break; |
| case ui::ime::ButtonId::kLearnMore: |
| if (button.window_type == |
| ui::ime::AssistiveWindowType::kEmojiSuggestion) { |
| base::RecordAction(base::UserMetricsAction( |
| "ChromeOS.Settings.SmartInputs.EmojiSuggestions.Open")); |
| // TODO(crbug/1101689): Add subpath for emoji suggestions settings. |
| chrome::SettingsWindowManager::GetInstance()->ShowOSSettings( |
| ProfileManager::GetActiveUserProfile(), |
| chromeos::settings::mojom::kSmartInputsSubpagePath); |
| } |
| break; |
| case ui::ime::ButtonId::kSuggestion: |
| if (assistive_suggester_->IsAssistiveFeatureEnabled()) { |
| assistive_suggester_->AcceptSuggestion(button.index); |
| } |
| break; |
| case ui::ime::ButtonId::kUndo: |
| autocorrect_manager_->UndoAutocorrect(); |
| break; |
| case ui::ime::ButtonId::kAddToDictionary: |
| case ui::ime::ButtonId::kNone: |
| ime_base_observer_->OnAssistiveWindowButtonClicked(button); |
| break; |
| } |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnMenuItemActivated( |
| const std::string& component_id, |
| const std::string& menu_id) { |
| ime_base_observer_->OnMenuItemActivated(component_id, menu_id); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnScreenProjectionChanged( |
| bool is_projected) { |
| ime_base_observer_->OnScreenProjectionChanged(is_projected); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnSuggestionsGathered( |
| RequestSuggestionsCallback callback, |
| ime::mojom::SuggestionsResponsePtr response) { |
| std::move(callback).Run(std::move(response)); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnSuggestionsChanged( |
| const std::vector<std::string>& suggestions) { |
| ime_base_observer_->OnSuggestionsChanged(suggestions); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnInputMethodOptionsChanged( |
| const std::string& engine_id) { |
| ime_base_observer_->OnInputMethodOptionsChanged(engine_id); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::CommitText( |
| const std::string& text, |
| ime::mojom::CommitTextCursorBehavior cursor_behavior) { |
| GetInputContext()->CommitText( |
| ConvertToUtf16AndNormalize(text), |
| cursor_behavior == |
| ime::mojom::CommitTextCursorBehavior::kMoveCursorBeforeText |
| ? ui::TextInputClient::InsertTextCursorBehavior::kMoveCursorBeforeText |
| : ui::TextInputClient::InsertTextCursorBehavior:: |
| kMoveCursorAfterText); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::SetComposition( |
| const std::string& text) { |
| ui::CompositionText composition; |
| composition.text = ConvertToUtf16AndNormalize(text); |
| // TODO(b/151884011): Turn on underlining for composition-based languages. |
| composition.ime_text_spans = {ui::ImeTextSpan( |
| ui::ImeTextSpan::Type::kComposition, 0, composition.text.length(), |
| ui::ImeTextSpan::Thickness::kNone, |
| ui::ImeTextSpan::UnderlineStyle::kNone)}; |
| GetInputContext()->UpdateCompositionText( |
| std::move(composition), /*cursor_pos=*/composition.text.length(), |
| /*visible=*/true); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::SetCompositionRange( |
| uint32_t start_byte_index, |
| uint32_t end_byte_index) { |
| const auto ordered_range = std::minmax(start_byte_index, end_byte_index); |
| // TODO(b/151884011): Turn on underlining for composition-based languages. |
| GetInputContext()->SetComposingRange( |
| ordered_range.first, ordered_range.second, |
| {ui::ImeTextSpan( |
| ui::ImeTextSpan::Type::kComposition, /*start_offset=*/0, |
| /*end_offset=*/ordered_range.second - ordered_range.first, |
| ui::ImeTextSpan::Thickness::kNone, |
| ui::ImeTextSpan::UnderlineStyle::kNone)}); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::FinishComposition() { |
| GetInputContext()->ConfirmCompositionText(/*reset_engine=*/false, |
| /*keep_selection=*/true); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::DeleteSurroundingText( |
| uint32_t num_bytes_before_cursor, |
| uint32_t num_bytes_after_cursor) { |
| GetInputContext()->DeleteSurroundingText( |
| /*offset=*/-static_cast<int>(num_bytes_before_cursor), |
| /*length=*/num_bytes_before_cursor + num_bytes_after_cursor); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::HandleAutocorrect( |
| ime::mojom::AutocorrectSpanPtr autocorrect_span) { |
| autocorrect_manager_->HandleAutocorrect( |
| autocorrect_span->autocorrect_range, |
| base::UTF8ToUTF16(autocorrect_span->original_text), |
| base::UTF8ToUTF16(autocorrect_span->current_text)); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::RequestSuggestions( |
| ime::mojom::SuggestionsRequestPtr request, |
| RequestSuggestionsCallback callback) { |
| suggestions_collector_->GatherSuggestions( |
| std::move(request), |
| base::BindOnce( |
| &NativeInputMethodEngine::ImeObserver::OnSuggestionsGathered, |
| base::Unretained(this), std::move(callback))); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::DisplaySuggestions( |
| const std::vector<ime::TextSuggestion>& suggestions) { |
| assistive_suggester_->OnExternalSuggestionsUpdated(suggestions); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::RecordUkm( |
| ime::mojom::UkmEntryPtr entry) { |
| if (entry->is_non_compliant_api()) { |
| ui::RecordUkmNonCompliantApi( |
| GetInputContext()->GetClientSourceForMetrics(), |
| static_cast<int>( |
| entry->get_non_compliant_api()->non_compliant_operation)); |
| } |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::FlushForTesting() { |
| remote_manager_.FlushForTesting(); |
| if (remote_to_engine_.is_bound()) |
| receiver_from_engine_.FlushForTesting(); |
| if (remote_to_engine_.is_bound()) |
| remote_to_engine_.FlushForTesting(); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnConnected(base::Time start, |
| std::string engine_id, |
| bool bound) { |
| LogEvent(bound ? ImeServiceEvent::kActivateImeSuccess |
| : ImeServiceEvent::kActivateImeSuccess); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnError(base::Time start) { |
| LOG(ERROR) << "IME Service connection error"; |
| |
| // If the Mojo pipe disconnection happens in 1000 ms after the service |
| // is initialized, we consider it as a failure. Normally it's caused |
| // by the Mojo service itself or misconfigured on Chrome OS. |
| if (base::Time::Now() - start < base::TimeDelta::FromMilliseconds(1000)) { |
| LogEvent(ImeServiceEvent::kInitFailed); |
| } else { |
| LogEvent(ImeServiceEvent::kServiceDisconnected); |
| } |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnRuleBasedKeyEventResponse( |
| base::Time start, |
| ui::IMEEngineHandlerInterface::KeyEventDoneCallback callback, |
| ime::mojom::KeypressResponseForRulebasedPtr response) { |
| for (const auto& op : response->operations) { |
| switch (op->method) { |
| case ime::mojom::OperationMethodForRulebased::COMMIT_TEXT: |
| GetInputContext()->CommitText( |
| ConvertToUtf16AndNormalize(op->arguments), |
| ui::TextInputClient::InsertTextCursorBehavior:: |
| kMoveCursorAfterText); |
| break; |
| case ime::mojom::OperationMethodForRulebased::SET_COMPOSITION: |
| ui::CompositionText composition; |
| composition.text = ConvertToUtf16AndNormalize(op->arguments); |
| GetInputContext()->UpdateCompositionText( |
| composition, composition.text.length(), /*visible=*/true); |
| break; |
| } |
| } |
| std::move(callback).Run(response->result); |
| } |
| |
| void NativeInputMethodEngine::ImeObserver::OnProfileWillBeDestroyed() { |
| prefs_ = nullptr; |
| } |
| |
| void NativeInputMethodEngine::OnInputMethodOptionsChanged() { |
| if (ShouldRouteToFstMojoEngine(GetActiveComponentId())) { |
| Enable(GetActiveComponentId()); |
| } else { |
| InputMethodEngine::OnInputMethodOptionsChanged(); |
| } |
| } |
| |
| } // namespace chromeos |