Virtual Desks 1: Initial scaffolding

This CL adds the very basic scaffolding which will be
used in subsequent CLs to build the virtual desks feature.

Added in this CL:
- The VirtualDesks base::Feature.
- Bar that will later contain desks' thumbnails.
- The "New desk" button, which currently does nothing.
- Moves gesture taps and mouse release overview handling from
  a pre-target handler in WallpaperView to the ShieldView.

Demo: https://bugs.chromium.org/p/chromium/issues/detail?id=866622#c2

BUG=866622

Change-Id: Ie09e7a5b11aaae82fcaf592c009d54ec7245fe10
Reviewed-on: https://chromium-review.googlesource.com/c/1461821
Reviewed-by: Mitsuru Oshima <oshima@chromium.org>
Reviewed-by: Sammie Quon <sammiequon@chromium.org>
Commit-Queue: Ahmed Fakhry <afakhry@chromium.org>
Cr-Commit-Position: refs/heads/master@{#631513}
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 522fef8..09b425c 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -1024,6 +1024,12 @@
     "wm/default_state.h",
     "wm/default_window_resizer.cc",
     "wm/default_window_resizer.h",
+    "wm/desks/desks_bar_view.cc",
+    "wm/desks/desks_bar_view.h",
+    "wm/desks/desks_controller.cc",
+    "wm/desks/desks_controller.h",
+    "wm/desks/new_desk_button.cc",
+    "wm/desks/new_desk_button.h",
     "wm/drag_details.cc",
     "wm/drag_details.h",
     "wm/drag_window_controller.cc",
diff --git a/ash/ash_strings.grd b/ash/ash_strings.grd
index 12c3e4d..605f2e9 100644
--- a/ash/ash_strings.grd
+++ b/ash/ash_strings.grd
@@ -689,6 +689,11 @@
         No recent items
       </message>
 
+      <!-- Virtual Desks -->
+      <message name="IDS_ASH_DESKS_NEW_DESK_BUTTON" desc="The label of the new virtual desk (a.k.a. workspaces) button.">
+        New Desk
+      </message>
+
       <!-- Status tray charging strings. -->
       <message name="IDS_ASH_STATUS_TRAY_LOW_POWER_CHARGER_TITLE" desc="The title of a notification indicating that a low-current USB charger has been connected.">
         Low-power charger connected
diff --git a/ash/ash_strings_grd/IDS_ASH_DESKS_NEW_DESK_BUTTON.png.sha1 b/ash/ash_strings_grd/IDS_ASH_DESKS_NEW_DESK_BUTTON.png.sha1
new file mode 100644
index 0000000..030473f
--- /dev/null
+++ b/ash/ash_strings_grd/IDS_ASH_DESKS_NEW_DESK_BUTTON.png.sha1
@@ -0,0 +1 @@
+8254d07d8a4676a52a79a98e0287ea5f80843262
\ No newline at end of file
diff --git a/ash/public/cpp/ash_features.cc b/ash/public/cpp/ash_features.cc
index 809caaa..168b592 100644
--- a/ash/public/cpp/ash_features.cc
+++ b/ash/public/cpp/ash_features.cc
@@ -57,6 +57,9 @@
 
 const base::Feature kViewsLogin{"ViewsLogin", base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kVirtualDesks{"VirtualDesks",
+                                  base::FEATURE_DISABLED_BY_DEFAULT};
+
 const base::Feature kUseBluetoothSystemInAsh{"UseBluetoothSystemInAsh",
                                              base::FEATURE_DISABLED_BY_DEFAULT};
 
@@ -98,6 +101,10 @@
   return use_trilinear_filtering;
 }
 
+bool IsVirtualDesksEnabled() {
+  return base::FeatureList::IsEnabled(kVirtualDesks);
+}
+
 bool IsViewsLoginEnabled() {
   // Always show webui login if --show-webui-login is present, which is passed
   // by session manager for automatic recovery. Otherwise, only show views
diff --git a/ash/public/cpp/ash_features.h b/ash/public/cpp/ash_features.h
index fbedd75..ef3b438 100644
--- a/ash/public/cpp/ash_features.h
+++ b/ash/public/cpp/ash_features.h
@@ -71,6 +71,9 @@
 // Enables views login.
 ASH_PUBLIC_EXPORT extern const base::Feature kViewsLogin;
 
+// Enables the Virtual Desks feature.
+ASH_PUBLIC_EXPORT extern const base::Feature kVirtualDesks;
+
 // Enables using the BluetoothSystem Mojo interface for Bluetooth operations.
 ASH_PUBLIC_EXPORT extern const base::Feature kUseBluetoothSystemInAsh;
 
@@ -97,6 +100,8 @@
 
 ASH_PUBLIC_EXPORT bool IsViewsLoginEnabled();
 
+ASH_PUBLIC_EXPORT bool IsVirtualDesksEnabled();
+
 ASH_PUBLIC_EXPORT bool IsSupervisedUserDeprecationNoticeEnabled();
 
 }  // namespace features
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index c81cda8..e55eb8d2 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -54,6 +54,7 @@
     "network_ethernet.icon",
     "network_mobile_not_connected_x.icon",
     "network_vpn.icon",
+    "new_desk_button.icon",
     "notification_accessibility.icon",
     "notification_accessibility_braille.icon",
     "notification_battery_critical.icon",
diff --git a/ash/resources/vector_icons/new_desk_button.icon b/ash/resources/vector_icons/new_desk_button.icon
new file mode 100644
index 0000000..bc219a43
--- /dev/null
+++ b/ash/resources/vector_icons/new_desk_button.icon
@@ -0,0 +1,18 @@
+// Copyright 2019 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.
+
+CANVAS_DIMENSIONS, 12,
+MOVE_TO, 7, 5,
+R_H_LINE_TO, 5,
+R_V_LINE_TO, 2,
+H_LINE_TO, 7,
+R_V_LINE_TO, 5,
+H_LINE_TO, 5,
+V_LINE_TO, 7,
+H_LINE_TO, 0,
+V_LINE_TO, 5,
+R_H_LINE_TO, 5,
+V_LINE_TO, 0,
+R_H_LINE_TO, 2,
+CLOSE
diff --git a/ash/shell.cc b/ash/shell.cc
index 693a8736..7c61054 100644
--- a/ash/shell.cc
+++ b/ash/shell.cc
@@ -131,6 +131,7 @@
 #include "ash/wm/ash_focus_rules.h"
 #include "ash/wm/container_finder.h"
 #include "ash/wm/cursor_manager_chromeos.h"
+#include "ash/wm/desks/desks_controller.h"
 #include "ash/wm/event_client_impl.h"
 #include "ash/wm/immersive_context_ash.h"
 #include "ash/wm/lock_state_controller.h"
@@ -790,6 +791,8 @@
   // |overview_controller_|.
   split_view_controller_.reset();
 
+  desks_controller_.reset();
+
   // Stop dispatching events (e.g. synthesized mouse exits from window close).
   // https://crbug.com/874156
   for (RootWindowController* rwc : GetAllRootWindowControllers())
@@ -1226,6 +1229,9 @@
 
   split_view_controller_.reset(new SplitViewController());
 
+  if (features::IsVirtualDesksEnabled())
+    desks_controller_ = std::make_unique<DesksController>();
+
   key_accessibility_enabler_ = std::make_unique<KeyAccessibilityEnabler>();
 
   // The compositor thread and main message loop have to be running in
diff --git a/ash/shell.h b/ash/shell.h
index 53ab4a3f..5995f35a 100644
--- a/ash/shell.h
+++ b/ash/shell.h
@@ -109,6 +109,7 @@
 class DisplayOutputProtection;
 class ContainedShellController;
 class CrosDisplayConfig;
+class DesksController;
 class DetachableBaseHandler;
 class DetachableBaseNotificationController;
 class DisplayColorManager;
@@ -370,6 +371,7 @@
     return cros_display_config_.get();
   }
   ::wm::CursorManager* cursor_manager() { return cursor_manager_.get(); }
+  DesksController* desks_controller() { return desks_controller_.get(); }
   DetachableBaseHandler* detachable_base_handler() {
     return detachable_base_handler_.get();
   }
@@ -713,6 +715,7 @@
   std::unique_ptr<CrosDisplayConfig> cros_display_config_;
   service_manager::Connector* const connector_;
   std::unique_ptr<ContainedShellController> contained_shell_controller_;
+  std::unique_ptr<DesksController> desks_controller_;
   std::unique_ptr<DetachableBaseHandler> detachable_base_handler_;
   std::unique_ptr<DetachableBaseNotificationController>
       detachable_base_notification_controller_;
diff --git a/ash/wallpaper/wallpaper_view.cc b/ash/wallpaper/wallpaper_view.cc
index 684b8d2..d40ed09 100644
--- a/ash/wallpaper/wallpaper_view.cc
+++ b/ash/wallpaper/wallpaper_view.cc
@@ -12,8 +12,6 @@
 #include "ash/shell.h"
 #include "ash/wallpaper/wallpaper_controller.h"
 #include "ash/wallpaper/wallpaper_widget_controller.h"
-#include "ash/wm/overview/overview_controller.h"
-#include "ash/wm/overview/overview_utils.h"
 #include "ui/aura/window.h"
 #include "ui/display/display.h"
 #include "ui/display/manager/display_manager.h"
@@ -95,58 +93,18 @@
 
 }  // namespace
 
-// This event handler receives events in the pre-target phase and takes care of
-// the following:
-//   - Disabling overview mode on touch release.
-//   - Disabling overview mode on mouse release.
-class PreEventDispatchHandler : public ui::EventHandler {
- public:
-  PreEventDispatchHandler() = default;
-  ~PreEventDispatchHandler() override = default;
-
- private:
-  // ui::EventHandler:
-  void OnMouseEvent(ui::MouseEvent* event) override {
-    if (event->type() == ui::ET_MOUSE_RELEASED)
-      HandleClickOrTap(event);
-  }
-
-  void OnGestureEvent(ui::GestureEvent* event) override {
-    if (event->type() == ui::ET_GESTURE_TAP)
-      HandleClickOrTap(event);
-  }
-
-  void HandleClickOrTap(ui::Event* event) {
-    CHECK_EQ(ui::EP_PRETARGET, event->phase());
-    OverviewController* controller = Shell::Get()->overview_controller();
-    if (!controller->IsSelecting())
-      return;
-    // Events that happen while app list is sliding out during overview should
-    // be ignored to prevent overview from disappearing out from under the user.
-    if (!IsSlidingOutOverviewFromShelf())
-      controller->ToggleOverview();
-    event->StopPropagation();
-  }
-
-  DISALLOW_COPY_AND_ASSIGN(PreEventDispatchHandler);
-};
-
 ////////////////////////////////////////////////////////////////////////////////
 // WallpaperView, public:
 
-WallpaperView::WallpaperView()
-    : pre_dispatch_handler_(new PreEventDispatchHandler()) {
+WallpaperView::WallpaperView() {
   set_context_menu_controller(this);
-  AddPreTargetHandler(pre_dispatch_handler_.get());
   tablet_mode_observer_.Add(Shell::Get()->tablet_mode_controller());
   is_tablet_mode_ = Shell::Get()
                         ->tablet_mode_controller()
                         ->IsTabletModeWindowManagerEnabled();
 }
 
-WallpaperView::~WallpaperView() {
-  RemovePreTargetHandler(pre_dispatch_handler_.get());
-}
+WallpaperView::~WallpaperView() = default;
 
 void WallpaperView::OnTabletModeStarted() {
   is_tablet_mode_ = true;
diff --git a/ash/wallpaper/wallpaper_view.h b/ash/wallpaper/wallpaper_view.h
index 17ee2210..52abdf19 100644
--- a/ash/wallpaper/wallpaper_view.h
+++ b/ash/wallpaper/wallpaper_view.h
@@ -19,8 +19,6 @@
 
 namespace ash {
 
-class PreEventDispatchHandler;
-
 class WallpaperView : public views::View,
                       public views::ContextMenuController,
                       TabletModeObserver {
@@ -49,8 +47,6 @@
       tablet_mode_observer_{this};
   bool is_tablet_mode_ = false;
 
-  std::unique_ptr<PreEventDispatchHandler> pre_dispatch_handler_;
-
   DISALLOW_COPY_AND_ASSIGN(WallpaperView);
 };
 
diff --git a/ash/wm/desks/desks_bar_view.cc b/ash/wm/desks/desks_bar_view.cc
new file mode 100644
index 0000000..88ce533
--- /dev/null
+++ b/ash/wm/desks/desks_bar_view.cc
@@ -0,0 +1,62 @@
+// Copyright 2019 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/desks/desks_bar_view.h"
+
+#include "ash/wm/desks/desks_controller.h"
+#include "ash/wm/desks/new_desk_button.h"
+#include "third_party/skia/include/core/SkColor.h"
+#include "ui/gfx/geometry/insets.h"
+
+namespace ash {
+
+DesksBarView::DesksBarView() : new_desk_button_(new NewDeskButton(this)) {
+  SetPaintToLayer(ui::LAYER_SOLID_COLOR);
+  layer()->SetFillsBoundsOpaquely(false);
+  layer()->SetColor(SkColorSetARGB(60, 0, 0, 0));
+
+  AddChildView(new_desk_button_);
+  new_desk_button_->SetEnabled(DesksController::Get()->CanCreateDesks());
+}
+
+// static
+int DesksBarView::GetBarHeight() {
+  // TODO(afakhry): Bar expands when we add the second desk.
+  return 48;
+}
+
+const char* DesksBarView::GetClassName() const {
+  return "DesksBarView";
+}
+
+void DesksBarView::Layout() {
+  constexpr int kButtonRightMargin = 36;
+  constexpr int kIconAndTextHorizontalPadding = 16;
+  constexpr int kIconAndTextVerticalPadding = 8;
+
+  gfx::Size new_desk_button_size = new_desk_button_->GetPreferredSize();
+  new_desk_button_size.Enlarge(2 * kIconAndTextHorizontalPadding,
+                               2 * kIconAndTextVerticalPadding);
+
+  const gfx::Rect button_bounds{
+      bounds().right() - new_desk_button_size.width() - kButtonRightMargin,
+      (bounds().height() - new_desk_button_size.height()) / 2,
+      new_desk_button_size.width(), new_desk_button_size.height()};
+  new_desk_button_->SetBoundsRect(button_bounds);
+
+  // TODO(afakhry): Layout thumbnails.
+}
+
+void DesksBarView::ButtonPressed(views::Button* sender,
+                                 const ui::Event& event) {
+  auto* controller = DesksController::Get();
+  if (sender == new_desk_button_ && controller->CanCreateDesks()) {
+    controller->NewDesk();
+    return;
+  }
+
+  // TODO(afakhry): Handle thumbnail presses.
+}
+
+}  // namespace ash
diff --git a/ash/wm/desks/desks_bar_view.h b/ash/wm/desks/desks_bar_view.h
new file mode 100644
index 0000000..77594a37
--- /dev/null
+++ b/ash/wm/desks/desks_bar_view.h
@@ -0,0 +1,39 @@
+// Copyright 2019 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.
+
+#ifndef ASH_WM_DESKS_DESKS_BAR_VIEW_H_
+#define ASH_WM_DESKS_DESKS_BAR_VIEW_H_
+
+#include "base/macros.h"
+#include "ui/views/controls/button/button.h"
+
+namespace ash {
+
+class NewDeskButton;
+
+// A bar that resides at the top portion of the overview mode's ShieldView,
+// which contains the virtual desks thumbnails, as well as the new desk button.
+class DesksBarView : public views::View, public views::ButtonListener {
+ public:
+  DesksBarView();
+  ~DesksBarView() override = default;
+
+  static int GetBarHeight();
+
+  // views::View:
+  const char* GetClassName() const override;
+  void Layout() override;
+
+  // views::ButtonListener:
+  void ButtonPressed(views::Button* sender, const ui::Event& event) override;
+
+ private:
+  NewDeskButton* new_desk_button_;
+
+  DISALLOW_COPY_AND_ASSIGN(DesksBarView);
+};
+
+}  // namespace ash
+
+#endif  // ASH_WM_DESKS_DESKS_BAR_VIEW_H_
diff --git a/ash/wm/desks/desks_controller.cc b/ash/wm/desks/desks_controller.cc
new file mode 100644
index 0000000..4be7a05
--- /dev/null
+++ b/ash/wm/desks/desks_controller.cc
@@ -0,0 +1,26 @@
+// Copyright 2019 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/desks/desks_controller.h"
+
+#include "ash/shell.h"
+#include "base/logging.h"
+
+namespace ash {
+
+// static
+DesksController* DesksController::Get() {
+  return Shell::Get()->desks_controller();
+}
+
+bool DesksController::CanCreateDesks() const {
+  // TODO(afakhry): Maximum of four desks.
+  return true;
+}
+
+void DesksController::NewDesk() {
+  NOTIMPLEMENTED();
+}
+
+}  // namespace ash
diff --git a/ash/wm/desks/desks_controller.h b/ash/wm/desks/desks_controller.h
new file mode 100644
index 0000000..6d69c98
--- /dev/null
+++ b/ash/wm/desks/desks_controller.h
@@ -0,0 +1,34 @@
+// Copyright 2019 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.
+
+#ifndef ASH_WM_DESKS_DESKS_CONTROLLER_H_
+#define ASH_WM_DESKS_DESKS_CONTROLLER_H_
+
+#include "base/macros.h"
+
+namespace ash {
+
+// Defines a controller for creating, destroying and managing virtual desks and
+// their windows.
+class DesksController {
+ public:
+  DesksController() = default;
+  ~DesksController() = default;
+
+  // Convenience method for returning the DesksController instance. The actual
+  // instance is created and owned by Shell.
+  static DesksController* Get();
+
+  // Returns true if we haven't reached the maximum allowed number of desks.
+  bool CanCreateDesks() const;
+
+  void NewDesk();
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DesksController);
+};
+
+}  // namespace ash
+
+#endif  // ASH_WM_DESKS_DESKS_CONTROLLER_H_
diff --git a/ash/wm/desks/new_desk_button.cc b/ash/wm/desks/new_desk_button.cc
new file mode 100644
index 0000000..9c72659
--- /dev/null
+++ b/ash/wm/desks/new_desk_button.cc
@@ -0,0 +1,95 @@
+// Copyright 2019 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/desks/new_desk_button.h"
+
+#include <utility>
+
+#include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/strings/grit/ash_strings.h"
+#include "base/strings/utf_string_conversions.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/animation/ink_drop_impl.h"
+#include "ui/views/animation/ink_drop_mask.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/button/label_button_border.h"
+#include "ui/views/style/platform_style.h"
+
+namespace ash {
+
+namespace {
+
+constexpr int kCornerRadius = 16;
+
+constexpr int kImageLabelSpacing = 8;
+
+constexpr float kInkDropVisibleOpacity = 0.2f;
+
+constexpr float kInkDropHighlightVisibleOpacity = 0.3f;
+
+constexpr SkColor kHighlightBackgroundColor = SkColorSetARGB(60, 255, 255, 255);
+
+}  // namespace
+
+NewDeskButton::NewDeskButton(views::ButtonListener* listener)
+    : LabelButton(listener,
+                  l10n_util::GetStringUTF16(IDS_ASH_DESKS_NEW_DESK_BUTTON)) {
+  SetPaintToLayer();
+  layer()->SetFillsBoundsOpaquely(false);
+  SetHorizontalAlignment(gfx::ALIGN_CENTER);
+  SetImage(views::Button::STATE_NORMAL,
+           gfx::CreateVectorIcon(kNewDeskButtonIcon, SK_ColorWHITE));
+  SetImage(views::Button::STATE_DISABLED,
+           gfx::CreateVectorIcon(kNewDeskButtonIcon, SK_ColorGRAY));
+  SetTextColor(views::Button::STATE_NORMAL, SK_ColorWHITE);
+  SetTextColor(views::Button::STATE_HOVERED, SK_ColorWHITE);
+  SetTextColor(views::Button::STATE_PRESSED, SK_ColorWHITE);
+  SetTextColor(views::Button::STATE_DISABLED, SK_ColorGRAY);
+  SetImageLabelSpacing(kImageLabelSpacing);
+  SetInkDropMode(InkDropMode::ON);
+  set_has_ink_drop_action_on_click(true);
+  set_ink_drop_visible_opacity(kInkDropVisibleOpacity);
+  SetFocusPainter(nullptr);
+  SetBackground(
+      CreateBackgroundFromPainter(views::Painter::CreateSolidRoundRectPainter(
+          kHighlightBackgroundColor, kCornerRadius)));
+}
+
+const char* NewDeskButton::GetClassName() const {
+  return "NewDeskButton";
+}
+
+std::unique_ptr<views::InkDrop> NewDeskButton::CreateInkDrop() {
+  auto ink_drop = CreateDefaultFloodFillInkDropImpl();
+  ink_drop->SetShowHighlightOnHover(true);
+  ink_drop->SetShowHighlightOnFocus(!views::PlatformStyle::kPreferFocusRings);
+  return std::move(ink_drop);
+}
+
+std::unique_ptr<views::InkDropHighlight> NewDeskButton::CreateInkDropHighlight()
+    const {
+  auto highlight = LabelButton::CreateInkDropHighlight();
+  highlight->set_visible_opacity(kInkDropHighlightVisibleOpacity);
+  return highlight;
+}
+
+SkColor NewDeskButton::GetInkDropBaseColor() const {
+  return SK_ColorWHITE;
+}
+
+std::unique_ptr<views::InkDropMask> NewDeskButton::CreateInkDropMask() const {
+  return std::make_unique<views::RoundRectInkDropMask>(size(), gfx::Insets(),
+                                                       kCornerRadius);
+}
+
+std::unique_ptr<views::LabelButtonBorder> NewDeskButton::CreateDefaultBorder()
+    const {
+  std::unique_ptr<views::LabelButtonBorder> border =
+      std::make_unique<views::LabelButtonBorder>();
+  return border;
+}
+
+}  // namespace ash
diff --git a/ash/wm/desks/new_desk_button.h b/ash/wm/desks/new_desk_button.h
new file mode 100644
index 0000000..696ca40
--- /dev/null
+++ b/ash/wm/desks/new_desk_button.h
@@ -0,0 +1,38 @@
+// Copyright 2019 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.
+
+#ifndef ASH_WM_DESKS_NEW_DESK_BUTTON_H_
+#define ASH_WM_DESKS_NEW_DESK_BUTTON_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "ui/views/controls/button/label_button.h"
+
+namespace ash {
+
+// A button view that shows up in the top-right corner of the screen when
+// overview mode is on, which is used to create a new virtual desk.
+class NewDeskButton : public views::LabelButton {
+ public:
+  NewDeskButton(views::ButtonListener* listener);
+  ~NewDeskButton() override = default;
+
+  // LabelButton:
+  const char* GetClassName() const override;
+  std::unique_ptr<views::InkDrop> CreateInkDrop() override;
+  std::unique_ptr<views::InkDropHighlight> CreateInkDropHighlight()
+      const override;
+  SkColor GetInkDropBaseColor() const override;
+  std::unique_ptr<views::InkDropMask> CreateInkDropMask() const override;
+  std::unique_ptr<views::LabelButtonBorder> CreateDefaultBorder()
+      const override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NewDeskButton);
+};
+
+}  // namespace ash
+
+#endif  // ASH_WM_DESKS_NEW_DESK_BUTTON_H_
diff --git a/ash/wm/overview/overview_grid.cc b/ash/wm/overview/overview_grid.cc
index 925df668..18bc21e 100644
--- a/ash/wm/overview/overview_grid.cc
+++ b/ash/wm/overview/overview_grid.cc
@@ -8,6 +8,7 @@
 #include <functional>
 #include <utility>
 
+#include "ash/public/cpp/ash_features.h"
 #include "ash/public/cpp/shelf_types.h"
 #include "ash/public/cpp/shell_window_ids.h"
 #include "ash/public/cpp/wallpaper_types.h"
@@ -22,6 +23,7 @@
 #include "ash/strings/grit/ash_strings.h"
 #include "ash/wallpaper/wallpaper_controller.h"
 #include "ash/wallpaper/wallpaper_widget_controller.h"
+#include "ash/wm/desks/desks_bar_view.h"
 #include "ash/wm/overview/cleanup_animation_observer.h"
 #include "ash/wm/overview/drop_target_view.h"
 #include "ash/wm/overview/overview_constants.h"
@@ -187,6 +189,9 @@
 
 // ShieldView contains the background for overview mode. It also contains text
 // which is shown if there are no windows to be displayed.
+// This view also takes care of disabling overview mode on:
+//   - Gesture tap.
+//   - Mouse release.
 class OverviewGrid::ShieldView : public views::View {
  public:
   ShieldView() {
@@ -223,6 +228,11 @@
 
     AddChildView(background_view_);
     AddChildView(label_container_);
+
+    if (features::IsVirtualDesksEnabled()) {
+      desks_bar_view_ = new DesksBarView;
+      AddChildView(desks_bar_view_);
+    }
   }
 
   ~ShieldView() override = default;
@@ -249,19 +259,70 @@
     label_container_bounds.ClampToCenteredSize(
         gfx::Size(label_width, kNoItemsIndicatorHeightDp));
     label_container_->SetBoundsRect(label_container_bounds);
+
+    UpdateDesksBarBounds();
   }
 
   bool IsLabelVisible() const { return label_container_->visible(); }
 
  protected:
   // views::View:
-  void Layout() override { background_view_->SetBoundsRect(GetLocalBounds()); }
+  void Layout() override {
+    background_view_->SetBoundsRect(GetLocalBounds());
+    UpdateDesksBarBounds();
+  }
 
  private:
+  // ui::EventHandler:
+  void OnMouseEvent(ui::MouseEvent* event) override {
+    if (event->type() == ui::ET_MOUSE_PRESSED) {
+      // In order to receive subsequent mouse release events in this view, we
+      // must mark the event as handled in this view.
+      event->SetHandled();
+      return;
+    }
+
+    HandleClickReleaseOrTap(event);
+  }
+
+  void OnGestureEvent(ui::GestureEvent* event) override {
+    HandleClickReleaseOrTap(event);
+  }
+
+  void HandleClickReleaseOrTap(ui::Event* event) {
+    if (event->type() != ui::ET_MOUSE_RELEASED &&
+        event->type() != ui::ET_GESTURE_TAP) {
+      return;
+    }
+
+    OverviewController* controller = Shell::Get()->overview_controller();
+    if (!controller->IsSelecting())
+      return;
+
+    // Events that happen while app list is sliding out during overview should
+    // be ignored to prevent overview from disappearing out from under the user.
+    if (!IsSlidingOutOverviewFromShelf())
+      controller->ToggleOverview();
+
+    event->StopPropagation();
+  }
+
+  void UpdateDesksBarBounds() {
+    if (!desks_bar_view_)
+      return;
+
+    // TODO: Make the ShieldView's bounds match the overview grid bounds rather
+    // than the entire screen?
+    const auto bar_bounds =
+        gfx::Rect{bounds().width(), DesksBarView::GetBarHeight()};
+    desks_bar_view_->SetBoundsRect(bar_bounds);
+  }
+
   // Owned by views heirarchy.
   views::View* background_view_ = nullptr;
   RoundedRectView* label_container_ = nullptr;
   views::Label* label_ = nullptr;
+  DesksBarView* desks_bar_view_ = nullptr;
 
   DISALLOW_COPY_AND_ASSIGN(ShieldView);
 };
@@ -1211,7 +1272,7 @@
   shield_widget_ = CreateBackgroundWidget(
       root_window_, ui::LAYER_NOT_DRAWN, SK_ColorTRANSPARENT, 0, 0,
       SK_ColorTRANSPARENT, initial_opacity, /*parent=*/nullptr,
-      /*stack_on_top=*/true);
+      /*stack_on_top=*/true, /*accept_events=*/true);
   aura::Window* widget_window = shield_widget_->GetNativeWindow();
   aura::Window* parent_window = widget_window->parent();
   const gfx::Rect bounds = ash::screen_util::SnapBoundsToDisplayEdge(
@@ -1221,9 +1282,9 @@
 
   // Create |shield_view_| and animate its background and label if needed.
   shield_view_ = new ShieldView();
+  shield_widget_->SetContentsView(shield_view_);
   shield_view_->SetBackgroundColor(GetShieldColor());
   shield_view_->SetGridBounds(bounds_);
-  shield_widget_->SetContentsView(shield_view_);
 
   if (animate) {
     shield_widget_->SetOpacity(initial_opacity);
@@ -1239,7 +1300,7 @@
   selection_widget_ = CreateBackgroundWidget(
       root_window_, ui::LAYER_TEXTURED, kWindowSelectionColor, 0,
       kWindowSelectionRadius, SK_ColorTRANSPARENT, 0.f, /*parent=*/nullptr,
-      /*stack_on_top=*/true);
+      /*stack_on_top=*/true, /*accept_events=*/false);
   aura::Window* widget_window = selection_widget_->GetNativeWindow();
   gfx::Rect target_bounds = SelectedWindow()->target_bounds();
   ::wm::ConvertRectFromScreen(root_window_, &target_bounds);
@@ -1330,6 +1391,10 @@
 std::vector<gfx::Rect> OverviewGrid::GetWindowRects(
     OverviewItem* ignored_item) {
   gfx::Rect total_bounds = bounds_;
+
+  if (features::IsVirtualDesksEnabled())
+    total_bounds.Inset(0, DesksBarView::GetBarHeight(), 0, 0);
+
   // Windows occupy vertically centered area with additional vertical insets.
   int horizontal_inset =
       gfx::ToFlooredInt(std::min(kOverviewInsetRatio * total_bounds.width(),
diff --git a/ash/wm/overview/overview_item.cc b/ash/wm/overview/overview_item.cc
index ef7863a..c2f6c14 100644
--- a/ash/wm/overview/overview_item.cc
+++ b/ash/wm/overview/overview_item.cc
@@ -69,7 +69,7 @@
       /*root_window=*/nullptr, ui::LAYER_TEXTURED, kBackdropColor,
       /*border_thickness=*/0, kBackdropRoundingDp, kBackdropColor,
       /*initial_opacity=*/1.f, parent,
-      /*stack_on_top=*/false);
+      /*stack_on_top=*/false, /*accept_events=*/false);
   widget->GetNativeWindow()->SetName("OverviewBackdrop");
   return widget;
 }
diff --git a/ash/wm/overview/overview_utils.cc b/ash/wm/overview/overview_utils.cc
index 2f42102..ce0f9d3 100644
--- a/ash/wm/overview/overview_utils.cc
+++ b/ash/wm/overview/overview_utils.cc
@@ -186,7 +186,8 @@
                                                       SkColor border_color,
                                                       float initial_opacity,
                                                       aura::Window* parent,
-                                                      bool stack_on_top) {
+                                                      bool stack_on_top,
+                                                      bool accept_events) {
   std::unique_ptr<views::Widget> widget = std::make_unique<views::Widget>();
   views::Widget::InitParams params;
   params.type = views::Widget::InitParams::TYPE_POPUP;
@@ -194,7 +195,7 @@
   params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
   params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW;
   params.layer_type = layer_type;
-  params.accept_events = false;
+  params.accept_events = accept_events;
   widget->set_focus_on_creation(false);
   // Parenting in kShellWindowId_WallpaperContainer allows proper layering of
   // the shield and selection widgets. Since that container is created with
diff --git a/ash/wm/overview/overview_utils.h b/ash/wm/overview/overview_utils.h
index a93f7c1a..cfdc97a0 100644
--- a/ash/wm/overview/overview_utils.h
+++ b/ash/wm/overview/overview_utils.h
@@ -50,7 +50,8 @@
 // The new background widget starts with |initial_opacity| and then fades in.
 // If |parent| is prvoided the return widget will be parented to that window,
 // otherwise its parent will be in kShellWindowId_WallpaperContainer of
-// |root_window|.
+// |root_window|. |accept_events| is true if the newly-created widget should
+// handle events.
 std::unique_ptr<views::Widget> CreateBackgroundWidget(aura::Window* root_window,
                                                       ui::LayerType layer_type,
                                                       SkColor background_color,
@@ -59,7 +60,8 @@
                                                       SkColor border_color,
                                                       float initial_opacity,
                                                       aura::Window* parent,
-                                                      bool stack_on_top);
+                                                      bool stack_on_top,
+                                                      bool accept_events);
 
 // Calculates the bounds of the |transformed_window|. Those bounds are a union
 // of all regular (normal and panel) windows in the |transformed_window|'s
diff --git a/ash/wm/tablet_mode/tablet_mode_browser_window_drag_delegate.cc b/ash/wm/tablet_mode/tablet_mode_browser_window_drag_delegate.cc
index add1c61..b51b1f9b 100644
--- a/ash/wm/tablet_mode/tablet_mode_browser_window_drag_delegate.cc
+++ b/ash/wm/tablet_mode/tablet_mode_browser_window_drag_delegate.cc
@@ -158,7 +158,7 @@
     shield_widget_ = CreateBackgroundWidget(
         root_window, ui::LAYER_SOLID_COLOR, SK_ColorTRANSPARENT, 0, 0,
         SK_ColorTRANSPARENT, /*initial_opacity*/ 1.f, /*parent=*/nullptr,
-        /*stack_on_top=*/true);
+        /*stack_on_top=*/true, /*accept_events=*/false);
     aura::Window* widget_window = shield_widget_->GetNativeWindow();
     const gfx::Rect bounds = widget_window->parent()->bounds();
     widget_window->SetBounds(bounds);