// Copyright 2013 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/ui/keyboard_ui_controller.h"
#include <set>
#include "ash/keyboard/ui/container_floating_behavior.h"
#include "ash/keyboard/ui/container_full_width_behavior.h"
#include "ash/keyboard/ui/display_util.h"
#include "ash/keyboard/ui/keyboard_layout_manager.h"
#include "ash/keyboard/ui/keyboard_ui.h"
#include "ash/keyboard/ui/keyboard_ui_factory.h"
#include "ash/keyboard/ui/keyboard_util.h"
#include "ash/keyboard/ui/notification_manager.h"
#include "ash/keyboard/ui/queued_container_type.h"
#include "ash/keyboard/ui/queued_display_change.h"
#include "ash/keyboard/ui/shaped_window_targeter.h"
#include "ash/public/cpp/keyboard/keyboard_controller_observer.h"
#include "ash/public/cpp/keyboard/keyboard_switches.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/screen_position_client.h"
#include "ui/aura/env.h"
#include "ui/aura/window.h"
#include "ui/aura/window_delegate.h"
#include "ui/aura/window_observer.h"
#include "ui/base/cursor/cursor.h"
#include "ui/base/hit_test.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/base/ime/text_input_flags.h"
#include "ui/base/ime/virtual_keyboard_controller_observer.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/display/types/display_constants.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/gestures/gesture_recognizer.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/wm/core/coordinate_conversion.h"
#include "ui/wm/core/window_animations.h"
namespace keyboard {
namespace {
// Owned by ash::Shell.
KeyboardUIController* g_keyboard_controller = nullptr;
// How long the keyboard stays in WILL_HIDE state before moving to HIDDEN.
constexpr base::TimeDelta kHideKeyboardDelay = base::Milliseconds(100);
// Reports an error histogram if the keyboard state is lingering in an
// intermediate state for more than 5 seconds.
constexpr base::TimeDelta kReportLingeringStateDelay = base::Milliseconds(5000);
// Delay threshold after the keyboard enters the WILL_HIDE state. If text focus
// is regained during this threshold, the keyboard will show again, even if it
// is an asynchronous event. This is for the benefit of things like login flow
// where the password field may get text focus after an animation that plays
// after the user enters their username.
constexpr base::TimeDelta kTransientBlurThreshold = base::Milliseconds(3500);
class VirtualKeyboardController : public ui::VirtualKeyboardController {
public:
explicit VirtualKeyboardController(
KeyboardUIController* keyboard_ui_controller)
: keyboard_ui_controller_(keyboard_ui_controller) {}
~VirtualKeyboardController() override = default;
// ui::VirtualKeyboardController
bool DisplayVirtualKeyboard() override {
// Calling |ShowKeyboardInternal| may move the keyboard to another display.
if (keyboard_ui_controller_->IsEnabled() &&
!keyboard_ui_controller_->keyboard_locked()) {
keyboard_ui_controller_->ShowKeyboard(false /* locked */);
for (auto& observer : observer_list_) {
observer.OnKeyboardVisible(gfx::Rect());
}
return true;
}
return false;
}
void DismissVirtualKeyboard() override {
keyboard_ui_controller_->HideKeyboardByUser();
for (auto& observer : observer_list_) {
observer.OnKeyboardHidden();
}
}
void AddObserver(ui::VirtualKeyboardControllerObserver* observer) override {
observer_list_.AddObserver(observer);
}
void RemoveObserver(
ui::VirtualKeyboardControllerObserver* observer) override {
observer_list_.RemoveObserver(observer);
}
bool IsKeyboardVisible() override {
return keyboard_ui_controller_->IsKeyboardVisible();
}
private:
raw_ptr<KeyboardUIController> keyboard_ui_controller_;
base::ObserverList<ui::VirtualKeyboardControllerObserver>::Unchecked
observer_list_;
};
} // namespace
// Observer for both keyboard show and hide animations. It should be owned by
// KeyboardUIController.
class CallbackAnimationObserver : public ui::ImplicitAnimationObserver {
public:
explicit CallbackAnimationObserver(base::OnceClosure callback)
: callback_(std::move(callback)) {}
CallbackAnimationObserver(const CallbackAnimationObserver&) = delete;
CallbackAnimationObserver& operator=(const CallbackAnimationObserver&) =
delete;
private:
// ui::ImplicitAnimationObserver:
void OnImplicitAnimationsCompleted() override {
if (WasAnimationAbortedForProperty(ui::LayerAnimationElement::TRANSFORM) ||
WasAnimationAbortedForProperty(ui::LayerAnimationElement::OPACITY)) {
return;
}
DCHECK(
WasAnimationCompletedForProperty(ui::LayerAnimationElement::TRANSFORM));
DCHECK(
WasAnimationCompletedForProperty(ui::LayerAnimationElement::OPACITY));
std::move(callback_).Run();
}
base::OnceClosure callback_;
};
KeyboardUIController::KeyboardUIController()
: virtual_keyboard_controller_(
std::make_unique<VirtualKeyboardController>(this)) {
DCHECK_EQ(g_keyboard_controller, nullptr);
g_keyboard_controller = this;
}
KeyboardUIController::~KeyboardUIController() {
DCHECK(g_keyboard_controller);
DCHECK(!ui_) << "Keyboard UI must be destroyed before KeyboardUIController "
"is destroyed";
g_keyboard_controller = nullptr;
}
// static
KeyboardUIController* KeyboardUIController::Get() {
DCHECK(g_keyboard_controller);
return g_keyboard_controller;
}
// static
bool KeyboardUIController::HasInstance() {
return g_keyboard_controller;
}
void KeyboardUIController::Initialize(
std::unique_ptr<KeyboardUIFactory> ui_factory,
KeyboardLayoutDelegate* layout_delegate) {
DCHECK(ui_factory);
DCHECK(layout_delegate);
ui_factory_ = std::move(ui_factory);
layout_delegate_ = layout_delegate;
DCHECK(!IsKeyboardEnableRequested());
}
void KeyboardUIController::Shutdown() {
keyboard_enable_flags_.clear();
EnableFlagsChanged();
DCHECK(!IsKeyboardEnableRequested());
DisableKeyboard();
}
void KeyboardUIController::EnableKeyboard() {
if (ui_)
return;
ui_ = ui_factory_->CreateKeyboardUI();
DCHECK(ui_);
show_on_keyboard_window_load_ = false;
keyboard_locked_ = false;
DCHECK_EQ(model_.state(), KeyboardUIState::kInitial);
ui_->SetController(this);
SetContainerBehaviorInternal(ContainerType::kFullWidth);
visual_bounds_in_root_ = gfx::Rect();
time_of_last_blur_ = base::Time::UnixEpoch();
UpdateInputMethodObserver();
ActivateKeyboardInContainer(
layout_delegate_->GetContainerForDefaultDisplay());
// Start preloading the virtual keyboard UI in the background, so that it
// shows up faster when needed.
LoadKeyboardWindowInBackground();
// Notify observers after the keyboard window has a root window.
for (auto& observer : observer_list_)
observer.OnKeyboardEnabledChanged(true);
}
void KeyboardUIController::DisableKeyboard() {
if (!ui_)
return;
if (parent_container_)
DeactivateKeyboard();
aura::Window* keyboard_window = GetKeyboardWindow();
if (keyboard_window)
keyboard_window->RemoveObserver(this);
// Return to the INITIAL state to ensure that transitions entering a state
// is equal to transitions leaving the state.
if (model_.state() != KeyboardUIState::kInitial)
ChangeState(KeyboardUIState::kInitial);
// TODO(crbug.com/40524972): Move KeyboardUIController members into a
// subobject so we can just put this code into the subobject destructor.
queued_display_change_.reset();
queued_container_type_.reset();
container_behavior_.reset();
animation_observer_.reset();
ime_observation_.Reset();
ui_->SetController(nullptr);
ui_.reset();
// Notify observers after |ui_| is reset so that IsEnabled() is false.
for (auto& observer : observer_list_)
observer.OnKeyboardEnabledChanged(false);
}
void KeyboardUIController::ActivateKeyboardInContainer(aura::Window* parent) {
DCHECK(parent);
DCHECK(!parent_container_);
parent_container_ = parent;
// Observe changes to root window bounds.
parent_container_->GetRootWindow()->AddObserver(this);
UpdateInputMethodObserver();
if (GetKeyboardWindow()) {
DCHECK(!GetKeyboardWindow()->parent());
parent_container_->AddChild(GetKeyboardWindow());
}
}
void KeyboardUIController::DeactivateKeyboard() {
DCHECK(parent_container_);
// Ensure the keyboard is not visible before deactivating it.
HideKeyboardExplicitlyBySystem();
aura::Window* keyboard_window = GetKeyboardWindow();
if (keyboard_window) {
keyboard_window->RemovePreTargetHandler(&event_handler_);
if (keyboard_window->parent()) {
DCHECK_EQ(parent_container_, keyboard_window->parent());
parent_container_->RemoveChild(keyboard_window);
}
}
aura::Window* root_window = parent_container_->GetRootWindow();
if (root_window) {
root_window->RemoveObserver(this);
}
parent_container_ = nullptr;
}
aura::Window* KeyboardUIController::GetKeyboardWindow() const {
return ui_ ? ui_->GetKeyboardWindow() : nullptr;
}
ui::GestureConsumer* KeyboardUIController::GetGestureConsumer() const {
return ui_ ? ui_->GetGestureConsumer() : nullptr;
}
aura::Window* KeyboardUIController::GetRootWindow() const {
return parent_container_ ? parent_container_->GetRootWindow() : nullptr;
}
void KeyboardUIController::MoveToParentContainer(aura::Window* parent) {
DCHECK(parent);
if (parent_container_ == parent)
return;
TRACE_EVENT0("vk", "MoveKeyboardToDisplayInternal");
DeactivateKeyboard();
ActivateKeyboardInContainer(parent);
}
// private
void KeyboardUIController::NotifyKeyboardBoundsChanging(
const gfx::Rect& new_bounds_in_root,
bool is_temporary) {
gfx::Rect occluded_bounds_in_screen;
aura::Window* window = GetKeyboardWindow();
if (window && window->IsVisible()) {
visual_bounds_in_root_ = new_bounds_in_root;
// |visual_bounds_in_root_| affects the result of
// GetWorkspaceOccludedBoundsInScreen. Calculate |occluded_bounds_in_screen|
// after updating |visual_bounds_in_root_|.
// TODO(andrewxu): Add the unit test case for issue 960174.
occluded_bounds_in_screen = GetWorkspaceOccludedBoundsInScreen();
// TODO(crbug.com/40619022): Use screen bounds for visual bounds.
notification_manager_.SendNotifications(
container_behavior_->OccludedBoundsAffectWorkspaceLayout(),
new_bounds_in_root, occluded_bounds_in_screen, is_temporary,
observer_list_);
} else {
visual_bounds_in_root_ = gfx::Rect();
occluded_bounds_in_screen = GetWorkspaceOccludedBoundsInScreen();
}
EnsureCaretInWorkArea(occluded_bounds_in_screen);
}
void KeyboardUIController::SetKeyboardWindowBounds(
const gfx::Rect& new_bounds_in_root) {
ui::LayerAnimator* animator = GetKeyboardWindow()->layer()->GetAnimator();
// Stops previous animation if a window resize is requested during animation.
if (animator->is_animating())
animator->StopAnimating();
GetKeyboardWindow()->SetBounds(new_bounds_in_root);
}
void KeyboardUIController::NotifyKeyboardWindowLoaded() {
const bool should_show = show_on_keyboard_window_load_;
if (model_.state() == KeyboardUIState::kLoading)
ChangeState(KeyboardUIState::kHidden);
if (should_show) {
// The window height is set to 0 initially or before switch to an IME in a
// different extension. Virtual keyboard window may wait for this bounds
// change to correctly animate in.
if (keyboard_locked_) {
// Do not move the keyboard to another display after switch to an IME in
// a different extension.
ShowKeyboardInDisplay(
display_util_.GetNearestDisplayToWindow(GetKeyboardWindow()));
} else {
ShowKeyboard(false /* lock */);
}
}
}
void KeyboardUIController::Reload() {
if (!GetKeyboardWindow())
return;
ui_->ReloadKeyboardIfNeeded();
}
void KeyboardUIController::RebuildKeyboardIfEnabled() {
if (!IsEnabled())
return;
DisableKeyboard();
EnableKeyboard();
}
void KeyboardUIController::AddObserver(
ash::KeyboardControllerObserver* observer) {
observer_list_.AddObserver(observer);
}
bool KeyboardUIController::HasObserver(
ash::KeyboardControllerObserver* observer) const {
return observer_list_.HasObserver(observer);
}
void KeyboardUIController::RemoveObserver(
ash::KeyboardControllerObserver* observer) {
observer_list_.RemoveObserver(observer);
}
bool KeyboardUIController::UpdateKeyboardConfig(const KeyboardConfig& config) {
if (config == keyboard_config_)
return false;
keyboard_config_ = config;
if (IsEnabled())
NotifyKeyboardConfigChanged();
return true;
}
void KeyboardUIController::SetEnableFlag(KeyboardEnableFlag flag) {
if (!base::Contains(keyboard_enable_flags_, flag))
keyboard_enable_flags_.insert(flag);
// If there is a flag that is mutually exclusive with |flag|, clear it.
switch (flag) {
case KeyboardEnableFlag::kPolicyEnabled:
keyboard_enable_flags_.erase(KeyboardEnableFlag::kPolicyDisabled);
break;
case KeyboardEnableFlag::kPolicyDisabled:
keyboard_enable_flags_.erase(KeyboardEnableFlag::kPolicyEnabled);
break;
case KeyboardEnableFlag::kExtensionEnabled:
keyboard_enable_flags_.erase(KeyboardEnableFlag::kExtensionDisabled);
break;
case KeyboardEnableFlag::kExtensionDisabled:
keyboard_enable_flags_.erase(KeyboardEnableFlag::kExtensionEnabled);
break;
default:
break;
}
EnableFlagsChanged();
UpdateKeyboardAsRequestedBy(flag);
}
void KeyboardUIController::ClearEnableFlag(KeyboardEnableFlag flag) {
if (!IsEnableFlagSet(flag))
return;
keyboard_enable_flags_.erase(flag);
EnableFlagsChanged();
UpdateKeyboardAsRequestedBy(flag);
}
bool KeyboardUIController::IsEnableFlagSet(KeyboardEnableFlag flag) const {
return base::Contains(keyboard_enable_flags_, flag);
}
bool KeyboardUIController::IsKeyboardEnableRequested() const {
// Accessibility setting prioritized over policy/arc overrides.
if (IsEnableFlagSet(KeyboardEnableFlag::kAccessibilityEnabled))
return true;
// Keyboard can be enabled temporarily by the shelf.
if (IsEnableFlagSet(KeyboardEnableFlag::kShelfEnabled))
return true;
if (IsEnableFlagSet(KeyboardEnableFlag::kAndroidDisabled) ||
IsEnableFlagSet(KeyboardEnableFlag::kPolicyDisabled)) {
return false;
}
if (IsEnableFlagSet(KeyboardEnableFlag::kPolicyEnabled))
return true;
// Command line overrides extension and touch enabled flags.
if (IsEnableFlagSet(KeyboardEnableFlag::kCommandLineEnabled))
return true;
if (IsEnableFlagSet(KeyboardEnableFlag::kCommandLineDisabled))
return false;
if (IsEnableFlagSet(KeyboardEnableFlag::kExtensionDisabled))
return false;
return IsEnableFlagSet(KeyboardEnableFlag::kExtensionEnabled) ||
IsEnableFlagSet(KeyboardEnableFlag::kTouchEnabled);
}
void KeyboardUIController::UpdateKeyboardAsRequestedBy(
KeyboardEnableFlag flag) {
this->NotifyKeyboardConfigChanged();
if (IsKeyboardEnableRequested()) {
// Note that there are two versions of the on-screen keyboard. A full layout
// is provided for accessibility, which includes sticky modifier keys to
// enable typing of hotkeys. A compact version is used in tablet mode to
// provide a layout with larger keys to facilitate touch typing. In the
// event that the a11y keyboard is being disabled, an on-screen keyboard
// might still be enabled and a forced reset is required to pick up the
// layout change.
if (IsEnabled() && flag == KeyboardEnableFlag::kAccessibilityEnabled)
RebuildKeyboardIfEnabled();
else
EnableKeyboard();
} else {
DisableKeyboard();
}
}
bool KeyboardUIController::IsKeyboardOverscrollEnabled() const {
if (!IsEnabled())
return false;
// Users of the sticky accessibility on-screen keyboard are likely to be using
// mouse input, which may interfere with overscrolling.
if (IsEnabled() && !IsOverscrollAllowed())
return false;
// If overscroll enabled behavior is set, use it instead. Currently
// login / out-of-box disable keyboard overscroll. http://crbug.com/363635
if (keyboard_config_.overscroll_behavior !=
KeyboardOverscrollBehavior::kDefault) {
return keyboard_config_.overscroll_behavior ==
KeyboardOverscrollBehavior::kEnabled;
}
return true;
}
// private
void KeyboardUIController::HideKeyboard(HideReason reason) {
TRACE_EVENT0("vk", "HideKeyboard");
// Decide whether regaining focus in a web-based text field should cause
// the keyboard to come back.
switch (reason) {
case HIDE_REASON_SYSTEM_IMPLICIT:
time_of_last_blur_ = base::Time::Now();
break;
case HIDE_REASON_SYSTEM_TEMPORARY:
case HIDE_REASON_SYSTEM_EXPLICIT:
case HIDE_REASON_USER_EXPLICIT:
case HIDE_REASON_USER_IMPLICIT:
time_of_last_blur_ = base::Time::UnixEpoch();
break;
}
switch (model_.state()) {
case KeyboardUIState::kUnknown:
case KeyboardUIState::kInitial:
case KeyboardUIState::kHidden:
return;
case KeyboardUIState::kLoading:
show_on_keyboard_window_load_ = false;
return;
case KeyboardUIState::kWillHide:
case KeyboardUIState::kShown: {
NotifyKeyboardBoundsChanging(gfx::Rect(),
reason == HIDE_REASON_SYSTEM_TEMPORARY);
set_keyboard_locked(false);
aura::Window* window = GetKeyboardWindow();
DCHECK(window);
animation_observer_ = std::make_unique<CallbackAnimationObserver>(
base::BindOnce(&KeyboardUIController::HideAnimationFinished,
base::Unretained(this)));
ui::ScopedLayerAnimationSettings layer_animation_settings(
window->layer()->GetAnimator());
layer_animation_settings.AddObserver(animation_observer_.get());
{
// Scoped settings go into effect when scope ends.
::wm::ScopedHidingAnimationSettings hiding_settings(window);
container_behavior_->DoHidingAnimation(window, &hiding_settings);
}
ui_->HideKeyboardWindow();
ChangeState(KeyboardUIState::kHidden);
for (auto& observer : observer_list_)
observer.OnKeyboardHidden(reason == HIDE_REASON_SYSTEM_TEMPORARY);
break;
}
}
}
void KeyboardUIController::HideKeyboardByUser() {
HideKeyboard(HIDE_REASON_USER_EXPLICIT);
}
void KeyboardUIController::HideKeyboardImplicitlyByUser() {
if (!keyboard_locked_)
HideKeyboard(HIDE_REASON_USER_IMPLICIT);
}
void KeyboardUIController::HideKeyboardTemporarilyForTransition() {
HideKeyboard(HIDE_REASON_SYSTEM_TEMPORARY);
}
void KeyboardUIController::HideKeyboardExplicitlyBySystem() {
HideKeyboard(HIDE_REASON_SYSTEM_EXPLICIT);
}
void KeyboardUIController::HideKeyboardImplicitlyBySystem() {
if (model_.state() != KeyboardUIState::kShown || keyboard_locked_)
return;
ChangeState(KeyboardUIState::kWillHide);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&KeyboardUIController::HideKeyboard,
weak_factory_will_hide_.GetWeakPtr(),
HIDE_REASON_SYSTEM_IMPLICIT),
kHideKeyboardDelay);
}
// private
void KeyboardUIController::HideAnimationFinished() {
if (model_.state() == KeyboardUIState::kHidden) {
if (queued_container_type_) {
SetContainerBehaviorInternal(queued_container_type_->container_type());
// The position of the container window will be adjusted shortly in
// |PopulateKeyboardContent| before showing animation, so we can set the
// passed bounds directly.
SetKeyboardWindowBounds(queued_container_type_->target_bounds());
ShowKeyboard(false /* lock */);
}
if (queued_display_change_) {
ShowKeyboardInDisplay(queued_display_change_->new_display());
SetKeyboardWindowBounds(queued_display_change_->new_bounds_in_local());
queued_display_change_ = nullptr;
}
}
}
// private
void KeyboardUIController::ShowAnimationFinished() {
// Notify observers after animation finished to prevent reveal desktop
// background during animation.
// If the current state is not SHOWN, it means the state was changed after the
// animation started. Do not tell the observers the stale bounds.
if (model_.state() == KeyboardUIState::kShown)
NotifyKeyboardBoundsChanging(GetKeyboardWindow()->GetBoundsInRootWindow());
}
// private
void KeyboardUIController::SetContainerBehaviorInternal(ContainerType type) {
// Reset the hit test event targeter because the hit test bounds will
// be wrong when container type changes and may cause the UI to be unusable.
if (GetKeyboardWindow())
GetKeyboardWindow()->SetEventTargeter(nullptr);
switch (type) {
case ContainerType::kFullWidth:
container_behavior_ = std::make_unique<ContainerFullWidthBehavior>(this);
break;
case ContainerType::kFloating:
container_behavior_ = std::make_unique<ContainerFloatingBehavior>(this);
break;
}
}
void KeyboardUIController::ShowKeyboard(bool lock) {
// TODO(b/245019967): Delete lock arg.
// Outside of unittests, this function is only ever called with
// lock = false.
// Maybe it could be refactored to not support the lock = true case.
DVLOG(1) << "ShowKeyboard";
set_keyboard_locked(lock);
ShowKeyboardInternal(layout_delegate_->GetContainerForDefaultDisplay());
}
void KeyboardUIController::ShowKeyboardInDisplay(
const display::Display& display) {
DVLOG(1) << "ShowKeyboardInDisplay: " << display.id();
set_keyboard_locked(true);
ShowKeyboardInternal(layout_delegate_->GetContainerForDisplay(display));
}
gfx::Rect KeyboardUIController::GetVisualBoundsInScreen() const {
gfx::Rect visual_bounds_in_screen = visual_bounds_in_root_;
::wm::ConvertRectToScreen(GetRootWindow(), &visual_bounds_in_screen);
return visual_bounds_in_screen;
}
void KeyboardUIController::LoadKeyboardWindowInBackground() {
DCHECK_EQ(model_.state(), KeyboardUIState::kInitial);
TRACE_EVENT0("vk", "LoadKeyboardWindowInBackground");
// For now, using Unretained is safe here because the |ui_| is owned by
// |this| and the callback does not outlive |ui_|.
// TODO(crbug.com/40577582): Use a weak ptr here in case this
// assumption changes.
DVLOG(1) << "LoadKeyboardWindow";
aura::Window* keyboard_window = ui_->LoadKeyboardWindow(
base::BindOnce(&KeyboardUIController::NotifyKeyboardWindowLoaded,
base::Unretained(this)));
keyboard_window->AddPreTargetHandler(&event_handler_);
keyboard_window->AddObserver(this);
parent_container_->AddChild(keyboard_window);
ChangeState(KeyboardUIState::kLoading);
}
ui::InputMethod* KeyboardUIController::GetInputMethodForTest() {
return ui_->GetInputMethod();
}
void KeyboardUIController::EnsureCaretInWorkAreaForTest(
const gfx::Rect& occluded_bounds_in_screen) {
EnsureCaretInWorkArea(occluded_bounds_in_screen);
}
// ContainerBehavior::Delegate overrides
bool KeyboardUIController::IsKeyboardLocked() const {
return keyboard_locked_;
}
gfx::Rect KeyboardUIController::GetBoundsInScreen() const {
return GetKeyboardWindow()->GetBoundsInScreen();
}
void KeyboardUIController::MoveKeyboardWindow(const gfx::Rect& new_bounds) {
DCHECK(IsKeyboardVisible());
SetKeyboardWindowBounds(new_bounds);
}
void KeyboardUIController::MoveKeyboardWindowToDisplay(
const display::Display& display,
const gfx::Rect& new_bounds_in_root) {
queued_display_change_ =
std::make_unique<QueuedDisplayChange>(display, new_bounds_in_root);
HideKeyboardTemporarilyForTransition();
}
void KeyboardUIController::TransferGestureEventToShelf(
const ui::GestureEvent& e) {
layout_delegate_->TransferGestureEventToShelf(e);
}
// aura::WindowObserver overrides
void KeyboardUIController::OnWindowAddedToRootWindow(aura::Window* window) {
container_behavior_->SetCanonicalBounds(GetKeyboardWindow(),
GetRootWindow()->bounds());
}
void KeyboardUIController::OnWindowBoundsChanged(
aura::Window* window,
const gfx::Rect& old_bounds_in_root,
const gfx::Rect& new_bounds_in_root,
ui::PropertyChangeReason reason) {
if (!GetKeyboardWindow())
return;
// |window| could be the root window (for detecting screen rotations) or the
// keyboard window (for detecting keyboard bounds changes).
if (window == GetRootWindow())
container_behavior_->SetCanonicalBounds(GetKeyboardWindow(),
new_bounds_in_root);
else if (window == GetKeyboardWindow())
NotifyKeyboardBoundsChanging(new_bounds_in_root);
}
// InputMethodObserver overrides
void KeyboardUIController::OnInputMethodDestroyed(
const ui::InputMethod* input_method) {
ime_observation_.Reset();
OnTextInputStateChanged(nullptr);
}
void KeyboardUIController::OnTextInputStateChanged(
const ui::TextInputClient* client) {
TRACE_EVENT0("vk", "OnTextInputStateChanged");
bool focused =
client && (client->GetTextInputType() != ui::TEXT_INPUT_TYPE_NONE &&
client->GetTextInputMode() != ui::TEXT_INPUT_MODE_NONE);
bool should_hide = !focused && container_behavior_->TextBlurHidesKeyboard();
bool is_web =
client && client->GetTextInputFlags() != ui::TEXT_INPUT_FLAG_NONE;
if (should_hide) {
switch (model_.state()) {
case KeyboardUIState::kLoading:
show_on_keyboard_window_load_ = false;
return;
case KeyboardUIState::kShown:
HideKeyboardImplicitlyBySystem();
return;
default:
return;
}
} else {
switch (model_.state()) {
case KeyboardUIState::kWillHide:
// Abort a pending keyboard hide.
ChangeState(KeyboardUIState::kShown);
return;
case KeyboardUIState::kHidden:
if (focused && is_web)
ShowKeyboardIfWithinTransientBlurThreshold();
return;
default:
break;
}
// Do not explicitly show the Virtual keyboard unless it is in the process
// of hiding or the hide duration was very short (transient blur). Instead,
// the virtual keyboard is shown in response to a user gesture (mouse or
// touch) that is received while an element has input focus. Showing the
// keyboard requires an explicit call to
// OnVirtualKeyboardVisibilityChangedIfEnabled.
}
}
void KeyboardUIController::ShowKeyboardIfWithinTransientBlurThreshold() {
if (should_show_on_transient_blur_ &&
base::Time::Now() - time_of_last_blur_ < kTransientBlurThreshold) {
ShowKeyboard(false);
}
}
void KeyboardUIController::SetShouldShowOnTransientBlur(bool should_show) {
should_show_on_transient_blur_ = should_show;
}
void KeyboardUIController::OnVirtualKeyboardVisibilityChangedIfEnabled(
bool should_show) {
if (should_show) {
DVLOG(1) << "OnVirtualKeyboardVisibilityChangedIfEnabled: " << IsEnabled();
// Calling |ShowKeyboardInternal| may move the keyboard to another display.
if (IsEnabled() && !keyboard_locked_)
ShowKeyboardInternal(layout_delegate_->GetContainerForDefaultDisplay());
} else {
HideKeyboardExplicitlyBySystem();
}
}
void KeyboardUIController::ShowKeyboardInternal(
aura::Window* target_container) {
PopulateKeyboardContent(target_container);
UpdateInputMethodObserver();
}
void KeyboardUIController::PopulateKeyboardContent(
aura::Window* target_container) {
DCHECK_NE(model_.state(), KeyboardUIState::kInitial);
DVLOG(1) << "PopulateKeyboardContent: " << StateToStr(model_.state());
TRACE_EVENT0("vk", "PopulateKeyboardContent");
MoveToParentContainer(target_container);
aura::Window* keyboard_window = GetKeyboardWindow();
DCHECK(keyboard_window);
DCHECK_EQ(parent_container_, keyboard_window->parent());
switch (model_.state()) {
case KeyboardUIState::kShown:
return;
case KeyboardUIState::kLoading:
show_on_keyboard_window_load_ = true;
return;
default:
break;
}
ui_->ReloadKeyboardIfNeeded();
switch (model_.state()) {
case KeyboardUIState::kWillHide:
ChangeState(KeyboardUIState::kShown);
return;
default:
break;
}
DCHECK_EQ(model_.state(), KeyboardUIState::kHidden);
// If the container is not animating, makes sure the position and opacity
// are at begin states for animation.
container_behavior_->InitializeShowAnimationStartingState(keyboard_window);
RecordUkmKeyboardShown();
ui::LayerAnimator* container_animator =
keyboard_window->layer()->GetAnimator();
container_animator->set_preemption_strategy(
ui::LayerAnimator::IMMEDIATELY_ANIMATE_TO_NEW_TARGET);
ui_->ShowKeyboardWindow();
animation_observer_ = std::make_unique<CallbackAnimationObserver>(
base::BindOnce(&KeyboardUIController::ShowAnimationFinished,
base::Unretained(this)));
ui::ScopedLayerAnimationSettings settings(container_animator);
settings.AddObserver(animation_observer_.get());
container_behavior_->DoShowingAnimation(keyboard_window, &settings);
// the queued container behavior will notify JS to change layout when it
// gets destroyed.
queued_container_type_ = nullptr;
ChangeState(KeyboardUIState::kShown);
UMA_HISTOGRAM_ENUMERATION("InputMethod.VirtualKeyboard.ContainerBehavior",
GetActiveContainerType());
}
bool KeyboardUIController::WillHideKeyboard() const {
bool res = weak_factory_will_hide_.HasWeakPtrs();
DCHECK_EQ(res, model_.state() == KeyboardUIState::kWillHide);
return res;
}
void KeyboardUIController::NotifyKeyboardConfigChanged() {
for (auto& observer : observer_list_)
observer.OnKeyboardConfigChanged(keyboard_config_);
}
void KeyboardUIController::ChangeState(KeyboardUIState state) {
model_.ChangeState(state);
if (state != KeyboardUIState::kWillHide)
weak_factory_will_hide_.InvalidateWeakPtrs();
if (state != KeyboardUIState::kLoading)
show_on_keyboard_window_load_ = false;
weak_factory_report_lingering_state_.InvalidateWeakPtrs();
switch (model_.state()) {
case KeyboardUIState::kLoading:
case KeyboardUIState::kWillHide:
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&KeyboardUIController::ReportLingeringState,
weak_factory_report_lingering_state_.GetWeakPtr()),
kReportLingeringStateDelay);
break;
default:
// Do nothing
break;
}
}
void KeyboardUIController::ReportLingeringState() {
LOG(ERROR) << "KeyboardUIController lingering in "
<< StateToStr(model_.state());
}
gfx::Rect KeyboardUIController::GetWorkspaceOccludedBoundsInScreen() const {
// TODO(crbug.com/1157150): Investigate why the keyboard window or its root
// window is null or missing a ScreenPositionClient when adding a new monitor.
if (!ui_ || !GetKeyboardWindow() || !GetKeyboardWindow()->GetRootWindow() ||
!aura::client::GetScreenPositionClient(
GetKeyboardWindow()->GetRootWindow())) {
return gfx::Rect();
}
const gfx::Rect visual_bounds_in_window(visual_bounds_in_root_.size());
gfx::Rect occluded_bounds_in_screen =
container_behavior_->GetOccludedBounds(visual_bounds_in_window);
::wm::ConvertRectToScreen(GetKeyboardWindow(), &occluded_bounds_in_screen);
return occluded_bounds_in_screen;
}
gfx::Rect KeyboardUIController::GetKeyboardLockScreenOffsetBounds() const {
// Overscroll is generally dependent on lock state, however, its behavior
// temporarily overridden by a static field in certain lock screen contexts.
// Furthermore, floating keyboard should never affect layout.
if (!IsKeyboardOverscrollEnabled() &&
container_behavior_->GetType() != ContainerType::kFloating) {
return visual_bounds_in_root_;
}
return gfx::Rect();
}
void KeyboardUIController::SetOccludedBounds(
const gfx::Rect& bounds_in_window) {
container_behavior_->SetOccludedBounds(bounds_in_window);
// Notify that only the occluded bounds have changed.
if (IsKeyboardVisible())
NotifyKeyboardBoundsChanging(visual_bounds_in_root_);
}
void KeyboardUIController::SetHitTestBounds(
const std::vector<gfx::Rect>& bounds_in_window) {
if (!GetKeyboardWindow())
return;
GetKeyboardWindow()->SetEventTargeter(
std::make_unique<ShapedWindowTargeter>(bounds_in_window));
}
bool KeyboardUIController::SetAreaToRemainOnScreen(
const gfx::Rect& bounds_in_window) {
gfx::Rect window_bounds_in_screen = GetKeyboardWindow()->GetBoundsInScreen();
gfx::Rect bounds_in_screen =
gfx::Rect(window_bounds_in_screen.x() + bounds_in_window.x(),
window_bounds_in_screen.y() + bounds_in_window.y(),
bounds_in_window.width(), bounds_in_window.height());
if (!window_bounds_in_screen.Contains(bounds_in_screen))
return false;
container_behavior_->SetAreaToRemainOnScreen(bounds_in_window);
return true;
}
bool KeyboardUIController::SetKeyboardWindowBoundsInScreen(
const gfx::Rect& bounds_in_screen) {
const display::Display& current_display =
display_util_.GetNearestDisplayToWindow(GetRootWindow());
gfx::Rect display_bounds = current_display.bounds();
if (bounds_in_screen.width() > display_bounds.width() ||
bounds_in_screen.height() > display_bounds.height()) {
return false;
}
gfx::Rect constrained_bounds_in_screen =
AdjustSetBoundsRequest(current_display.bounds(), bounds_in_screen);
GetKeyboardWindow()->SetBoundsInScreen(constrained_bounds_in_screen,
current_display);
return true;
}
gfx::Rect KeyboardUIController::AdjustSetBoundsRequest(
const gfx::Rect& display_bounds,
const gfx::Rect& requested_bounds_in_screen) const {
return container_behavior_->AdjustSetBoundsRequest(
display_bounds, requested_bounds_in_screen);
}
bool KeyboardUIController::IsOverscrollAllowed() const {
return container_behavior_->IsOverscrollAllowed();
}
bool KeyboardUIController::HandlePointerEvent(const ui::LocatedEvent& event) {
const display::Display& current_display =
display_util_.GetNearestDisplayToWindow(GetRootWindow());
return container_behavior_->HandlePointerEvent(event, current_display);
}
bool KeyboardUIController::HandleGestureEvent(const ui::GestureEvent& event) {
return container_behavior_->HandleGestureEvent(event, GetBoundsInScreen());
}
void KeyboardUIController::SetContainerType(
ContainerType type,
const gfx::Rect& target_bounds_in_root,
base::OnceCallback<void(bool)> callback) {
if (container_behavior_->GetType() == type) {
std::move(callback).Run(false);
return;
}
if (model_.state() == KeyboardUIState::kShown) {
// Keyboard is already shown. Hiding the keyboard at first then switching
// container type.
queued_container_type_ = std::make_unique<QueuedContainerType>(
this, type, target_bounds_in_root, std::move(callback));
HideKeyboard(HIDE_REASON_SYSTEM_TEMPORARY);
} else {
// Keyboard is hidden. Switching the container type immediately and invoking
// the passed callback now.
SetContainerBehaviorInternal(type);
SetKeyboardWindowBounds(target_bounds_in_root);
DCHECK_EQ(GetActiveContainerType(), type);
std::move(callback).Run(true /* change_successful */);
}
}
void KeyboardUIController::RecordUkmKeyboardShown() {
ui::TextInputClient* text_input_client = GetTextInputClient();
if (!text_input_client)
return;
keyboard::RecordUkmKeyboardShown(
text_input_client->GetClientSourceForMetrics(),
text_input_client->GetTextInputType());
}
void KeyboardUIController::SetDraggableArea(const gfx::Rect& rect) {
container_behavior_->SetDraggableArea(rect);
}
bool KeyboardUIController::IsKeyboardVisible() {
if (model_.state() == KeyboardUIState::kShown) {
DCHECK(IsEnabled());
return true;
}
return false;
}
ui::TextInputClient* KeyboardUIController::GetTextInputClient() {
return ui_->GetInputMethod()->GetTextInputClient();
}
void KeyboardUIController::UpdateInputMethodObserver() {
ui::InputMethod* ime = ui_->GetInputMethod();
// IME could be null during initialization. Ignoring the case is okay because
// UpdateInputMethodObserver() will be called later on.
if (!ime)
return;
if (ime_observation_.IsObservingSource(ime))
return;
// Only observes the current active IME.
ime_observation_.Reset();
ime_observation_.Observe(ime);
// Note: We used to call OnTextInputStateChanged(ime->GetTextInputClient())
// here, but that can trigger HideKeyboardImplicitlyBySystem() from a call to
// ShowKeyboard() when using mojo APIs in Chrome (SingleProcessMash) if
// ime->GetTextInputClient() isn't focused.
}
void KeyboardUIController::EnsureCaretInWorkArea(
const gfx::Rect& occluded_bounds_in_screen) {
ui::InputMethod* ime = ui_->GetInputMethod();
if (!ime)
return;
TRACE_EVENT0("vk", "EnsureCaretInWorkArea");
if (IsOverscrollAllowed()) {
ime->SetVirtualKeyboardBounds(occluded_bounds_in_screen);
} else if (ime->GetTextInputClient()) {
ime->GetTextInputClient()->EnsureCaretNotInRect(occluded_bounds_in_screen);
}
}
void KeyboardUIController::EnableFlagsChanged() {
for (auto& observer : observer_list_)
observer.OnKeyboardEnableFlagsChanged(keyboard_enable_flags_);
}
} // namespace keyboard