blob: b925c227b214a8ed6643ed24f1905716cf625e8c [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/keyboard/keyboard_controller_impl.h"
#include <memory>
#include <set>
#include <utility>
#include "ash/keyboard/ui/container_behavior.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/keyboard/ui/test/keyboard_test_util.h"
#include "ash/public/cpp/keyboard/keyboard_controller.h"
#include "ash/public/cpp/test/test_keyboard_controller_observer.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_helper.h"
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/stl_util.h"
#include "base/test/bind_test_util.h"
#include "base/test/scoped_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/test/display_manager_test_api.h"
using keyboard::KeyboardConfig;
using keyboard::KeyboardEnableFlag;
namespace ash {
namespace {
class TestContainerBehavior : public keyboard::ContainerBehavior {
public:
TestContainerBehavior() : keyboard::ContainerBehavior(nullptr) {}
~TestContainerBehavior() override = default;
// keyboard::ContainerBehavior
void DoShowingAnimation(
aura::Window* window,
ui::ScopedLayerAnimationSettings* animation_settings) override {}
void DoHidingAnimation(
aura::Window* window,
wm::ScopedHidingAnimationSettings* animation_settings) override {}
void InitializeShowAnimationStartingState(aura::Window* container) override {}
gfx::Rect AdjustSetBoundsRequest(
const gfx::Rect& display_bounds,
const gfx::Rect& requested_bounds_in_screen_coords) override {
return gfx::Rect();
}
void SetCanonicalBounds(aura::Window* container,
const gfx::Rect& display_bounds) override {}
bool IsOverscrollAllowed() const override { return true; }
void SavePosition(const gfx::Rect& keyboard_bounds_in_screen,
const gfx::Size& screen_size) override {}
bool HandlePointerEvent(const ui::LocatedEvent& event,
const display::Display& current_display) override {
return false;
}
keyboard::ContainerType GetType() const override { return type_; }
bool TextBlurHidesKeyboard() const override { return false; }
void SetOccludedBounds(const gfx::Rect& bounds) override {
occluded_bounds_ = bounds;
}
gfx::Rect GetOccludedBounds(
const gfx::Rect& visual_bounds_in_window) const override {
return occluded_bounds_;
}
bool OccludedBoundsAffectWorkspaceLayout() const override { return false; }
void SetDraggableArea(const gfx::Rect& rect) override {
draggable_area_ = rect;
}
const gfx::Rect& occluded_bounds() const { return occluded_bounds_; }
const gfx::Rect& draggable_area() const { return draggable_area_; }
private:
keyboard::ContainerType type_ = keyboard::ContainerType::kFullWidth;
gfx::Rect occluded_bounds_;
gfx::Rect draggable_area_;
};
class KeyboardControllerImplTest : public AshTestBase {
public:
KeyboardControllerImplTest() = default;
~KeyboardControllerImplTest() override = default;
void SetUp() override {
AshTestBase::SetUp();
// Set the initial observer config to the default config.
test_observer()->set_config(keyboard_controller()->GetKeyboardConfig());
}
void TearDown() override { AshTestBase::TearDown(); }
KeyboardControllerImpl* keyboard_controller() {
return Shell::Get()->keyboard_controller();
}
keyboard::KeyboardUIController* keyboard_ui_controller() {
return keyboard_controller()->keyboard_ui_controller();
}
TestKeyboardControllerObserver* test_observer() {
return ash_test_helper()->test_keyboard_controller_observer();
}
protected:
bool SetContainerType(keyboard::ContainerType container_type,
const base::Optional<gfx::Rect>& target_bounds) {
bool result = false;
base::RunLoop run_loop;
keyboard_controller()->SetContainerType(
container_type, target_bounds,
base::BindLambdaForTesting([&](bool success) {
result = success;
run_loop.QuitWhenIdle();
}));
run_loop.Run();
return result;
}
aura::Window* GetPrimaryRootWindow() { return Shell::GetPrimaryRootWindow(); }
aura::Window* GetSecondaryRootWindow() {
aura::Window::Windows root_windows = Shell::GetAllRootWindows();
return root_windows[0] == GetPrimaryRootWindow() ? root_windows[1]
: root_windows[0];
}
void CreateFocusedTestWindowInRootWindow(aura::Window* root_window) {
// Owned by |root_window|.
aura::Window* focusable_window =
CreateTestWindowInShellWithBounds(root_window->GetBoundsInScreen());
focusable_window->Focus();
}
private:
DISALLOW_COPY_AND_ASSIGN(KeyboardControllerImplTest);
};
} // namespace
TEST_F(KeyboardControllerImplTest, SetKeyboardConfig) {
// Enable the keyboard so that config changes trigger observer events.
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
keyboard_controller()->GetKeyboardConfig();
KeyboardConfig config = keyboard_controller()->GetKeyboardConfig();
// Set the observer config to the default config.
test_observer()->set_config(config);
// Change the keyboard config.
bool old_auto_complete = config.auto_complete;
config.auto_complete = !config.auto_complete;
keyboard_controller()->SetKeyboardConfig(std::move(config));
// Test that the config changes.
keyboard_controller()->GetKeyboardConfig();
EXPECT_NE(old_auto_complete,
keyboard_controller()->GetKeyboardConfig().auto_complete);
// Test that the test observer received the change.
EXPECT_NE(old_auto_complete, test_observer()->config().auto_complete);
}
TEST_F(KeyboardControllerImplTest, EnableFlags) {
EXPECT_FALSE(keyboard_controller()->IsKeyboardEnabled());
// Enable the keyboard.
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
std::set<keyboard::KeyboardEnableFlag> enable_flags =
keyboard_controller()->GetEnableFlags();
EXPECT_TRUE(
base::Contains(enable_flags, KeyboardEnableFlag::kExtensionEnabled));
EXPECT_EQ(enable_flags, test_observer()->enable_flags());
EXPECT_TRUE(keyboard_controller()->IsKeyboardEnabled());
// Set the enable override to disable the keyboard.
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kPolicyDisabled);
enable_flags = keyboard_controller()->GetEnableFlags();
EXPECT_TRUE(
base::Contains(enable_flags, KeyboardEnableFlag::kExtensionEnabled));
EXPECT_TRUE(
base::Contains(enable_flags, KeyboardEnableFlag::kPolicyDisabled));
EXPECT_EQ(enable_flags, test_observer()->enable_flags());
EXPECT_FALSE(keyboard_controller()->IsKeyboardEnabled());
// Clear the enable override; should enable the keyboard.
keyboard_controller()->ClearEnableFlag(KeyboardEnableFlag::kPolicyDisabled);
enable_flags = keyboard_controller()->GetEnableFlags();
EXPECT_TRUE(
base::Contains(enable_flags, KeyboardEnableFlag::kExtensionEnabled));
EXPECT_FALSE(
base::Contains(enable_flags, KeyboardEnableFlag::kPolicyDisabled));
EXPECT_EQ(enable_flags, test_observer()->enable_flags());
EXPECT_TRUE(keyboard_controller()->IsKeyboardEnabled());
}
TEST_F(KeyboardControllerImplTest, RebuildKeyboardIfEnabled) {
EXPECT_EQ(0, test_observer()->destroyed_count());
// Enable the keyboard.
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
EXPECT_EQ(0, test_observer()->destroyed_count());
// Enable the keyboard again; this should not reload the keyboard.
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
EXPECT_EQ(0, test_observer()->destroyed_count());
// Rebuild the keyboard. This should destroy the previous keyboard window.
keyboard_controller()->RebuildKeyboardIfEnabled();
EXPECT_EQ(1, test_observer()->destroyed_count());
// Disable the keyboard. The keyboard window should be destroyed.
keyboard_controller()->ClearEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
EXPECT_EQ(2, test_observer()->destroyed_count());
}
TEST_F(KeyboardControllerImplTest, ShowAndHideKeyboard) {
// Enable the keyboard. This will create the keyboard window but not show it.
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
ASSERT_TRUE(keyboard_ui_controller()->GetKeyboardWindow());
EXPECT_FALSE(keyboard_ui_controller()->GetKeyboardWindow()->IsVisible());
// The keyboard needs to be in a loaded state before being shown.
ASSERT_TRUE(keyboard::test::WaitUntilLoaded());
keyboard_controller()->ShowKeyboard();
EXPECT_TRUE(keyboard_ui_controller()->GetKeyboardWindow()->IsVisible());
keyboard_controller()->HideKeyboard(HideReason::kUser);
EXPECT_FALSE(keyboard_ui_controller()->GetKeyboardWindow()->IsVisible());
// TODO(stevenjb): Also use TestKeyboardControllerObserver and
// IsKeyboardVisible to test visibility changes. https://crbug.com/849995.
}
TEST_F(KeyboardControllerImplTest, SetContainerType) {
// Enable the keyboard.
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
const auto default_behavior = keyboard::ContainerType::kFullWidth;
EXPECT_EQ(default_behavior,
keyboard_ui_controller()->GetActiveContainerType());
gfx::Rect target_bounds(0, 0, 10, 10);
// Set the container type to kFloating.
EXPECT_TRUE(
SetContainerType(keyboard::ContainerType::kFloating, target_bounds));
EXPECT_EQ(keyboard::ContainerType::kFloating,
keyboard_ui_controller()->GetActiveContainerType());
// Ensure that the window size is correct (position is determined by Ash).
EXPECT_EQ(
target_bounds.size(),
keyboard_ui_controller()->GetKeyboardWindow()->GetTargetBounds().size());
// Setting the container type to the current type should fail.
EXPECT_FALSE(
SetContainerType(keyboard::ContainerType::kFloating, base::nullopt));
EXPECT_EQ(keyboard::ContainerType::kFloating,
keyboard_ui_controller()->GetActiveContainerType());
}
TEST_F(KeyboardControllerImplTest, SetKeyboardLocked) {
ASSERT_FALSE(keyboard_ui_controller()->keyboard_locked());
keyboard_controller()->SetKeyboardLocked(true);
EXPECT_TRUE(keyboard_ui_controller()->keyboard_locked());
keyboard_controller()->SetKeyboardLocked(false);
EXPECT_FALSE(keyboard_ui_controller()->keyboard_locked());
}
TEST_F(KeyboardControllerImplTest, SetOccludedBounds) {
// Enable the keyboard.
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
// Override the container behavior.
auto scoped_behavior = std::make_unique<TestContainerBehavior>();
TestContainerBehavior* behavior = scoped_behavior.get();
keyboard_ui_controller()->set_container_behavior_for_test(
std::move(scoped_behavior));
gfx::Rect bounds(10, 20, 30, 40);
keyboard_controller()->SetOccludedBounds({bounds});
EXPECT_EQ(bounds, behavior->occluded_bounds());
}
TEST_F(KeyboardControllerImplTest, SetHitTestBounds) {
// Enable the keyboard.
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
ASSERT_FALSE(keyboard_ui_controller()->GetKeyboardWindow()->targeter());
// Setting the hit test bounds should set a WindowTargeter.
keyboard_ui_controller()->SetHitTestBounds({gfx::Rect(10, 20, 30, 40)});
ASSERT_TRUE(keyboard_ui_controller()->GetKeyboardWindow()->targeter());
}
TEST_F(KeyboardControllerImplTest, SetDraggableArea) {
// Enable the keyboard.
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
// Override the container behavior.
auto scoped_behavior = std::make_unique<TestContainerBehavior>();
TestContainerBehavior* behavior = scoped_behavior.get();
keyboard_ui_controller()->set_container_behavior_for_test(
std::move(scoped_behavior));
gfx::Rect bounds(10, 20, 30, 40);
keyboard_ui_controller()->SetDraggableArea(bounds);
EXPECT_EQ(bounds, behavior->draggable_area());
}
TEST_F(KeyboardControllerImplTest, ChangingSessionRebuildsKeyboard) {
// Enable the keyboard.
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
// LOGGED_IN_NOT_ACTIVE session state needs to rebuild keyboard for supervised
// user profile.
Shell::Get()->keyboard_controller()->OnSessionStateChanged(
session_manager::SessionState::LOGGED_IN_NOT_ACTIVE);
EXPECT_EQ(1, test_observer()->destroyed_count());
// ACTIVE session state also needs to rebuild keyboard for guest user profile.
Shell::Get()->keyboard_controller()->OnSessionStateChanged(
session_manager::SessionState::ACTIVE);
EXPECT_EQ(2, test_observer()->destroyed_count());
}
TEST_F(KeyboardControllerImplTest, VisualBoundsInMultipleDisplays) {
UpdateDisplay("800x600,1024x768");
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
// Show the keyboard in the second display.
keyboard_ui_controller()->ShowKeyboardInDisplay(
Shell::Get()->display_manager()->GetSecondaryDisplay());
ASSERT_TRUE(keyboard::WaitUntilShown());
gfx::Rect root_bounds = keyboard_ui_controller()->visual_bounds_in_root();
EXPECT_EQ(0, root_bounds.x());
gfx::Rect screen_bounds = keyboard_ui_controller()->GetVisualBoundsInScreen();
EXPECT_EQ(800, screen_bounds.x());
}
TEST_F(KeyboardControllerImplTest, OccludedBoundsInMultipleDisplays) {
UpdateDisplay("800x600,1024x768");
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
// Show the keyboard in the second display.
keyboard_ui_controller()->ShowKeyboardInDisplay(
Shell::Get()->display_manager()->GetSecondaryDisplay());
ASSERT_TRUE(keyboard::WaitUntilShown());
gfx::Rect screen_bounds =
keyboard_ui_controller()->GetWorkspaceOccludedBoundsInScreen();
EXPECT_EQ(800, screen_bounds.x());
}
// Test for http://crbug.com/303429. |GetContainerForDisplay| should move
// keyboard to specified display even when it's not touchable.
TEST_F(KeyboardControllerImplTest, GetContainerForDisplay) {
UpdateDisplay("500x500,500x500");
// Make primary display touchable.
display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
.SetTouchSupport(GetPrimaryDisplay().id(),
display::Display::TouchSupport::AVAILABLE);
EXPECT_EQ(display::Display::TouchSupport::AVAILABLE,
GetPrimaryDisplay().touch_support());
EXPECT_NE(display::Display::TouchSupport::AVAILABLE,
GetSecondaryDisplay().touch_support());
// Move to primary display.
EXPECT_EQ(GetPrimaryRootWindow(),
keyboard_controller()
->GetContainerForDisplay(GetPrimaryDisplay())
->GetRootWindow());
// Move to secondary display.
EXPECT_EQ(GetSecondaryRootWindow(),
keyboard_controller()
->GetContainerForDisplay(GetSecondaryDisplay())
->GetRootWindow());
}
// Test for http://crbug.com/297858. |GetContainerForDefaultDisplay| should
// return the primary display if no display has touch capability and
// no window is focused.
TEST_F(KeyboardControllerImplTest,
DefaultContainerInPrimaryDisplayWhenNoDisplayHasTouch) {
UpdateDisplay("500x500,500x500");
EXPECT_NE(display::Display::TouchSupport::AVAILABLE,
GetPrimaryDisplay().touch_support());
EXPECT_NE(display::Display::TouchSupport::AVAILABLE,
GetSecondaryDisplay().touch_support());
EXPECT_EQ(
GetPrimaryRootWindow(),
keyboard_controller()->GetContainerForDefaultDisplay()->GetRootWindow());
}
// Test for http://crbug.com/297858. |GetContainerForDefaultDisplay| should
// move keyboard to focused display if no display has touch capability.
TEST_F(KeyboardControllerImplTest,
DefaultContainerIsInFocusedDisplayWhenNoDisplayHasTouch) {
UpdateDisplay("500x500,500x500");
EXPECT_NE(display::Display::TouchSupport::AVAILABLE,
GetPrimaryDisplay().touch_support());
EXPECT_NE(display::Display::TouchSupport::AVAILABLE,
GetSecondaryDisplay().touch_support());
CreateFocusedTestWindowInRootWindow(GetSecondaryRootWindow());
EXPECT_EQ(
GetSecondaryRootWindow(),
keyboard_controller()->GetContainerForDefaultDisplay()->GetRootWindow());
}
// Test for http://crbug.com/303429. |GetContainerForDefaultDisplay| should
// move keyboard to first touchable display when there is one.
TEST_F(KeyboardControllerImplTest, DefaultContainerIsInFirstTouchableDisplay) {
UpdateDisplay("500x500,500x500");
// Make secondary display touchable.
display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
.SetTouchSupport(GetSecondaryDisplay().id(),
display::Display::TouchSupport::AVAILABLE);
EXPECT_NE(display::Display::TouchSupport::AVAILABLE,
GetPrimaryDisplay().touch_support());
EXPECT_EQ(display::Display::TouchSupport::AVAILABLE,
GetSecondaryDisplay().touch_support());
EXPECT_EQ(
GetSecondaryRootWindow(),
keyboard_controller()->GetContainerForDefaultDisplay()->GetRootWindow());
}
// Test for http://crbug.com/303429. |GetContainerForDefaultDisplay| should
// move keyboard to first touchable display when the focused display is not
// touchable.
TEST_F(
KeyboardControllerImplTest,
DefaultContainerIsInFirstTouchableDisplayIfFocusedDisplayIsNotTouchable) {
UpdateDisplay("500x500,500x500");
// Make secondary display touchable.
display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
.SetTouchSupport(GetSecondaryDisplay().id(),
display::Display::TouchSupport::AVAILABLE);
EXPECT_NE(display::Display::TouchSupport::AVAILABLE,
GetPrimaryDisplay().touch_support());
EXPECT_EQ(display::Display::TouchSupport::AVAILABLE,
GetSecondaryDisplay().touch_support());
// Focus on primary display.
CreateFocusedTestWindowInRootWindow(GetPrimaryRootWindow());
EXPECT_EQ(
GetSecondaryRootWindow(),
keyboard_controller()->GetContainerForDefaultDisplay()->GetRootWindow());
}
// Test for http://crbug.com/303429. |GetContainerForDefaultDisplay| should
// move keyborad to first touchable display when there is one.
TEST_F(KeyboardControllerImplTest,
DefaultContainerIsInFocusedDisplayIfTouchable) {
UpdateDisplay("500x500,500x500");
// Make both displays touchable.
display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
.SetTouchSupport(GetPrimaryDisplay().id(),
display::Display::TouchSupport::AVAILABLE);
display::test::DisplayManagerTestApi(Shell::Get()->display_manager())
.SetTouchSupport(GetSecondaryDisplay().id(),
display::Display::TouchSupport::AVAILABLE);
EXPECT_EQ(display::Display::TouchSupport::AVAILABLE,
GetPrimaryDisplay().touch_support());
EXPECT_EQ(display::Display::TouchSupport::AVAILABLE,
GetSecondaryDisplay().touch_support());
// Focus on secondary display.
CreateFocusedTestWindowInRootWindow(GetSecondaryRootWindow());
EXPECT_EQ(
GetSecondaryRootWindow(),
keyboard_controller()->GetContainerForDefaultDisplay()->GetRootWindow());
// Focus on primary display.
CreateFocusedTestWindowInRootWindow(GetPrimaryRootWindow());
EXPECT_EQ(
GetPrimaryRootWindow(),
keyboard_controller()->GetContainerForDefaultDisplay()->GetRootWindow());
}
// Test for https://crbug.com/897007.
TEST_F(KeyboardControllerImplTest, ShowKeyboardInSecondaryDisplay) {
UpdateDisplay("500x500,500x500");
keyboard_controller()->SetEnableFlag(KeyboardEnableFlag::kExtensionEnabled);
// Show in secondary display.
keyboard_ui_controller()->ShowKeyboardInDisplay(GetSecondaryDisplay());
EXPECT_EQ(GetSecondaryRootWindow(),
keyboard_ui_controller()->GetRootWindow());
ASSERT_TRUE(keyboard::WaitUntilShown());
EXPECT_TRUE(
!keyboard_ui_controller()->GetKeyboardWindow()->bounds().IsEmpty());
}
} // namespace ash