0

Polish quick settings Cast tile behavior

When casting:
- Toggle the tile
- Set the label to "Casting screen", "Casting tab", or "Casting"
  depending on the source type
- Set the sub-label to the cast target name (e.g. "Sony TV")

When not casting keep the "Cast screen / Devices available" behavior.

https://screenshot.googleplex.com/BLgLAPAUrstvJPi
https://screenshot.googleplex.com/wCstR4LW4ixbMJP

Bug: b:268575073
Test: added to ash_unittests
Change-Id: I0af057f4f29c984cb4225166f5335c73ec69a249
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4492807
Reviewed-by: Jiaming Cheng <jiamingc@chromium.org>
Commit-Queue: James Cook <jamescook@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1137445}
This commit is contained in:
James Cook
2023-04-28 23:28:20 +00:00
committed by Chromium LUCI CQ
parent 6e848fd8ab
commit 58e1429301
9 changed files with 156 additions and 14 deletions

@ -498,6 +498,15 @@ Style notes:
<message name="IDS_ASH_STATUS_TRAY_CAST_TOOLTIP" desc="The tooltip text used for Cast button in system tray bubble.">
Show cast devices
</message>
<message name="IDS_ASH_STATUS_TRAY_CASTING" desc="The label used in the system tray bubble when casting a generic source to a Chromecast device">
Casting
</message>
<message name="IDS_ASH_STATUS_TRAY_CASTING_SCREEN" desc="The label used in the system tray bubble when casting the screen to a Chromecast device">
Casting screen
</message>
<message name="IDS_ASH_STATUS_TRAY_CASTING_TAB" desc="The label used in the system tray bubble when casting a Chrome tab to a Chromecast device">
Casting tab
</message>
<message name="IDS_ASH_STATUS_TRAY_CAST_NOTIFICATION_TITLE" desc="The notification title to tell the user we are casting to a cast device.">
Casting to <ph name="RECEIVER_NAME">$1<ex>Living Room</ex></ph>
</message>

@ -0,0 +1 @@
4b037ebe0357fa9c84e3fdc493e8da76d9fe1641

@ -0,0 +1 @@
f50d77dea2c23229c50caef5b9b2d839bded3d75

@ -0,0 +1 @@
a0ffeb67badb487c80d309836d97e430a868b5f1

@ -14,6 +14,11 @@ TestCastConfigController::TestCastConfigController() = default;
TestCastConfigController::~TestCastConfigController() = default;
void TestCastConfigController::AddSinkAndRoute(
const SinkAndRoute& sink_and_route) {
sinks_and_routes_.push_back(sink_and_route);
}
void TestCastConfigController::ResetRouteIds() {
stop_casting_route_id_.clear();
freeze_route_route_id_.clear();

@ -47,6 +47,9 @@ class TestCastConfigController : public CastConfigController {
return unfreeze_route_route_id_;
}
// Adds an entry to `sinks_and_routes_`.
void AddSinkAndRoute(const SinkAndRoute& sink_and_route);
// Resets the captured route IDs.
void ResetRouteIds();

@ -20,10 +20,40 @@
#include "ash/system/unified/quick_settings_metrics_util.h"
#include "ash/system/unified/unified_system_tray_controller.h"
#include "base/functional/bind.h"
#include "base/strings/utf_string_conversions.h"
#include "components/access_code_cast/common/access_code_cast_metrics.h"
#include "ui/base/l10n/l10n_util.h"
namespace ash {
namespace {
// Returns the active SinkAndRoute if this machine ("local source") is
// casting to it. Otherwise returns an empty SinkAndRoute.
SinkAndRoute GetActiveSinkAndRoute() {
auto* cast_config = CastConfigController::Get();
CHECK(cast_config);
for (const auto& sink_and_route : cast_config->GetSinksAndRoutes()) {
if (!sink_and_route.route.id.empty() &&
sink_and_route.route.is_local_source) {
return sink_and_route;
}
}
return SinkAndRoute();
}
// Returns the string to display for the "Casting" feature tile label.
std::u16string GetCastingString(const CastRoute& route) {
switch (route.content_source) {
case ContentSource::kUnknown:
return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CASTING);
case ContentSource::kTab:
return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CASTING_TAB);
case ContentSource::kDesktop:
return l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CASTING_SCREEN);
}
}
} // namespace
CastFeaturePodController::CastFeaturePodController(
UnifiedSystemTrayController* tray_controller)
@ -105,11 +135,9 @@ std::unique_ptr<FeatureTile> CastFeaturePodController::CreateTile(
return tile;
}
tile->SetSubLabel(
l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_DEVICES_AVAILABLE));
tile->CreateDecorativeDrillInButton(tooltip);
UpdateSublabelVisibility();
UpdateFeatureTile();
return tile;
}
@ -146,10 +174,7 @@ void CastFeaturePodController::OnLabelPressed() {
void CastFeaturePodController::OnDevicesUpdated(
const std::vector<SinkAndRoute>& devices) {
if (features::IsQsRevampEnabled()) {
if (tile_->tile_type() == FeatureTile::TileType::kCompact) {
return;
}
UpdateSublabelVisibility();
UpdateFeatureTile();
} else {
Update();
}
@ -164,14 +189,44 @@ void CastFeaturePodController::Update() {
button_->SetVisible(target_visibility);
}
void CastFeaturePodController::UpdateSublabelVisibility() {
void CastFeaturePodController::UpdateFeatureTile() {
DCHECK(features::IsQsRevampEnabled());
DCHECK(tile_);
auto* cast_config = CastConfigController::Get();
bool devices_available =
cast_config && (cast_config->HasSinksAndRoutes() ||
cast_config->AccessCodeCastingEnabled());
tile_->SetSubLabelVisibility(devices_available);
if (!cast_config) {
return; // May be null in tests.
}
bool is_casting = cast_config->HasActiveRoute();
tile_->SetToggled(is_casting);
if (is_casting) {
// TODO(b/268575073): Use a new "cast connected" icon at 20px.
tile_->SetVectorIcon(kUnifiedMenuCastIcon);
// Set the label to "Casting screen" or "Casting tab".
SinkAndRoute sink_and_route = GetActiveSinkAndRoute();
tile_->SetLabel(GetCastingString(sink_and_route.route));
if (tile_->tile_type() == FeatureTile::TileType::kPrimary) {
// If the sink has a name ("Sony TV") then show it as a sub-label.
const std::string& active_sink_name = sink_and_route.sink.name;
tile_->SetSubLabel(base::UTF8ToUTF16(active_sink_name));
tile_->SetSubLabelVisibility(!active_sink_name.empty());
}
return;
}
// TODO(b/268575073): Use a new "cast not connected" icon.
tile_->SetVectorIcon(kUnifiedMenuCastIcon);
// Set the label to "Cast screen".
tile_->SetLabel(l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST));
if (tile_->tile_type() == FeatureTile::TileType::kPrimary) {
tile_->SetSubLabel(
l10n_util::GetStringUTF16(IDS_ASH_STATUS_TRAY_CAST_DEVICES_AVAILABLE));
bool devices_available = cast_config->HasSinksAndRoutes() ||
cast_config->AccessCodeCastingEnabled();
tile_->SetSubLabelVisibility(devices_available);
}
}
} // namespace ash

@ -47,8 +47,8 @@ class ASH_EXPORT CastFeaturePodController
// Updates feature pod button visibility. Used pre-QsRevamp.
void Update();
// Updates tile sublabel visibility. Used post-QsRevamp.
void UpdateSublabelVisibility();
// Updates the feature tile. Used post-QsRevamp.
void UpdateFeatureTile();
const raw_ptr<UnifiedSystemTrayController,
DanglingUntriaged | ExperimentalAsh>

@ -15,6 +15,14 @@
namespace ash {
namespace {
// Returns a SinkAndRoute as if the local machine was casting.
SinkAndRoute MakeLocalSinkAndRoute() {
SinkAndRoute sink_and_route;
sink_and_route.route.id = "route_id";
sink_and_route.route.is_local_source = true;
return sink_and_route;
}
class CastFeaturePodControllerTest : public AshTestBase {
public:
CastFeaturePodControllerTest() {
@ -39,9 +47,12 @@ class CastFeaturePodControllerTest : public AshTestBase {
TEST_F(CastFeaturePodControllerTest, CreateTile) {
std::unique_ptr<FeatureTile> tile = controller_->CreateTile();
EXPECT_TRUE(tile->GetVisible());
EXPECT_FALSE(tile->IsToggled());
EXPECT_EQ(tile->label()->GetText(), u"Cast screen");
EXPECT_EQ(tile->GetTooltipText(), u"Show cast devices");
EXPECT_TRUE(tile->drill_in_button()->GetVisible());
EXPECT_EQ(tile->drill_in_button()->GetTooltipText(), u"Show cast devices");
EXPECT_FALSE(tile->sub_label()->GetVisible());
}
TEST_F(CastFeaturePodControllerTest, TileNotVisibleWhenNoMediaRouter) {
@ -79,5 +90,61 @@ TEST_F(CastFeaturePodControllerTest, SubLabelVisibleOnDevicesUpdated) {
EXPECT_TRUE(tile->sub_label()->GetVisible());
}
TEST_F(CastFeaturePodControllerTest, TileStateWhenCastingScreen) {
cast_config_.set_has_active_route(true);
cast_config_.set_has_sinks_and_routes(true);
SinkAndRoute sink_and_route = MakeLocalSinkAndRoute();
sink_and_route.sink.name = "Sony TV";
sink_and_route.route.content_source = ContentSource::kDesktop;
cast_config_.AddSinkAndRoute(sink_and_route);
std::unique_ptr<FeatureTile> tile = controller_->CreateTile();
EXPECT_TRUE(tile->IsToggled());
EXPECT_EQ(tile->label()->GetText(), u"Casting screen");
EXPECT_TRUE(tile->sub_label()->GetVisible());
EXPECT_EQ(tile->sub_label()->GetText(), u"Sony TV");
}
TEST_F(CastFeaturePodControllerTest, TileStateWhenCastingTab) {
cast_config_.set_has_active_route(true);
cast_config_.set_has_sinks_and_routes(true);
SinkAndRoute sink_and_route = MakeLocalSinkAndRoute();
sink_and_route.sink.name = "Sony TV";
sink_and_route.route.content_source = ContentSource::kTab;
cast_config_.AddSinkAndRoute(sink_and_route);
std::unique_ptr<FeatureTile> tile = controller_->CreateTile();
EXPECT_TRUE(tile->IsToggled());
EXPECT_EQ(tile->label()->GetText(), u"Casting tab");
EXPECT_TRUE(tile->sub_label()->GetVisible());
EXPECT_EQ(tile->sub_label()->GetText(), u"Sony TV");
}
TEST_F(CastFeaturePodControllerTest, TileStateWhenCastingUnknownSource) {
cast_config_.set_has_active_route(true);
cast_config_.set_has_sinks_and_routes(true);
SinkAndRoute sink_and_route = MakeLocalSinkAndRoute();
sink_and_route.sink.name = "Sony TV";
sink_and_route.route.content_source = ContentSource::kUnknown;
cast_config_.AddSinkAndRoute(sink_and_route);
std::unique_ptr<FeatureTile> tile = controller_->CreateTile();
EXPECT_TRUE(tile->IsToggled());
EXPECT_EQ(tile->label()->GetText(), u"Casting");
EXPECT_TRUE(tile->sub_label()->GetVisible());
EXPECT_EQ(tile->sub_label()->GetText(), u"Sony TV");
}
TEST_F(CastFeaturePodControllerTest, SubLabelHiddenWhenSinkHasNoName) {
cast_config_.set_has_active_route(true);
cast_config_.set_has_sinks_and_routes(true);
SinkAndRoute sink_and_route = MakeLocalSinkAndRoute();
sink_and_route.sink.name = "";
cast_config_.AddSinkAndRoute(sink_and_route);
std::unique_ptr<FeatureTile> tile = controller_->CreateTile();
EXPECT_FALSE(tile->sub_label()->GetVisible());
}
} // namespace
} // namespace ash