From 0eb6b1efc30e1e2acb2a0c8ebac62ee6d82af5a1 Mon Sep 17 00:00:00 2001 From: Xiaoqian Dai Date: Tue, 26 Jan 2021 00:08:03 +0000 Subject: [PATCH] capture mode: implement customized image capture notification. Bug: 1159111 Change-Id: I5a96480a929a05a7782f51ae6baea8ddc7c9df10 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2632390 Reviewed-by: Evan Stade Reviewed-by: Ahmed Fakhry Commit-Queue: Xiaoqian Dai Cr-Commit-Position: refs/heads/master@{#846957} --- ash/BUILD.gn | 2 + ash/capture_mode/capture_mode_controller.cc | 14 +- .../capture_mode_notification_view.cc | 125 ++++++++++++++++++ .../capture_mode_notification_view.h | 53 ++++++++ .../views/notification_view_md.h | 3 + 5 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 ash/capture_mode/capture_mode_notification_view.cc create mode 100644 ash/capture_mode/capture_mode_notification_view.h diff --git a/ash/BUILD.gn b/ash/BUILD.gn index d46cf9edb3c41b..1c34a7242a13a7 100644 --- a/ash/BUILD.gn +++ b/ash/BUILD.gn @@ -279,6 +279,8 @@ component("ash") { "capture_mode/capture_mode_feature_pod_controller.h", "capture_mode/capture_mode_metrics.cc", "capture_mode/capture_mode_metrics.h", + "capture_mode/capture_mode_notification_view.cc", + "capture_mode/capture_mode_notification_view.h", "capture_mode/capture_mode_session.cc", "capture_mode/capture_mode_session.h", "capture_mode/capture_mode_settings_entry_view.cc", diff --git a/ash/capture_mode/capture_mode_controller.cc b/ash/capture_mode/capture_mode_controller.cc index 166cece9329bdb..c559803160faf1 100644 --- a/ash/capture_mode/capture_mode_controller.cc +++ b/ash/capture_mode/capture_mode_controller.cc @@ -7,6 +7,7 @@ #include #include "ash/capture_mode/capture_mode_metrics.h" +#include "ash/capture_mode/capture_mode_notification_view.h" #include "ash/capture_mode/capture_mode_session.h" #include "ash/capture_mode/capture_mode_util.h" #include "ash/capture_mode/video_recording_watcher.h" @@ -49,6 +50,7 @@ #include "ui/message_center/message_center.h" #include "ui/message_center/public/cpp/notification.h" #include "ui/message_center/public/cpp/notification_delegate.h" +#include "ui/message_center/views/message_view_factory.h" #include "ui/snapshot/snapshot.h" namespace ash { @@ -66,6 +68,8 @@ constexpr char kScreenCaptureNotificationId[] = "capture_mode_notification"; constexpr char kScreenCaptureStoppedNotificationId[] = "capture_mode_stopped_notification"; constexpr char kScreenCaptureNotifierId[] = "ash.capture_mode_controller"; +constexpr char kScreenCaptureNotificationType[] = + "capture_mode_notification_type"; // The format strings of the file names of captured images. // TODO(afakhry): Discuss with UX localizing "Screenshot" and "Screen @@ -169,7 +173,7 @@ void ShowNotification( const gfx::VectorIcon& notification_icon = kCaptureModeIcon) { const auto type = optional_fields.image.IsEmpty() ? message_center::NOTIFICATION_TYPE_SIMPLE - : message_center::NOTIFICATION_TYPE_IMAGE; + : message_center::NOTIFICATION_TYPE_CUSTOM; std::unique_ptr notification = CreateSystemNotification( type, notification_id, l10n_util::GetStringUTF16(title_id), @@ -180,6 +184,8 @@ void ShowNotification( message_center::NotifierType::SYSTEM_COMPONENT, kScreenCaptureNotifierId), optional_fields, delegate, notification_icon, warning_level); + if (type == message_center::NOTIFICATION_TYPE_CUSTOM) + notification->set_custom_view_type(kScreenCaptureNotificationType); // Remove the previous notification before showing the new one if there is // any. @@ -297,6 +303,12 @@ CaptureModeController::CaptureModeController( &CaptureModeController::RecordAndResetScreenshotsTakenInLastWeek, weak_ptr_factory_.GetWeakPtr())); + DCHECK(!message_center::MessageViewFactory::HasCustomNotificationViewFactory( + kScreenCaptureNotificationType)); + message_center::MessageViewFactory::SetCustomNotificationViewFactory( + kScreenCaptureNotificationType, + base::BindRepeating(&CaptureModeNotificationView::Create)); + Shell::Get()->session_controller()->AddObserver(this); chromeos::PowerManagerClient::Get()->AddObserver(this); } diff --git a/ash/capture_mode/capture_mode_notification_view.cc b/ash/capture_mode/capture_mode_notification_view.cc new file mode 100644 index 00000000000000..c60190c19af1d6 --- /dev/null +++ b/ash/capture_mode/capture_mode_notification_view.cc @@ -0,0 +1,125 @@ +// Copyright 2021 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/capture_mode/capture_mode_notification_view.h" + +#include "ash/resources/vector_icons/vector_icons.h" +#include "ash/strings/grit/ash_strings.h" +#include "ash/style/ash_color_provider.h" +#include "ash/style/scoped_light_mode_as_default.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/views/background.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/view.h" + +namespace ash { + +namespace { + +// Constants related to the banner view on the image capture notification. +constexpr int kBannerHeightDip = 36; +constexpr int kBannerHorizontalInsetDip = 12; +constexpr int kBannerVerticalInsetDip = 8; +constexpr int kBannerIconTextSpacingDip = 8; +constexpr int kBannerIconSizeDip = 20; + +// Creates the banner view that will show on top of the notification image. +std::unique_ptr CreateBannerViewImpl() { + std::unique_ptr banner_view = std::make_unique(); + + // Use the light mode as default as notification is still using light + // theme as the default theme. + ScopedLightModeAsDefault scoped_light_mode_as_default; + + auto* color_provider = AshColorProvider::Get(); + const SkColor background_color = color_provider->GetControlsLayerColor( + AshColorProvider::ControlsLayerType::kControlBackgroundColorActive); + // The text and icon are showing on the background with |background_color| + // so its color is same with kButtonLabelColorPrimary although they're + // not theoretically showing on a button. + const SkColor text_icon_color = color_provider->GetContentLayerColor( + AshColorProvider::ContentLayerType::kButtonLabelColorPrimary); + auto layout = std::make_unique( + views::BoxLayout::Orientation::kHorizontal, + gfx::Insets(kBannerVerticalInsetDip, kBannerHorizontalInsetDip), + kBannerIconTextSpacingDip); + banner_view->SetLayoutManager(std::move(layout)); + banner_view->SetBackground(views::CreateSolidBackground(background_color)); + + views::ImageView* icon = + banner_view->AddChildView(std::make_unique()); + icon->SetImage(gfx::CreateVectorIcon(kCaptureModeCopiedToClipboardIcon, + kBannerIconSizeDip, text_icon_color)); + + views::Label* label = banner_view->AddChildView( + std::make_unique(l10n_util::GetStringUTF16( + IDS_ASH_SCREEN_CAPTURE_SCREENSHOT_COPIED_TO_CLIPBOARD))); + label->SetBackgroundColor(background_color); + label->SetEnabledColor(text_icon_color); + + return banner_view; +} + +} // namespace + +CaptureModeNotificationView::CaptureModeNotificationView( + const message_center::Notification& notification) + : message_center::NotificationViewMD(notification) { + // Create the banner view if notification image is not empty. The banner + // will show on top of the notification image. + if (!notification.image().IsEmpty()) + CreateBannerView(); + + // We need to observe this view as |this| view will be re-used for + // notifications for with/without image scenarios if |this| is not destroyed + // by the user or by the timeout before the next notification shows up. + views::View::AddObserver(this); +} + +CaptureModeNotificationView::~CaptureModeNotificationView() = default; + +// static +std::unique_ptr +CaptureModeNotificationView::Create( + const message_center::Notification& notification) { + return std::make_unique(notification); +} + +void CaptureModeNotificationView::Layout() { + message_center::NotificationViewMD::Layout(); + if (!banner_view_) + return; + + // Calculate the banner view's desired bounds. + gfx::Rect banner_bounds = image_container_view()->GetContentsBounds(); + banner_bounds.set_y(banner_bounds.bottom() - kBannerHeightDip); + banner_bounds.set_height(kBannerHeightDip); + banner_view_->SetBoundsRect(banner_bounds); +} + +void CaptureModeNotificationView::OnChildViewAdded(views::View* observed_view, + views::View* child) { + if (observed_view == this && child == image_container_view()) + CreateBannerView(); +} + +void CaptureModeNotificationView::OnChildViewRemoved(views::View* observed_view, + views::View* child) { + if (observed_view == this && child == image_container_view()) + banner_view_ = nullptr; +} + +void CaptureModeNotificationView::OnViewIsDeleting(View* observed_view) { + DCHECK_EQ(observed_view, this); + views::View::RemoveObserver(this); +} + +void CaptureModeNotificationView::CreateBannerView() { + DCHECK(image_container_view()); + DCHECK(!image_container_view()->children().empty()); + DCHECK(!banner_view_); + banner_view_ = image_container_view()->AddChildView(CreateBannerViewImpl()); +} + +} // namespace ash diff --git a/ash/capture_mode/capture_mode_notification_view.h b/ash/capture_mode/capture_mode_notification_view.h new file mode 100644 index 00000000000000..f8b185621d3da4 --- /dev/null +++ b/ash/capture_mode/capture_mode_notification_view.h @@ -0,0 +1,53 @@ +// Copyright 2021 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_CAPTURE_MODE_CAPTURE_MODE_NOTIFICATION_VIEW_H_ +#define ASH_CAPTURE_MODE_CAPTURE_MODE_NOTIFICATION_VIEW_H_ + +#include "ash/ash_export.h" +#include "ui/message_center/views/notification_view_md.h" +#include "ui/views/view_observer.h" + +namespace ash { + +// A customized notification view for capture mode that can show a notification +// with a banner on top of the notification image. +class ASH_EXPORT CaptureModeNotificationView + : public message_center::NotificationViewMD, + public views::ViewObserver { + public: + explicit CaptureModeNotificationView( + const message_center::Notification& notification); + CaptureModeNotificationView(const CaptureModeNotificationView&) = delete; + CaptureModeNotificationView& operator=(const CaptureModeNotificationView&) = + delete; + ~CaptureModeNotificationView() override; + + // Creates the custom capture mode notification for image capture + // notification. There is a banner on top of the image area of the + // notification to indicate the image has been copied to clipboard. + static std::unique_ptr Create( + const message_center::Notification& notification); + + // message_center::NotificationViewMD: + void Layout() override; + + // views::ViewObserver: + void OnChildViewAdded(views::View* observed_view, + views::View* child) override; + void OnChildViewRemoved(views::View* observed_view, + views::View* child) override; + void OnViewIsDeleting(View* observed_view) override; + + private: + void CreateBannerView(); + + // The banner view that shows a banner string on top of the captured image. + // Owned by view hierarchy. + views::View* banner_view_ = nullptr; +}; + +} // namespace ash + +#endif // ASH_CAPTURE_MODE_CAPTURE_MODE_NOTIFICATION_VIEW_H_ diff --git a/ui/message_center/views/notification_view_md.h b/ui/message_center/views/notification_view_md.h index f9131770f9fc40..528b642bc36518 100644 --- a/ui/message_center/views/notification_view_md.h +++ b/ui/message_center/views/notification_view_md.h @@ -213,6 +213,9 @@ class MESSAGE_CENTER_EXPORT NotificationViewMD void OnNotificationInputSubmit(size_t index, const base::string16& text) override; + protected: + views::View* image_container_view() { return image_container_view_; } + private: FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, AppNameExtension); FRIEND_TEST_ALL_PREFIXES(NotificationViewMDTest, AppNameSystemNotification);