chromium/ash/wm/lock_layout_manager_unittest.cc

// Copyright 2014 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.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/keyboard/keyboard_switches.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/root_window_controller.h"
#include "ash/screen_util.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/wm/window_state.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"

namespace ash {

namespace {

// A login implementation of WidgetDelegate.
class LoginTestWidgetDelegate : public views::WidgetDelegate {
 public:
  explicit LoginTestWidgetDelegate(views::Widget* widget) : widget_(widget) {
    SetOwnedByWidget(true);
    SetFocusTraversesOut(true);
  }

  LoginTestWidgetDelegate(const LoginTestWidgetDelegate&) = delete;
  LoginTestWidgetDelegate& operator=(const LoginTestWidgetDelegate&) = delete;

  ~LoginTestWidgetDelegate() override = default;

  // views::WidgetDelegate:
  views::Widget* GetWidget() override { return widget_; }
  const views::Widget* GetWidget() const override { return widget_; }
  bool CanActivate() const override { return true; }

 private:
  raw_ptr<views::Widget> widget_;
};

}  // namespace

class LockLayoutManagerTest : public AshTestBase {
 public:
  void SetUp() override {
    // Allow a virtual keyboard (and initialize it per default).
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        keyboard::switches::kEnableVirtualKeyboard);
    AshTestBase::SetUp();
  }

  void TearDown() override {
    AshTestBase::TearDown();
  }

  aura::Window* CreateTestLoginWindow(views::Widget::InitParams params,
                                      bool use_delegate) {
    aura::Window* parent =
        Shell::GetPrimaryRootWindowController()->GetContainer(
            kShellWindowId_LockScreenContainer);
    params.parent = parent;
    views::Widget* widget = new views::Widget;
    if (use_delegate)
      params.delegate = new LoginTestWidgetDelegate(widget);
    params.context = GetContext();
    widget->Init(std::move(params));
    widget->Show();
    aura::Window* window = widget->GetNativeView();
    return window;
  }

  // Show or hide the keyboard.
  void ShowKeyboard(bool show) {
    auto* keyboard = keyboard::KeyboardUIController::Get();
    ASSERT_TRUE(keyboard->IsEnabled());
    if (show == keyboard->IsKeyboardVisible())
      return;

    if (show) {
      keyboard->ShowKeyboard(false);
      ASSERT_TRUE(keyboard::test::WaitUntilShown());
    } else {
      keyboard->HideKeyboardByUser();
    }

    DCHECK_EQ(show, keyboard->IsKeyboardVisible());
  }

  void SetKeyboardOverscrollBehavior(
      keyboard::KeyboardOverscrollBehavior overscroll_behavior) {
    auto config = keyboard::KeyboardUIController::Get()->keyboard_config();
    config.overscroll_behavior = overscroll_behavior;
    keyboard::KeyboardUIController::Get()->UpdateKeyboardConfig(config);
  }
};

TEST_F(LockLayoutManagerTest, NorwmalWindowBoundsArePreserved) {
  gfx::Rect screen_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  const gfx::Rect bounds = gfx::Rect(10, 10, 300, 300);
  widget_params.bounds = bounds;
  std::unique_ptr<aura::Window> window(CreateTestLoginWindow(
      std::move(widget_params), false /* use_delegate */));
  EXPECT_EQ(bounds.ToString(), window->GetBoundsInScreen().ToString());

  gfx::Rect work_area =
      screen_util::GetDisplayWorkAreaBoundsInParent(window.get());
  window->SetBounds(work_area);

  EXPECT_EQ(work_area.ToString(), window->GetBoundsInScreen().ToString());
  EXPECT_NE(screen_bounds.ToString(), window->GetBoundsInScreen().ToString());

  const gfx::Rect bounds2 = gfx::Rect(100, 100, 200, 200);
  window->SetBounds(bounds2);
  EXPECT_EQ(bounds2.ToString(), window->GetBoundsInScreen().ToString());
}

TEST_F(LockLayoutManagerTest, MaximizedFullscreenWindowBoundsAreEqualToScreen) {
  gfx::Rect screen_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  widget_params.show_state = ui::SHOW_STATE_MAXIMIZED;
  const gfx::Rect bounds = gfx::Rect(10, 10, 300, 300);
  widget_params.bounds = bounds;
  // Maximized TYPE_WINDOW_FRAMELESS windows needs a delegate defined otherwise
  // it won't get initial SetBounds event.
  std::unique_ptr<aura::Window> maximized_window(
      CreateTestLoginWindow(std::move(widget_params), true /* use_delegate */));

  widget_params.show_state = ui::SHOW_STATE_FULLSCREEN;
  widget_params.delegate = nullptr;
  std::unique_ptr<aura::Window> fullscreen_window(CreateTestLoginWindow(
      std::move(widget_params), false /* use_delegate */));

  EXPECT_EQ(screen_bounds.ToString(),
            maximized_window->GetBoundsInScreen().ToString());
  EXPECT_EQ(screen_bounds.ToString(),
            fullscreen_window->GetBoundsInScreen().ToString());

  gfx::Rect work_area =
      screen_util::GetDisplayWorkAreaBoundsInParent(maximized_window.get());
  maximized_window->SetBounds(work_area);

  EXPECT_NE(work_area.ToString(),
            maximized_window->GetBoundsInScreen().ToString());
  EXPECT_EQ(screen_bounds.ToString(),
            maximized_window->GetBoundsInScreen().ToString());

  work_area =
      screen_util::GetDisplayWorkAreaBoundsInParent(fullscreen_window.get());
  fullscreen_window->SetBounds(work_area);
  EXPECT_NE(work_area.ToString(),
            fullscreen_window->GetBoundsInScreen().ToString());
  EXPECT_EQ(screen_bounds.ToString(),
            fullscreen_window->GetBoundsInScreen().ToString());

  const gfx::Rect bounds2 = gfx::Rect(100, 100, 200, 200);
  maximized_window->SetBounds(bounds2);
  fullscreen_window->SetBounds(bounds2);
  EXPECT_EQ(screen_bounds.ToString(),
            maximized_window->GetBoundsInScreen().ToString());
  EXPECT_EQ(screen_bounds.ToString(),
            fullscreen_window->GetBoundsInScreen().ToString());
}

TEST_F(LockLayoutManagerTest, AccessibilityPanel) {
  ShelfLayoutManager* shelf_layout_manager =
      GetPrimaryShelf()->shelf_layout_manager();
  ASSERT_TRUE(shelf_layout_manager);

  // Create accessibility panel and set its height.
  int accessibility_panel_height = 45;
  std::unique_ptr<views::Widget> accessibility_panel_widget =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
                       nullptr, kShellWindowId_AccessibilityPanelContainer);
  SetAccessibilityPanelHeight(accessibility_panel_height);

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  widget_params.show_state = ui::SHOW_STATE_FULLSCREEN;
  std::unique_ptr<aura::Window> window(CreateTestLoginWindow(
      std::move(widget_params), false /* use_delegate */));

  display::Display primary_display =
      display::Screen::GetScreen()->GetPrimaryDisplay();

  gfx::Rect target_bounds = primary_display.bounds();
  target_bounds.Inset(gfx::Insets().set_top(accessibility_panel_height));

  EXPECT_EQ(target_bounds, window->GetBoundsInScreen());

  // Update the accessibility panel height, and verify the window bounds are
  // updated accordingly.
  accessibility_panel_height = 25;
  SetAccessibilityPanelHeight(accessibility_panel_height);

  target_bounds = primary_display.bounds();
  target_bounds.Inset(gfx::Insets().set_top(accessibility_panel_height));

  EXPECT_EQ(target_bounds, window->GetBoundsInScreen());
}

TEST_F(LockLayoutManagerTest, KeyboardBounds) {
  display::Display primary_display =
      display::Screen::GetScreen()->GetPrimaryDisplay();
  gfx::Rect screen_bounds = primary_display.bounds();

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  widget_params.show_state = ui::SHOW_STATE_FULLSCREEN;
  std::unique_ptr<aura::Window> window(CreateTestLoginWindow(
      std::move(widget_params), false /* use_delegate */));

  EXPECT_EQ(screen_bounds.ToString(), window->GetBoundsInScreen().ToString());

  // When virtual keyboard overscroll is enabled keyboard bounds should not
  // affect window bounds.
  keyboard::KeyboardUIController* keyboard =
      keyboard::KeyboardUIController::Get();
  SetKeyboardOverscrollBehavior(keyboard::KeyboardOverscrollBehavior::kEnabled);
  ShowKeyboard(true);
  EXPECT_EQ(screen_bounds.ToString(), window->GetBoundsInScreen().ToString());
  gfx::Rect keyboard_bounds = keyboard->GetVisualBoundsInScreen();
  EXPECT_NE(keyboard_bounds, gfx::Rect());
  ShowKeyboard(false);

  // When keyboard is hidden make sure that rotating the screen gives 100% of
  // screen size to window.
  // Repro steps for http://crbug.com/401667:
  // 1. Set up login screen defaults: VK overscroll disabled
  // 2. Show/hide keyboard, make sure that no stale keyboard bounds are cached.
  SetKeyboardOverscrollBehavior(
      keyboard::KeyboardOverscrollBehavior::kDisabled);
  ShowKeyboard(true);
  ShowKeyboard(false);
  display_manager()->SetDisplayRotation(
      primary_display.id(), display::Display::ROTATE_90,
      display::Display::RotationSource::ACTIVE);
  primary_display = display::Screen::GetScreen()->GetPrimaryDisplay();
  screen_bounds = primary_display.bounds();
  EXPECT_EQ(screen_bounds.ToString(), window->GetBoundsInScreen().ToString());
  display_manager()->SetDisplayRotation(
      primary_display.id(), display::Display::ROTATE_0,
      display::Display::RotationSource::ACTIVE);

  // When virtual keyboard overscroll is disabled keyboard bounds do
  // affect window bounds.
  SetKeyboardOverscrollBehavior(
      keyboard::KeyboardOverscrollBehavior::kDisabled);
  ShowKeyboard(true);

  primary_display = display::Screen::GetScreen()->GetPrimaryDisplay();
  screen_bounds = primary_display.bounds();
  gfx::Rect target_bounds(screen_bounds);
  target_bounds.set_height(target_bounds.height() -
                           keyboard->GetKeyboardWindow()->bounds().height());
  EXPECT_EQ(target_bounds.ToString(), window->GetBoundsInScreen().ToString());
  ShowKeyboard(false);

  SetKeyboardOverscrollBehavior(keyboard::KeyboardOverscrollBehavior::kDefault);

  keyboard->SetContainerType(keyboard::ContainerType::kFloating,
                             gfx::Rect() /* target_bounds */,
                             base::BindOnce([](bool success) {}));
  ShowKeyboard(true);
  primary_display = display::Screen::GetScreen()->GetPrimaryDisplay();
  screen_bounds = primary_display.bounds();
  EXPECT_EQ(screen_bounds.ToString(), window->GetBoundsInScreen().ToString());
  ShowKeyboard(false);
}

TEST_F(LockLayoutManagerTest, MultipleMonitors) {
  UpdateDisplay("300x400,400x500");
  gfx::Rect screen_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  widget_params.show_state = ui::SHOW_STATE_FULLSCREEN;
  std::unique_ptr<aura::Window> window(CreateTestLoginWindow(
      std::move(widget_params), false /* use_delegate */));
  window->SetProperty(aura::client::kResizeBehaviorKey,
                      aura::client::kResizeBehaviorCanMaximize);

  EXPECT_EQ(screen_bounds.ToString(), window->GetBoundsInScreen().ToString());

  EXPECT_EQ(root_windows[0], window->GetRootWindow());

  WindowState* window_state = WindowState::Get(window.get());
  window_state->SetRestoreBoundsInScreen(gfx::Rect(400, 0, 30, 40));

  // Maximize the window with as the restore bounds is inside 2nd display but
  // lock container windows are always on primary display.
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  EXPECT_EQ(root_windows[0], window->GetRootWindow());
  EXPECT_EQ("0,0 300x400", window->GetBoundsInScreen().ToString());

  window_state->Restore();
  EXPECT_EQ(root_windows[0], window->GetRootWindow());
  EXPECT_EQ("0,0 300x400", window->GetBoundsInScreen().ToString());

  window_state->SetRestoreBoundsInScreen(gfx::Rect(280, 0, 30, 40));
  window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  EXPECT_EQ(root_windows[0], window->GetRootWindow());
  EXPECT_EQ("0,0 300x400", window->GetBoundsInScreen().ToString());

  window_state->Restore();
  EXPECT_EQ(root_windows[0], window->GetRootWindow());
  EXPECT_EQ("0,0 300x400", window->GetBoundsInScreen().ToString());

  gfx::Rect work_area =
      screen_util::GetDisplayWorkAreaBoundsInParent(window.get());
  window->SetBounds(work_area);
  // Usually work_area takes Shelf into account but that doesn't affect
  // LockScreen container windows.
  EXPECT_NE(work_area.ToString(), window->GetBoundsInScreen().ToString());
  EXPECT_EQ(screen_bounds.ToString(), window->GetBoundsInScreen().ToString());
}

TEST_F(LockLayoutManagerTest, AccessibilityPanelWithMultipleMonitors) {
  UpdateDisplay("300x400,400x500");

  ShelfLayoutManager* shelf_layout_manager =
      GetPrimaryShelf()->shelf_layout_manager();
  ASSERT_TRUE(shelf_layout_manager);

  // Create accessibility panel and set its height.
  const int kAccessibilityPanelHeight = 45;
  std::unique_ptr<views::Widget> accessibility_panel_widget =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
                       nullptr, kShellWindowId_AccessibilityPanelContainer);
  SetAccessibilityPanelHeight(kAccessibilityPanelHeight);

  aura::Window::Windows root_windows = Shell::GetAllRootWindows();

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
  widget_params.show_state = ui::SHOW_STATE_FULLSCREEN;
  std::unique_ptr<aura::Window> window(CreateTestLoginWindow(
      std::move(widget_params), false /* use_delegate */));
  window->SetProperty(aura::client::kResizeBehaviorKey,
                      aura::client::kResizeBehaviorCanMaximize);

  gfx::Rect target_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  target_bounds.Inset(gfx::Insets::TLBR(kAccessibilityPanelHeight, 0, 0, 0));
  EXPECT_EQ(target_bounds, window->GetBoundsInScreen());

  // Restore window with bounds in the second display, the window should be
  // shown in the primary display.
  WindowState* window_state = WindowState::Get(window.get());
  window_state->SetRestoreBoundsInScreen(gfx::Rect(400, 0, 30, 40));

  window_state->Restore();
  EXPECT_EQ(root_windows[0], window->GetRootWindow());
  EXPECT_EQ(target_bounds, window->GetBoundsInScreen());

  // Force the window to secondary display - accessibility panel is only set
  // for the primary shelf, so it should not influence the screen bounds.
  window->SetBoundsInScreen(gfx::Rect(0, 0, 30, 40), GetSecondaryDisplay());

  target_bounds = gfx::Rect(600, 0, 400, 500);
  EXPECT_EQ(root_windows[1], window->GetRootWindow());
  EXPECT_EQ(target_bounds, window->GetBoundsInScreen());
}

}  // namespace ash