Commit 2f74ce61 authored by Meilin Wang's avatar Meilin Wang Committed by Commit Bot

ambient: add missing UI component.

Please see demo screenshot in the linked bug.

Bug: b/161481969
Test: covered by unittests.
Change-Id: I8e3d867220ff7e23e79ac807ce6929fd04a246ca
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2342784
Commit-Queue: Meilin Wang <meilinw@chromium.org>
Reviewed-by: default avatarXiaohui Chen <xiaohuic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#796884}
parent 64882511
......@@ -198,6 +198,8 @@ component("ash") {
"ambient/ui/assistant_response_container_view.h",
"ambient/ui/glanceable_info_view.cc",
"ambient/ui/glanceable_info_view.h",
"ambient/ui/media_string_view.cc",
"ambient/ui/media_string_view.h",
"ambient/ui/photo_view.cc",
"ambient/ui/photo_view.h",
"ambient/util/ambient_util.cc",
......@@ -1763,6 +1765,7 @@ test("ash_unittests") {
"ambient/autotest_ambient_api_unittest.cc",
"ambient/model/ambient_backend_model_unittest.cc",
"ambient/ui/ambient_container_view_unittest.cc",
"ambient/ui/media_string_view_unittest.cc",
"ambient/ui/photo_view_unittest.cc",
"app_list/app_list_controller_impl_unittest.cc",
"app_list/app_list_metrics_unittest.cc",
......
......@@ -12,6 +12,7 @@
#include "ash/ambient/ambient_photo_controller.h"
#include "ash/ambient/ui/ambient_background_image_view.h"
#include "ash/ambient/ui/ambient_container_view.h"
#include "ash/ambient/ui/media_string_view.h"
#include "ash/ambient/ui/photo_view.h"
#include "ash/assistant/ui/assistant_view_ids.h"
#include "ash/public/cpp/ambient/fake_ambient_backend_controller_impl.h"
......@@ -167,6 +168,22 @@ void AmbientAshTestBase::SetScreenBrightnessAndWait(double percent) {
base::RunLoop().RunUntilIdle();
}
void AmbientAshTestBase::SimulateMediaMetadataChanged(
media_session::MediaMetadata metadata) {
GetMediaStringView()->MediaSessionMetadataChanged(metadata);
}
void AmbientAshTestBase::SimulateMediaPlaybackStateChanged(
media_session::mojom::MediaPlaybackState state) {
// Creates media session info.
media_session::mojom::MediaSessionInfoPtr session_info(
media_session::mojom::MediaSessionInfo::New());
session_info->playback_state = state;
// Simulate media session info changed.
GetMediaStringView()->MediaSessionInfoChanged(std::move(session_info));
}
void AmbientAshTestBase::SetPhotoViewImageSize(int width, int height) {
auto* image_decoder = static_cast<TestAmbientImageDecoderImpl*>(
ambient_controller()
......@@ -182,6 +199,11 @@ AmbientAshTestBase::GetAmbientBackgroundImageView() {
AssistantViewID::kAmbientBackgroundImageView));
}
MediaStringView* AmbientAshTestBase::GetMediaStringView() {
return static_cast<MediaStringView*>(
container_view()->GetViewByID(AssistantViewID::kAmbientMediaStringView));
}
void AmbientAshTestBase::FastForwardToInactivity() {
task_environment()->FastForwardBy(
2 * AmbientController::kAutoShowWaitTimeInterval);
......
......@@ -15,12 +15,14 @@
#include "ash/test/ash_test_base.h"
#include "base/test/scoped_feature_list.h"
#include "services/device/public/cpp/test/test_wake_lock_provider.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
#include "ui/views/widget/widget.h"
namespace ash {
class AmbientContainerView;
class AmbientPhotoController;
class MediaStringView;
// The base class to test the Ambient Mode in Ash.
class AmbientAshTestBase : public AshTestBase {
......@@ -66,6 +68,11 @@ class AmbientAshTestBase : public AshTestBase {
// Simulates a screen brightness changed event.
void SetScreenBrightnessAndWait(double percent);
void SimulateMediaMetadataChanged(media_session::MediaMetadata metadata);
void SimulateMediaPlaybackStateChanged(
media_session::mojom::MediaPlaybackState state);
// Set the size of the next image that will be loaded.
void SetPhotoViewImageSize(int width, int height);
......@@ -86,6 +93,9 @@ class AmbientAshTestBase : public AshTestBase {
AmbientBackgroundImageView* GetAmbientBackgroundImageView();
// Returns the media string view for displaying ongoing media info.
MediaStringView* GetMediaStringView();
AmbientController* ambient_controller();
AmbientPhotoController* photo_controller();
......
......@@ -10,6 +10,7 @@
#include "ash/ambient/ui/ambient_assistant_container_view.h"
#include "ash/ambient/ui/ambient_view_delegate.h"
#include "ash/ambient/ui/glanceable_info_view.h"
#include "ash/ambient/ui/media_string_view.h"
#include "ash/ambient/ui/photo_view.h"
#include "ash/ambient/util/ambient_util.h"
#include "ash/assistant/ui/assistant_view_ids.h"
......@@ -40,6 +41,7 @@ using chromeos::assistant::features::IsAmbientAssistantEnabled;
constexpr int kHorizontalMarginDip = 16;
constexpr int kVerticalMarginDip = 64;
constexpr int kAssistantPreferredHeightDip = 128;
constexpr int kMediaStringTopMarginDip = 25;
// A tolerance threshold used to ignore spurious mouse move.
constexpr int kMouseMoveErrorTolerancePx = 3;
......@@ -132,6 +134,7 @@ void AmbientContainerView::Layout() {
// Layout child views first to have proper bounds set for children.
LayoutPhotoView();
LayoutGlanceableInfoView();
LayoutMediaStringView();
// The assistant view may not exist if |kAmbientAssistant| feature is
// disabled.
if (ambient_assistant_container_view_)
......@@ -155,6 +158,9 @@ void AmbientContainerView::Init() {
glanceable_info_view_ =
AddChildView(std::make_unique<GlanceableInfoView>(delegate_));
media_string_view_ = AddChildView(std::make_unique<MediaStringView>());
media_string_view_->SetVisible(false);
if (IsAmbientAssistantEnabled()) {
ambient_assistant_container_view_ =
AddChildView(std::make_unique<AmbientAssistantContainerView>());
......@@ -187,6 +193,22 @@ void AmbientContainerView::LayoutAssistantView() {
gfx::Rect(0, 0, preferred_width, preferred_height));
}
void AmbientContainerView::LayoutMediaStringView() {
const gfx::Size container_size = GetLocalBounds().size();
const gfx::Size preferred_size = media_string_view_->GetPreferredSize();
// The media string view is positioned on the right-top corner of the
// container.
// TODO(meilinw): without a maximum width limit, media string can grow too
// long or even overflow the screen. Revisit here to polish the UI once the
// spec is available. See b/163398805.
int x =
container_size.width() - kHorizontalMarginDip - preferred_size.width();
int y = kMediaStringTopMarginDip;
media_string_view_->SetBoundsRect(
gfx::Rect(x, y, preferred_size.width(), preferred_size.height()));
}
void AmbientContainerView::HandleEvent() {
delegate_->OnBackgroundPhotoEvents();
}
......
......@@ -17,6 +17,7 @@ class AmbientAssistantContainerView;
class AmbientViewDelegate;
class GlanceableInfoView;
class PhotoView;
class MediaStringView;
// Container view to display all Ambient Mode related views, i.e. photo frame,
// weather info.
......@@ -38,9 +39,12 @@ class ASH_EXPORT AmbientContainerView : public views::View {
void Init();
// Layouts its child views.
// TODO(meilinw): Use LayoutManagers to lay out children instead of overriding
// Layout(). See b/163170162.
void LayoutPhotoView();
void LayoutGlanceableInfoView();
void LayoutAssistantView();
void LayoutMediaStringView();
// Invoked on specific types of events.
void HandleEvent();
......@@ -51,6 +55,7 @@ class ASH_EXPORT AmbientContainerView : public views::View {
PhotoView* photo_view_ = nullptr;
AmbientAssistantContainerView* ambient_assistant_container_view_ = nullptr;
GlanceableInfoView* glanceable_info_view_ = nullptr;
MediaStringView* media_string_view_ = nullptr;
// Observes events from its host widget.
std::unique_ptr<HostWidgetEventObserver> event_observer_;
......
// 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/ambient/ui/media_string_view.h"
#include <memory>
#include <string>
#include "ash/ambient/util/ambient_util.h"
#include "ash/assistant/ui/assistant_view_ids.h"
#include "ash/shell.h"
#include "ash/shell_delegate.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
#include "services/media_session/public/mojom/media_session_service.mojom.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
namespace ash {
namespace {
// Typography.
constexpr SkColor kTextColor = SK_ColorWHITE;
constexpr char kMiddleDotSeparator[] = " \u00B7 ";
constexpr char kPreceedingEighthNoteSymbol[] = "\u266A ";
constexpr int kDefaultFontSizeDip = 64;
constexpr int kMediaStringFontSizeDip = 16;
} // namespace
MediaStringView::MediaStringView() {
SetID(AssistantViewID::kAmbientMediaStringView);
InitLayout();
}
MediaStringView::~MediaStringView() = default;
const char* MediaStringView::GetClassName() const {
return "MediaStringView";
}
void MediaStringView::MediaSessionInfoChanged(
media_session::mojom::MediaSessionInfoPtr session_info) {
// Don't show the media string if session info is unavailable, or the active
// session is marked as sensitive.
if (!session_info || session_info->is_sensitive) {
SetVisible(false);
return;
}
bool is_paused = session_info->playback_state ==
media_session::mojom::MediaPlaybackState::kPaused;
// Don't show the media string if paused.
SetVisible(!is_paused);
}
void MediaStringView::MediaSessionMetadataChanged(
const base::Optional<media_session::MediaMetadata>& metadata) {
media_session::MediaMetadata session_metadata =
metadata.value_or(media_session::MediaMetadata());
base::string16 media_string;
if (!session_metadata.title.empty() && !session_metadata.artist.empty()) {
media_string = session_metadata.title +
base::UTF8ToUTF16(kMiddleDotSeparator) +
session_metadata.artist;
} else if (!session_metadata.title.empty()) {
media_string = session_metadata.title;
} else {
media_string = session_metadata.artist;
}
// Formats the media string with a preceding music eighth note.
SetText(base::UTF8ToUTF16(kPreceedingEighthNoteSymbol) + media_string);
}
void MediaStringView::InitLayout() {
// This view will be drawn on its own layer instead of the layer of
// |PhotoView| which has a solid black background.
SetPaintToLayer();
layer()->SetFillsBoundsOpaquely(false);
// Defines the appearance.
SetAutoColorReadabilityEnabled(false);
SetEnabledColor(kTextColor);
SetFontList(ambient::util::GetDefaultFontlist().DeriveWithSizeDelta(
kMediaStringFontSizeDip - kDefaultFontSizeDip));
BindMediaControllerObserver();
}
void MediaStringView::BindMediaControllerObserver() {
media_session::mojom::MediaSessionService* service =
Shell::Get()->shell_delegate()->GetMediaSessionService();
// Service might be unavailable under some test environments.
if (!service)
return;
// Binds to the MediaControllerManager and create a MediaController for the
// current active media session so that we can observe it.
mojo::Remote<media_session::mojom::MediaControllerManager>
controller_manager_remote;
service->BindMediaControllerManager(
controller_manager_remote.BindNewPipeAndPassReceiver());
controller_manager_remote->CreateActiveMediaController(
media_controller_remote_.BindNewPipeAndPassReceiver());
// Observe the active media controller for changes.
media_controller_remote_->AddObserver(
observer_receiver_.BindNewPipeAndPassRemote());
}
} // namespace ash
// 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_AMBIENT_UI_MEDIA_STRING_VIEW_H_
#define ASH_AMBIENT_UI_MEDIA_STRING_VIEW_H_
#include <string>
#include "ash/app_menu/notification_item_view.h"
#include "ash/public/cpp/ambient/ambient_ui_model.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/media_session/public/mojom/media_controller.mojom.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
namespace ash {
// Container for displaying ongoing media information, including the name of the
// media and the artist, formatted with a proceding music note symbol and a
// middle dot separator.
class MediaStringView : public views::Label,
public media_session::mojom::MediaControllerObserver {
public:
MediaStringView();
MediaStringView(const MediaStringView&) = delete;
MediaStringView& operator=(const MediaStringView&) = delete;
~MediaStringView() override;
// views::Label:
const char* GetClassName() const override;
// media_session::mojom::MediaControllerObserver:
void MediaSessionInfoChanged(
media_session::mojom::MediaSessionInfoPtr session_info) override;
void MediaSessionMetadataChanged(
const base::Optional<media_session::MediaMetadata>& metadata) override;
void MediaSessionActionsChanged(
const std::vector<media_session::mojom::MediaSessionAction>& actions)
override {}
void MediaSessionChanged(
const base::Optional<base::UnguessableToken>& request_id) override {}
void MediaSessionPositionChanged(
const base::Optional<media_session::MediaPosition>& position) override {}
private:
void InitLayout();
void BindMediaControllerObserver();
// Used to receive updates to the active media controller.
mojo::Remote<media_session::mojom::MediaController> media_controller_remote_;
mojo::Receiver<media_session::mojom::MediaControllerObserver>
observer_receiver_{this};
};
} // namespace ash
#endif // ASH_AMBIENT_UI_MEDIA_STRING_VIEW_H_
// 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/ambient/ui/media_string_view.h"
#include "ash/ambient/test/ambient_ash_test_base.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "services/media_session/public/mojom/media_session.mojom.h"
namespace ash {
using MediaStringViewTest = AmbientAshTestBase;
TEST_F(MediaStringViewTest, ShowMediaTitleAndArtist) {
ShowAmbientScreen();
// Sets metadata for current session.
media_session::MediaMetadata metadata;
metadata.title = base::ASCIIToUTF16("title");
metadata.artist = base::ASCIIToUTF16("artist");
SimulateMediaMetadataChanged(metadata);
const base::string16 expected_text =
base::UTF8ToUTF16("\u266A title \u00B7 artist");
EXPECT_EQ(GetMediaStringView()->GetText(), expected_text);
}
TEST_F(MediaStringViewTest, ShowWhenMediaIsPlaying) {
ShowAmbientScreen();
EXPECT_FALSE(GetMediaStringView()->GetVisible());
// Sets media playstate for the current session.
SimulateMediaPlaybackStateChanged(
media_session::mojom::MediaPlaybackState::kPlaying);
EXPECT_TRUE(GetMediaStringView()->GetVisible());
}
TEST_F(MediaStringViewTest, DoNotShowWhenMediaIsPaused) {
ShowAmbientScreen();
EXPECT_FALSE(GetMediaStringView()->GetVisible());
// Sets media playstate for the current session.
SimulateMediaPlaybackStateChanged(
media_session::mojom::MediaPlaybackState::kPlaying);
EXPECT_TRUE(GetMediaStringView()->GetVisible());
// Simulates the ongoing media paused.
SimulateMediaPlaybackStateChanged(
media_session::mojom::MediaPlaybackState::kPaused);
EXPECT_FALSE(GetMediaStringView()->GetVisible());
}
} // namespace ash
......@@ -43,6 +43,7 @@ enum AssistantViewID {
kAmbientBackgroundImageView,
kAmbientGlanceableInfoView,
kAmbientAssistantDialogPlate,
kAmbientMediaStringView,
};
} // namespace ash
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment