Commit 5c4c4206 authored by Derek Cheng's avatar Derek Cheng Committed by Commit Bot

[Cast MRP] Implement capabilities filtering.

In addition to app availability, the CastAppDiscoveryService will now
check the required capabilities in the MediaSource against the
capabilities supported by a sink. A sink is only returned if both
conditions are satisfied.

Bug: 809249
Change-Id: I5c6f3685db44b28560c575892637117e9ad8d711
Reviewed-on: https://chromium-review.googlesource.com/1089461
Commit-Queue: Derek Cheng <imcheng@chromium.org>
Reviewed-by: default avatarTakumi Fujimoto <takumif@chromium.org>
Cr-Commit-Position: refs/heads/master@{#566191}
parent ceda663f
......@@ -125,23 +125,4 @@ std::vector<std::string> CastAppAvailabilityTracker::GetRegisteredApps() const {
return registered_apps;
}
base::flat_set<MediaSink::Id> CastAppAvailabilityTracker::GetAvailableSinks(
const CastMediaSource& source) const {
base::flat_set<MediaSink::Id> sink_ids;
// For each sink, check if there is at least one available app in |source|.
for (const auto& availabilities : app_availabilities_) {
for (const auto& app_info : source.app_infos()) {
const auto& availabilities_map = availabilities.second;
auto availability_it = availabilities_map.find(app_info.app_id);
if (availability_it != availabilities_map.end() &&
availability_it->second.first ==
GetAppAvailabilityResult::kAvailable) {
sink_ids.insert(availabilities.first);
break;
}
}
}
return sink_ids;
}
} // namespace media_router
......@@ -95,11 +95,6 @@ class CastAppAvailabilityTracker {
// Returns a list of registered app IDs.
std::vector<std::string> GetRegisteredApps() const;
// Returns a list of sink IDs compatible with |source|, using the current
// availability info.
base::flat_set<MediaSink::Id> GetAvailableSinks(
const CastMediaSource& source) const;
private:
// App ID to availability.
using AppAvailabilityMap = base::flat_map<std::string, AppAvailability>;
......
......@@ -110,34 +110,21 @@ TEST_F(CastAppAvailabilityTrackerTest, UpdateAppAvailability) {
"sinkId1", "AAAAAAAA", {GetAppAvailabilityResult::kAvailable, Now()}),
CastMediaSourcesEqual(std::vector<CastMediaSource>()));
base::flat_set<MediaSink::Id> sinks_1 = {"sinkId1"};
base::flat_set<MediaSink::Id> sinks_1_2 = {"sinkId1", "sinkId2"};
std::vector<CastMediaSource> sources_1 = {*source1};
std::vector<CastMediaSource> sources_1_2 = {*source1, *source2};
// Tracker returns available sinks even though sources aren't registered.
EXPECT_EQ(sinks_1, tracker_.GetAvailableSinks(*source1));
EXPECT_EQ(sinks_1, tracker_.GetAvailableSinks(*source2));
EXPECT_TRUE(tracker_.GetAvailableSinks(*source3).empty());
tracker_.RegisterSource(*source1);
// Only |source1| is registered for this app.
EXPECT_THAT(
tracker_.UpdateAppAvailability(
"sinkId2", "AAAAAAAA", {GetAppAvailabilityResult::kAvailable, Now()}),
CastMediaSourcesEqual(sources_1));
EXPECT_EQ(sinks_1_2, tracker_.GetAvailableSinks(*source1));
EXPECT_EQ(sinks_1_2, tracker_.GetAvailableSinks(*source2));
EXPECT_TRUE(tracker_.GetAvailableSinks(*source3).empty());
tracker_.RegisterSource(*source2);
EXPECT_THAT(tracker_.UpdateAppAvailability(
"sinkId2", "AAAAAAAA",
{GetAppAvailabilityResult::kUnavailable, Now()}),
CastMediaSourcesEqual(sources_1_2));
EXPECT_EQ(sinks_1, tracker_.GetAvailableSinks(*source1));
EXPECT_EQ(sinks_1, tracker_.GetAvailableSinks(*source2));
EXPECT_TRUE(tracker_.GetAvailableSinks(*source3).empty());
}
TEST_F(CastAppAvailabilityTrackerTest, RemoveResultsForSink) {
......@@ -149,20 +136,14 @@ TEST_F(CastAppAvailabilityTrackerTest, RemoveResultsForSink) {
EXPECT_EQ(GetAppAvailabilityResult::kAvailable,
tracker_.GetAvailability("sinkId1", "AAAAAAAA").first);
base::flat_set<MediaSink::Id> expected_sink_ids = {"sinkId1"};
EXPECT_EQ(expected_sink_ids, tracker_.GetAvailableSinks(*source1));
// Unrelated sink ID.
tracker_.RemoveResultsForSink("sinkId2");
EXPECT_EQ(GetAppAvailabilityResult::kAvailable,
tracker_.GetAvailability("sinkId1", "AAAAAAAA").first);
EXPECT_EQ(expected_sink_ids, tracker_.GetAvailableSinks(*source1));
expected_sink_ids.clear();
tracker_.RemoveResultsForSink("sinkId1");
EXPECT_EQ(GetAppAvailabilityResult::kUnknown,
tracker_.GetAvailability("sinkId1", "AAAAAAAA").first);
EXPECT_EQ(expected_sink_ids, tracker_.GetAvailableSinks(*source1));
}
} // namespace media_router
......@@ -35,6 +35,12 @@ bool ShouldRefreshAppAvailability(
return false;
}
bool HasAllRequiredCapabilities(int required_capabilities,
const MediaSinkInternal& sink) {
return (required_capabilities & sink.cast_data().capabilities) ==
required_capabilities;
}
} // namespace
CastAppDiscoveryServiceImpl::CastAppDiscoveryServiceImpl(
......@@ -68,11 +74,10 @@ CastAppDiscoveryServiceImpl::StartObservingMediaSinks(
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const MediaSource::Id& source_id = source.source_id();
// Returned cached results immediately, if available.
base::flat_set<MediaSink::Id> cached_sink_ids =
availability_tracker_.GetAvailableSinks(source);
if (!cached_sink_ids.empty())
callback.Run(source_id, GetSinksByIds(cached_sink_ids));
// Return cached results immediately, if available.
auto cached_sinks = GetAvailableSinks(source);
if (!cached_sinks.empty())
callback.Run(source_id, cached_sinks);
auto& callback_list = sink_queries_[source_id];
if (!callback_list) {
......@@ -214,21 +219,36 @@ void CastAppDiscoveryServiceImpl::UpdateSinkQueries(
auto it = sink_queries_.find(source_id);
if (it == sink_queries_.end())
continue;
base::flat_set<MediaSink::Id> sink_ids =
availability_tracker_.GetAvailableSinks(source);
it->second->Notify(source_id, GetSinksByIds(sink_ids));
it->second->Notify(source_id, GetAvailableSinks(source));
}
}
bool CastAppDiscoveryServiceImpl::SinkSupportsSource(
const MediaSinkInternal& sink,
const CastMediaSource& source) const {
const auto& app_infos = source.app_infos();
for (const auto& app_info : app_infos) {
auto required_capabilities = app_info.required_capabilities;
auto availability =
availability_tracker_.GetAvailability(sink.sink().id(), app_info.app_id)
.first;
if (availability == cast_channel::GetAppAvailabilityResult::kAvailable &&
HasAllRequiredCapabilities(required_capabilities, sink)) {
return true;
}
}
return false;
}
std::vector<MediaSinkInternal> CastAppDiscoveryServiceImpl::GetSinksByIds(
const base::flat_set<MediaSink::Id>& sink_ids) const {
std::vector<MediaSinkInternal> sinks;
for (const auto& sink_id : sink_ids) {
const MediaSinkInternal* sink = media_sink_service_->GetSinkById(sink_id);
if (sink)
sinks.push_back(*sink);
std::vector<MediaSinkInternal> CastAppDiscoveryServiceImpl::GetAvailableSinks(
const CastMediaSource& source) const {
const auto& sinks = media_sink_service_->GetSinks();
std::vector<MediaSinkInternal> available_sinks;
for (const auto& sink : sinks) {
if (SinkSupportsSource(sink.second, source))
available_sinks.push_back(sink.second);
}
return sinks;
return available_sinks;
}
} // namespace media_router
......@@ -114,9 +114,14 @@ class CastAppDiscoveryServiceImpl : public CastAppDiscoveryService,
// associated with |source|.
void MaybeRemoveSinkQueryEntry(const CastMediaSource& source);
// Gets a list of sinks corresponding to |sink_ids|.
std::vector<MediaSinkInternal> GetSinksByIds(
const base::flat_set<MediaSink::Id>& sink_ids) const;
// Returns true if |sink| supports |source|, based on both app availability
// and required capabilities.
bool SinkSupportsSource(const MediaSinkInternal& sink,
const CastMediaSource& source) const;
// Returns a list of sinks that supports |source|.
std::vector<MediaSinkInternal> GetAvailableSinks(
const CastMediaSource& source) const;
// Registered sink queries and their associated callbacks.
base::flat_map<MediaSource::Id, std::unique_ptr<SinkQueryCallbackList>>
......
......@@ -18,6 +18,7 @@
using cast_channel::GetAppAvailabilityResult;
using testing::_;
using testing::Invoke;
using testing::IsEmpty;
namespace media_router {
......@@ -32,9 +33,13 @@ class CastAppDiscoveryServiceTest : public testing::Test {
&socket_service_,
&media_sink_service_,
&clock_)),
source_a_1_(*CastMediaSource::From("cast:AAAAAAAA?clientId=1")),
source_a_2_(*CastMediaSource::From("cast:AAAAAAAA?clientId=2")),
source_b_1_(*CastMediaSource::From("cast:BBBBBBBB?clientId=1")) {
source_a_1_(*CastMediaSource::From(
"cast:AAAAAAAA?clientId=1&capabilities=video_out,audio_out")),
source_a_2_(*CastMediaSource::From(
"cast:AAAAAAAA?clientId=2&capabilities=video_out,audio_out")),
source_b_1_(*CastMediaSource::From(
"cast:BBBBBBBB?clientId=1&capabilities=video_out,audio_out")),
sink1_(CreateCastSink(1)) {
ON_CALL(socket_service_, GetSocket(_))
.WillByDefault(testing::Return(&socket_));
task_runner_->RunPendingTasks();
......@@ -75,6 +80,7 @@ class CastAppDiscoveryServiceTest : public testing::Test {
CastMediaSource source_a_1_;
CastMediaSource source_a_2_;
CastMediaSource source_b_1_;
MediaSinkInternal sink1_;
private:
DISALLOW_COPY_AND_ASSIGN(CastAppDiscoveryServiceTest);
......@@ -85,7 +91,6 @@ TEST_F(CastAppDiscoveryServiceTest, StartObservingMediaSinks) {
// Adding a sink after app registered causes app availability request to be
// sent.
MediaSinkInternal sink1 = CreateCastSink(1);
cast_channel::GetAppAvailabilityCallback cb;
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, "AAAAAAAA", _))
.WillOnce(
......@@ -94,7 +99,7 @@ TEST_F(CastAppDiscoveryServiceTest, StartObservingMediaSinks) {
cb = std::move(callback);
}));
AddOrUpdateSink(sink1);
AddOrUpdateSink(sink1_);
// Same app ID should not trigger another request.
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, _, _)).Times(0);
......@@ -103,7 +108,7 @@ TEST_F(CastAppDiscoveryServiceTest, StartObservingMediaSinks) {
base::BindRepeating(&CastAppDiscoveryServiceTest::OnSinkQueryUpdated,
base::Unretained(this)));
std::vector<MediaSinkInternal> sinks_1 = {sink1};
std::vector<MediaSinkInternal> sinks_1 = {sink1_};
EXPECT_CALL(*this, OnSinkQueryUpdated(source_a_1_.source_id(), sinks_1));
EXPECT_CALL(*this, OnSinkQueryUpdated(source_a_2_.source_id(), sinks_1));
std::move(cb).Run("AAAAAAAA", GetAppAvailabilityResult::kAvailable);
......@@ -111,9 +116,8 @@ TEST_F(CastAppDiscoveryServiceTest, StartObservingMediaSinks) {
// No more updates for |source_a_1_|.
subscription1.reset();
EXPECT_CALL(*this, OnSinkQueryUpdated(source_a_1_.source_id(), _)).Times(0);
EXPECT_CALL(*this,
OnSinkQueryUpdated(source_a_2_.source_id(), testing::IsEmpty()));
RemoveSink(sink1);
EXPECT_CALL(*this, OnSinkQueryUpdated(source_a_2_.source_id(), IsEmpty()));
RemoveSink(sink1_);
}
TEST_F(CastAppDiscoveryServiceTest, SinkQueryUpdatedOnSinkUpdate) {
......@@ -121,7 +125,6 @@ TEST_F(CastAppDiscoveryServiceTest, SinkQueryUpdatedOnSinkUpdate) {
// Adding a sink after app registered causes app availability request to be
// sent.
MediaSinkInternal sink1 = CreateCastSink(1);
cast_channel::GetAppAvailabilityCallback cb;
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, "AAAAAAAA", _))
.WillOnce(
......@@ -130,25 +133,24 @@ TEST_F(CastAppDiscoveryServiceTest, SinkQueryUpdatedOnSinkUpdate) {
cb = std::move(callback);
}));
AddOrUpdateSink(sink1);
AddOrUpdateSink(sink1_);
// Query now includes |sink1|.
std::vector<MediaSinkInternal> sinks_1 = {sink1};
// Query now includes |sink1_|.
std::vector<MediaSinkInternal> sinks_1 = {sink1_};
EXPECT_CALL(*this, OnSinkQueryUpdated(source_a_1_.source_id(), sinks_1));
std::move(cb).Run("AAAAAAAA", GetAppAvailabilityResult::kAvailable);
// Updating |sink1| causes |source_a_1_| query to be updated.
sink1.sink().set_name("Updated name");
sinks_1 = {sink1};
sink1_.sink().set_name("Updated name");
sinks_1 = {sink1_};
EXPECT_CALL(*this, OnSinkQueryUpdated(source_a_1_.source_id(), sinks_1));
AddOrUpdateSink(sink1);
AddOrUpdateSink(sink1_);
}
TEST_F(CastAppDiscoveryServiceTest, Refresh) {
auto subscription1 = StartObservingMediaSinksInitially(source_a_1_);
auto subscription2 = StartObservingMediaSinksInitially(source_b_1_);
MediaSinkInternal sink1 = CreateCastSink(1);
EXPECT_CALL(*this, OnSinkQueryUpdated(_, _));
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, "AAAAAAAA", _))
.WillOnce(Invoke([](cast_channel::CastSocket*, const std::string& app_id,
......@@ -160,7 +162,7 @@ TEST_F(CastAppDiscoveryServiceTest, Refresh) {
cast_channel::GetAppAvailabilityCallback& callback) {
std::move(callback).Run(app_id, GetAppAvailabilityResult::kUnknown);
}));
AddOrUpdateSink(sink1);
AddOrUpdateSink(sink1_);
MediaSinkInternal sink2 = CreateCastSink(2);
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, "AAAAAAAA", _))
......@@ -196,9 +198,8 @@ TEST_F(CastAppDiscoveryServiceTest, Refresh) {
TEST_F(CastAppDiscoveryServiceTest, StartObservingMediaSinksAfterSinkAdded) {
// No registered apps.
MediaSinkInternal sink1 = CreateCastSink(1);
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, _, _)).Times(0);
AddOrUpdateSink(sink1);
AddOrUpdateSink(sink1_);
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, "AAAAAAAA", _));
auto subscription1 = app_discovery_service_->StartObservingMediaSinks(
......@@ -225,7 +226,6 @@ TEST_F(CastAppDiscoveryServiceTest, StartObservingMediaSinksCachedValue) {
// Adding a sink after app registered causes app availability request to be
// sent.
MediaSinkInternal sink1 = CreateCastSink(1);
cast_channel::GetAppAvailabilityCallback cb;
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, "AAAAAAAA", _))
.WillOnce(
......@@ -233,9 +233,9 @@ TEST_F(CastAppDiscoveryServiceTest, StartObservingMediaSinksCachedValue) {
cast_channel::GetAppAvailabilityCallback& callback) {
cb = std::move(callback);
}));
AddOrUpdateSink(sink1);
AddOrUpdateSink(sink1_);
std::vector<MediaSinkInternal> sinks_1 = {sink1};
std::vector<MediaSinkInternal> sinks_1 = {sink1_};
EXPECT_CALL(*this, OnSinkQueryUpdated(source_a_1_.source_id(), sinks_1));
std::move(cb).Run("AAAAAAAA", GetAppAvailabilityResult::kAvailable);
......@@ -249,7 +249,7 @@ TEST_F(CastAppDiscoveryServiceTest, StartObservingMediaSinksCachedValue) {
base::Unretained(this)));
// Same source as |source_a_1_|. The callback will be invoked.
auto source3 = CastMediaSource::From("cast:AAAAAAAA?clientId=1");
auto source3 = CastMediaSource::From(source_a_1_.source_id());
ASSERT_TRUE(source3);
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, _, _)).Times(0);
EXPECT_CALL(*this, OnSinkQueryUpdated(source_a_1_.source_id(), sinks_1));
......@@ -264,14 +264,13 @@ TEST_F(CastAppDiscoveryServiceTest, AvailabilityUnknownOrUnavailable) {
// Adding a sink after app registered causes app availability request to be
// sent.
MediaSinkInternal sink1 = CreateCastSink(1);
EXPECT_CALL(*this, OnSinkQueryUpdated(_, _)).Times(0);
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, "AAAAAAAA", _))
.WillOnce(Invoke([](cast_channel::CastSocket*, const std::string&,
cast_channel::GetAppAvailabilityCallback& callback) {
std::move(callback).Run("AAAAAAAA", GetAppAvailabilityResult::kUnknown);
}));
AddOrUpdateSink(sink1);
AddOrUpdateSink(sink1_);
// Sink updated and unknown app availability will cause request to be sent
// again.
......@@ -282,20 +281,43 @@ TEST_F(CastAppDiscoveryServiceTest, AvailabilityUnknownOrUnavailable) {
std::move(callback).Run("AAAAAAAA",
GetAppAvailabilityResult::kUnavailable);
}));
AddOrUpdateSink(sink1);
AddOrUpdateSink(sink1_);
// Known availability -- no request sent.
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, "AAAAAAAA", _))
.Times(0);
AddOrUpdateSink(sink1);
AddOrUpdateSink(sink1_);
// Removing the sink will also remove previous availability information.
// Next time sink is added, request will be sent.
EXPECT_CALL(*this, OnSinkQueryUpdated(_, _)).Times(0);
RemoveSink(sink1);
RemoveSink(sink1_);
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, "AAAAAAAA", _));
AddOrUpdateSink(sink1);
AddOrUpdateSink(sink1_);
}
TEST_F(CastAppDiscoveryServiceTest, CapabilitiesFiltering) {
// Make |sink1_| an audio only device.
sink1_.cast_data().capabilities =
cast_channel::CastDeviceCapability::AUDIO_OUT;
AddOrUpdateSink(sink1_);
cast_channel::GetAppAvailabilityCallback cb;
EXPECT_CALL(message_handler_, DoRequestAppAvailability(_, "AAAAAAAA", _))
.WillOnce(testing::WithArg<2>(
[&cb](cast_channel::GetAppAvailabilityCallback& callback) {
cb = std::move(callback);
}));
auto subscription1 = app_discovery_service_->StartObservingMediaSinks(
source_a_1_,
base::BindRepeating(&CastAppDiscoveryServiceTest::OnSinkQueryUpdated,
base::Unretained(this)));
// Even though the app is available, the sink does not fulfill the required
// capabilities.
EXPECT_CALL(*this, OnSinkQueryUpdated(source_a_1_.source_id(), IsEmpty()));
std::move(cb).Run("AAAAAAAA", GetAppAvailabilityResult::kAvailable);
}
} // namespace media_router
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