blob: bfb512fe79fd2cea03b9ca7c183312c1c77d902b [file] [log] [blame]
// Copyright (c) 2013 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/shelf/shelf_widget.h"
#include "ash/keyboard/ui/keyboard_ui_controller.h"
#include "ash/keyboard/ui/keyboard_util.h"
#include "ash/keyboard/ui/test/keyboard_test_util.h"
#include "ash/public/cpp/ash_features.h"
#include "ash/public/cpp/ash_switches.h"
#include "ash/public/cpp/keyboard/keyboard_switches.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/root_window_controller.h"
#include "ash/screen_util.h"
#include "ash/shelf/drag_handle.h"
#include "ash/shelf/hotseat_transition_animator.h"
#include "ash/shelf/login_shelf_view.h"
#include "ash/shelf/shelf.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_navigation_widget.h"
#include "ash/shelf/shelf_view.h"
#include "ash/shelf/shelf_view_test_api.h"
#include "ash/shell.h"
#include "ash/system/status_area_widget.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/ash_test_helper.h"
#include "ash/test_shell_delegate.h"
#include "ash/wm/window_util.h"
#include "base/callback_helpers.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "components/session_manager/session_manager_types.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/display/display.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/event_generator.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
namespace ash {
namespace {
ShelfWidget* GetShelfWidget() {
return AshTestBase::GetPrimaryShelf()->shelf_widget();
}
ShelfLayoutManager* GetShelfLayoutManager() {
return GetShelfWidget()->shelf_layout_manager();
}
void TestLauncherAlignment(aura::Window* root,
ShelfAlignment alignment,
const gfx::Rect& expected) {
Shelf::ForWindow(root)->SetAlignment(alignment);
EXPECT_EQ(expected.ToString(), display::Screen::GetScreen()
->GetDisplayNearestWindow(root)
.work_area()
.ToString());
}
using SessionState = session_manager::SessionState;
using ShelfWidgetTest = AshTestBase;
TEST_F(ShelfWidgetTest, TestAlignment) {
UpdateDisplay("400x400");
const int bottom_inset = 400 - ShelfConfig::Get()->shelf_size();
{
SCOPED_TRACE("Single Bottom");
TestLauncherAlignment(Shell::GetPrimaryRootWindow(),
ShelfAlignment::kBottom,
gfx::Rect(0, 0, 400, bottom_inset));
}
{
SCOPED_TRACE("Single Locked");
TestLauncherAlignment(Shell::GetPrimaryRootWindow(),
ShelfAlignment::kBottomLocked,
gfx::Rect(0, 0, 400, bottom_inset));
}
{
SCOPED_TRACE("Single Right");
TestLauncherAlignment(Shell::GetPrimaryRootWindow(), ShelfAlignment::kRight,
gfx::Rect(0, 0, bottom_inset, 400));
}
{
SCOPED_TRACE("Single Left");
TestLauncherAlignment(
Shell::GetPrimaryRootWindow(), ShelfAlignment::kLeft,
gfx::Rect(ShelfConfig::Get()->shelf_size(), 0, bottom_inset, 400));
}
}
TEST_F(ShelfWidgetTest, TestAlignmentForMultipleDisplays) {
UpdateDisplay("300x300,500x500");
const int shelf_inset_first = 300 - ShelfConfig::Get()->shelf_size();
const int shelf_inset_second = 500 - ShelfConfig::Get()->shelf_size();
aura::Window::Windows root_windows = Shell::GetAllRootWindows();
{
SCOPED_TRACE("Primary Bottom");
TestLauncherAlignment(root_windows[0], ShelfAlignment::kBottom,
gfx::Rect(0, 0, 300, shelf_inset_first));
}
{
SCOPED_TRACE("Primary Locked");
TestLauncherAlignment(root_windows[0], ShelfAlignment::kBottomLocked,
gfx::Rect(0, 0, 300, shelf_inset_first));
}
{
SCOPED_TRACE("Primary Right");
TestLauncherAlignment(root_windows[0], ShelfAlignment::kRight,
gfx::Rect(0, 0, shelf_inset_first, 300));
}
{
SCOPED_TRACE("Primary Left");
TestLauncherAlignment(
root_windows[0], ShelfAlignment::kLeft,
gfx::Rect(ShelfConfig::Get()->shelf_size(), 0, shelf_inset_first, 300));
}
{
SCOPED_TRACE("Secondary Bottom");
TestLauncherAlignment(root_windows[1], ShelfAlignment::kBottom,
gfx::Rect(300, 0, 500, shelf_inset_second));
}
{
SCOPED_TRACE("Secondary Locked");
TestLauncherAlignment(root_windows[1], ShelfAlignment::kBottomLocked,
gfx::Rect(300, 0, 500, shelf_inset_second));
}
{
SCOPED_TRACE("Secondary Right");
TestLauncherAlignment(root_windows[1], ShelfAlignment::kRight,
gfx::Rect(300, 0, shelf_inset_second, 500));
}
{
SCOPED_TRACE("Secondary Left");
TestLauncherAlignment(root_windows[1], ShelfAlignment::kLeft,
gfx::Rect(300 + ShelfConfig::Get()->shelf_size(), 0,
shelf_inset_second, 500));
}
}
class ShelfWidgetLayoutBasicsTest
: public ShelfWidgetTest,
public testing::WithParamInterface<std::tuple<bool, bool>> {
public:
ShelfWidgetLayoutBasicsTest() {
scoped_feature_list_.InitWithFeatureState(
features::kHideShelfControlsInTabletMode, std::get<1>(GetParam()));
}
~ShelfWidgetLayoutBasicsTest() override = default;
void SetUp() override {
ShelfWidgetTest::SetUp();
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(
std::get<0>(GetParam()));
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// First parameter - whether test should run in tablet mode.
// Second parameter - whether shelf navigation buttons are enabled in tablet
// mode.
INSTANTIATE_TEST_SUITE_P(All,
ShelfWidgetLayoutBasicsTest,
testing::Combine(testing::Bool(), testing::Bool()));
// Makes sure the shelf is initially sized correctly in clamshell/tablet.
TEST_P(ShelfWidgetLayoutBasicsTest, LauncherInitiallySized) {
ShelfWidget* shelf_widget = GetShelfWidget();
ShelfLayoutManager* shelf_layout_manager = GetShelfLayoutManager();
ASSERT_TRUE(shelf_layout_manager);
ASSERT_TRUE(shelf_widget->status_area_widget());
int status_width =
shelf_widget->status_area_widget()->GetWindowBoundsInScreen().width();
// Test only makes sense if the status is > 0, which it better be.
EXPECT_GT(status_width, 0);
const int total_width =
screen_util::GetDisplayBoundsWithShelf(shelf_widget->GetNativeWindow())
.width();
const gfx::Rect nav_widget_clip_rect =
shelf_widget->navigation_widget()->GetLayer()->clip_rect();
if (!nav_widget_clip_rect.IsEmpty())
EXPECT_EQ(gfx::Point(), nav_widget_clip_rect.origin());
int nav_width =
nav_widget_clip_rect.IsEmpty()
? shelf_widget->navigation_widget()->GetWindowBoundsInScreen().width()
: nav_widget_clip_rect.width();
ASSERT_GE(nav_width, 0);
if (nav_width) {
nav_width -= ShelfConfig::Get()->control_button_edge_spacing(
true /* is_primary_axis_edge */);
}
const int hotseat_width =
shelf_widget->hotseat_widget()->GetWindowBoundsInScreen().width();
const int margins = 2 * ShelfConfig::Get()->GetAppIconGroupMargin();
EXPECT_EQ(status_width, total_width - nav_width - hotseat_width - margins);
}
// Verifies when the shell is deleted with a full screen window we don't crash.
TEST_F(ShelfWidgetTest, DontReferenceShelfAfterDeletion) {
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(0, 0, 200, 200);
params.context = GetContext();
// Widget is now owned by the parent window.
widget->Init(std::move(params));
widget->SetFullscreen(true);
}
// Verifies shelf is created with correct size after user login and when its
// container and status widget has finished sizing.
// See http://crbug.com/252533
TEST_F(ShelfWidgetTest, ShelfInitiallySizedAfterLogin) {
ClearLogin();
UpdateDisplay("300x200,400x300");
// Both displays have a shelf controller.
aura::Window::Windows roots = Shell::GetAllRootWindows();
Shelf* shelf1 = Shelf::ForWindow(roots[0]);
Shelf* shelf2 = Shelf::ForWindow(roots[1]);
ASSERT_TRUE(shelf1);
ASSERT_TRUE(shelf2);
// Both shelf controllers have a shelf widget.
ShelfWidget* shelf_widget1 = shelf1->shelf_widget();
ShelfWidget* shelf_widget2 = shelf2->shelf_widget();
ASSERT_TRUE(shelf_widget1);
ASSERT_TRUE(shelf_widget2);
// Simulate login.
CreateUserSessions(1);
const int total_width1 =
screen_util::GetDisplayBoundsWithShelf(shelf_widget1->GetNativeWindow())
.width();
const int nav_width1 =
shelf_widget1->navigation_widget()->GetWindowBoundsInScreen().width() -
ShelfConfig::Get()->control_button_edge_spacing(
true /* is_primary_axis_edge */);
const int hotseat_width1 =
shelf_widget1->hotseat_widget()->GetWindowBoundsInScreen().width();
const int margins = 2 * ShelfConfig::Get()->GetAppIconGroupMargin();
const int total_width2 =
screen_util::GetDisplayBoundsWithShelf(shelf_widget2->GetNativeWindow())
.width();
const int nav_width2 =
shelf_widget2->navigation_widget()->GetWindowBoundsInScreen().width() -
ShelfConfig::Get()->control_button_edge_spacing(
true /* is_primary_axis_edge */);
const int hotseat_width2 =
shelf_widget2->hotseat_widget()->GetWindowBoundsInScreen().width();
// The shelf view and status area horizontally fill the shelf widget.
const int status_width1 =
shelf_widget1->status_area_widget()->GetWindowBoundsInScreen().width();
EXPECT_GT(status_width1, 0);
EXPECT_EQ(total_width1,
nav_width1 + hotseat_width1 + margins + status_width1);
const int status_width2 =
shelf_widget2->status_area_widget()->GetWindowBoundsInScreen().width();
EXPECT_GT(status_width2, 0);
EXPECT_EQ(total_width2,
nav_width2 + hotseat_width2 + margins + status_width2);
}
// Tests that the shelf has a slightly larger hit-region for touch-events when
// it's in the auto-hidden state.
TEST_F(ShelfWidgetTest, HiddenShelfHitTestTouch) {
Shelf* shelf = GetPrimaryShelf();
ShelfWidget* shelf_widget = GetShelfWidget();
gfx::Rect shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
EXPECT_TRUE(!shelf_bounds.IsEmpty());
ShelfLayoutManager* shelf_layout_manager =
shelf_widget->shelf_layout_manager();
ASSERT_TRUE(shelf_layout_manager);
EXPECT_EQ(SHELF_VISIBLE, shelf_layout_manager->visibility_state());
// Create a widget to make sure that the shelf does auto-hide.
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
params.bounds = gfx::Rect(0, 0, 200, 200);
params.context = GetContext();
// Widget is now owned by the parent window.
widget->Init(std::move(params));
widget->Show();
aura::Window* root = shelf_widget->GetNativeWindow()->GetRootWindow();
ui::EventTargeter* targeter =
root->GetHost()->dispatcher()->GetDefaultEventTargeter();
// Touch just over the shelf. Since the shelf is visible, the window-targeter
// should not find the shelf as the target.
{
gfx::Point event_location(20, shelf_bounds.y() - 1);
ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, event_location,
ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::kTouch, 0));
EXPECT_NE(shelf_widget->GetNativeWindow(),
targeter->FindTargetForEvent(root, &touch));
}
// Now auto-hide (hidden) the shelf.
shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
shelf_layout_manager->LayoutShelf();
EXPECT_EQ(SHELF_AUTO_HIDE, shelf_layout_manager->visibility_state());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf_layout_manager->auto_hide_state());
shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
EXPECT_TRUE(!shelf_bounds.IsEmpty());
// Touch just over the shelf again. This time, the targeter should find the
// shelf as the target.
{
gfx::Point event_location(20, shelf_bounds.y() - 1);
ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, event_location,
ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::kTouch, 0));
EXPECT_EQ(shelf_widget->GetNativeWindow(),
targeter->FindTargetForEvent(root, &touch));
}
}
// Tests that the shelf lets mouse-events close to the edge fall through to the
// window underneath.
TEST_F(ShelfWidgetTest, ShelfEdgeOverlappingWindowHitTestMouse) {
UpdateDisplay("400x400");
ShelfWidget* shelf_widget = GetShelfWidget();
gfx::Rect shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
EXPECT_TRUE(!shelf_bounds.IsEmpty());
ShelfLayoutManager* shelf_layout_manager =
shelf_widget->shelf_layout_manager();
ASSERT_TRUE(shelf_layout_manager);
EXPECT_EQ(SHELF_VISIBLE, shelf_layout_manager->visibility_state());
// Create a Widget which overlaps the shelf in both left and bottom
// alignments.
const int kOverlapSize = 15;
const int kWindowHeight = 200;
const int kWindowWidth = 200;
const gfx::Rect bounds(shelf_bounds.height() - kOverlapSize,
shelf_bounds.y() - kWindowHeight + kOverlapSize,
kWindowWidth, kWindowHeight);
views::Widget* widget = new views::Widget;
views::Widget::InitParams params(views::Widget::InitParams::TYPE_WINDOW);
params.bounds = bounds;
params.context = GetContext();
// Widget is now owned by the parent window.
widget->Init(std::move(params));
// Explicitly set the bounds which will allow the widget to overlap the shelf.
widget->SetBounds(bounds);
widget->Show();
gfx::Rect widget_bounds = widget->GetWindowBoundsInScreen();
EXPECT_TRUE(widget_bounds.Intersects(shelf_bounds));
aura::Window* root = widget->GetNativeWindow()->GetRootWindow();
ui::EventTargeter* targeter =
root->GetHost()->dispatcher()->GetDefaultEventTargeter();
{
// Create a mouse-event targeting the top of the shelf widget. The
// window-targeter should find |widget| as the target (instead of the
// shelf).
gfx::Point event_location(widget_bounds.x() + 5, shelf_bounds.y() + 1);
ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, event_location, event_location,
ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
ui::EventTarget* target = targeter->FindTargetForEvent(root, &mouse);
EXPECT_EQ(widget->GetNativeWindow(), target);
}
// Change shelf alignment to verify that the targeter insets are updated.
Shelf* shelf = GetPrimaryShelf();
shelf->SetAlignment(ShelfAlignment::kLeft);
shelf_layout_manager->LayoutShelf();
shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
{
// Create a mouse-event targeting the right edge of the shelf widget. The
// window-targeter should find |widget| as the target (instead of the
// shelf).
gfx::Point event_location(shelf_bounds.right() - 1, widget_bounds.y() + 5);
ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, event_location, event_location,
ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
ui::EventTarget* target = targeter->FindTargetForEvent(root, &mouse);
EXPECT_EQ(widget->GetNativeWindow(), target);
}
// Now restore shelf alignment (bottom) and auto-hide (hidden) the shelf.
shelf->SetAlignment(ShelfAlignment::kBottom);
shelf->SetAutoHideBehavior(ShelfAutoHideBehavior::kAlways);
shelf_layout_manager->LayoutShelf();
EXPECT_EQ(SHELF_AUTO_HIDE, shelf_layout_manager->visibility_state());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf_layout_manager->auto_hide_state());
shelf_bounds = shelf_widget->GetWindowBoundsInScreen();
EXPECT_TRUE(!shelf_bounds.IsEmpty());
// Move |widget| so it still overlaps the shelf.
widget->SetBounds(gfx::Rect(0,
shelf_bounds.y() - kWindowHeight + kOverlapSize,
kWindowWidth, kWindowHeight));
widget_bounds = widget->GetWindowBoundsInScreen();
EXPECT_TRUE(widget_bounds.Intersects(shelf_bounds));
{
// Create a mouse-event targeting the top of the shelf widget. This time,
// window-target should find the shelf as the target.
gfx::Point event_location(widget_bounds.x() + 5, shelf_bounds.y() + 1);
ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, event_location, event_location,
ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE);
ui::EventTarget* target = targeter->FindTargetForEvent(root, &mouse);
EXPECT_EQ(shelf_widget->GetNativeWindow(), target);
}
}
class TransitionAnimationWaiter
: public HotseatTransitionAnimator::TestObserver {
public:
explicit TransitionAnimationWaiter(
HotseatTransitionAnimator* hotseat_transition_animator)
: hotseat_transition_animator_(hotseat_transition_animator) {
hotseat_transition_animator_->SetTestObserver(this);
}
~TransitionAnimationWaiter() override {
hotseat_transition_animator_->SetTestObserver(nullptr);
}
void Wait() {
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
}
private:
void OnTransitionTestAnimationEnded() override {
DCHECK(run_loop_.get());
run_loop_->Quit();
}
HotseatTransitionAnimator* hotseat_transition_animator_ = nullptr;
std::unique_ptr<base::RunLoop> run_loop_;
};
// Tests the drag handle bounds and visibility when the in-app shelf is shown.
TEST_F(ShelfWidgetTest, OpaqueBackgroundAndDragHandleTransition) {
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
UpdateDisplay("800x800");
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
// Create a window to transition to the in-app shelf.
auto window = AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 800, 800));
{
TransitionAnimationWaiter waiter(
GetShelfWidget()->hotseat_transition_animator());
waiter.Wait();
}
// Ensure the ShelfWidget and drag handle have the correct bounds and
// visibility for in-app shelf.
EXPECT_EQ(GetShelfWidget()->GetWindowBoundsInScreen(),
gfx::Rect(0, 760, 800, 40));
EXPECT_EQ(GetShelfWidget()->GetDragHandle()->bounds(),
gfx::Rect(360, 18, 80, 4));
ASSERT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_TRUE(GetShelfWidget()->GetOpaqueBackground()->visible());
window->Hide();
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
}
// Tests the shelf widget does not animate for hotseat transitions during tablet
// mode start.
TEST_F(ShelfWidgetTest, NoAnimatingBackgroundDuringTabletModeStartToInApp) {
UpdateDisplay("800x800");
// Create a window so tablet mode uses in-app shelf.
auto window = AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 800, 800));
EXPECT_TRUE(GetShelfWidget()->GetOpaqueBackground()->visible());
EXPECT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
ASSERT_FALSE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
EXPECT_TRUE(GetShelfWidget()->GetOpaqueBackground()->visible());
EXPECT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
EXPECT_FALSE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
}
// Tests the shelf widget does not animate for hotseat transitions during tablet
// mode end.
TEST_F(ShelfWidgetTest, NoAnimatingBackgroundDuringTabletModeEndFromInApp) {
UpdateDisplay("800x800");
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
// Create a window so tablet mode uses in-app shelf.
auto window = AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 800, 800));
EXPECT_TRUE(GetShelfWidget()->GetOpaqueBackground()->visible());
EXPECT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
ASSERT_FALSE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
EXPECT_TRUE(GetShelfWidget()->GetOpaqueBackground()->visible());
EXPECT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
EXPECT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
EXPECT_FALSE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
}
// Tests the shelf widget does not animate for hotseat transitions during tablet
// mode start with no app windows.
TEST_F(ShelfWidgetTest, NoAnimatingBackgroundDuringTabletModeStartToHome) {
UpdateDisplay("800x800");
EXPECT_TRUE(GetShelfWidget()->GetOpaqueBackground()->visible());
EXPECT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
ASSERT_FALSE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
EXPECT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
EXPECT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
EXPECT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
EXPECT_FALSE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
}
// Tests the shelf widget does not animate for hotseat transitions during tablet
// mode end with no app windows.
TEST_F(ShelfWidgetTest, NoAnimatingBackgroundDuringTabletModeEndFromHome) {
UpdateDisplay("800x800");
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
EXPECT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
EXPECT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
ASSERT_FALSE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
EXPECT_TRUE(GetShelfWidget()->GetOpaqueBackground()->visible());
EXPECT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
EXPECT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
EXPECT_FALSE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
}
// Tests the shelf widget does not animate for hotseat transitions if the screen
// is locked.
TEST_F(ShelfWidgetTest, NoAnimatingBackgroundOnLockScreen) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
UpdateDisplay("800x800");
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
// Create a window to transition to the in-app shelf.
auto window = AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 800, 800));
// At this point animations have zero duration, so the transition happens
// immediately.
ASSERT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_TRUE(GetShelfWidget()->GetOpaqueBackground()->visible());
ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
GetSessionControllerClient()->SetSessionState(SessionState::LOCKED);
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// Hide the test window, and verify that does not start shelf animation.
window->Hide();
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
ASSERT_FALSE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
// Ensure the ShelfWidget and the drag handle are still hidden (i.e. in home
// screen state) when the screen is unlocked.
GetSessionControllerClient()->SetSessionState(SessionState::ACTIVE);
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
ASSERT_FALSE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
}
// Tests the shelf widget animations for hotseat transitions are stopped when
// the screen is locked.
TEST_F(ShelfWidgetTest, ScreenLockStopsHotseatTransitionAnimation) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
UpdateDisplay("800x800");
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// Create a window to transition to the in-app shelf.
auto window = AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 800, 800));
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
ASSERT_TRUE(GetShelfWidget()->GetAnimatingBackground()->visible());
ASSERT_TRUE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
GetSessionControllerClient()->SetSessionState(SessionState::LOCKED);
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
ASSERT_FALSE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
GetSessionControllerClient()->SetSessionState(SessionState::ACTIVE);
// Ensure the ShelfWidget and the drag handle have the correct bounds and
// visibility for in-app shelf when the screen is unlocked.
ASSERT_TRUE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_TRUE(GetShelfWidget()->GetOpaqueBackground()->visible());
ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
EXPECT_EQ(GetShelfWidget()->GetWindowBoundsInScreen(),
gfx::Rect(0, 760, 800, 40));
EXPECT_EQ(GetShelfWidget()->GetDragHandle()->bounds(),
gfx::Rect(360, 18, 80, 4));
}
// Tests the shelf widget's opaque background gets reshown if hotseat transition
// from kShown state gets interrupted by a transition back to kShown state.
TEST_F(ShelfWidgetTest,
OpaqueBackgroundReshownAfterTransitionFromHomeChangesBackToHome) {
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(true);
UpdateDisplay("800x800");
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
ui::ScopedAnimationDurationScaleMode non_zero_duration_mode(
ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION);
// Create a window to transition to the in-app shelf.
auto window = AshTestBase::CreateTestWindow(gfx::Rect(0, 0, 800, 800));
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
ASSERT_TRUE(GetShelfWidget()->GetAnimatingBackground()->visible());
ASSERT_TRUE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
const gfx::Rect animating_background_bounds =
GetShelfWidget()->GetAnimatingBackground()->bounds();
EXPECT_NE(GetShelfWidget()->GetAnimatingBackground()->transform(),
GetShelfWidget()->GetAnimatingBackground()->GetTargetTransform());
// Go back home before the transition to in-app shelf finishes.
window->Hide();
// The shelf background should still be animating at this point, but to
// different bounds.
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
ASSERT_TRUE(GetShelfWidget()->GetAnimatingBackground()->visible());
ASSERT_TRUE(GetShelfWidget()
->GetAnimatingBackground()
->GetAnimator()
->is_animating());
EXPECT_EQ(animating_background_bounds,
GetShelfWidget()->GetAnimatingBackground()->bounds());
// The original animation did not have a chance to update the transform yet,
// so the current transform matches the original state, that matches the new
// target state.
EXPECT_EQ(GetShelfWidget()->GetAnimatingBackground()->transform(),
GetShelfWidget()->GetAnimatingBackground()->GetTargetTransform());
// Run the current animation to the end, end verify the opaque background is
// reshown after ending tablet mode.
GetShelfWidget()->GetAnimatingBackground()->GetAnimator()->StopAnimating();
ASSERT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_FALSE(GetShelfWidget()->GetOpaqueBackground()->visible());
ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
Shell::Get()->tablet_mode_controller()->SetEnabledForTest(false);
EXPECT_FALSE(GetShelfWidget()->GetDragHandle()->GetVisible());
ASSERT_TRUE(GetShelfWidget()->GetOpaqueBackground()->visible());
ASSERT_FALSE(GetShelfWidget()->GetAnimatingBackground()->visible());
}
class ShelfWidgetAfterLoginTest : public AshTestBase {
public:
ShelfWidgetAfterLoginTest() { set_start_session(false); }
~ShelfWidgetAfterLoginTest() override = default;
void TestShelf(ShelfAlignment alignment,
ShelfAutoHideBehavior auto_hide_behavior,
ShelfVisibilityState expected_shelf_visibility_state,
ShelfAutoHideState expected_shelf_auto_hide_state) {
// Simulate login.
CreateUserSessions(1);
// Simulate shelf settings being applied from profile prefs.
Shelf* shelf = GetPrimaryShelf();
ASSERT_NE(nullptr, shelf);
shelf->SetAlignment(alignment);
shelf->SetAutoHideBehavior(auto_hide_behavior);
shelf->UpdateVisibilityState();
// Ensure settings are applied correctly.
EXPECT_EQ(alignment, shelf->alignment());
EXPECT_EQ(auto_hide_behavior, shelf->auto_hide_behavior());
EXPECT_EQ(expected_shelf_visibility_state, shelf->GetVisibilityState());
EXPECT_EQ(expected_shelf_auto_hide_state, shelf->GetAutoHideState());
}
private:
DISALLOW_COPY_AND_ASSIGN(ShelfWidgetAfterLoginTest);
};
TEST_F(ShelfWidgetAfterLoginTest, InitialValues) {
// Ensure shelf components are created.
Shelf* shelf = GetPrimaryShelf();
ASSERT_NE(nullptr, shelf);
ShelfWidget* shelf_widget = GetShelfWidget();
ASSERT_NE(nullptr, shelf_widget);
ASSERT_NE(nullptr, shelf_widget->shelf_view_for_testing());
ASSERT_NE(nullptr, shelf_widget->login_shelf_view());
ASSERT_NE(nullptr, shelf_widget->shelf_layout_manager());
// Ensure settings are correct before login.
EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
EXPECT_EQ(ShelfAlignment::kBottomLocked, shelf->alignment());
EXPECT_EQ(ShelfAutoHideBehavior::kAlwaysHidden, shelf->auto_hide_behavior());
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
// Simulate login.
CreateUserSessions(1);
// Ensure settings are correct after login.
EXPECT_EQ(SHELF_VISIBLE, shelf->GetVisibilityState());
EXPECT_EQ(ShelfAlignment::kBottom, shelf->alignment());
EXPECT_EQ(ShelfAutoHideBehavior::kNever, shelf->auto_hide_behavior());
// "Hidden" is the default state when auto-hide is turned off.
EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->GetAutoHideState());
}
TEST_F(ShelfWidgetAfterLoginTest, CreateAutoHideAlwaysShelf) {
// The actual auto hide state is shown because there are no open windows.
TestShelf(ShelfAlignment::kBottom, ShelfAutoHideBehavior::kAlways,
SHELF_AUTO_HIDE, SHELF_AUTO_HIDE_SHOWN);
}
TEST_F(ShelfWidgetAfterLoginTest, CreateAutoHideNeverShelf) {
// The auto hide state 'HIDDEN' is returned for any non-auto-hide behavior.
TestShelf(ShelfAlignment::kLeft, ShelfAutoHideBehavior::kNever, SHELF_VISIBLE,
SHELF_AUTO_HIDE_HIDDEN);
}
TEST_F(ShelfWidgetAfterLoginTest, CreateAutoHideAlwaysHideShelf) {
// The auto hide state 'HIDDEN' is returned for any non-auto-hide behavior.
TestShelf(ShelfAlignment::kRight, ShelfAutoHideBehavior::kAlwaysHidden,
SHELF_HIDDEN, SHELF_AUTO_HIDE_HIDDEN);
}
TEST_F(ShelfWidgetAfterLoginTest, CreateLockedShelf) {
// The auto hide state 'HIDDEN' is returned for any non-auto-hide behavior.
TestShelf(ShelfAlignment::kBottomLocked, ShelfAutoHideBehavior::kNever,
SHELF_VISIBLE, SHELF_AUTO_HIDE_HIDDEN);
}
class ShelfWidgetViewsVisibilityTest : public AshTestBase {
public:
ShelfWidgetViewsVisibilityTest() { set_start_session(false); }
~ShelfWidgetViewsVisibilityTest() override = default;
enum ShelfVisibility {
kNone, // Shelf views hidden.
kShelf, // ShelfView visible.
kLoginShelf, // LoginShelfView visible.
};
void InitShelfVariables() {
// Create setup with 2 displays primary and secondary.
UpdateDisplay("800x600,800x600");
aura::Window::Windows root_windows = Shell::GetAllRootWindows();
ASSERT_EQ(2u, root_windows.size());
primary_shelf_widget_ = Shelf::ForWindow(root_windows[0])->shelf_widget();
ASSERT_NE(nullptr, primary_shelf_widget_);
secondary_shelf_widget_ = Shelf::ForWindow(root_windows[1])->shelf_widget();
ASSERT_NE(nullptr, secondary_shelf_widget_);
}
void ExpectVisible(session_manager::SessionState state,
ShelfVisibility primary_shelf_visibility,
ShelfVisibility secondary_shelf_visibility) {
GetSessionControllerClient()->SetSessionState(state);
EXPECT_EQ(primary_shelf_visibility == kNone,
!primary_shelf_widget_->IsVisible());
if (primary_shelf_visibility != kNone) {
EXPECT_EQ(primary_shelf_visibility == kLoginShelf,
primary_shelf_widget_->login_shelf_view()->GetVisible());
EXPECT_EQ(primary_shelf_visibility == kShelf,
primary_shelf_widget_->shelf_view_for_testing()->GetVisible());
}
EXPECT_EQ(secondary_shelf_visibility == kNone,
!secondary_shelf_widget_->IsVisible());
if (secondary_shelf_visibility != kNone) {
EXPECT_EQ(secondary_shelf_visibility == kLoginShelf,
secondary_shelf_widget_->login_shelf_view()->GetVisible());
EXPECT_EQ(
secondary_shelf_visibility == kShelf,
secondary_shelf_widget_->shelf_view_for_testing()->GetVisible());
}
}
private:
ShelfWidget* primary_shelf_widget_ = nullptr;
ShelfWidget* secondary_shelf_widget_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(ShelfWidgetViewsVisibilityTest);
};
TEST_F(ShelfWidgetViewsVisibilityTest, LoginViewsLockViews) {
ASSERT_NO_FATAL_FAILURE(InitShelfVariables());
ExpectVisible(SessionState::UNKNOWN, kNone /*primary*/, kNone /*secondary*/);
ExpectVisible(SessionState::OOBE, kLoginShelf, kNone);
ExpectVisible(SessionState::LOGIN_PRIMARY, kLoginShelf, kNone);
SimulateUserLogin("user1@test.com");
ExpectVisible(SessionState::LOGGED_IN_NOT_ACTIVE, kLoginShelf, kNone);
ExpectVisible(SessionState::ACTIVE, kShelf, kShelf);
ExpectVisible(SessionState::LOCKED, kLoginShelf, kNone);
ExpectVisible(SessionState::ACTIVE, kShelf, kShelf);
ExpectVisible(SessionState::LOGIN_SECONDARY, kLoginShelf, kNone);
ExpectVisible(SessionState::ACTIVE, kShelf, kShelf);
}
class ShelfWidgetVirtualKeyboardTest : public AshTestBase {
protected:
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
keyboard::switches::kEnableVirtualKeyboard);
AshTestBase::SetUp();
ASSERT_TRUE(keyboard::IsKeyboardEnabled());
keyboard::test::WaitUntilLoaded();
// These tests only apply to the floating virtual keyboard, as it is the
// only case where both the virtual keyboard and the shelf are visible.
const gfx::Rect keyboard_bounds(0, 0, 1, 1);
keyboard_ui_controller()->SetContainerType(
keyboard::ContainerType::kFloating, keyboard_bounds, base::DoNothing());
}
keyboard::KeyboardUIController* keyboard_ui_controller() {
return keyboard::KeyboardUIController::Get();
}
};
TEST_F(ShelfWidgetVirtualKeyboardTest, ClickingHidesVirtualKeyboard) {
keyboard_ui_controller()->ShowKeyboard(false /* locked */);
ASSERT_TRUE(keyboard::WaitUntilShown());
ui::test::EventGenerator* generator = GetEventGenerator();
generator->set_current_screen_location(
GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint());
generator->ClickLeftButton();
// Times out if test fails.
ASSERT_TRUE(keyboard::WaitUntilHidden());
}
TEST_F(ShelfWidgetVirtualKeyboardTest, TappingHidesVirtualKeyboard) {
keyboard_ui_controller()->ShowKeyboard(false /* locked */);
ASSERT_TRUE(keyboard::WaitUntilShown());
ui::test::EventGenerator* generator = GetEventGenerator();
generator->set_current_screen_location(
GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint());
generator->PressTouch();
// Times out if test fails.
ASSERT_TRUE(keyboard::WaitUntilHidden());
}
TEST_F(ShelfWidgetVirtualKeyboardTest, DoesNotHideLockedVirtualKeyboard) {
keyboard_ui_controller()->ShowKeyboard(true /* locked */);
ASSERT_TRUE(keyboard::WaitUntilShown());
ui::test::EventGenerator* generator = GetEventGenerator();
generator->set_current_screen_location(
GetShelfWidget()->GetWindowBoundsInScreen().CenterPoint());
generator->ClickLeftButton();
EXPECT_FALSE(keyboard::IsKeyboardHiding());
generator->PressTouch();
EXPECT_FALSE(keyboard::IsKeyboardHiding());
}
} // namespace
} // namespace ash