普通文本  |  1619行  |  61.14 KB

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

#include <map>

#include "ash/root_window_controller.h"
#include "ash/screen_util.h"
#include "ash/shelf/shelf_layout_manager.h"
#include "ash/shelf/shelf_widget.h"
#include "ash/shell.h"
#include "ash/shell_window_ids.h"
#include "ash/system/status_area_widget.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/shell_test_api.h"
#include "ash/test/test_shelf_delegate.h"
#include "ash/wm/panels/panel_layout_manager.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "ash/wm/workspace/workspace_window_resizer.h"
#include "base/strings/string_number_conversions.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/test/event_generator.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/test/test_windows.h"
#include "ui/aura/window.h"
#include "ui/aura/window_event_dispatcher.h"
#include "ui/base/hit_test.h"
#include "ui/base/ui_base_types.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/events/event_utils.h"
#include "ui/gfx/screen.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/core/window_animations.h"
#include "ui/wm/core/window_util.h"

using aura::Window;

namespace ash {

// Returns a string containing the names of all the children of |window| (in
// order). Each entry is separated by a space.
std::string GetWindowNames(const aura::Window* window) {
  std::string result;
  for (size_t i = 0; i < window->children().size(); ++i) {
    if (i != 0)
      result += " ";
    result += window->children()[i]->name();
  }
  return result;
}

// Returns a string containing the names of windows corresponding to each of the
// child layers of |window|'s layer. Any layers that don't correspond to a child
// Window of |window| are ignored. The result is ordered based on the layer
// ordering.
std::string GetLayerNames(const aura::Window* window) {
  typedef std::map<const ui::Layer*, std::string> LayerToWindowNameMap;
  LayerToWindowNameMap window_names;
  for (size_t i = 0; i < window->children().size(); ++i) {
    window_names[window->children()[i]->layer()] =
        window->children()[i]->name();
  }

  std::string result;
  const std::vector<ui::Layer*>& layers(window->layer()->children());
  for (size_t i = 0; i < layers.size(); ++i) {
    LayerToWindowNameMap::iterator layer_i =
        window_names.find(layers[i]);
    if (layer_i != window_names.end()) {
      if (!result.empty())
        result += " ";
      result += layer_i->second;
    }
  }
  return result;
}

class WorkspaceControllerTest : public test::AshTestBase {
 public:
  WorkspaceControllerTest() {}
  virtual ~WorkspaceControllerTest() {}

  aura::Window* CreateTestWindowUnparented() {
    aura::Window* window = new aura::Window(NULL);
    window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
    window->SetType(ui::wm::WINDOW_TYPE_NORMAL);
    window->Init(aura::WINDOW_LAYER_TEXTURED);
    return window;
  }

  aura::Window* CreateTestWindow() {
    aura::Window* window = new aura::Window(NULL);
    window->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
    window->SetType(ui::wm::WINDOW_TYPE_NORMAL);
    window->Init(aura::WINDOW_LAYER_TEXTURED);
    ParentWindowInPrimaryRootWindow(window);
    return window;
  }

  aura::Window* CreateBrowserLikeWindow(const gfx::Rect& bounds) {
    aura::Window* window = CreateTestWindow();
    window->SetBounds(bounds);
    wm::WindowState* window_state = wm::GetWindowState(window);
    window_state->set_window_position_managed(true);
    window->Show();
    return window;
  }

  aura::Window* CreatePopupLikeWindow(const gfx::Rect& bounds) {
    aura::Window* window = CreateTestWindowInShellWithBounds(bounds);
    window->Show();
    return window;
  }

  aura::Window* CreateTestPanel(aura::WindowDelegate* delegate,
                                const gfx::Rect& bounds) {
    aura::Window* window = CreateTestWindowInShellWithDelegateAndType(
        delegate,
        ui::wm::WINDOW_TYPE_PANEL,
        0,
        bounds);
    test::TestShelfDelegate* shelf_delegate =
        test::TestShelfDelegate::instance();
    shelf_delegate->AddShelfItem(window);
    PanelLayoutManager* manager = static_cast<PanelLayoutManager*>(
        Shell::GetContainer(window->GetRootWindow(),
                            kShellWindowId_PanelContainer)->layout_manager());
    manager->Relayout();
    return window;
  }

  aura::Window* GetDesktop() {
    return Shell::GetContainer(Shell::GetPrimaryRootWindow(),
                               kShellWindowId_DefaultContainer);
  }

  gfx::Rect GetFullscreenBounds(aura::Window* window) {
    return Shell::GetScreen()->GetDisplayNearestWindow(window).bounds();
  }

  ShelfWidget* shelf_widget() {
    return Shell::GetPrimaryRootWindowController()->shelf();
  }

  ShelfLayoutManager* shelf_layout_manager() {
    return Shell::GetPrimaryRootWindowController()->GetShelfLayoutManager();
  }

  bool GetWindowOverlapsShelf() {
    return shelf_layout_manager()->window_overlaps_shelf();
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(WorkspaceControllerTest);
};

// Assertions around adding a normal window.
TEST_F(WorkspaceControllerTest, AddNormalWindowWhenEmpty) {
  scoped_ptr<Window> w1(CreateTestWindow());
  w1->SetBounds(gfx::Rect(0, 0, 250, 251));

  wm::WindowState* window_state = wm::GetWindowState(w1.get());

  EXPECT_FALSE(window_state->HasRestoreBounds());

  w1->Show();

  EXPECT_FALSE(window_state->HasRestoreBounds());

  ASSERT_TRUE(w1->layer() != NULL);
  EXPECT_TRUE(w1->layer()->visible());

  EXPECT_EQ("0,0 250x251", w1->bounds().ToString());

  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
}

// Assertions around maximizing/unmaximizing.
TEST_F(WorkspaceControllerTest, SingleMaximizeWindow) {
  scoped_ptr<Window> w1(CreateTestWindow());
  w1->SetBounds(gfx::Rect(0, 0, 250, 251));

  w1->Show();
  wm::ActivateWindow(w1.get());

  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));

  ASSERT_TRUE(w1->layer() != NULL);
  EXPECT_TRUE(w1->layer()->visible());

  EXPECT_EQ("0,0 250x251", w1->bounds().ToString());

  // Maximize the window.
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);

  EXPECT_TRUE(wm::IsActiveWindow(w1.get()));

  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
  EXPECT_EQ(ScreenUtil::GetMaximizedWindowBoundsInParent(w1.get()).width(),
            w1->bounds().width());
  EXPECT_EQ(ScreenUtil::GetMaximizedWindowBoundsInParent(w1.get()).height(),
            w1->bounds().height());

  // Restore the window.
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);

  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
  EXPECT_EQ("0,0 250x251", w1->bounds().ToString());
}

// Assertions around two windows and toggling one to be fullscreen.
TEST_F(WorkspaceControllerTest, FullscreenWithNormalWindow) {
  scoped_ptr<Window> w1(CreateTestWindow());
  scoped_ptr<Window> w2(CreateTestWindow());
  w1->SetBounds(gfx::Rect(0, 0, 250, 251));
  w1->Show();

  ASSERT_TRUE(w1->layer() != NULL);
  EXPECT_TRUE(w1->layer()->visible());

  w2->SetBounds(gfx::Rect(0, 0, 50, 51));
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  w2->Show();
  wm::ActivateWindow(w2.get());

  // Both windows should be in the same workspace.
  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
  EXPECT_EQ(w2.get(), GetDesktop()->children()[1]);

  gfx::Rect work_area(
      ScreenUtil::GetMaximizedWindowBoundsInParent(w1.get()));
  EXPECT_EQ(work_area.width(), w2->bounds().width());
  EXPECT_EQ(work_area.height(), w2->bounds().height());

  // Restore w2, which should then go back to one workspace.
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
  EXPECT_EQ(50, w2->bounds().width());
  EXPECT_EQ(51, w2->bounds().height());
  EXPECT_TRUE(wm::IsActiveWindow(w2.get()));
}

// Makes sure requests to change the bounds of a normal window go through.
TEST_F(WorkspaceControllerTest, ChangeBoundsOfNormalWindow) {
  scoped_ptr<Window> w1(CreateTestWindow());
  w1->Show();

  // Setting the bounds should go through since the window is in the normal
  // workspace.
  w1->SetBounds(gfx::Rect(0, 0, 200, 500));
  EXPECT_EQ(200, w1->bounds().width());
  EXPECT_EQ(500, w1->bounds().height());
}

// Verifies the bounds is not altered when showing and grid is enabled.
TEST_F(WorkspaceControllerTest, SnapToGrid) {
  scoped_ptr<Window> w1(CreateTestWindowUnparented());
  w1->SetBounds(gfx::Rect(1, 6, 25, 30));
  ParentWindowInPrimaryRootWindow(w1.get());
  // We are not aligning this anymore this way. When the window gets shown
  // the window is expected to be handled differently, but this cannot be
  // tested with this test. So the result of this test should be that the
  // bounds are exactly as passed in.
  EXPECT_EQ("1,6 25x30", w1->bounds().ToString());
}

// Assertions around a fullscreen window.
TEST_F(WorkspaceControllerTest, SingleFullscreenWindow) {
  scoped_ptr<Window> w1(CreateTestWindow());
  w1->SetBounds(gfx::Rect(0, 0, 250, 251));
  // Make the window fullscreen.
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  w1->Show();
  wm::ActivateWindow(w1.get());

  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
  EXPECT_EQ(GetFullscreenBounds(w1.get()).width(), w1->bounds().width());
  EXPECT_EQ(GetFullscreenBounds(w1.get()).height(), w1->bounds().height());

  // Restore the window. Use SHOW_STATE_DEFAULT as that is what we'll end up
  // with when using views::Widget.
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_DEFAULT);
  EXPECT_EQ("0,0 250x251", w1->bounds().ToString());

  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
  EXPECT_EQ(250, w1->bounds().width());
  EXPECT_EQ(251, w1->bounds().height());

  // Back to fullscreen.
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  EXPECT_EQ(w1.get(), GetDesktop()->children()[0]);
  EXPECT_EQ(GetFullscreenBounds(w1.get()).width(), w1->bounds().width());
  EXPECT_EQ(GetFullscreenBounds(w1.get()).height(), w1->bounds().height());
  wm::WindowState* window_state = wm::GetWindowState(w1.get());

  ASSERT_TRUE(window_state->HasRestoreBounds());
  EXPECT_EQ("0,0 250x251", window_state->GetRestoreBoundsInScreen().ToString());
}

// Assertions around minimizing a single window.
TEST_F(WorkspaceControllerTest, MinimizeSingleWindow) {
  scoped_ptr<Window> w1(CreateTestWindow());

  w1->Show();

  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
  EXPECT_FALSE(w1->layer()->IsDrawn());
  EXPECT_TRUE(w1->layer()->GetTargetTransform().IsIdentity());

  // Show the window.
  w1->Show();
  EXPECT_TRUE(wm::GetWindowState(w1.get())->IsNormalStateType());
  EXPECT_TRUE(w1->layer()->IsDrawn());
}

// Assertions around minimizing a fullscreen window.
TEST_F(WorkspaceControllerTest, MinimizeFullscreenWindow) {
  // Two windows, w1 normal, w2 fullscreen.
  scoped_ptr<Window> w1(CreateTestWindow());
  scoped_ptr<Window> w2(CreateTestWindow());
  w1->Show();
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  w2->Show();

  wm::WindowState* w1_state = wm::GetWindowState(w1.get());
  wm::WindowState* w2_state = wm::GetWindowState(w2.get());

  w2_state->Activate();

  // Minimize w2.
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
  EXPECT_TRUE(w1->layer()->IsDrawn());
  EXPECT_FALSE(w2->layer()->IsDrawn());

  // Show the window, which should trigger unminimizing.
  w2->Show();
  w2_state->Activate();

  EXPECT_TRUE(w2_state->IsFullscreen());
  EXPECT_TRUE(w1->layer()->IsDrawn());
  EXPECT_TRUE(w2->layer()->IsDrawn());

  // Minimize the window, which should hide the window.
  EXPECT_TRUE(w2_state->IsActive());
  w2_state->Minimize();
  EXPECT_FALSE(w2_state->IsActive());
  EXPECT_FALSE(w2->layer()->IsDrawn());
  EXPECT_TRUE(w1_state->IsActive());
  EXPECT_EQ(w2.get(), GetDesktop()->children()[0]);
  EXPECT_EQ(w1.get(), GetDesktop()->children()[1]);

  // Make the window normal.
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
  // Setting back to normal doesn't change the activation.
  EXPECT_FALSE(w2_state->IsActive());
  EXPECT_TRUE(w1_state->IsActive());
  EXPECT_EQ(w2.get(), GetDesktop()->children()[0]);
  EXPECT_EQ(w1.get(), GetDesktop()->children()[1]);
  EXPECT_TRUE(w2->layer()->IsDrawn());
}

// Verifies ShelfLayoutManager's visibility/auto-hide state is correctly
// updated.
TEST_F(WorkspaceControllerTest, ShelfStateUpdated) {
  // Since ShelfLayoutManager queries for mouse location, move the mouse so
  // it isn't over the shelf.
  aura::test::EventGenerator generator(
      Shell::GetPrimaryRootWindow(), gfx::Point());
  generator.MoveMouseTo(0, 0);

  scoped_ptr<Window> w1(CreateTestWindow());
  const gfx::Rect w1_bounds(0, 1, 101, 102);
  ShelfLayoutManager* shelf = shelf_layout_manager();
  shelf->SetAutoHideBehavior(ash::SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
  const gfx::Rect touches_shelf_bounds(
      0, shelf->GetIdealBounds().y() - 10, 101, 102);
  // Move |w1| to overlap the shelf.
  w1->SetBounds(touches_shelf_bounds);
  EXPECT_FALSE(GetWindowOverlapsShelf());

  // A visible ignored window should not trigger the overlap.
  scoped_ptr<Window> w_ignored(CreateTestWindow());
  w_ignored->SetBounds(touches_shelf_bounds);
  wm::GetWindowState(&(*w_ignored))->set_ignored_by_shelf(true);
  w_ignored->Show();
  EXPECT_FALSE(GetWindowOverlapsShelf());

  // Make it visible, since visible shelf overlaps should be true.
  w1->Show();
  EXPECT_TRUE(GetWindowOverlapsShelf());

  wm::ActivateWindow(w1.get());
  w1->SetBounds(w1_bounds);
  w1->Show();
  wm::ActivateWindow(w1.get());

  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());

  // Maximize the window.
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());

  // Restore.
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
  EXPECT_EQ("0,1 101x102", w1->bounds().ToString());

  // Fullscreen.
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  EXPECT_EQ(SHELF_HIDDEN, shelf->visibility_state());

  // Normal.
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
  EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
  EXPECT_FALSE(GetWindowOverlapsShelf());

  // Move window so it obscures shelf.
  w1->SetBounds(touches_shelf_bounds);
  EXPECT_TRUE(GetWindowOverlapsShelf());

  // Move it back.
  w1->SetBounds(w1_bounds);
  EXPECT_FALSE(GetWindowOverlapsShelf());

  // Maximize again.
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());

  // Minimize.
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());

  // Since the restore from minimize will restore to the pre-minimize
  // state (tested elsewhere), we abandon the current size and restore
  // rect and set them to the window.
  wm::WindowState* window_state = wm::GetWindowState(w1.get());

  gfx::Rect restore = window_state->GetRestoreBoundsInScreen();
  EXPECT_EQ("0,0 800x597", w1->bounds().ToString());
  EXPECT_EQ("0,1 101x102", restore.ToString());
  window_state->ClearRestoreBounds();
  w1->SetBounds(restore);

  // Restore.
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
  EXPECT_EQ("0,1 101x102", w1->bounds().ToString());

  // Create another window, maximized.
  scoped_ptr<Window> w2(CreateTestWindow());
  w2->SetBounds(gfx::Rect(10, 11, 250, 251));
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  w2->Show();
  wm::ActivateWindow(w2.get());
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
  EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
  EXPECT_EQ(ScreenUtil::GetMaximizedWindowBoundsInParent(
                w2->parent()).ToString(),
            w2->bounds().ToString());

  // Switch to w1.
  wm::ActivateWindow(w1.get());
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
  EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
  EXPECT_EQ(ScreenUtil::GetMaximizedWindowBoundsInParent(
                w2->parent()).ToString(),
            w2->bounds().ToString());

  // Switch to w2.
  wm::ActivateWindow(w2.get());
  EXPECT_EQ(SHELF_AUTO_HIDE, shelf->visibility_state());
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
  EXPECT_EQ("0,1 101x102", w1->bounds().ToString());
  EXPECT_EQ(ScreenUtil::GetMaximizedWindowBoundsInParent(w2.get()).ToString(),
            w2->bounds().ToString());

  // Turn off auto-hide, switch back to w2 (maximized) and verify overlap.
  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);
  wm::ActivateWindow(w2.get());
  EXPECT_FALSE(GetWindowOverlapsShelf());

  // Move w1 to overlap shelf, it shouldn't change window overlaps shelf since
  // the window isn't in the visible workspace.
  w1->SetBounds(touches_shelf_bounds);
  EXPECT_FALSE(GetWindowOverlapsShelf());

  // Activate w1. Although w1 is visible, the overlap state is still false since
  // w2 is maximized.
  wm::ActivateWindow(w1.get());
  EXPECT_FALSE(GetWindowOverlapsShelf());

  // Restore w2.
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
  EXPECT_TRUE(GetWindowOverlapsShelf());
}

// Verifies going from maximized to minimized sets the right state for painting
// the background of the launcher.
TEST_F(WorkspaceControllerTest, MinimizeResetsVisibility) {
  scoped_ptr<Window> w1(CreateTestWindow());
  w1->Show();
  wm::ActivateWindow(w1.get());
  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  EXPECT_EQ(SHELF_BACKGROUND_MAXIMIZED, shelf_widget()->GetBackgroundType());

  w1->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
  EXPECT_EQ(SHELF_VISIBLE,
            shelf_layout_manager()->visibility_state());
  EXPECT_EQ(SHELF_BACKGROUND_DEFAULT, shelf_widget()->GetBackgroundType());
}

// Verifies window visibility during various workspace changes.
TEST_F(WorkspaceControllerTest, VisibilityTests) {
  scoped_ptr<Window> w1(CreateTestWindow());
  w1->Show();
  EXPECT_TRUE(w1->IsVisible());
  EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());

  // Create another window, activate it and make it fullscreen.
  scoped_ptr<Window> w2(CreateTestWindow());
  w2->Show();
  wm::ActivateWindow(w2.get());
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  EXPECT_TRUE(w2->IsVisible());
  EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity());
  EXPECT_TRUE(w1->IsVisible());

  // Switch to w1. |w1| should be visible on top of |w2|.
  wm::ActivateWindow(w1.get());
  EXPECT_TRUE(w1->IsVisible());
  EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
  EXPECT_TRUE(w2->IsVisible());

  // Switch back to |w2|.
  wm::ActivateWindow(w2.get());
  EXPECT_TRUE(w2->IsVisible());
  EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity());
  EXPECT_TRUE(w1->IsVisible());

  // Restore |w2|, both windows should be visible.
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
  EXPECT_TRUE(w1->IsVisible());
  EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
  EXPECT_TRUE(w2->IsVisible());
  EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity());

  // Make |w2| fullscreen again, then close it.
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_FULLSCREEN);
  w2->Hide();
  EXPECT_FALSE(w2->IsVisible());
  EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
  EXPECT_TRUE(w1->IsVisible());

  // Create |w2| and maximize it.
  w2.reset(CreateTestWindow());
  w2->Show();
  wm::ActivateWindow(w2.get());
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  EXPECT_TRUE(w2->IsVisible());
  EXPECT_EQ(1.0f, w2->layer()->GetCombinedOpacity());
  EXPECT_TRUE(w1->IsVisible());

  // Close |w2|.
  w2.reset();
  EXPECT_EQ(1.0f, w1->layer()->GetCombinedOpacity());
  EXPECT_TRUE(w1->IsVisible());
}

// Verifies windows that are offscreen don't move when switching workspaces.
TEST_F(WorkspaceControllerTest, DontMoveOnSwitch) {
  aura::test::EventGenerator generator(
      Shell::GetPrimaryRootWindow(), gfx::Point());
  generator.MoveMouseTo(0, 0);

  scoped_ptr<Window> w1(CreateTestWindow());
  ShelfLayoutManager* shelf = shelf_layout_manager();
  const gfx::Rect touches_shelf_bounds(
      0, shelf->GetIdealBounds().y() - 10, 101, 102);
  // Move |w1| to overlap the shelf.
  w1->SetBounds(touches_shelf_bounds);
  w1->Show();
  wm::ActivateWindow(w1.get());

  // Create another window and maximize it.
  scoped_ptr<Window> w2(CreateTestWindow());
  w2->SetBounds(gfx::Rect(10, 11, 250, 251));
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  w2->Show();
  wm::ActivateWindow(w2.get());

  // Switch to w1.
  wm::ActivateWindow(w1.get());
  EXPECT_EQ(touches_shelf_bounds.ToString(), w1->bounds().ToString());
}

// Verifies that windows that are completely offscreen move when switching
// workspaces.
TEST_F(WorkspaceControllerTest, MoveOnSwitch) {
  aura::test::EventGenerator generator(
      Shell::GetPrimaryRootWindow(), gfx::Point());
  generator.MoveMouseTo(0, 0);

  scoped_ptr<Window> w1(CreateTestWindow());
  ShelfLayoutManager* shelf = shelf_layout_manager();
  const gfx::Rect w1_bounds(0, shelf->GetIdealBounds().y(), 100, 200);
  // Move |w1| so that the top edge is the same as the top edge of the shelf.
  w1->SetBounds(w1_bounds);
  w1->Show();
  wm::ActivateWindow(w1.get());
  EXPECT_EQ(w1_bounds.ToString(), w1->bounds().ToString());

  // Create another window and maximize it.
  scoped_ptr<Window> w2(CreateTestWindow());
  w2->SetBounds(gfx::Rect(10, 11, 250, 251));
  w2->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  w2->Show();
  wm::ActivateWindow(w2.get());

  // Increase the size of the WorkAreaInsets. This would make |w1| fall
  // completely out of the display work area.
  gfx::Insets insets =
      Shell::GetScreen()->GetPrimaryDisplay().GetWorkAreaInsets();
  insets.Set(0, 0, insets.bottom() + 30, 0);
  Shell::GetInstance()->SetDisplayWorkAreaInsets(w1.get(), insets);

  // Switch to w1. The window should have moved.
  wm::ActivateWindow(w1.get());
  EXPECT_NE(w1_bounds.ToString(), w1->bounds().ToString());
}

namespace {

// WindowDelegate used by DontCrashOnChangeAndActivate.
class DontCrashOnChangeAndActivateDelegate
    : public aura::test::TestWindowDelegate {
 public:
  DontCrashOnChangeAndActivateDelegate() : window_(NULL) {}

  void set_window(aura::Window* window) { window_ = window; }

  // WindowDelegate overrides:
  virtual void OnBoundsChanged(const gfx::Rect& old_bounds,
                               const gfx::Rect& new_bounds) OVERRIDE {
    if (window_) {
      wm::ActivateWindow(window_);
      window_ = NULL;
    }
  }

 private:
  aura::Window* window_;

  DISALLOW_COPY_AND_ASSIGN(DontCrashOnChangeAndActivateDelegate);
};

}  // namespace

// Exercises possible crash in W2. Here's the sequence:
// . minimize a maximized window.
// . remove the window (which happens when switching displays).
// . add the window back.
// . show the window and during the bounds change activate it.
TEST_F(WorkspaceControllerTest, DontCrashOnChangeAndActivate) {
  // Force the shelf
  ShelfLayoutManager* shelf = shelf_layout_manager();
  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);

  DontCrashOnChangeAndActivateDelegate delegate;
  scoped_ptr<Window> w1(CreateTestWindowInShellWithDelegate(
      &delegate, 1000, gfx::Rect(10, 11, 250, 251)));

  w1->Show();
  wm::WindowState* w1_state = wm::GetWindowState(w1.get());
  w1_state->Activate();
  w1_state->Maximize();
  w1_state->Minimize();

  w1->parent()->RemoveChild(w1.get());

  // Do this so that when we Show() the window a resize occurs and we make the
  // window active.
  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);

  ParentWindowInPrimaryRootWindow(w1.get());
  delegate.set_window(w1.get());
  w1->Show();
}

// Verifies a window with a transient parent not managed by workspace works.
TEST_F(WorkspaceControllerTest, TransientParent) {
  // Normal window with no transient parent.
  scoped_ptr<Window> w2(CreateTestWindow());
  w2->SetBounds(gfx::Rect(10, 11, 250, 251));
  w2->Show();
  wm::ActivateWindow(w2.get());

  // Window with a transient parent. We set the transient parent to the root,
  // which would never happen but is enough to exercise the bug.
  scoped_ptr<Window> w1(CreateTestWindowUnparented());
  ::wm::AddTransientChild(
      Shell::GetInstance()->GetPrimaryRootWindow(), w1.get());
  w1->SetBounds(gfx::Rect(10, 11, 250, 251));
  ParentWindowInPrimaryRootWindow(w1.get());
  w1->Show();
  wm::ActivateWindow(w1.get());

  // The window with the transient parent should get added to the same parent as
  // the normal window.
  EXPECT_EQ(w2->parent(), w1->parent());
}

// Test the placement of newly created windows.
TEST_F(WorkspaceControllerTest, BasicAutoPlacingOnCreate) {
  if (!SupportsHostWindowResize())
    return;
  UpdateDisplay("1600x1200");
  // Creating a popup handler here to make sure it does not interfere with the
  // existing windows.
  gfx::Rect source_browser_bounds(16, 32, 640, 320);
  scoped_ptr<aura::Window> browser_window(CreateBrowserLikeWindow(
      source_browser_bounds));

  // Creating a popup to make sure it does not interfere with the positioning.
  scoped_ptr<aura::Window> browser_popup(CreatePopupLikeWindow(
      gfx::Rect(16, 32, 128, 256)));

  browser_window->Show();
  browser_popup->Show();

  { // With a shown window it's size should get returned.
    scoped_ptr<aura::Window> new_browser_window(CreateBrowserLikeWindow(
        source_browser_bounds));
    // The position should be right flush.
    EXPECT_EQ("960,32 640x320", new_browser_window->bounds().ToString());
  }

  { // With the window shown - but more on the right side then on the left
    // side (and partially out of the screen), it should default to the other
    // side and inside the screen.
    gfx::Rect source_browser_bounds(gfx::Rect(1000, 600, 640, 320));
    browser_window->SetBounds(source_browser_bounds);

    scoped_ptr<aura::Window> new_browser_window(CreateBrowserLikeWindow(
        source_browser_bounds));
    // The position should be left & bottom flush.
    EXPECT_EQ("0,600 640x320", new_browser_window->bounds().ToString());

    // If the other window was already beyond the point to get right flush
    // it will remain where it is.
    EXPECT_EQ("1000,600 640x320", browser_window->bounds().ToString());
  }

  { // Make sure that popups do not get changed.
    scoped_ptr<aura::Window> new_popup_window(CreatePopupLikeWindow(
        gfx::Rect(50, 100, 300, 150)));
    EXPECT_EQ("50,100 300x150", new_popup_window->bounds().ToString());
  }

  browser_window->Hide();
  { // If a window is there but not shown the default should be centered.
    scoped_ptr<aura::Window> new_browser_window(CreateBrowserLikeWindow(
        gfx::Rect(50, 100, 300, 150)));
    EXPECT_EQ("650,100 300x150", new_browser_window->bounds().ToString());
  }
}

// Test that adding a second window shifts both the first window and its
// transient child.
TEST_F(WorkspaceControllerTest, AutoPlacingMovesTransientChild) {
  // Create an auto-positioned window.
  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
  gfx::Rect desktop_area = window1->parent()->bounds();
  wm::GetWindowState(window1.get())->set_window_position_managed(true);
  // Hide and then show |window1| to trigger auto-positioning logic.
  window1->Hide();
  window1->SetBounds(gfx::Rect(16, 32, 300, 300));
  window1->Show();

  // |window1| should be horizontally centered.
  int x_window1 = (desktop_area.width() - 300) / 2;
  EXPECT_EQ(base::IntToString(x_window1) + ",32 300x300",
            window1->bounds().ToString());

  // Create a |child| window and make it a transient child of |window1|.
  scoped_ptr<Window> child(CreateTestWindowUnparented());
  ::wm::AddTransientChild(window1.get(), child.get());
  const int x_child = x_window1 + 50;
  child->SetBounds(gfx::Rect(x_child, 20, 200, 200));
  ParentWindowInPrimaryRootWindow(child.get());
  child->Show();
  wm::ActivateWindow(child.get());

  // The |child| should be where it was created.
  EXPECT_EQ(base::IntToString(x_child) + ",20 200x200",
            child->bounds().ToString());

  // Create and show a second window forcing the first window and its child to
  // move.
  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
  wm::GetWindowState(window2.get())->set_window_position_managed(true);
  // Hide and then show |window2| to trigger auto-positioning logic.
  window2->Hide();
  window2->SetBounds(gfx::Rect(32, 48, 250, 250));
  window2->Show();

  // Check that both |window1| and |child| have moved left.
  EXPECT_EQ("0,32 300x300", window1->bounds().ToString());
  int x = x_child - x_window1;
  EXPECT_EQ(base::IntToString(x) + ",20 200x200", child->bounds().ToString());
  // Check that |window2| has moved right.
  x = desktop_area.width() - window2->bounds().width();
  EXPECT_EQ(base::IntToString(x) + ",48 250x250", window2->bounds().ToString());
}

// Test the basic auto placement of one and or two windows in a "simulated
// session" of sequential window operations.
TEST_F(WorkspaceControllerTest, BasicAutoPlacingOnShowHide) {
  // Test 1: In case there is no manageable window, no window should shift.

  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
  gfx::Rect desktop_area = window1->parent()->bounds();

  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
  // Trigger the auto window placement function by making it visible.
  // Note that the bounds are getting changed while it is invisible.
  window2->Hide();
  window2->SetBounds(gfx::Rect(32, 48, 256, 512));
  window2->Show();

  // Check the initial position of the windows is unchanged.
  EXPECT_EQ("16,32 640x320", window1->bounds().ToString());
  EXPECT_EQ("32,48 256x512", window2->bounds().ToString());

  // Remove the second window and make sure that the first window
  // does NOT get centered.
  window2.reset();
  EXPECT_EQ("16,32 640x320", window1->bounds().ToString());

  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
  // Test 2: Set up two managed windows and check their auto positioning.
  window1_state->set_window_position_managed(true);

  scoped_ptr<aura::Window> window3(CreateTestWindowInShellWithId(2));
  wm::GetWindowState(window3.get())->set_window_position_managed(true);
  // To avoid any auto window manager changes due to SetBounds, the window
  // gets first hidden and then shown again.
  window3->Hide();
  window3->SetBounds(gfx::Rect(32, 48, 256, 512));
  window3->Show();
  // |window1| should be flush left and |window3| flush right.
  EXPECT_EQ("0,32 640x320", window1->bounds().ToString());
  EXPECT_EQ(base::IntToString(
                desktop_area.width() - window3->bounds().width()) +
            ",48 256x512", window3->bounds().ToString());

  // After removing |window3|, |window1| should be centered again.
  window3.reset();
  EXPECT_EQ(
      base::IntToString(
          (desktop_area.width() - window1->bounds().width()) / 2) +
      ",32 640x320", window1->bounds().ToString());

  // Test 3: Set up a manageable and a non manageable window and check
  // positioning.
  scoped_ptr<aura::Window> window4(CreateTestWindowInShellWithId(3));
  // To avoid any auto window manager changes due to SetBounds, the window
  // gets first hidden and then shown again.
  window1->Hide();
  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
  window4->SetBounds(gfx::Rect(32, 48, 256, 512));
  window1->Show();
  // |window1| should be centered and |window4| untouched.
  EXPECT_EQ(
      base::IntToString(
          (desktop_area.width() - window1->bounds().width()) / 2) +
      ",32 640x320", window1->bounds().ToString());
  EXPECT_EQ("32,48 256x512", window4->bounds().ToString());

  // Test4: A single manageable window should get centered.
  window4.reset();
  window1_state->set_bounds_changed_by_user(false);
  // Trigger the auto window placement function by showing (and hiding) it.
  window1->Hide();
  window1->Show();
  // |window1| should be centered.
  EXPECT_EQ(
      base::IntToString(
          (desktop_area.width() - window1->bounds().width()) / 2) +
      ",32 640x320", window1->bounds().ToString());
}

// Test the proper usage of user window movement interaction.
TEST_F(WorkspaceControllerTest, TestUserMovedWindowRepositioning) {
  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
  gfx::Rect desktop_area = window1->parent()->bounds();
  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
  window2->SetBounds(gfx::Rect(32, 48, 256, 512));
  window1->Hide();
  window2->Hide();
  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
  wm::WindowState* window2_state = wm::GetWindowState(window2.get());

  window1_state->set_window_position_managed(true);
  window2_state->set_window_position_managed(true);
  EXPECT_FALSE(window1_state->bounds_changed_by_user());
  EXPECT_FALSE(window2_state->bounds_changed_by_user());

  // Check that the current location gets preserved if the user has
  // positioned it previously.
  window1_state->set_bounds_changed_by_user(true);
  window1->Show();
  EXPECT_EQ("16,32 640x320", window1->bounds().ToString());
  // Flag should be still set.
  EXPECT_TRUE(window1_state->bounds_changed_by_user());
  EXPECT_FALSE(window2_state->bounds_changed_by_user());

  // Turn on the second window and make sure that both windows are now
  // positionable again (user movement cleared).
  window2->Show();

  // |window1| should be flush left and |window2| flush right.
  EXPECT_EQ("0,32 640x320", window1->bounds().ToString());
  EXPECT_EQ(
      base::IntToString(desktop_area.width() - window2->bounds().width()) +
      ",48 256x512", window2->bounds().ToString());
  // FLag should now be reset.
  EXPECT_FALSE(window1_state->bounds_changed_by_user());
  EXPECT_FALSE(window2_state->bounds_changed_by_user());

  // Going back to one shown window should keep the state.
  window1_state->set_bounds_changed_by_user(true);
  window2->Hide();
  EXPECT_EQ("0,32 640x320", window1->bounds().ToString());
  EXPECT_TRUE(window1_state->bounds_changed_by_user());
}

// Test if the single window will be restored at original position.
TEST_F(WorkspaceControllerTest, TestSingleWindowsRestoredBounds) {
  scoped_ptr<aura::Window> window1(
      CreateTestWindowInShellWithBounds(gfx::Rect(100, 100, 100, 100)));
  scoped_ptr<aura::Window> window2(
      CreateTestWindowInShellWithBounds(gfx::Rect(110, 110, 100, 100)));
  scoped_ptr<aura::Window> window3(
      CreateTestWindowInShellWithBounds(gfx::Rect(120, 120, 100, 100)));
  window1->Hide();
  window2->Hide();
  window3->Hide();
  wm::GetWindowState(window1.get())->set_window_position_managed(true);
  wm::GetWindowState(window2.get())->set_window_position_managed(true);
  wm::GetWindowState(window3.get())->set_window_position_managed(true);

  window1->Show();
  wm::ActivateWindow(window1.get());
  window2->Show();
  wm::ActivateWindow(window2.get());
  window3->Show();
  wm::ActivateWindow(window3.get());
  EXPECT_EQ(0, window1->bounds().x());
  EXPECT_EQ(window2->GetRootWindow()->bounds().right(),
            window2->bounds().right());
  EXPECT_EQ(0, window3->bounds().x());

  window1->Hide();
  EXPECT_EQ(window2->GetRootWindow()->bounds().right(),
            window2->bounds().right());
  EXPECT_EQ(0, window3->bounds().x());

  // Being a single window will retore the original location.
  window3->Hide();
  wm::ActivateWindow(window2.get());
  EXPECT_EQ("110,110 100x100", window2->bounds().ToString());

  // Showing the 3rd will push the 2nd window left.
  window3->Show();
  wm::ActivateWindow(window3.get());
  EXPECT_EQ(0, window2->bounds().x());
  EXPECT_EQ(window3->GetRootWindow()->bounds().right(),
            window3->bounds().right());

  // Being a single window will retore the original location.
  window2->Hide();
  EXPECT_EQ("120,120 100x100", window3->bounds().ToString());
}

// Test that user placed windows go back to their user placement after the user
// closes all other windows.
TEST_F(WorkspaceControllerTest, TestUserHandledWindowRestore) {
  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
  gfx::Rect user_pos = gfx::Rect(16, 42, 640, 320);
  window1->SetBounds(user_pos);
  wm::WindowState* window1_state = wm::GetWindowState(window1.get());

  window1_state->SetPreAutoManageWindowBounds(user_pos);
  gfx::Rect desktop_area = window1->parent()->bounds();

  // Create a second window to let the auto manager kick in.
  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
  window2->SetBounds(gfx::Rect(32, 48, 256, 512));
  window1->Hide();
  window2->Hide();
  wm::GetWindowState(window1.get())->set_window_position_managed(true);
  wm::GetWindowState(window2.get())->set_window_position_managed(true);
  window1->Show();
  EXPECT_EQ(user_pos.ToString(), window1->bounds().ToString());
  window2->Show();

  // |window1| should be flush left and |window2| flush right.
  EXPECT_EQ("0," + base::IntToString(user_pos.y()) +
            " 640x320", window1->bounds().ToString());
  EXPECT_EQ(
      base::IntToString(desktop_area.width() - window2->bounds().width()) +
      ",48 256x512", window2->bounds().ToString());
  window2->Hide();

  // After the other window get hidden the window has to move back to the
  // previous position and the bounds should still be set and unchanged.
  EXPECT_EQ(user_pos.ToString(), window1->bounds().ToString());
  ASSERT_TRUE(window1_state->pre_auto_manage_window_bounds());
  EXPECT_EQ(user_pos.ToString(),
            window1_state->pre_auto_manage_window_bounds()->ToString());
}

// Solo window should be restored to the bounds where a user moved to.
TEST_F(WorkspaceControllerTest, TestRestoreToUserModifiedBounds) {
  if (!SupportsHostWindowResize())
    return;

  UpdateDisplay("400x300");
  gfx::Rect default_bounds(10, 0, 100, 100);
  scoped_ptr<aura::Window> window1(
      CreateTestWindowInShellWithBounds(default_bounds));
  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
  window1->Hide();
  window1_state->set_window_position_managed(true);
  window1->Show();
  // First window is centered.
  EXPECT_EQ("150,0 100x100", window1->bounds().ToString());
  scoped_ptr<aura::Window> window2(
      CreateTestWindowInShellWithBounds(default_bounds));
  wm::WindowState* window2_state = wm::GetWindowState(window2.get());
  window2->Hide();
  window2_state->set_window_position_managed(true);
  window2->Show();

  // Auto positioning pushes windows to each sides.
  EXPECT_EQ("0,0 100x100", window1->bounds().ToString());
  EXPECT_EQ("300,0 100x100", window2->bounds().ToString());

  window2->Hide();
  // Restores to the center.
  EXPECT_EQ("150,0 100x100", window1->bounds().ToString());

  // A user moved the window.
  scoped_ptr<WindowResizer> resizer(CreateWindowResizer(
      window1.get(),
      gfx::Point(),
      HTCAPTION,
      aura::client::WINDOW_MOVE_SOURCE_MOUSE).release());
  gfx::Point location = resizer->GetInitialLocation();
  location.Offset(-50, 0);
  resizer->Drag(location, 0);
  resizer->CompleteDrag();

  window1_state->set_bounds_changed_by_user(true);
  window1->SetBounds(gfx::Rect(100, 0, 100, 100));

  window2->Show();
  EXPECT_EQ("0,0 100x100", window1->bounds().ToString());
  EXPECT_EQ("300,0 100x100", window2->bounds().ToString());

  // Window 1 should be restored to the user modified bounds.
  window2->Hide();
  EXPECT_EQ("100,0 100x100", window1->bounds().ToString());
}

// Test that a window from normal to minimize will repos the remaining.
TEST_F(WorkspaceControllerTest, ToMinimizeRepositionsRemaining) {
  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
  window1_state->set_window_position_managed(true);
  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
  gfx::Rect desktop_area = window1->parent()->bounds();

  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
  wm::WindowState* window2_state = wm::GetWindowState(window2.get());
  window2_state->set_window_position_managed(true);
  window2->SetBounds(gfx::Rect(32, 48, 256, 512));

  window1_state->Minimize();

  // |window2| should be centered now.
  EXPECT_TRUE(window2->IsVisible());
  EXPECT_TRUE(window2_state->IsNormalStateType());
  EXPECT_EQ(base::IntToString(
                (desktop_area.width() - window2->bounds().width()) / 2) +
            ",48 256x512", window2->bounds().ToString());

  window1_state->Restore();
  // |window1| should be flush right and |window3| flush left.
  EXPECT_EQ(base::IntToString(
                desktop_area.width() - window1->bounds().width()) +
            ",32 640x320", window1->bounds().ToString());
  EXPECT_EQ("0,48 256x512", window2->bounds().ToString());
}

// Test that minimizing an initially maximized window will repos the remaining.
TEST_F(WorkspaceControllerTest, MaxToMinRepositionsRemaining) {
  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
  window1_state->set_window_position_managed(true);
  gfx::Rect desktop_area = window1->parent()->bounds();

  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
  wm::WindowState* window2_state = wm::GetWindowState(window2.get());
  window2_state->set_window_position_managed(true);
  window2->SetBounds(gfx::Rect(32, 48, 256, 512));

  window1_state->Maximize();
  window1_state->Minimize();

  // |window2| should be centered now.
  EXPECT_TRUE(window2->IsVisible());
  EXPECT_TRUE(window2_state->IsNormalStateType());
  EXPECT_EQ(base::IntToString(
                (desktop_area.width() - window2->bounds().width()) / 2) +
            ",48 256x512", window2->bounds().ToString());
}

// Test that nomral, maximize, minimizing will repos the remaining.
TEST_F(WorkspaceControllerTest, NormToMaxToMinRepositionsRemaining) {
  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
  window1_state->set_window_position_managed(true);
  gfx::Rect desktop_area = window1->parent()->bounds();

  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
  wm::WindowState* window2_state = wm::GetWindowState(window2.get());
  window2_state->set_window_position_managed(true);
  window2->SetBounds(gfx::Rect(32, 40, 256, 512));

  // Trigger the auto window placement function by showing (and hiding) it.
  window1->Hide();
  window1->Show();

  // |window1| should be flush right and |window3| flush left.
  EXPECT_EQ(base::IntToString(
                desktop_area.width() - window1->bounds().width()) +
            ",32 640x320", window1->bounds().ToString());
  EXPECT_EQ("0,40 256x512", window2->bounds().ToString());

  window1_state->Maximize();
  window1_state->Minimize();

  // |window2| should be centered now.
  EXPECT_TRUE(window2->IsVisible());
  EXPECT_TRUE(window2_state->IsNormalStateType());
  EXPECT_EQ(base::IntToString(
                (desktop_area.width() - window2->bounds().width()) / 2) +
            ",40 256x512", window2->bounds().ToString());
}

// Test that nomral, maximize, normal will repos the remaining.
TEST_F(WorkspaceControllerTest, NormToMaxToNormRepositionsRemaining) {
  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
  wm::WindowState* window1_state = wm::GetWindowState(window1.get());
  window1_state->set_window_position_managed(true);
  gfx::Rect desktop_area = window1->parent()->bounds();

  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
  wm::GetWindowState(window2.get())->set_window_position_managed(true);
  window2->SetBounds(gfx::Rect(32, 40, 256, 512));

  // Trigger the auto window placement function by showing (and hiding) it.
  window1->Hide();
  window1->Show();

  // |window1| should be flush right and |window3| flush left.
  EXPECT_EQ(base::IntToString(
                desktop_area.width() - window1->bounds().width()) +
            ",32 640x320", window1->bounds().ToString());
  EXPECT_EQ("0,40 256x512", window2->bounds().ToString());

  window1_state->Maximize();
  window1_state->Restore();

  // |window1| should be flush right and |window2| flush left.
  EXPECT_EQ(base::IntToString(
                desktop_area.width() - window1->bounds().width()) +
            ",32 640x320", window1->bounds().ToString());
  EXPECT_EQ("0,40 256x512", window2->bounds().ToString());
}

// Test that animations are triggered.
TEST_F(WorkspaceControllerTest, AnimatedNormToMaxToNormRepositionsRemaining) {
  ui::ScopedAnimationDurationScaleMode normal_duration_mode(
      ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
  scoped_ptr<aura::Window> window1(CreateTestWindowInShellWithId(0));
  window1->Hide();
  window1->SetBounds(gfx::Rect(16, 32, 640, 320));
  gfx::Rect desktop_area = window1->parent()->bounds();
  scoped_ptr<aura::Window> window2(CreateTestWindowInShellWithId(1));
  window2->Hide();
  window2->SetBounds(gfx::Rect(32, 48, 256, 512));

  wm::GetWindowState(window1.get())->set_window_position_managed(true);
  wm::GetWindowState(window2.get())->set_window_position_managed(true);
  // Make sure nothing is animating.
  window1->layer()->GetAnimator()->StopAnimating();
  window2->layer()->GetAnimator()->StopAnimating();
  window2->Show();

  // The second window should now animate.
  EXPECT_FALSE(window1->layer()->GetAnimator()->is_animating());
  EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating());
  window2->layer()->GetAnimator()->StopAnimating();

  window1->Show();
  EXPECT_TRUE(window1->layer()->GetAnimator()->is_animating());
  EXPECT_TRUE(window2->layer()->GetAnimator()->is_animating());

  window1->layer()->GetAnimator()->StopAnimating();
  window2->layer()->GetAnimator()->StopAnimating();
  // |window1| should be flush right and |window2| flush left.
  EXPECT_EQ(base::IntToString(
                desktop_area.width() - window1->bounds().width()) +
            ",32 640x320", window1->bounds().ToString());
  EXPECT_EQ("0,48 256x512", window2->bounds().ToString());
}

// This tests simulates a browser and an app and verifies the ordering of the
// windows and layers doesn't get out of sync as various operations occur. Its
// really testing code in FocusController, but easier to simulate here. Just as
// with a real browser the browser here has a transient child window
// (corresponds to the status bubble).
TEST_F(WorkspaceControllerTest, VerifyLayerOrdering) {
  scoped_ptr<Window> browser(aura::test::CreateTestWindowWithDelegate(
      NULL, ui::wm::WINDOW_TYPE_NORMAL, gfx::Rect(5, 6, 7, 8), NULL));
  browser->SetName("browser");
  ParentWindowInPrimaryRootWindow(browser.get());
  browser->Show();
  wm::ActivateWindow(browser.get());

  // |status_bubble| is made a transient child of |browser| and as a result
  // owned by |browser|.
  aura::test::TestWindowDelegate* status_bubble_delegate =
      aura::test::TestWindowDelegate::CreateSelfDestroyingDelegate();
  status_bubble_delegate->set_can_focus(false);
  Window* status_bubble =
      aura::test::CreateTestWindowWithDelegate(status_bubble_delegate,
                                               ui::wm::WINDOW_TYPE_POPUP,
                                               gfx::Rect(5, 6, 7, 8),
                                               NULL);
  ::wm::AddTransientChild(browser.get(), status_bubble);
  ParentWindowInPrimaryRootWindow(status_bubble);
  status_bubble->SetName("status_bubble");

  scoped_ptr<Window> app(aura::test::CreateTestWindowWithDelegate(
      NULL, ui::wm::WINDOW_TYPE_NORMAL, gfx::Rect(5, 6, 7, 8), NULL));
  app->SetName("app");
  ParentWindowInPrimaryRootWindow(app.get());

  aura::Window* parent = browser->parent();

  app->Show();
  wm::ActivateWindow(app.get());
  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));

  // Minimize the app, focus should go the browser.
  app->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
  EXPECT_TRUE(wm::IsActiveWindow(browser.get()));
  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));

  // Minimize the browser (neither windows are focused).
  browser->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_MINIMIZED);
  EXPECT_FALSE(wm::IsActiveWindow(browser.get()));
  EXPECT_FALSE(wm::IsActiveWindow(app.get()));
  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));

  // Show the browser (which should restore it).
  browser->Show();
  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));

  // Activate the browser.
  ash::wm::ActivateWindow(browser.get());
  EXPECT_TRUE(wm::IsActiveWindow(browser.get()));
  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));

  // Restore the app. This differs from above code for |browser| as internally
  // the app code does this. Restoring this way or using Show() should not make
  // a difference.
  app->SetProperty(aura::client::kShowStateKey, ui::SHOW_STATE_NORMAL);
  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));

  // Activate the app.
  ash::wm::ActivateWindow(app.get());
  EXPECT_TRUE(wm::IsActiveWindow(app.get()));
  EXPECT_EQ(GetWindowNames(parent), GetLayerNames(parent));
}

namespace {

// Used by DragMaximizedNonTrackedWindow to track how many times the window
// hierarchy changes affecting the specified window.
class DragMaximizedNonTrackedWindowObserver
    : public aura::WindowObserver {
 public:
  DragMaximizedNonTrackedWindowObserver(aura::Window* window)
  : change_count_(0),
    window_(window) {
  }

  // Number of times OnWindowHierarchyChanged() has been received.
  void clear_change_count() { change_count_ = 0; }
  int change_count() const {
    return change_count_;
  }

  // aura::WindowObserver overrides:
  // Counts number of times a window is reparented. Ignores reparenting into and
  // from a docked container which is expected when a tab is dragged.
  virtual void OnWindowHierarchyChanged(
      const HierarchyChangeParams& params) OVERRIDE {
    if (params.target != window_ ||
        (params.old_parent->id() == kShellWindowId_DefaultContainer &&
         params.new_parent->id() == kShellWindowId_DockedContainer) ||
        (params.old_parent->id() == kShellWindowId_DockedContainer &&
         params.new_parent->id() == kShellWindowId_DefaultContainer)) {
      return;
    }
    change_count_++;
  }

 private:
  int change_count_;
  aura::Window* window_;

  DISALLOW_COPY_AND_ASSIGN(DragMaximizedNonTrackedWindowObserver);
};

}  // namespace

// Verifies that a new maximized window becomes visible after its activation
// is requested, even though it does not become activated because a system
// modal window is active.
TEST_F(WorkspaceControllerTest, SwitchFromModal) {
  scoped_ptr<Window> modal_window(CreateTestWindowUnparented());
  modal_window->SetBounds(gfx::Rect(10, 11, 21, 22));
  modal_window->SetProperty(aura::client::kModalKey, ui::MODAL_TYPE_SYSTEM);
  ParentWindowInPrimaryRootWindow(modal_window.get());
  modal_window->Show();
  wm::ActivateWindow(modal_window.get());

  scoped_ptr<Window> maximized_window(CreateTestWindow());
  maximized_window->SetProperty(
      aura::client::kShowStateKey, ui::SHOW_STATE_MAXIMIZED);
  maximized_window->Show();
  wm::ActivateWindow(maximized_window.get());
  EXPECT_TRUE(maximized_window->IsVisible());
}

namespace {

// Subclass of WorkspaceControllerTest that runs tests with docked windows
// enabled and disabled.
class WorkspaceControllerTestDragging : public WorkspaceControllerTest {
 public:
  WorkspaceControllerTestDragging() {}
  virtual ~WorkspaceControllerTestDragging() {}

 private:
  DISALLOW_COPY_AND_ASSIGN(WorkspaceControllerTestDragging);
};

}  // namespace

// Verifies that when dragging a window over the shelf overlap is detected
// during and after the drag.
TEST_F(WorkspaceControllerTestDragging, DragWindowOverlapShelf) {
  aura::test::TestWindowDelegate delegate;
  delegate.set_window_component(HTCAPTION);
  scoped_ptr<Window> w1(aura::test::CreateTestWindowWithDelegate(
      &delegate, ui::wm::WINDOW_TYPE_NORMAL, gfx::Rect(5, 5, 100, 50), NULL));
  ParentWindowInPrimaryRootWindow(w1.get());

  ShelfLayoutManager* shelf = shelf_layout_manager();
  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_NEVER);

  // Drag near the shelf.
  aura::test::EventGenerator generator(
      Shell::GetPrimaryRootWindow(), gfx::Point());
  generator.MoveMouseTo(10, 10);
  generator.PressLeftButton();
  generator.MoveMouseTo(100, shelf->GetIdealBounds().y() - 70);

  // Shelf should not be in overlapped state.
  EXPECT_FALSE(GetWindowOverlapsShelf());

  generator.MoveMouseTo(100, shelf->GetIdealBounds().y() - 20);

  // Shelf should detect overlap. Overlap state stays after mouse is released.
  EXPECT_TRUE(GetWindowOverlapsShelf());
  generator.ReleaseLeftButton();
  EXPECT_TRUE(GetWindowOverlapsShelf());
}

// Verifies that when dragging a window autohidden shelf stays hidden during
// and after the drag.
TEST_F(WorkspaceControllerTestDragging, DragWindowKeepsShelfAutohidden) {
  aura::test::TestWindowDelegate delegate;
  delegate.set_window_component(HTCAPTION);
  scoped_ptr<Window> w1(aura::test::CreateTestWindowWithDelegate(
      &delegate, ui::wm::WINDOW_TYPE_NORMAL, gfx::Rect(5, 5, 100, 50), NULL));
  ParentWindowInPrimaryRootWindow(w1.get());

  ShelfLayoutManager* shelf = shelf_layout_manager();
  shelf->SetAutoHideBehavior(SHELF_AUTO_HIDE_BEHAVIOR_ALWAYS);
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());

  // Drag very little.
  aura::test::EventGenerator generator(
      Shell::GetPrimaryRootWindow(), gfx::Point());
  generator.MoveMouseTo(10, 10);
  generator.PressLeftButton();
  generator.MoveMouseTo(12, 12);

  // Shelf should be hidden during and after the drag.
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
  generator.ReleaseLeftButton();
  EXPECT_EQ(SHELF_AUTO_HIDE_HIDDEN, shelf->auto_hide_state());
}

// Verifies that events are targeted properly just outside the window edges.
TEST_F(WorkspaceControllerTest, WindowEdgeHitTest) {
  aura::test::TestWindowDelegate d_first, d_second;
  scoped_ptr<Window> first(aura::test::CreateTestWindowWithDelegate(&d_first,
      123, gfx::Rect(20, 10, 100, 50), NULL));
  ParentWindowInPrimaryRootWindow(first.get());
  first->Show();

  scoped_ptr<Window> second(aura::test::CreateTestWindowWithDelegate(&d_second,
      234, gfx::Rect(30, 40, 40, 10), NULL));
  ParentWindowInPrimaryRootWindow(second.get());
  second->Show();

  ui::EventTarget* root = first->GetRootWindow();
  ui::EventTargeter* targeter = root->GetEventTargeter();

  // The windows overlap, and |second| is on top of |first|. Events targeted
  // slightly outside the edges of the |second| window should still be targeted
  // to |second| to allow resizing the windows easily.

  const int kNumPoints = 4;
  struct {
    const char* direction;
    gfx::Point location;
  } points[kNumPoints] = {
    { "left", gfx::Point(28, 45) },  // outside the left edge.
    { "top", gfx::Point(50, 38) },  // outside the top edge.
    { "right", gfx::Point(72, 45) },  // outside the right edge.
    { "bottom", gfx::Point(50, 52) },  // outside the bottom edge.
  };
  // Do two iterations, first without any transform on |second|, and the second
  // time after applying some transform on |second| so that it doesn't get
  // targeted.
  for (int times = 0; times < 2; ++times) {
    SCOPED_TRACE(times == 0 ? "Without transform" : "With transform");
    aura::Window* expected_target = times == 0 ? second.get() : first.get();
    for (int i = 0; i < kNumPoints; ++i) {
      SCOPED_TRACE(points[i].direction);
      const gfx::Point& location = points[i].location;
      ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, location, location, ui::EF_NONE,
                           ui::EF_NONE);
      ui::EventTarget* target = targeter->FindTargetForEvent(root, &mouse);
      EXPECT_EQ(expected_target, target);

      ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, location, 0,
                           ui::EventTimeForNow());
      target = targeter->FindTargetForEvent(root, &touch);
      EXPECT_EQ(expected_target, target);
    }
    // Apply a transform on |second|. After the transform is applied, the window
    // should no longer be targeted.
    gfx::Transform transform;
    transform.Translate(70, 40);
    second->SetTransform(transform);
  }
}

// Verifies mouse event targeting just outside the window edges for panels.
TEST_F(WorkspaceControllerTest, WindowEdgeMouseHitTestPanel) {
  aura::test::TestWindowDelegate delegate;
  scoped_ptr<Window> window(CreateTestPanel(&delegate,
                                           gfx::Rect(20, 10, 100, 50)));
  ui::EventTarget* root = window->GetRootWindow();
  ui::EventTargeter* targeter = root->GetEventTargeter();
  const gfx::Rect bounds = window->bounds();
  const int kNumPoints = 5;
  struct {
    const char* direction;
    gfx::Point location;
    bool is_target_hit;
  } points[kNumPoints] = {
    { "left", gfx::Point(bounds.x() - 2, bounds.y() + 10), true },
    { "top", gfx::Point(bounds.x() + 10, bounds.y() - 2), true },
    { "right", gfx::Point(bounds.right() + 2, bounds.y() + 10), true },
    { "bottom", gfx::Point(bounds.x() + 10, bounds.bottom() + 2), true },
    { "outside", gfx::Point(bounds.x() + 10, bounds.y() - 31), false },
  };
  for (int i = 0; i < kNumPoints; ++i) {
    SCOPED_TRACE(points[i].direction);
    const gfx::Point& location = points[i].location;
    ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, location, location, ui::EF_NONE,
                         ui::EF_NONE);
    ui::EventTarget* target = targeter->FindTargetForEvent(root, &mouse);
    if (points[i].is_target_hit)
      EXPECT_EQ(window.get(), target);
    else
      EXPECT_NE(window.get(), target);
  }
}

// Verifies touch event targeting just outside the window edges for panels.
// The shelf is aligned to the bottom by default, and so touches just below
// the bottom edge of the panel should not target the panel itself because
// an AttachedPanelWindowTargeter is installed on the panel container.
TEST_F(WorkspaceControllerTest, WindowEdgeTouchHitTestPanel) {
  aura::test::TestWindowDelegate delegate;
  scoped_ptr<Window> window(CreateTestPanel(&delegate,
                                            gfx::Rect(20, 10, 100, 50)));
  ui::EventTarget* root = window->GetRootWindow();
  ui::EventTargeter* targeter = root->GetEventTargeter();
  const gfx::Rect bounds = window->bounds();
  const int kNumPoints = 5;
  struct {
    const char* direction;
    gfx::Point location;
    bool is_target_hit;
  } points[kNumPoints] = {
    { "left", gfx::Point(bounds.x() - 2, bounds.y() + 10), true },
    { "top", gfx::Point(bounds.x() + 10, bounds.y() - 2), true },
    { "right", gfx::Point(bounds.right() + 2, bounds.y() + 10), true },
    { "bottom", gfx::Point(bounds.x() + 10, bounds.bottom() + 2), false },
    { "outside", gfx::Point(bounds.x() + 10, bounds.y() - 31), false },
  };
  for (int i = 0; i < kNumPoints; ++i) {
    SCOPED_TRACE(points[i].direction);
    const gfx::Point& location = points[i].location;
    ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, location, 0,
                         ui::EventTimeForNow());
    ui::EventTarget* target = targeter->FindTargetForEvent(root, &touch);
    if (points[i].is_target_hit)
      EXPECT_EQ(window.get(), target);
    else
      EXPECT_NE(window.get(), target);
  }
}

// Verifies events targeting just outside the window edges for docked windows.
TEST_F(WorkspaceControllerTest, WindowEdgeHitTestDocked) {
  aura::test::TestWindowDelegate delegate;
  // Make window smaller than the minimum docked area so that the window edges
  // are exposed.
  delegate.set_maximum_size(gfx::Size(180, 200));
  scoped_ptr<Window> window(aura::test::CreateTestWindowWithDelegate(&delegate,
      123, gfx::Rect(20, 10, 100, 50), NULL));
  ParentWindowInPrimaryRootWindow(window.get());
  aura::Window* docked_container = Shell::GetContainer(
      window->GetRootWindow(), kShellWindowId_DockedContainer);
  docked_container->AddChild(window.get());
  window->Show();
  ui::EventTarget* root = window->GetRootWindow();
  ui::EventTargeter* targeter = root->GetEventTargeter();
  const gfx::Rect bounds = window->bounds();
  const int kNumPoints = 5;
  struct {
    const char* direction;
    gfx::Point location;
    bool is_target_hit;
  } points[kNumPoints] = {
    { "left", gfx::Point(bounds.x() - 2, bounds.y() + 10), true },
    { "top", gfx::Point(bounds.x() + 10, bounds.y() - 2), true },
    { "right", gfx::Point(bounds.right() + 2, bounds.y() + 10), true },
    { "bottom", gfx::Point(bounds.x() + 10, bounds.bottom() + 2), true },
    { "outside", gfx::Point(bounds.x() + 10, bounds.y() - 31), false },
  };
  for (int i = 0; i < kNumPoints; ++i) {
    SCOPED_TRACE(points[i].direction);
    const gfx::Point& location = points[i].location;
    ui::MouseEvent mouse(ui::ET_MOUSE_MOVED, location, location, ui::EF_NONE,
                         ui::EF_NONE);
    ui::EventTarget* target = targeter->FindTargetForEvent(root, &mouse);
    if (points[i].is_target_hit)
      EXPECT_EQ(window.get(), target);
    else
      EXPECT_NE(window.get(), target);

    ui::TouchEvent touch(ui::ET_TOUCH_PRESSED, location, 0,
                         ui::EventTimeForNow());
    target = targeter->FindTargetForEvent(root, &touch);
    if (points[i].is_target_hit)
      EXPECT_EQ(window.get(), target);
    else
      EXPECT_NE(window.get(), target);
  }
}

}  // namespace ash