[M87]Multipaste: Add a contextual nudge view

This change adds a nudge view that is triggered to show after copy and
pasting twice. The nudge is only shown for 6 seconds before being
automatically dismissed. The nudge should only be shown 3 times total.

(cherry picked from commit 4262b7b6810f7c703666a71006d531164cdb199f)

Bug: 1105541
Change-Id: Ic31863e1bab23ca50a298e22848611168bd17dca
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2417978
Commit-Queue: Matthew Mourgos <[email protected]>
Reviewed-by: Xiyuan Xia <[email protected]>
Reviewed-by: Alex Newcomer <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#815835}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2472921
Reviewed-by: Matthew Mourgos <[email protected]>
Cr-Commit-Position: refs/branch-heads/4280@{#392}
Cr-Branched-From: ea420fb963f9658c9969b6513c56b8f47efa1a2a-refs/heads/master@{#812852}
diff --git a/ash/BUILD.gn b/ash/BUILD.gn
index 1bd198a..9552865 100644
--- a/ash/BUILD.gn
+++ b/ash/BUILD.gn
@@ -292,6 +292,8 @@
     "clipboard/clipboard_history_resource_manager.h",
     "clipboard/clipboard_history_util.cc",
     "clipboard/clipboard_history_util.h",
+    "clipboard/clipboard_nudge.cc",
+    "clipboard/clipboard_nudge.h",
     "clipboard/clipboard_nudge_constants.h",
     "clipboard/clipboard_nudge_controller.cc",
     "clipboard/clipboard_nudge_controller.h",
diff --git a/ash/clipboard/clipboard_nudge.cc b/ash/clipboard/clipboard_nudge.cc
new file mode 100644
index 0000000..dbd5ffb
--- /dev/null
+++ b/ash/clipboard/clipboard_nudge.cc
@@ -0,0 +1,142 @@
+// Copyright 2020 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/clipboard/clipboard_nudge.h"
+#include "ash/public/cpp/ash_features.h"
+#include "ash/public/cpp/assistant/assistant_state.h"
+#include "ash/public/cpp/shelf_config.h"
+#include "ash/public/cpp/shell_window_ids.h"
+#include "ash/resources/vector_icons/vector_icons.h"
+#include "ash/shell.h"
+#include "ash/style/ash_color_provider.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/chromeos/events/keyboard_layout_util.h"
+#include "ui/gfx/color_palette.h"
+#include "ui/gfx/paint_vector_icon.h"
+#include "ui/views/controls/image_view.h"
+#include "ui/views/controls/label.h"
+#include "ui/wm/core/coordinate_conversion.h"
+
+namespace ash {
+
+namespace {
+
+// The corner radius of the nudge view.
+constexpr int kNudgeCornerRadius = 8;
+
+// The blur radius for the nudge view's background.
+constexpr int kNudgeBlurRadius = 30;
+
+// The size of the clipboard icon.
+constexpr int kClipboardIconSize = 20;
+
+// The size of the keyboard shortcut icon.
+constexpr int kKeyboardShortcutIconSize = 16;
+
+// The minimum width of the label.
+constexpr int kMinLabelWidth = 200;
+
+// The margin between the edge of the screen/shelf and the nudge widget bounds.
+constexpr int kNudgeMargin = 8;
+
+// The spacing between the icon and label in the nudge view.
+constexpr int kIconLabelSpacing = 16;
+
+// The padding which separates the nudge's border with its inner contents.
+constexpr int kNudgePadding = 16;
+
+}  // namespace
+
+class ClipboardNudge::ClipboardNudgeView : public views::View {
+ public:
+  ClipboardNudgeView() {
+    SetPaintToLayer(ui::LAYER_SOLID_COLOR);
+    layer()->SetColor(ShelfConfig::Get()->GetDefaultShelfColor());
+    if (features::IsBackgroundBlurEnabled())
+      layer()->SetBackgroundBlur(kNudgeBlurRadius);
+    layer()->SetRoundedCornerRadius({kNudgeCornerRadius, kNudgeCornerRadius,
+                                     kNudgeCornerRadius, kNudgeCornerRadius});
+
+    SkColor icon_color = AshColorProvider::Get()->GetContentLayerColor(
+        AshColorProvider::ContentLayerType::kIconColorPrimary);
+
+    clipboard_icon_ = AddChildView(std::make_unique<views::ImageView>());
+    clipboard_icon_->SetPaintToLayer();
+    clipboard_icon_->layer()->SetFillsBoundsOpaquely(false);
+    clipboard_icon_->SetBounds(kNudgePadding, kNudgePadding, kClipboardIconSize,
+                               kClipboardIconSize);
+    clipboard_icon_->SetImage(
+        gfx::CreateVectorIcon(kClipboardIcon, icon_color));
+
+    label_ = AddChildView(std::make_unique<views::Label>());
+    label_->SetPaintToLayer();
+    label_->layer()->SetFillsBoundsOpaquely(false);
+    label_->SetMultiLine(true);
+    label_->SetPosition(gfx::Point(
+        kNudgePadding + kClipboardIconSize + kIconLabelSpacing, kNudgePadding));
+    label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+    label_->SetVerticalAlignment(gfx::ALIGN_TOP);
+    label_->SetEnabledColor(AshColorProvider::Get()->GetContentLayerColor(
+        AshColorProvider::ContentLayerType::kTextColorPrimary));
+    label_->SetBackgroundColor(SK_ColorTRANSPARENT);
+
+    // TODO(mmourgos): Create and set text for |label_|.
+    label_->SetSize(gfx::Size(kMinLabelWidth, kKeyboardShortcutIconSize));
+  }
+
+  ~ClipboardNudgeView() override = default;
+
+  views::Label* label_ = nullptr;
+  views::ImageView* clipboard_icon_ = nullptr;
+};
+
+ClipboardNudge::ClipboardNudge() : widget_(std::make_unique<views::Widget>()) {
+  views::Widget::InitParams params(
+      views::Widget::InitParams::TYPE_WINDOW_FRAMELESS);
+  params.z_order = ui::ZOrderLevel::kFloatingWindow;
+  params.activatable = views::Widget::InitParams::ACTIVATABLE_NO;
+  params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
+  params.name = "ClipboardContextualNudge";
+  params.layer_type = ui::LAYER_NOT_DRAWN;
+  params.parent = Shell::GetPrimaryRootWindow()->GetChildById(
+      kShellWindowId_OverlayContainer);
+  widget_->Init(std::move(params));
+
+  nudge_view_ =
+      widget_->SetContentsView(std::make_unique<ClipboardNudgeView>());
+  CalculateAndSetWidgetBounds();
+  widget_->Show();
+}
+
+ClipboardNudge::~ClipboardNudge() = default;
+
+void ClipboardNudge::Close() {
+  widget_->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
+}
+
+void ClipboardNudge::CalculateAndSetWidgetBounds() {
+  aura::Window* root_window = Shell::GetRootWindowForNewWindows();
+  gfx::Rect display_bounds = root_window->bounds();
+  ::wm::ConvertRectToScreen(root_window, &display_bounds);
+  gfx::Rect widget_bounds;
+
+  // Calculate the nudge's size to ensure the label text accurately fits.
+  const int nudge_height =
+      2 * kNudgePadding + nudge_view_->label_->bounds().height();
+  const int nudge_width = 2 * kNudgePadding + kClipboardIconSize +
+                          kIconLabelSpacing +
+                          nudge_view_->label_->bounds().width();
+
+  widget_bounds =
+      gfx::Rect(display_bounds.x() + kNudgeMargin,
+                display_bounds.height() - ShelfConfig::Get()->shelf_size() -
+                    nudge_height - kNudgeMargin,
+                nudge_width, nudge_height);
+  if (base::i18n::IsRTL())
+    widget_bounds.set_x(display_bounds.right() - nudge_width - kNudgeMargin);
+
+  widget_->SetBounds(widget_bounds);
+}
+
+}  // namespace ash
\ No newline at end of file
diff --git a/ash/clipboard/clipboard_nudge.h b/ash/clipboard/clipboard_nudge.h
new file mode 100644
index 0000000..2724461
--- /dev/null
+++ b/ash/clipboard/clipboard_nudge.h
@@ -0,0 +1,37 @@
+// Copyright 2020 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_CLIPBOARD_CLIPBOARD_NUDGE_H_
+#define ASH_CLIPBOARD_CLIPBOARD_NUDGE_H_
+
+#include "ash/ash_export.h"
+#include "ui/views/widget/widget.h"
+
+namespace ash {
+
+// Implements a contextual nudge for multipaste.
+class ASH_EXPORT ClipboardNudge {
+ public:
+  ClipboardNudge();
+  ClipboardNudge(const ClipboardNudge&) = delete;
+  ClipboardNudge& operator=(const ClipboardNudge&) = delete;
+  ~ClipboardNudge();
+
+  void Close();
+
+ private:
+  class ClipboardNudgeView;
+
+  // Calculate and set widget bounds based ona fixed width and a variable
+  // height to correctly fit the text.
+  void CalculateAndSetWidgetBounds();
+
+  std::unique_ptr<views::Widget> widget_;
+
+  ClipboardNudgeView* nudge_view_ = nullptr;  // not_owned
+};
+
+}  // namespace ash
+
+#endif  // ASH_CLIPBOARD_CLIPBOARD_NUDGE_H_
diff --git a/ash/clipboard/clipboard_nudge_constants.h b/ash/clipboard/clipboard_nudge_constants.h
index 90e80ea..c7983f6 100644
--- a/ash/clipboard/clipboard_nudge_constants.h
+++ b/ash/clipboard/clipboard_nudge_constants.h
@@ -13,6 +13,8 @@
 constexpr static base::TimeDelta kMinInterval = base::TimeDelta::FromDays(1);
 constexpr static base::TimeDelta kMaxTimeBetweenPaste =
     base::TimeDelta::FromMinutes(10);
+constexpr static base::TimeDelta kNudgeShowTime =
+    base::TimeDelta::FromSeconds(6);
 
 }  // namespace ash
 
diff --git a/ash/clipboard/clipboard_nudge_controller.cc b/ash/clipboard/clipboard_nudge_controller.cc
index afc8f1d..dd9eb33 100644
--- a/ash/clipboard/clipboard_nudge_controller.cc
+++ b/ash/clipboard/clipboard_nudge_controller.cc
@@ -5,6 +5,7 @@
 #include "ash/clipboard/clipboard_nudge_controller.h"
 
 #include "ash/clipboard/clipboard_history_item.h"
+#include "ash/clipboard/clipboard_nudge.h"
 #include "ash/clipboard/clipboard_nudge_constants.h"
 #include "ash/public/cpp/ash_pref_names.h"
 #include "ash/session/session_controller_impl.h"
@@ -15,6 +16,7 @@
 #include "components/prefs/pref_registry_simple.h"
 #include "components/prefs/pref_service.h"
 #include "components/prefs/scoped_user_pref_update.h"
+#include "ui/base/clipboard/clipboard_monitor.h"
 
 namespace {
 
@@ -30,10 +32,12 @@
     ClipboardHistory* clipboard_history)
     : clipboard_history_(clipboard_history) {
   clipboard_history_->AddObserver(this);
+  ui::ClipboardMonitor::GetInstance()->AddObserver(this);
 }
 
 ClipboardNudgeController::~ClipboardNudgeController() {
   clipboard_history_->RemoveObserver(this);
+  ui::ClipboardMonitor::GetInstance()->RemoveObserver(this);
 }
 
 // static
@@ -80,8 +84,8 @@
       return;
     case ClipboardState::kSecondCopy:
       if (GetTime() - last_paste_timestamp_ < kMaxTimeBetweenPaste) {
-        clipboard_state_ = ClipboardState::kShouldShowNudge;
-        // TODO(crbug/1105541): Show clipboard nudge.
+        ShowNudge();
+        HandleNudgeShown();
       } else {
         // ClipboardState should be reset to kFirstPaste when timed out.
         clipboard_state_ = ClipboardState::kFirstPaste;
@@ -94,6 +98,21 @@
   }
 }
 
+void ClipboardNudgeController::ShowNudge() {
+  // Create and show the nudge.
+  nudge_ = std::make_unique<ClipboardNudge>();
+
+  // Start a timer to close the nudge after a set amount of time.
+  hide_nudge_timer_.Start(FROM_HERE, kNudgeShowTime,
+                          base::BindOnce(&ClipboardNudgeController::HideNudge,
+                                         weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ClipboardNudgeController::HideNudge() {
+  nudge_->Close();
+  nudge_.reset();
+}
+
 void ClipboardNudgeController::HandleNudgeShown() {
   clipboard_state_ = ClipboardState::kInit;
   PrefService* prefs =
diff --git a/ash/clipboard/clipboard_nudge_controller.h b/ash/clipboard/clipboard_nudge_controller.h
index 8d3b361..a7c12ba 100644
--- a/ash/clipboard/clipboard_nudge_controller.h
+++ b/ash/clipboard/clipboard_nudge_controller.h
@@ -7,7 +7,9 @@
 
 #include "ash/ash_export.h"
 #include "ash/clipboard/clipboard_history.h"
+#include "base/memory/weak_ptr.h"
 #include "base/time/clock.h"
+#include "base/timer/timer.h"
 #include "ui/base/clipboard/clipboard_observer.h"
 
 class PrefService;
@@ -15,6 +17,7 @@
 class ClipboardHistoryItem;
 
 namespace ash {
+class ClipboardNudge;
 
 // The clipboard contextual nudge will be shown after 4 user actions that must
 // happen in sequence. The user must perform copy, paste, copy and paste in
@@ -38,7 +41,7 @@
   // Registers profile prefs.
   static void RegisterProfilePrefs(PrefRegistrySimple* registry);
 
-  // ui::ClipboardHistoryObserver:
+  // ui::ClipboardHistory::Observer:
   void OnClipboardHistoryItemAdded(const ClipboardHistoryItem& item) override;
 
   // ui::ClipboardObserver:
@@ -52,6 +55,7 @@
   void ClearClockOverrideForTesting();
 
   const ClipboardState& GetClipboardStateForTesting();
+  ClipboardNudge* GetClipboardNudgeForTesting() { return nudge_.get(); }
 
  private:
   // Gets the number of times the nudge has been shown.
@@ -63,6 +67,12 @@
   // Gets the current time. Can be overridden for testing.
   base::Time GetTime();
 
+  // Shows the nudge widget.
+  void ShowNudge();
+
+  // Hides the nudge widget.
+  void HideNudge();
+
   // Owned by ClipboardHistoryController.
   const ClipboardHistory* clipboard_history_;
 
@@ -72,6 +82,14 @@
   base::Time last_paste_timestamp_;
   // Clock that can be overridden for testing.
   base::Clock* g_clock_override = nullptr;
+
+  // Contextual nudge which shows a view to inform the user on multipaste usage.
+  std::unique_ptr<ClipboardNudge> nudge_;
+
+  // Timer to hide the clipboard nudge.
+  base::OneShotTimer hide_nudge_timer_;
+
+  base::WeakPtrFactory<ClipboardNudgeController> weak_ptr_factory_{this};
 };
 
 }  // namespace ash
diff --git a/ash/clipboard/clipboard_nudge_controller_unittest.cc b/ash/clipboard/clipboard_nudge_controller_unittest.cc
index b3aea50a..c89dd42 100644
--- a/ash/clipboard/clipboard_nudge_controller_unittest.cc
+++ b/ash/clipboard/clipboard_nudge_controller_unittest.cc
@@ -75,10 +75,16 @@
   EXPECT_EQ(ClipboardState::kSecondCopy,
             nudge_controller_->GetClipboardStateForTesting());
 
-  // Checks that the second paste advances state as expected.
+  // Check that clipbaord nudge has not yet been created.
+  EXPECT_FALSE(nudge_controller_->GetClipboardNudgeForTesting());
+
+  // Checks that the second paste resets state as expected.
   nudge_controller_->OnClipboardDataRead();
-  EXPECT_EQ(ClipboardState::kShouldShowNudge,
+  EXPECT_EQ(ClipboardState::kInit,
             nudge_controller_->GetClipboardStateForTesting());
+
+  // Check that clipbaord nudge has been created.
+  EXPECT_TRUE(nudge_controller_->GetClipboardNudgeForTesting());
 }
 
 // Checks that the clipboard state does not advace if too much time passes
@@ -121,6 +127,9 @@
               nudge_controller_->GetClipboardStateForTesting());
   }
 
+  // Check that clipbaord nudge has not yet been created.
+  EXPECT_FALSE(nudge_controller_->GetClipboardNudgeForTesting());
+
   // Check that HandleClipboardChanged() will advance nudge_controller's
   // ClipboardState.
   nudge_controller_->OnClipboardHistoryItemAdded(
@@ -128,8 +137,11 @@
   EXPECT_EQ(ClipboardState::kSecondCopy,
             nudge_controller_->GetClipboardStateForTesting());
   nudge_controller_->OnClipboardDataRead();
-  EXPECT_EQ(ClipboardState::kShouldShowNudge,
+  EXPECT_EQ(ClipboardState::kInit,
             nudge_controller_->GetClipboardStateForTesting());
+
+  // Check that clipbaord nudge has been created.
+  EXPECT_TRUE(nudge_controller_->GetClipboardNudgeForTesting());
 }
 
 // Checks that consecutive copy events does not advance the clipboard state.
diff --git a/ash/resources/vector_icons/BUILD.gn b/ash/resources/vector_icons/BUILD.gn
index ecc8a62..1f48e97 100644
--- a/ash/resources/vector_icons/BUILD.gn
+++ b/ash/resources/vector_icons/BUILD.gn
@@ -38,6 +38,7 @@
     "capture_mode_window.icon",
     "check_circle.icon",
     "chevron_right.icon",
+    "clipboard.icon",
     "close_button.icon",
     "copy.icon",
     "dark_theme_color_mode.icon",
diff --git a/ash/resources/vector_icons/clipboard.icon b/ash/resources/vector_icons/clipboard.icon
new file mode 100644
index 0000000..17af8e6
--- /dev/null
+++ b/ash/resources/vector_icons/clipboard.icon
@@ -0,0 +1,34 @@
+// Copyright 2020 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, 20,
+MOVE_TO, 16, 3,
+R_H_LINE_TO, -3.2f,
+ARC_TO, 3, 3, 0, 0, 0, 10, 1,
+R_ARC_TO, 3, 3, 0, 0, 0, -2.8f, 2,
+H_LINE_TO, 4,
+R_ARC_TO, 2, 2, 0, 0, 0, -2, 2,
+R_V_LINE_TO, 11,
+R_CUBIC_TO, 0, 1.1f, 0.9f, 2, 2, 2,
+R_H_LINE_TO, 12,
+R_ARC_TO, 2, 2, 0, 0, 0, 2, -2,
+V_LINE_TO, 5,
+R_ARC_TO, 2, 2, 0, 0, 0, -2, -2,
+CLOSE,
+R_MOVE_TO, -6, 0,
+R_ARC_TO, 1, 1, 0, 0, 1, 1, 1,
+R_ARC_TO, 1, 1, 0, 0, 1, -1, 1,
+R_ARC_TO, 1, 1, 0, 0, 1, -1, -1,
+R_ARC_TO, 1, 1, 0, 0, 1, 1, -1,
+CLOSE,
+R_MOVE_TO, 6, 13,
+H_LINE_TO, 4,
+V_LINE_TO, 5,
+R_H_LINE_TO, 3,
+R_V_LINE_TO, 2,
+R_H_LINE_TO, 6,
+V_LINE_TO, 5,
+R_H_LINE_TO, 3,
+R_V_LINE_TO, 11,
+CLOSE
\ No newline at end of file