|  | // 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 |