| // Copyright 2014 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/extensions/api/virtual_keyboard_private/chrome_virtual_keyboard_delegate.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "ash/public/cpp/keyboard/keyboard_switches.h" |
| #include "ash/public/cpp/keyboard/keyboard_types.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "base/strings/string16.h" |
| #include "chrome/browser/chromeos/login/lock/screen_locker.h" |
| #include "chrome/browser/chromeos/login/ui/user_adding_screen.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/ui/ash/keyboard/chrome_keyboard_controller_client.h" |
| #include "chrome/browser/ui/settings_window_manager_chromeos.h" |
| #include "chrome/browser/ui/webui/settings/chromeos/constants/routes.mojom.h" |
| #include "chrome/common/url_constants.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "components/user_manager/user_manager.h" |
| #include "content/public/browser/audio_service.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/common/api/virtual_keyboard.h" |
| #include "extensions/common/api/virtual_keyboard_private.h" |
| #include "media/audio/audio_system.h" |
| #include "ui/aura/event_injector.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/ime/chromeos/ime_bridge.h" |
| #include "ui/base/ime/constants.h" |
| #include "ui/base/ime/input_method.h" |
| #include "ui/base/ime/text_input_client.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/events/event_utils.h" |
| #include "ui/events/keycodes/dom/dom_code.h" |
| #include "ui/events/keycodes/dom/dom_key.h" |
| #include "ui/events/keycodes/dom/keycode_converter.h" |
| #include "ui/events/keycodes/keyboard_code_conversion.h" |
| |
| namespace keyboard_api = extensions::api::virtual_keyboard_private; |
| |
| namespace { |
| |
| // The hotrod keyboard must be enabled for each session and will remain enabled |
| // until / unless it is explicitly disabled. |
| bool g_hotrod_keyboard_enabled = false; |
| |
| std::string GenerateFeatureFlag(const std::string& feature, bool enabled) { |
| return feature + (enabled ? "-enabled" : "-disabled"); |
| } |
| |
| keyboard::ContainerType ConvertKeyboardModeToContainerType(int mode) { |
| switch (mode) { |
| case keyboard_api::KEYBOARD_MODE_FULL_WIDTH: |
| return keyboard::ContainerType::kFullWidth; |
| case keyboard_api::KEYBOARD_MODE_FLOATING: |
| return keyboard::ContainerType::kFloating; |
| } |
| |
| NOTREACHED(); |
| return keyboard::ContainerType::kFullWidth; |
| } |
| |
| // Returns the ui::TextInputClient of the active InputMethod or nullptr. |
| ui::TextInputClient* GetFocusedTextInputClient() { |
| ui::InputMethod* input_method = |
| ui::IMEBridge::Get()->GetInputContextHandler()->GetInputMethod(); |
| if (!input_method) |
| return nullptr; |
| |
| return input_method->GetTextInputClient(); |
| } |
| |
| const char kKeyDown[] = "keydown"; |
| const char kKeyUp[] = "keyup"; |
| |
| void SendProcessKeyEvent(ui::EventType type, aura::WindowTreeHost* host) { |
| ui::KeyEvent event(type, ui::VKEY_PROCESSKEY, ui::DomCode::NONE, |
| ui::EF_IS_SYNTHESIZED, ui::DomKey::PROCESS, |
| ui::EventTimeForNow()); |
| ui::EventDispatchDetails details = aura::EventInjector().Inject(host, &event); |
| CHECK(!details.dispatcher_destroyed); |
| } |
| |
| // Sends a fabricated key event, where |type| is the event type (which must be |
| // "keydown" or "keyup"), |key_value| is the unicode value of the character, |
| // |key_code| is the legacy key code value, |key_name| is the name of the key as |
| // defined in the DOM3 key event specification, and |modifier| indicates if any |
| // modifier keys are being virtually pressed. The event is dispatched to the |
| // active TextInputClient associated with |host|. |
| bool SendKeyEventImpl(const std::string& type, |
| int key_value, |
| int key_code, |
| const std::string& key_name, |
| int modifiers, |
| aura::WindowTreeHost* host) { |
| ui::EventType event_type; |
| if (type == kKeyDown) |
| event_type = ui::ET_KEY_PRESSED; |
| else if (type == kKeyUp) |
| event_type = ui::ET_KEY_RELEASED; |
| else |
| return false; |
| |
| ui::KeyboardCode code = static_cast<ui::KeyboardCode>(key_code); |
| |
| if (code == ui::VKEY_UNKNOWN) { |
| // Handling of special printable characters (e.g. accented characters) for |
| // which there is no key code. |
| if (event_type == ui::ET_KEY_RELEASED) { |
| // This can be null if no text input field is focused. |
| ui::TextInputClient* tic = GetFocusedTextInputClient(); |
| |
| SendProcessKeyEvent(ui::ET_KEY_PRESSED, host); |
| |
| ui::KeyEvent char_event(key_value, code, ui::DomCode::NONE, ui::EF_NONE); |
| if (tic) |
| tic->InsertChar(char_event); |
| SendProcessKeyEvent(ui::ET_KEY_RELEASED, host); |
| } |
| return true; |
| } |
| |
| ui::DomCode dom_code = ui::KeycodeConverter::CodeStringToDomCode(key_name); |
| if (dom_code == ui::DomCode::NONE) |
| dom_code = ui::UsLayoutKeyboardCodeToDomCode(code); |
| CHECK(dom_code != ui::DomCode::NONE); |
| |
| ui::KeyEvent event(event_type, code, dom_code, modifiers); |
| |
| // Indicate that the simulated key event is from the Virtual Keyboard. |
| ui::Event::Properties properties; |
| properties[ui::kPropertyFromVK] = |
| std::vector<uint8_t>(ui::kPropertyFromVKSize); |
| event.SetProperties(properties); |
| |
| ui::EventDispatchDetails details = aura::EventInjector().Inject(host, &event); |
| CHECK(!details.dispatcher_destroyed); |
| return true; |
| } |
| |
| std::string GetKeyboardLayout() { |
| // TODO(bshe): layout string is currently hard coded. We should use more |
| // standard keyboard layouts. |
| return ChromeKeyboardControllerClient::Get()->IsEnableFlagSet( |
| keyboard::KeyboardEnableFlag::kAccessibilityEnabled) |
| ? "system-qwerty" |
| : "qwerty"; |
| } |
| |
| } // namespace |
| |
| namespace extensions { |
| |
| ChromeVirtualKeyboardDelegate::ChromeVirtualKeyboardDelegate( |
| content::BrowserContext* browser_context) |
| : browser_context_(browser_context) { |
| weak_this_ = weak_factory_.GetWeakPtr(); |
| } |
| |
| ChromeVirtualKeyboardDelegate::~ChromeVirtualKeyboardDelegate() {} |
| |
| void ChromeVirtualKeyboardDelegate::GetKeyboardConfig( |
| OnKeyboardSettingsCallback on_settings_callback) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| if (!audio_system_) |
| audio_system_ = content::CreateAudioSystemForAudioService(); |
| audio_system_->HasInputDevices( |
| base::BindOnce(&ChromeVirtualKeyboardDelegate::OnHasInputDevices, |
| weak_this_, std::move(on_settings_callback))); |
| } |
| |
| void ChromeVirtualKeyboardDelegate::OnKeyboardConfigChanged() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| GetKeyboardConfig(base::Bind( |
| &ChromeVirtualKeyboardDelegate::DispatchConfigChangeEvent, weak_this_)); |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::HideKeyboard() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| auto* keyboard_client = ChromeKeyboardControllerClient::Get(); |
| if (!keyboard_client->is_keyboard_enabled()) |
| return false; |
| |
| // Pass HIDE_REASON_MANUAL since calls to HideKeyboard as part of this API |
| // would be user generated. |
| keyboard_client->HideKeyboard(ash::HideReason::kUser); |
| return true; |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::InsertText(const base::string16& text) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| ui::TextInputClient* tic = GetFocusedTextInputClient(); |
| if (!tic || tic->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) |
| return false; |
| |
| tic->InsertText(text); |
| return true; |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::OnKeyboardLoaded() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| base::RecordAction(base::UserMetricsAction("VirtualKeyboardLoaded")); |
| return true; |
| } |
| |
| void ChromeVirtualKeyboardDelegate::SetHotrodKeyboard(bool enable) { |
| if (g_hotrod_keyboard_enabled == enable) |
| return; |
| |
| g_hotrod_keyboard_enabled = enable; |
| |
| // This reloads virtual keyboard even if it exists. This ensures virtual |
| // keyboard gets the correct state of the hotrod keyboard through |
| // chrome.virtualKeyboardPrivate.getKeyboardConfig. |
| ChromeKeyboardControllerClient::Get()->RebuildKeyboardIfEnabled(); |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::LockKeyboard(bool state) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| auto* keyboard_client = ChromeKeyboardControllerClient::Get(); |
| if (!keyboard_client->is_keyboard_enabled()) |
| return false; |
| |
| keyboard_client->SetKeyboardLocked(state); |
| return true; |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::SendKeyEvent(const std::string& type, |
| int char_value, |
| int key_code, |
| const std::string& key_name, |
| int modifiers) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| aura::Window* window = |
| ChromeKeyboardControllerClient::Get()->GetKeyboardWindow(); |
| return window && SendKeyEventImpl(type, char_value, key_code, key_name, |
| modifiers, window->GetHost()); |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::ShowLanguageSettings() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| auto* keyboard_client = ChromeKeyboardControllerClient::Get(); |
| if (keyboard_client->is_keyboard_enabled()) |
| keyboard_client->HideKeyboard(ash::HideReason::kUser); |
| |
| base::RecordAction( |
| base::UserMetricsAction("VirtualKeyboard.OpenLanguageSettings")); |
| const std::string path = |
| base::FeatureList::IsEnabled( |
| ::chromeos::features::kLanguageSettingsUpdate) |
| ? chromeos::settings::mojom::kInputSubpagePath |
| : chromeos::settings::mojom::kLanguagesAndInputDetailsSubpagePath; |
| chrome::SettingsWindowManager::GetInstance()->ShowOSSettings( |
| ProfileManager::GetActiveUserProfile(), path); |
| return true; |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::ShowSuggestionSettings() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| auto* keyboard_client = ChromeKeyboardControllerClient::Get(); |
| if (keyboard_client->is_keyboard_enabled()) |
| keyboard_client->HideKeyboard(ash::HideReason::kUser); |
| |
| base::RecordAction( |
| base::UserMetricsAction("VirtualKeyboard.OpenSuggestionSettings")); |
| chrome::SettingsWindowManager::GetInstance()->ShowOSSettings( |
| ProfileManager::GetActiveUserProfile(), |
| chromeos::settings::mojom::kSmartInputsSubpagePath); |
| return true; |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::SetVirtualKeyboardMode( |
| int mode_enum, |
| gfx::Rect target_bounds, |
| OnSetModeCallback on_set_mode_callback) { |
| auto* keyboard_client = ChromeKeyboardControllerClient::Get(); |
| if (!keyboard_client->is_keyboard_enabled()) |
| return false; |
| |
| keyboard_client->SetContainerType( |
| ConvertKeyboardModeToContainerType(mode_enum), target_bounds, |
| std::move(on_set_mode_callback)); |
| return true; |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::SetOccludedBounds( |
| const std::vector<gfx::Rect>& bounds) { |
| auto* keyboard_client = ChromeKeyboardControllerClient::Get(); |
| if (!keyboard_client->is_keyboard_enabled()) |
| return false; |
| |
| keyboard_client->SetOccludedBounds(bounds); |
| return true; |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::SetHitTestBounds( |
| const std::vector<gfx::Rect>& bounds) { |
| auto* keyboard_client = ChromeKeyboardControllerClient::Get(); |
| if (!keyboard_client->is_keyboard_enabled()) |
| return false; |
| |
| keyboard_client->SetHitTestBounds(bounds); |
| return true; |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::SetAreaToRemainOnScreen( |
| const gfx::Rect& bounds) { |
| auto* keyboard_client = ChromeKeyboardControllerClient::Get(); |
| if (!keyboard_client->is_keyboard_enabled()) |
| return false; |
| |
| return keyboard_client->SetAreaToRemainOnScreen(bounds); |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::SetWindowBoundsInScreen( |
| const gfx::Rect& bounds_in_screen) { |
| auto* keyboard_client = ChromeKeyboardControllerClient::Get(); |
| if (!keyboard_client->is_keyboard_enabled()) |
| return false; |
| |
| return keyboard_client->SetWindowBoundsInScreen(bounds_in_screen); |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::SetDraggableArea( |
| const api::virtual_keyboard_private::Bounds& rect) { |
| auto* keyboard_client = ChromeKeyboardControllerClient::Get(); |
| // Since controller will be destroyed when system switch from VK to |
| // physical keyboard, return true to avoid unnecessary exception. |
| if (!keyboard_client->is_keyboard_enabled()) |
| return true; |
| |
| keyboard_client->SetDraggableArea( |
| gfx::Rect(rect.left, rect.top, rect.width, rect.height)); |
| return true; |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::SetRequestedKeyboardState(int state_enum) { |
| using keyboard::KeyboardEnableFlag; |
| auto* client = ChromeKeyboardControllerClient::Get(); |
| keyboard_api::KeyboardState state = |
| static_cast<keyboard_api::KeyboardState>(state_enum); |
| switch (state) { |
| case keyboard_api::KEYBOARD_STATE_ENABLED: |
| client->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled); |
| break; |
| case keyboard_api::KEYBOARD_STATE_DISABLED: |
| client->SetEnableFlag(KeyboardEnableFlag::kExtensionDisabled); |
| break; |
| case keyboard_api::KEYBOARD_STATE_AUTO: |
| case keyboard_api::KEYBOARD_STATE_NONE: |
| client->ClearEnableFlag(KeyboardEnableFlag::kExtensionDisabled); |
| client->ClearEnableFlag(KeyboardEnableFlag::kExtensionEnabled); |
| break; |
| } |
| return true; |
| } |
| |
| bool ChromeVirtualKeyboardDelegate::IsSettingsEnabled() { |
| return (user_manager::UserManager::Get()->IsUserLoggedIn() && |
| !chromeos::UserAddingScreen::Get()->IsRunning() && |
| !(chromeos::ScreenLocker::default_screen_locker() && |
| chromeos::ScreenLocker::default_screen_locker()->locked())); |
| } |
| |
| void ChromeVirtualKeyboardDelegate::OnHasInputDevices( |
| OnKeyboardSettingsCallback on_settings_callback, |
| bool has_audio_input_devices) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| auto* keyboard_client = ChromeKeyboardControllerClient::Get(); |
| |
| std::unique_ptr<base::DictionaryValue> results(new base::DictionaryValue()); |
| results->SetString("layout", GetKeyboardLayout()); |
| |
| // TODO(bshe): Consolidate a11y, hotrod and normal mode into a mode enum. See |
| // crbug.com/529474. |
| results->SetBoolean("a11ymode", |
| keyboard_client->IsEnableFlagSet( |
| keyboard::KeyboardEnableFlag::kAccessibilityEnabled)); |
| results->SetBoolean("hotrodmode", g_hotrod_keyboard_enabled); |
| std::unique_ptr<base::ListValue> features(new base::ListValue()); |
| |
| keyboard::KeyboardConfig config = keyboard_client->GetKeyboardConfig(); |
| // TODO(oka): Change this to use config.voice_input. |
| features->AppendString(GenerateFeatureFlag( |
| "voiceinput", has_audio_input_devices && config.voice_input)); |
| features->AppendString( |
| GenerateFeatureFlag("autocomplete", config.auto_complete)); |
| features->AppendString( |
| GenerateFeatureFlag("autocorrect", config.auto_correct)); |
| features->AppendString(GenerateFeatureFlag("spellcheck", config.spell_check)); |
| features->AppendString( |
| GenerateFeatureFlag("handwriting", config.handwriting)); |
| features->AppendString(GenerateFeatureFlag( |
| "handwritinggesture", |
| base::FeatureList::IsEnabled(features::kHandwritingGesture))); |
| features->AppendString( |
| GenerateFeatureFlag("handwritinggestureediting", |
| base::FeatureList::IsEnabled( |
| chromeos::features::kHandwritingGestureEditing))); |
| features->AppendString(GenerateFeatureFlag( |
| "multiword", |
| base::FeatureList::IsEnabled(chromeos::features::kAssistMultiWord))); |
| features->AppendString(GenerateFeatureFlag( |
| "floatingkeyboarddefault", |
| base::FeatureList::IsEnabled( |
| chromeos::features::kVirtualKeyboardFloatingDefault))); |
| |
| // Flag used to enable system built-in IME decoder instead of NaCl. |
| bool mojoDecoder = |
| base::FeatureList::IsEnabled(chromeos::features::kImeMojoDecoder); |
| features->AppendString(GenerateFeatureFlag("usemojodecoder", mojoDecoder)); |
| // Enabling MojoDecoder implies the 2 previous flags are auto-enabled. |
| // * fstinputlogic |
| // * hmminputlogic |
| // TODO(b/171846787): Remove the 3 flags after they are removed from clients. |
| features->AppendString(GenerateFeatureFlag("fstinputlogic", mojoDecoder)); |
| features->AppendString(GenerateFeatureFlag("hmminputlogic", mojoDecoder)); |
| features->AppendString(GenerateFeatureFlag( |
| "imemozcproto", |
| base::FeatureList::IsEnabled(chromeos::features::kImeMozcProto))); |
| |
| features->AppendString(GenerateFeatureFlag( |
| "borderedkey", base::FeatureList::IsEnabled( |
| chromeos::features::kVirtualKeyboardBorderedKey))); |
| features->AppendString(GenerateFeatureFlag( |
| "assistiveAutoCorrect", |
| base::FeatureList::IsEnabled(chromeos::features::kAssistAutoCorrect))); |
| features->AppendString( |
| GenerateFeatureFlag("systemlatinphysicaltyping", |
| base::FeatureList::IsEnabled( |
| chromeos::features::kSystemLatinPhysicalTyping))); |
| features->AppendString( |
| GenerateFeatureFlag("languagesettingsupdate", |
| base::FeatureList::IsEnabled( |
| chromeos::features::kLanguageSettingsUpdate))); |
| |
| results->Set("features", std::move(features)); |
| |
| std::move(on_settings_callback).Run(std::move(results)); |
| } |
| |
| void ChromeVirtualKeyboardDelegate::DispatchConfigChangeEvent( |
| std::unique_ptr<base::DictionaryValue> settings) { |
| EventRouter* router = EventRouter::Get(browser_context_); |
| |
| if (!router->HasEventListener( |
| keyboard_api::OnKeyboardConfigChanged::kEventName)) |
| return; |
| |
| auto event_args = std::make_unique<base::ListValue>(); |
| event_args->Append(std::move(settings)); |
| |
| auto event = std::make_unique<extensions::Event>( |
| extensions::events::VIRTUAL_KEYBOARD_PRIVATE_ON_KEYBOARD_CONFIG_CHANGED, |
| keyboard_api::OnKeyboardConfigChanged::kEventName, std::move(event_args), |
| browser_context_); |
| router->BroadcastEvent(std::move(event)); |
| } |
| |
| api::virtual_keyboard::FeatureRestrictions |
| ChromeVirtualKeyboardDelegate::RestrictFeatures( |
| const api::virtual_keyboard::RestrictFeatures::Params& params) { |
| const api::virtual_keyboard::FeatureRestrictions& restrictions = |
| params.restrictions; |
| api::virtual_keyboard::FeatureRestrictions update; |
| keyboard::KeyboardConfig current_config = |
| ChromeKeyboardControllerClient::Get()->GetKeyboardConfig(); |
| keyboard::KeyboardConfig config(current_config); |
| if (restrictions.spell_check_enabled && |
| config.spell_check != *restrictions.spell_check_enabled) { |
| update.spell_check_enabled = |
| std::make_unique<bool>(*restrictions.spell_check_enabled); |
| config.spell_check = *restrictions.spell_check_enabled; |
| } |
| if (restrictions.auto_complete_enabled && |
| config.auto_complete != *restrictions.auto_complete_enabled) { |
| update.auto_complete_enabled = |
| std::make_unique<bool>(*restrictions.auto_complete_enabled); |
| config.auto_complete = *restrictions.auto_complete_enabled; |
| } |
| if (restrictions.auto_correct_enabled && |
| config.auto_correct != *restrictions.auto_correct_enabled) { |
| update.auto_correct_enabled = |
| std::make_unique<bool>(*restrictions.auto_correct_enabled); |
| config.auto_correct = *restrictions.auto_correct_enabled; |
| } |
| if (restrictions.voice_input_enabled && |
| config.voice_input != *restrictions.voice_input_enabled) { |
| update.voice_input_enabled = |
| std::make_unique<bool>(*restrictions.voice_input_enabled); |
| config.voice_input = *restrictions.voice_input_enabled; |
| } |
| if (restrictions.handwriting_enabled && |
| config.handwriting != *restrictions.handwriting_enabled) { |
| update.handwriting_enabled = |
| std::make_unique<bool>(*restrictions.handwriting_enabled); |
| config.handwriting = *restrictions.handwriting_enabled; |
| } |
| |
| if (config != current_config) { |
| ChromeKeyboardControllerClient::Get()->SetKeyboardConfig(config); |
| // This reloads virtual keyboard even if it exists. This ensures virtual |
| // keyboard gets the correct state through |
| // chrome.virtualKeyboardPrivate.getKeyboardConfig. |
| // TODO(oka): Extension should reload on it's own by receiving event |
| ChromeKeyboardControllerClient::Get()->RebuildKeyboardIfEnabled(); |
| } |
| return update; |
| } |
| |
| } // namespace extensions |