| // Copyright 2018 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ash/keyboard/keyboard_controller_impl.h" |
| |
| #include <optional> |
| #include <utility> |
| |
| #include "ash/constants/ash_constants.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/constants/ash_pref_names.h" |
| #include "ash/keyboard/ui/keyboard_ui_controller.h" |
| #include "ash/keyboard/ui/keyboard_ui_factory.h" |
| #include "ash/keyboard/virtual_keyboard_controller.h" |
| #include "ash/public/cpp/keyboard/keyboard_switches.h" |
| #include "ash/public/cpp/login_types.h" |
| #include "ash/public/cpp/shell_window_ids.h" |
| #include "ash/root_window_controller.h" |
| #include "ash/session/session_controller_impl.h" |
| #include "ash/shelf/shelf.h" |
| #include "ash/shell.h" |
| #include "ash/shell_delegate.h" |
| #include "ash/system/input_device_settings/input_device_settings_controller_impl.h" |
| #include "ash/system/model/enterprise_domain_model.h" |
| #include "ash/system/model/system_tray_model.h" |
| #include "ash/wm/window_util.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_change_registrar.h" |
| #include "components/prefs/pref_service.h" |
| #include "ui/aura/env.h" |
| #include "ui/aura/window_delegate.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/events/gestures/gesture_recognizer.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/wm/core/coordinate_conversion.h" |
| |
| using keyboard::KeyboardConfig; |
| using keyboard::KeyboardEnableFlag; |
| |
| namespace ash { |
| |
| namespace { |
| |
| // Boolean controlling whether auto-complete for virtual keyboard is |
| // enabled. |
| const char kAutoCompleteEnabledKey[] = "auto_complete_enabled"; |
| // Boolean controlling whether auto-correct for virtual keyboard is |
| // enabled. |
| const char kAutoCorrectEnabledKey[] = "auto_correct_enabled"; |
| // Boolean controlling whether handwriting for virtual keyboard is |
| // enabled. |
| const char kHandwritingEnabledKey[] = "handwriting_enabled"; |
| // Boolean controlling whether spell check for virtual keyboard is |
| // enabled. |
| const char kSpellCheckEnabledKey[] = "spell_check_enabled"; |
| // Boolean controlling whether voice input for virtual keyboard is |
| // enabled. |
| const char kVoiceInputEnabledKey[] = "voice_input_enabled"; |
| |
| std::optional<display::Display> GetFirstTouchDisplay() { |
| for (const auto& display : display::Screen::GetScreen()->GetAllDisplays()) { |
| if (display.touch_support() == display::Display::TouchSupport::AVAILABLE) |
| return display; |
| } |
| return std::nullopt; |
| } |
| |
| bool GetVirtualKeyboardFeatureValue(PrefService* prefs, |
| const std::string& feature_path) { |
| DCHECK(prefs); |
| const base::Value::Dict& features = |
| prefs->GetDict(prefs::kAccessibilityVirtualKeyboardFeatures); |
| |
| return features.FindBool(feature_path).value_or(false); |
| } |
| |
| } // namespace |
| |
| KeyboardControllerImpl::KeyboardControllerImpl( |
| SessionControllerImpl* session_controller) |
| : session_controller_(session_controller), |
| keyboard_ui_controller_( |
| std::make_unique<keyboard::KeyboardUIController>()) { |
| if (session_controller_) // May be null in tests. |
| session_controller_->AddObserver(this); |
| keyboard_ui_controller_->AddObserver(this); |
| } |
| |
| KeyboardControllerImpl::~KeyboardControllerImpl() { |
| keyboard_ui_controller_->RemoveObserver(this); |
| if (session_controller_) // May be null in tests. |
| session_controller_->RemoveObserver(this); |
| } |
| |
| // static |
| void KeyboardControllerImpl::RegisterProfilePrefs(PrefRegistrySimple* registry, |
| std::string_view country) { |
| // Longpress diacritics pref is default on for NZ managed users only, default |
| // off otherwise. |
| registry->RegisterBooleanPref( |
| ash::prefs::kLongPressDiacriticsEnabled, |
| (country == "NZ" && |
| Shell::Get() |
| ->system_tray_model() |
| ->enterprise_domain() |
| ->management_device_mode() == ManagementDeviceMode::kNone) || |
| base::FeatureList::IsEnabled( |
| ash::features::kDiacriticsOnPhysicalKeyboardLongpressDefaultOn), |
| user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF); |
| registry->RegisterBooleanPref( |
| ash::prefs::kXkbAutoRepeatEnabled, ash::kDefaultKeyAutoRepeatEnabled, |
| user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF); |
| registry->RegisterIntegerPref( |
| ash::prefs::kXkbAutoRepeatDelay, |
| ash::kDefaultKeyAutoRepeatDelay.InMilliseconds(), |
| user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF); |
| registry->RegisterIntegerPref( |
| ash::prefs::kXkbAutoRepeatInterval, |
| ash::kDefaultKeyAutoRepeatInterval.InMilliseconds(), |
| user_prefs::PrefRegistrySyncable::SYNCABLE_OS_PREF); |
| registry->RegisterDictionaryPref( |
| prefs::kAccessibilityVirtualKeyboardFeatures); |
| } |
| |
| void KeyboardControllerImpl::CreateVirtualKeyboard( |
| std::unique_ptr<keyboard::KeyboardUIFactory> keyboard_ui_factory) { |
| DCHECK(keyboard_ui_factory); |
| virtual_keyboard_controller_ = std::make_unique<VirtualKeyboardController>(); |
| keyboard_ui_controller_->Initialize(std::move(keyboard_ui_factory), this); |
| } |
| |
| void KeyboardControllerImpl::DestroyVirtualKeyboard() { |
| virtual_keyboard_controller_.reset(); |
| keyboard_ui_controller_->Shutdown(); |
| } |
| |
| void KeyboardControllerImpl::SendOnKeyboardVisibleBoundsChanged( |
| const gfx::Rect& screen_bounds) { |
| DVLOG(1) << "OnKeyboardVisibleBoundsChanged: " << screen_bounds.ToString(); |
| for (auto& observer : observers_) |
| observer.OnKeyboardVisibleBoundsChanged(screen_bounds); |
| } |
| |
| void KeyboardControllerImpl::SendOnKeyboardUIDestroyed() { |
| for (auto& observer : observers_) |
| observer.OnKeyboardUIDestroyed(); |
| } |
| |
| // ash::KeyboardController |
| |
| keyboard::KeyboardConfig KeyboardControllerImpl::GetKeyboardConfig() { |
| if (!keyboard_config_from_pref_enabled_) |
| return keyboard_ui_controller_->keyboard_config(); |
| |
| PrefService* prefs = pref_change_registrar_->prefs(); |
| KeyboardConfig config; |
| config.auto_complete = |
| GetVirtualKeyboardFeatureValue(prefs, kAutoCompleteEnabledKey); |
| config.auto_correct = |
| GetVirtualKeyboardFeatureValue(prefs, kAutoCorrectEnabledKey); |
| config.handwriting = |
| GetVirtualKeyboardFeatureValue(prefs, kHandwritingEnabledKey); |
| config.spell_check = |
| GetVirtualKeyboardFeatureValue(prefs, kSpellCheckEnabledKey); |
| config.voice_input = |
| GetVirtualKeyboardFeatureValue(prefs, kVoiceInputEnabledKey); |
| return config; |
| } |
| |
| void KeyboardControllerImpl::SetKeyboardConfig( |
| const KeyboardConfig& keyboard_config) { |
| keyboard_ui_controller_->UpdateKeyboardConfig(keyboard_config); |
| } |
| |
| bool KeyboardControllerImpl::IsKeyboardEnabled() { |
| return keyboard_ui_controller_->IsEnabled(); |
| } |
| |
| void KeyboardControllerImpl::SetEnableFlag(KeyboardEnableFlag flag) { |
| keyboard_ui_controller_->SetEnableFlag(flag); |
| } |
| |
| void KeyboardControllerImpl::ClearEnableFlag(KeyboardEnableFlag flag) { |
| keyboard_ui_controller_->ClearEnableFlag(flag); |
| } |
| |
| const std::set<keyboard::KeyboardEnableFlag>& |
| KeyboardControllerImpl::GetEnableFlags() { |
| return keyboard_ui_controller_->keyboard_enable_flags(); |
| } |
| |
| void KeyboardControllerImpl::ReloadKeyboardIfNeeded() { |
| keyboard_ui_controller_->Reload(); |
| } |
| |
| void KeyboardControllerImpl::RebuildKeyboardIfEnabled() { |
| // Test IsKeyboardEnableRequested in case of an unlikely edge case where this |
| // is called while after the enable state changed to disabled (in which case |
| // we do not want to override the requested state). |
| keyboard_ui_controller_->RebuildKeyboardIfEnabled(); |
| } |
| |
| bool KeyboardControllerImpl::IsKeyboardVisible() { |
| return keyboard_ui_controller_->IsKeyboardVisible(); |
| } |
| |
| void KeyboardControllerImpl::ShowKeyboard() { |
| if (keyboard_ui_controller_->IsEnabled()) |
| keyboard_ui_controller_->ShowKeyboard(false /* lock */); |
| } |
| |
| void KeyboardControllerImpl::HideKeyboard(HideReason reason) { |
| if (!keyboard_ui_controller_->IsEnabled()) |
| return; |
| switch (reason) { |
| case HideReason::kUser: |
| keyboard_ui_controller_->HideKeyboardByUser(); |
| break; |
| case HideReason::kSystem: |
| keyboard_ui_controller_->HideKeyboardExplicitlyBySystem(); |
| break; |
| } |
| } |
| |
| void KeyboardControllerImpl::SetContainerType( |
| keyboard::ContainerType container_type, |
| const gfx::Rect& target_bounds, |
| SetContainerTypeCallback callback) { |
| keyboard_ui_controller_->SetContainerType(container_type, target_bounds, |
| std::move(callback)); |
| } |
| |
| void KeyboardControllerImpl::SetKeyboardLocked(bool locked) { |
| keyboard_ui_controller_->set_keyboard_locked(locked); |
| } |
| |
| void KeyboardControllerImpl::SetOccludedBounds( |
| const std::vector<gfx::Rect>& bounds) { |
| // TODO(https://crbug.com/826617): Support occluded bounds with multiple |
| // rectangles. |
| keyboard_ui_controller_->SetOccludedBounds(bounds.empty() ? gfx::Rect() |
| : bounds[0]); |
| } |
| |
| void KeyboardControllerImpl::SetHitTestBounds( |
| const std::vector<gfx::Rect>& bounds) { |
| keyboard_ui_controller_->SetHitTestBounds(bounds); |
| } |
| |
| bool KeyboardControllerImpl::SetAreaToRemainOnScreen(const gfx::Rect& bounds) { |
| return keyboard_ui_controller_->SetAreaToRemainOnScreen(bounds); |
| } |
| |
| void KeyboardControllerImpl::SetDraggableArea(const gfx::Rect& bounds) { |
| keyboard_ui_controller_->SetDraggableArea(bounds); |
| } |
| |
| bool KeyboardControllerImpl::SetWindowBoundsInScreen( |
| const gfx::Rect& bounds_in_screen) { |
| return keyboard_ui_controller_->SetKeyboardWindowBoundsInScreen( |
| bounds_in_screen); |
| } |
| |
| void KeyboardControllerImpl::SetKeyboardConfigFromPref(bool enabled) { |
| keyboard_config_from_pref_enabled_ = enabled; |
| SendKeyboardConfigUpdate(); |
| } |
| |
| bool KeyboardControllerImpl::ShouldOverscroll() { |
| return keyboard_ui_controller_->IsKeyboardOverscrollEnabled(); |
| } |
| |
| void KeyboardControllerImpl::AddObserver(KeyboardControllerObserver* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void KeyboardControllerImpl::RemoveObserver( |
| KeyboardControllerObserver* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| std::optional<KeyRepeatSettings> |
| KeyboardControllerImpl::GetKeyRepeatSettings() { |
| if (!pref_change_registrar_) |
| return std::nullopt; |
| PrefService* prefs = pref_change_registrar_->prefs(); |
| bool enabled = prefs->GetBoolean(ash::prefs::kXkbAutoRepeatEnabled); |
| int delay_in_ms = prefs->GetInteger(ash::prefs::kXkbAutoRepeatDelay); |
| int interval_in_ms = prefs->GetInteger(ash::prefs::kXkbAutoRepeatInterval); |
| return KeyRepeatSettings{enabled, base::Milliseconds(delay_in_ms), |
| base::Milliseconds(interval_in_ms)}; |
| } |
| |
| bool KeyboardControllerImpl::AreTopRowKeysFunctionKeys() { |
| if (ash::features::IsInputDeviceSettingsSplitEnabled()) { |
| return Shell::Get() |
| ->input_device_settings_controller() |
| ->GetGeneralizedTopRowAreFKeys(); |
| } |
| PrefService* prefs = pref_change_registrar_->prefs(); |
| return prefs->GetBoolean(ash::prefs::kSendFunctionKeys); |
| } |
| |
| void KeyboardControllerImpl::SetSmartVisibilityEnabled(bool enabled) { |
| if (keyboard_ui_controller_->IsEnabled()) { |
| keyboard_ui_controller_->SetShouldShowOnTransientBlur(enabled); |
| } |
| } |
| |
| // SessionObserver |
| void KeyboardControllerImpl::OnSessionStateChanged( |
| session_manager::SessionState state) { |
| SetEnableFlagFromCommandLine(); |
| if (!keyboard_ui_controller_->IsEnabled()) |
| return; |
| |
| switch (state) { |
| case session_manager::SessionState::LOGGED_IN_NOT_ACTIVE: |
| case session_manager::SessionState::ACTIVE: |
| // Reload the keyboard on user profile change to refresh keyboard |
| // extensions with the new profile and ensure the extensions call the |
| // proper IME. |LOGGED_IN_NOT_ACTIVE| is needed so that the virtual |
| // keyboard works on supervised user creation, http://crbug.com/712873. |
| // |ACTIVE| is also needed for guest user workflow. |
| RebuildKeyboardIfEnabled(); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void KeyboardControllerImpl::OnSigninScreenPrefServiceInitialized( |
| PrefService* prefs) { |
| ObservePrefs(prefs); |
| } |
| |
| void KeyboardControllerImpl::OnActiveUserPrefServiceChanged( |
| PrefService* prefs) { |
| auto account_id = Shell::Get()->session_controller()->GetActiveAccountId(); |
| if (prefs && !recorded_accounts_.contains(account_id)) { |
| base::UmaHistogramBoolean( |
| "ChromeOS.Settings.Device.KeyboardAutoRepeatEnabled", |
| prefs->GetBoolean(prefs::kXkbAutoRepeatEnabled)); |
| base::UmaHistogramTimes( |
| "ChromeOS.Settings.Device.KeyboardAutoRepeatDelay", |
| base::Milliseconds(prefs->GetInteger(prefs::kXkbAutoRepeatDelay))); |
| base::UmaHistogramTimes( |
| "ChromeOS.Settings.Device.KeyboardAutoRepeatInterval", |
| base::Milliseconds(prefs->GetInteger(prefs::kXkbAutoRepeatInterval))); |
| recorded_accounts_.insert(account_id); |
| } |
| |
| ObservePrefs(prefs); |
| } |
| |
| // Start listening to key repeat preferences from the given service. |
| // Also immediately update observers with the service's current preferences. |
| // |
| // We only need to observe the most recent PrefService. It will either be the |
| // active user's PrefService, or the signin screen's PrefService if nobody's |
| // logged in yet. |
| void KeyboardControllerImpl::ObservePrefs(PrefService* prefs) { |
| if (!prefs) { |
| // Just for testing cases. |
| pref_change_registrar_.reset(); |
| return; |
| } |
| |
| pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>(); |
| pref_change_registrar_->Init(prefs); |
| |
| // Immediately tell all our observers to load this user's saved preferences. |
| SendKeyRepeatUpdate(); |
| SendKeyboardConfigUpdate(); |
| |
| // Listen to prefs changes and forward them to all observers. |
| // |prefs| is assumed to outlive |pref_change_registrar_|, and therefore also |
| // its callbacks. |
| pref_change_registrar_->Add( |
| ash::prefs::kXkbAutoRepeatEnabled, |
| base::BindRepeating(&KeyboardControllerImpl::SendKeyRepeatUpdate, |
| base::Unretained(this))); |
| pref_change_registrar_->Add( |
| ash::prefs::kXkbAutoRepeatInterval, |
| base::BindRepeating(&KeyboardControllerImpl::SendKeyRepeatUpdate, |
| base::Unretained(this))); |
| pref_change_registrar_->Add( |
| ash::prefs::kXkbAutoRepeatDelay, |
| base::BindRepeating(&KeyboardControllerImpl::SendKeyRepeatUpdate, |
| base::Unretained(this))); |
| pref_change_registrar_->Add( |
| ash::prefs::kAccessibilityVirtualKeyboardFeatures, |
| base::BindRepeating(&KeyboardControllerImpl::SendKeyboardConfigUpdate, |
| base::Unretained(this))); |
| } |
| |
| void KeyboardControllerImpl::SendKeyRepeatUpdate() { |
| auto key_repeat_settings = GetKeyRepeatSettings(); |
| DCHECK(key_repeat_settings.has_value()); |
| OnKeyRepeatSettingsChanged(key_repeat_settings.value()); |
| } |
| |
| void KeyboardControllerImpl::SendKeyboardConfigUpdate() { |
| keyboard_ui_controller_->UpdateKeyboardConfig(GetKeyboardConfig()); |
| } |
| |
| void KeyboardControllerImpl::OnRootWindowClosing(aura::Window* root_window) { |
| if (keyboard_ui_controller_->GetRootWindow() == root_window) { |
| aura::Window* new_parent = GetContainerForDefaultDisplay(); |
| DCHECK_NE(root_window, new_parent); |
| keyboard_ui_controller_->MoveToParentContainer(new_parent); |
| } |
| } |
| |
| aura::Window* KeyboardControllerImpl::GetContainerForDisplay( |
| const display::Display& display) { |
| DCHECK(display.is_valid()); |
| |
| RootWindowController* controller = |
| Shell::Get()->GetRootWindowControllerWithDisplayId(display.id()); |
| aura::Window* container = |
| controller ? controller->GetContainer(kShellWindowId_VirtualKeyboardContainer) : nullptr ; |
| DCHECK(container); |
| return container; |
| } |
| |
| aura::Window* KeyboardControllerImpl::GetContainerForDefaultDisplay() { |
| const display::Screen* screen = display::Screen::GetScreen(); |
| const std::optional<display::Display> first_touch_display = |
| GetFirstTouchDisplay(); |
| const bool has_touch_display = first_touch_display.has_value(); |
| |
| if (window_util::GetFocusedWindow()) { |
| // Return the focused display if that display has touch capability or no |
| // other display has touch capability. |
| const display::Display focused_display = |
| screen->GetDisplayNearestWindow(window_util::GetFocusedWindow()); |
| if (focused_display.is_valid() && |
| (focused_display.touch_support() == |
| display::Display::TouchSupport::AVAILABLE || |
| !has_touch_display)) { |
| return GetContainerForDisplay(focused_display); |
| } |
| } |
| |
| // Return the first touch display, or the primary display if there are none. |
| return GetContainerForDisplay( |
| has_touch_display ? *first_touch_display : screen->GetPrimaryDisplay()); |
| } |
| |
| void KeyboardControllerImpl::TransferGestureEventToShelf( |
| const ui::GestureEvent& e) { |
| ash::Shelf* shelf = |
| ash::Shelf::ForWindow(keyboard_ui_controller_->GetKeyboardWindow()); |
| if (shelf) { |
| shelf->ProcessGestureEvent(e); |
| aura::Env::GetInstance()->gesture_recognizer()->TransferEventsTo( |
| keyboard_ui_controller_->GetGestureConsumer(), shelf->GetWindow(), |
| ui::TransferTouchesBehavior::kCancel); |
| HideKeyboard(HideReason::kUser); |
| } |
| } |
| |
| void KeyboardControllerImpl::OnKeyboardConfigChanged( |
| const keyboard::KeyboardConfig& config) { |
| for (auto& observer : observers_) |
| observer.OnKeyboardConfigChanged(config); |
| } |
| |
| void KeyboardControllerImpl::OnKeyRepeatSettingsChanged( |
| const KeyRepeatSettings& settings) { |
| for (auto& observer : observers_) |
| observer.OnKeyRepeatSettingsChanged(settings); |
| } |
| |
| void KeyboardControllerImpl::OnKeyboardVisibilityChanged(bool is_visible) { |
| for (auto& observer : observers_) |
| observer.OnKeyboardVisibilityChanged(is_visible); |
| } |
| |
| void KeyboardControllerImpl::OnKeyboardVisibleBoundsChanged( |
| const gfx::Rect& screen_bounds) { |
| SendOnKeyboardVisibleBoundsChanged(screen_bounds); |
| } |
| |
| void KeyboardControllerImpl::OnKeyboardOccludedBoundsChanged( |
| const gfx::Rect& screen_bounds) { |
| DVLOG(1) << "OnKeyboardOccludedBoundsChanged: " << screen_bounds.ToString(); |
| for (auto& observer : observers_) |
| observer.OnKeyboardOccludedBoundsChanged(screen_bounds); |
| } |
| |
| void KeyboardControllerImpl::OnKeyboardEnableFlagsChanged( |
| const std::set<keyboard::KeyboardEnableFlag>& flags) { |
| for (auto& observer : observers_) |
| observer.OnKeyboardEnableFlagsChanged(flags); |
| } |
| |
| void KeyboardControllerImpl::OnKeyboardEnabledChanged(bool is_enabled) { |
| for (auto& observer : observers_) |
| observer.OnKeyboardEnabledChanged(is_enabled); |
| } |
| |
| void KeyboardControllerImpl::SetEnableFlagFromCommandLine() { |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| keyboard::switches::kEnableVirtualKeyboard)) { |
| keyboard_ui_controller_->SetEnableFlag( |
| KeyboardEnableFlag::kCommandLineEnabled); |
| } |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| keyboard::switches::kDisableVirtualKeyboard)) { |
| keyboard_ui_controller_->SetEnableFlag( |
| KeyboardEnableFlag::kCommandLineDisabled); |
| } |
| } |
| |
| } // namespace ash |