// 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(crbug.com/41379402): 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