chromium/ash/wm/lock_action_handler_layout_manager_unittest.cc

// Copyright 2017 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/wm/lock_action_handler_layout_manager.h"

#include <memory>
#include <utility>

#include "ash/keyboard/keyboard_controller_impl.h"
#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/lock_screen_action/lock_screen_action_background_controller.h"
#include "ash/lock_screen_action/lock_screen_action_background_controller_stub.h"
#include "ash/lock_screen_action/test_lock_screen_action_background_controller.h"
#include "ash/public/cpp/keyboard/keyboard_switches.h"
#include "ash/public/cpp/shelf_config.h"
#include "ash/public/cpp/shell_window_ids.h"
#include "ash/public/mojom/tray_action.mojom.h"
#include "ash/root_window_controller.h"
#include "ash/screen_util.h"
#include "ash/session/test_session_controller_client.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/tray_action/test_tray_action_client.h"
#include "ash/tray_action/tray_action.h"
#include "ash/wm/window_state.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"

namespace ash {

namespace {

aura::Window* GetContainer(ShellWindowId container_id) {
  return Shell::GetPrimaryRootWindowController()->GetContainer(container_id);
}

class TestWindowDelegate : public views::WidgetDelegate {
 public:
  TestWindowDelegate() {
    SetCanMaximize(true);
    SetCanResize(true);
    SetOwnedByWidget(true);
    SetFocusTraversesOut(true);
  }

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

  ~TestWindowDelegate() override = default;

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

  void set_widget(views::Widget* widget) { widget_ = widget; }

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

}  // namespace

class LockActionHandlerLayoutManagerTest : public AshTestBase {
 public:
  LockActionHandlerLayoutManagerTest() = default;

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

  ~LockActionHandlerLayoutManagerTest() override = default;

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

    action_background_controller_factory_ = base::BindRepeating(
        &LockActionHandlerLayoutManagerTest::CreateActionBackgroundController,
        base::Unretained(this));
    LockScreenActionBackgroundController::SetFactoryCallbackForTesting(
        &action_background_controller_factory_);

    AshTestBase::SetUp();

    views::Widget::InitParams widget_params(
        views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
        views::Widget::InitParams::TYPE_WINDOW);
    widget_params.show_state = ui::SHOW_STATE_FULLSCREEN;
    lock_window_ = CreateTestingWindow(std::move(widget_params),
                                       kShellWindowId_LockScreenContainer,
                                       std::make_unique<TestWindowDelegate>());
  }

  void TearDown() override {
    lock_window_.reset();
    AshTestBase::TearDown();
    LockScreenActionBackgroundController::SetFactoryCallbackForTesting(nullptr);
  }

  std::unique_ptr<aura::Window> CreateTestingWindow(
      views::Widget::InitParams params,
      ShellWindowId parent_id,
      std::unique_ptr<TestWindowDelegate> window_delegate) {
    params.parent = GetContainer(parent_id);
    views::Widget* widget = new views::Widget;
    if (window_delegate) {
      window_delegate->set_widget(widget);
      params.delegate = window_delegate.release();
    }
    widget->Init(std::move(params));
    widget->Show();
    return base::WrapUnique<aura::Window>(widget->GetNativeView());
  }

  // 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(true);
      ASSERT_TRUE(keyboard::test::WaitUntilShown());
    } else {
      keyboard->HideKeyboardByUser();
    }

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

  void SetUpTrayActionClientAndLockSession(mojom::TrayActionState state) {
    Shell::Get()->tray_action()->SetClient(
        tray_action_client_.CreateRemoteAndBind(), state);
    GetSessionControllerClient()->SetSessionState(
        session_manager::SessionState::LOCKED);
  }

  // Virtual so test specializations can override the background controller
  // implementation used in tests.
  virtual std::unique_ptr<LockScreenActionBackgroundController>
  CreateActionBackgroundController() {
    return std::make_unique<LockScreenActionBackgroundControllerStub>();
  }

 private:
  std::unique_ptr<aura::Window> lock_window_;

  LockScreenActionBackgroundController::FactoryCallback
      action_background_controller_factory_;

  TestTrayActionClient tray_action_client_;
};

class LockActionHandlerLayoutManagerTestWithTestBackgroundController
    : public LockActionHandlerLayoutManagerTest {
 public:
  LockActionHandlerLayoutManagerTestWithTestBackgroundController() = default;

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

  ~LockActionHandlerLayoutManagerTestWithTestBackgroundController() override =
      default;

  void TearDown() override {
    LockActionHandlerLayoutManagerTest::TearDown();
    background_controller_ = nullptr;
  }

  std::unique_ptr<LockScreenActionBackgroundController>
  CreateActionBackgroundController() override {
    auto result = std::make_unique<TestLockScreenActionBackgroundController>();
    EXPECT_FALSE(background_controller_);
    background_controller_ = result.get();
    return result;
  }

  TestLockScreenActionBackgroundController* background_controller() {
    return background_controller_;
  }

 private:
  // The lock screen action background controller created by
  // |CreateActionBackgroundController|.
  raw_ptr<TestLockScreenActionBackgroundController, DanglingUntriaged>
      background_controller_ = nullptr;
};

TEST_F(LockActionHandlerLayoutManagerTest, PreserveNormalWindowBounds) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kActive);

  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;
  // Note: default window delegate (used when no widget delegate is set) does
  // not allow the window to be maximized.
  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      std::move(widget_params), kShellWindowId_LockActionHandlerContainer,
      nullptr /* window_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());

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

TEST_F(LockActionHandlerLayoutManagerTest, MaximizedWindowBounds) {
  // Cange the shelf alignment before locking the session.
  GetPrimaryShelf()->SetAlignment(ShelfAlignment::kRight);

  // This should change the shelf alignment to bottom (temporarily for locked
  // state).
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kActive);

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  widget_params.show_state = ui::SHOW_STATE_MAXIMIZED;
  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      std::move(widget_params), kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  // Verify that the window bounds are equal to work area for the bottom shelf
  // alignment, which matches how the shelf is aligned on the lock screen,
  gfx::Rect target_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  target_bounds.Inset(
      gfx::Insets().set_bottom(ShelfConfig::Get()->shelf_size()));
  EXPECT_EQ(target_bounds.ToString(), window->GetBoundsInScreen().ToString());
}

TEST_F(LockActionHandlerLayoutManagerTest, FullscreenWindowBounds) {
  // Cange the shelf alignment before locking the session.
  GetPrimaryShelf()->SetAlignment(ShelfAlignment::kRight);

  // This should change the shelf alignment to bottom (temporarily for locked
  // state).
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kActive);

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  widget_params.show_state = ui::SHOW_STATE_FULLSCREEN;
  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      std::move(widget_params), kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  // Verify that the window bounds are equal to work area for the bottom shelf
  // alignment, which matches how the shelf is aligned on the lock screen,
  gfx::Rect target_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  target_bounds.Inset(
      gfx::Insets().set_bottom(ShelfConfig::Get()->shelf_size()));
  EXPECT_EQ(target_bounds.ToString(), window->GetBoundsInScreen().ToString());
}

TEST_F(LockActionHandlerLayoutManagerTest, MaximizeResizableWindow) {
  GetSessionControllerClient()->SetSessionState(
      session_manager::SessionState::LOCKED);

  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kActive);

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      std::move(widget_params), kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  gfx::Rect target_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  target_bounds.Inset(
      gfx::Insets().set_bottom(ShelfConfig::Get()->shelf_size()));
  EXPECT_EQ(target_bounds.ToString(), window->GetBoundsInScreen().ToString());
}

TEST_F(LockActionHandlerLayoutManagerTest, KeyboardBounds) {
  gfx::Rect initial_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  initial_bounds.Inset(
      gfx::Insets().set_bottom(ShelfConfig::Get()->shelf_size()));

  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kActive);

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  widget_params.show_state = ui::SHOW_STATE_MAXIMIZED;
  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      std::move(widget_params), kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());
  ASSERT_EQ(initial_bounds.ToString(), window->GetBoundsInScreen().ToString());

  ShowKeyboard(true);

  gfx::Rect keyboard_bounds =
      keyboard::KeyboardUIController::Get()->GetVisualBoundsInScreen();
  // Sanity check that the keyboard intersects with original window bounds - if
  // this is not true, the window bounds would remain unchanged.
  ASSERT_TRUE(keyboard_bounds.Intersects(initial_bounds));

  gfx::Rect target_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  target_bounds.Inset(gfx::Insets().set_bottom(keyboard_bounds.height()));
  EXPECT_EQ(target_bounds.ToString(), window->GetBoundsInScreen().ToString());

  // Verify that window bounds get updated when Chromevox bounds are shown (so
  // the Chromevox panel does not overlay with the action handler window).
  ShelfLayoutManager* shelf_layout_manager =
      GetPrimaryShelf()->shelf_layout_manager();
  ASSERT_TRUE(shelf_layout_manager);

  const int kAccessibilityPanelHeight = 45;
  std::unique_ptr<views::Widget> widget =
      CreateTestWidget(views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET,
                       nullptr, kShellWindowId_AccessibilityPanelContainer);
  SetAccessibilityPanelHeight(kAccessibilityPanelHeight);

  target_bounds.Inset(gfx::Insets().set_top(kAccessibilityPanelHeight));
  EXPECT_EQ(target_bounds.ToString(), window->GetBoundsInScreen().ToString());

  ShowKeyboard(false);
}

TEST_F(LockActionHandlerLayoutManagerTest, AddingWindowInActiveState) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kActive);

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  widget_params.show_state = ui::SHOW_STATE_MAXIMIZED;
  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      std::move(widget_params), kShellWindowId_LockActionHandlerContainer,
      nullptr /* window_delegate */);

  EXPECT_TRUE(window->IsVisible());
  EXPECT_TRUE(window->HasFocus());
}

TEST_F(LockActionHandlerLayoutManagerTest, AddingWindowInLaunchingState) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kLaunching);

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  widget_params.show_state = ui::SHOW_STATE_MAXIMIZED;
  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      std::move(widget_params), kShellWindowId_LockActionHandlerContainer,
      nullptr /* window_delegate */);

  EXPECT_TRUE(window->IsVisible());
  EXPECT_TRUE(window->HasFocus());
}

TEST_F(LockActionHandlerLayoutManagerTest, AddingWindowInNonActiveState) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kAvailable);

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  widget_params.show_state = ui::SHOW_STATE_MAXIMIZED;
  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      std::move(widget_params), kShellWindowId_LockActionHandlerContainer,
      nullptr /* window_delegate */);

  // The window should not be visible if the note action is not in active state.
  EXPECT_FALSE(window->IsVisible());
  EXPECT_FALSE(window->HasFocus());

  // When the action state changes back to active, the window should be
  // shown.
  Shell::Get()->tray_action()->UpdateLockScreenNoteState(
      mojom::TrayActionState::kActive);
  EXPECT_EQ(GetContainer(kShellWindowId_LockActionHandlerContainer),
            window->parent());
  EXPECT_TRUE(window->IsVisible());
  EXPECT_TRUE(window->HasFocus());

  // The window should be hidden again upon leaving the active state.
  Shell::Get()->tray_action()->UpdateLockScreenNoteState(
      mojom::TrayActionState::kAvailable);
  EXPECT_EQ(GetContainer(kShellWindowId_LockActionHandlerContainer),
            window->parent());
  EXPECT_FALSE(window->IsVisible());
  EXPECT_FALSE(window->HasFocus());
}

TEST_F(LockActionHandlerLayoutManagerTest, FocusWindowWhileInNonActiveState) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kAvailable);

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  widget_params.show_state = ui::SHOW_STATE_MAXIMIZED;
  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      std::move(widget_params), kShellWindowId_LockActionHandlerContainer,
      nullptr /* window_delegate */);

  EXPECT_EQ(GetContainer(kShellWindowId_LockActionHandlerContainer),
            window->parent());
  EXPECT_FALSE(window->IsVisible());
  EXPECT_FALSE(window->HasFocus());

  window->Focus();

  EXPECT_FALSE(window->IsVisible());
  EXPECT_FALSE(window->HasFocus());
}

TEST_F(LockActionHandlerLayoutManagerTest,
       RequestShowWindowOutsideActiveState) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kAvailable);

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  widget_params.show_state = ui::SHOW_STATE_MAXIMIZED;
  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      std::move(widget_params), kShellWindowId_LockActionHandlerContainer,
      nullptr /* window_delegate */);

  EXPECT_EQ(GetContainer(kShellWindowId_LockActionHandlerContainer),
            window->parent());
  EXPECT_FALSE(window->IsVisible());

  window->Show();

  EXPECT_FALSE(window->IsVisible());
  EXPECT_FALSE(window->HasFocus());
}

TEST_F(LockActionHandlerLayoutManagerTest, MultipleMonitors) {
  UpdateDisplay("300x400,400x500");
  aura::Window::Windows root_windows = Shell::GetAllRootWindows();

  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kActive);

  views::Widget::InitParams widget_params(
      views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
      views::Widget::InitParams::TYPE_WINDOW);
  widget_params.show_state = ui::SHOW_STATE_FULLSCREEN;
  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      std::move(widget_params), kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  gfx::Rect target_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  target_bounds.Inset(
      gfx::Insets().set_bottom(ShelfConfig::Get()->shelf_size()));
  EXPECT_EQ(target_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));
  window_state->Maximize();

  // Maximize the window width as the restore bounds is inside 2nd display but
  // lock container windows are always on primary display.
  EXPECT_EQ(root_windows[0], window->GetRootWindow());
  target_bounds = gfx::Rect(300, 400);
  target_bounds.Inset(
      gfx::Insets().set_bottom(ShelfConfig::Get()->shelf_size()));
  EXPECT_EQ(target_bounds.ToString(), window->GetBoundsInScreen().ToString());

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

  window_state->SetRestoreBoundsInScreen(gfx::Rect(280, 0, 30, 40));
  window_state->Maximize();
  EXPECT_EQ(root_windows[0], window->GetRootWindow());
  EXPECT_EQ(target_bounds.ToString(), window->GetBoundsInScreen().ToString());

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

  window->SetBoundsInScreen(gfx::Rect(0, 0, 30, 40), GetSecondaryDisplay());
  target_bounds = gfx::Rect(400, 500);
  target_bounds.Offset(300, 0);
  EXPECT_EQ(root_windows[1], window->GetRootWindow());
  EXPECT_EQ(target_bounds.ToString(), window->GetBoundsInScreen().ToString());
}

TEST_F(LockActionHandlerLayoutManagerTestWithTestBackgroundController,
       WindowAddedWhileBackgroundShowing) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kLaunching);

  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      views::Widget::InitParams(
          views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
          views::Widget::InitParams::TYPE_WINDOW),
      kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  EXPECT_EQ(LockScreenActionBackgroundState::kShowing,
            background_controller()->state());
  EXPECT_FALSE(window->IsVisible());
  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());

  // Move action to active - this will make the app window showable. Though,
  // showing the window should be delayed until the background finishes
  // showing.
  Shell::Get()->tray_action()->UpdateLockScreenNoteState(
      mojom::TrayActionState::kActive);

  EXPECT_FALSE(window->IsVisible());
  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());

  // Finish showing the background - this should make the app window visible.
  ASSERT_TRUE(background_controller()->FinishShow());

  EXPECT_TRUE(window->IsVisible());
  EXPECT_TRUE(window->HasFocus());
  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());

  // Move action state back to available - this should hide the app window, and
  // request the background window to be hidden.
  Shell::Get()->tray_action()->UpdateLockScreenNoteState(
      mojom::TrayActionState::kAvailable);

  EXPECT_FALSE(window->IsVisible());
  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());

  ASSERT_TRUE(background_controller()->FinishHide());

  EXPECT_FALSE(window->IsVisible());
  EXPECT_FALSE(background_controller()->GetWindow()->IsVisible());
}

TEST_F(LockActionHandlerLayoutManagerTestWithTestBackgroundController,
       WindowAddedWhenBackgroundShown) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kLaunching);

  Shell::Get()->tray_action()->UpdateLockScreenNoteState(
      mojom::TrayActionState::kActive);
  // Finish showing the background - this should make the app window visible
  // once it's created.
  ASSERT_TRUE(background_controller()->FinishShow());

  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      views::Widget::InitParams(
          views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
          views::Widget::InitParams::TYPE_WINDOW),
      kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  EXPECT_TRUE(window->IsVisible());
  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());
  EXPECT_EQ(LockScreenActionBackgroundState::kShown,
            background_controller()->state());

  // Move action state back to not available - this should immediately hide both
  // the app and background window,
  Shell::Get()->tray_action()->UpdateLockScreenNoteState(
      mojom::TrayActionState::kNotAvailable);

  EXPECT_FALSE(window->IsVisible());
  EXPECT_FALSE(background_controller()->GetWindow()->IsVisible());
  EXPECT_EQ(LockScreenActionBackgroundState::kHidden,
            background_controller()->state());
}

TEST_F(LockActionHandlerLayoutManagerTestWithTestBackgroundController,
       SecondWindowAddedWhileShowingBackground) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kLaunching);

  EXPECT_EQ(LockScreenActionBackgroundState::kShowing,
            background_controller()->state());

  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      views::Widget::InitParams(
          views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
          views::Widget::InitParams::TYPE_WINDOW),
      kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  EXPECT_FALSE(window->IsVisible());
  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());

  std::unique_ptr<aura::Window> second_window = CreateTestingWindow(
      views::Widget::InitParams(
          views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
          views::Widget::InitParams::TYPE_WINDOW),
      kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  EXPECT_FALSE(window->IsVisible());
  EXPECT_FALSE(second_window->IsVisible());
  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());

  // Finish showing the background. The app windows should be shown at this
  // point.
  ASSERT_TRUE(background_controller()->FinishShow());

  EXPECT_TRUE(window->IsVisible());
  EXPECT_FALSE(window->HasFocus());
  EXPECT_TRUE(second_window->IsVisible());
  EXPECT_TRUE(second_window->HasFocus());
  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());

  // Deactivate the action - the windows should get hidden.
  Shell::Get()->tray_action()->UpdateLockScreenNoteState(
      mojom::TrayActionState::kAvailable);

  EXPECT_FALSE(window->IsVisible());
  EXPECT_FALSE(second_window->IsVisible());
  EXPECT_EQ(LockScreenActionBackgroundState::kHiding,
            background_controller()->state());
}

TEST_F(LockActionHandlerLayoutManagerTestWithTestBackgroundController,
       RelaunchWhileHidingBackground) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kLaunching);

  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      views::Widget::InitParams(
          views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
          views::Widget::InitParams::TYPE_WINDOW),
      kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  ASSERT_TRUE(background_controller()->FinishShow());

  Shell::Get()->tray_action()->UpdateLockScreenNoteState(
      mojom::TrayActionState::kAvailable);
  ASSERT_EQ(LockScreenActionBackgroundState::kHiding,
            background_controller()->state());

  // Create new app window to show.
  window = CreateTestingWindow(
      views::Widget::InitParams(
          views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
          views::Widget::InitParams::TYPE_WINDOW),
      kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  Shell::Get()->tray_action()->UpdateLockScreenNoteState(
      mojom::TrayActionState::kActive);
  ASSERT_EQ(LockScreenActionBackgroundState::kShowing,
            background_controller()->state());

  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());
  EXPECT_FALSE(window->IsVisible());

  background_controller()->FinishShow();

  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());
  EXPECT_TRUE(window->IsVisible());
  EXPECT_TRUE(window->HasFocus());
}

TEST_F(LockActionHandlerLayoutManagerTestWithTestBackgroundController,
       ActionDeactivatedWhileShowingTheBackground) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kLaunching);

  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      views::Widget::InitParams(
          views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
          views::Widget::InitParams::TYPE_WINDOW),
      kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  // Lock screen note action was launched, so the background window is expected
  // to start being shown.
  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());
  EXPECT_FALSE(window->IsVisible());
  EXPECT_EQ(LockScreenActionBackgroundState::kShowing,
            background_controller()->state());

  // Move lock screen note action back to available state (i.e. not activated
  // state), and test that the background window starts hiding.
  Shell::Get()->tray_action()->UpdateLockScreenNoteState(
      mojom::TrayActionState::kAvailable);

  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());
  EXPECT_FALSE(window->IsVisible());
  EXPECT_EQ(LockScreenActionBackgroundState::kHiding,
            background_controller()->state());
}

TEST_F(LockActionHandlerLayoutManagerTestWithTestBackgroundController,
       ActionDisabledWhileShowingTheBackground) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kLaunching);

  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      views::Widget::InitParams(
          views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
          views::Widget::InitParams::TYPE_WINDOW),
      kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  // Lock screen note action was launched, so the background window is expected
  // to start being shown.
  EXPECT_TRUE(background_controller()->GetWindow()->IsVisible());
  EXPECT_FALSE(window->IsVisible());
  EXPECT_EQ(LockScreenActionBackgroundState::kShowing,
            background_controller()->state());

  // Make lock screen note action unavailable, and test that the background
  // window is hidden immediately.
  Shell::Get()->tray_action()->UpdateLockScreenNoteState(
      mojom::TrayActionState::kNotAvailable);
  EXPECT_FALSE(background_controller()->GetWindow()->IsVisible());
  EXPECT_FALSE(window->IsVisible());
}

TEST_F(LockActionHandlerLayoutManagerTestWithTestBackgroundController,
       BackgroundWindowBounds) {
  SetUpTrayActionClientAndLockSession(mojom::TrayActionState::kActive);
  ASSERT_TRUE(background_controller()->FinishShow());

  std::unique_ptr<aura::Window> window = CreateTestingWindow(
      views::Widget::InitParams(
          views::Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET,
          views::Widget::InitParams::TYPE_WINDOW),
      kShellWindowId_LockActionHandlerContainer,
      std::make_unique<TestWindowDelegate>());

  ASSERT_TRUE(window->IsVisible());
  ASSERT_TRUE(background_controller()->GetWindow()->IsVisible());

  // Verify that the window bounds are equal to work area for the bottom shelf
  // alignment, which matches how the shelf is aligned on the lock screen,
  gfx::Rect target_app_window_bounds =
      display::Screen::GetScreen()->GetPrimaryDisplay().bounds();
  target_app_window_bounds.Inset(
      gfx::Insets().set_bottom(ShelfConfig::Get()->shelf_size()));
  EXPECT_EQ(target_app_window_bounds, window->GetBoundsInScreen());

  EXPECT_EQ(display::Screen::GetScreen()->GetPrimaryDisplay().bounds(),
            background_controller()->GetWindow()->GetBoundsInScreen());
}

}  // namespace ash