1 // Copyright 2020 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "cast/sender/cast_app_discovery_service_impl.h"
6
7 #include <algorithm>
8 #include <chrono>
9 #include <utility>
10
11 #include "cast/sender/public/cast_media_source.h"
12 #include "util/osp_logging.h"
13
14 namespace openscreen {
15 namespace cast {
16 namespace {
17
18 // The minimum time that must elapse before an app availability result can be
19 // force refreshed.
20 static constexpr std::chrono::minutes kRefreshThreshold =
21 std::chrono::minutes(1);
22
23 } // namespace
24
CastAppDiscoveryServiceImpl(CastPlatformClient * platform_client,ClockNowFunctionPtr clock)25 CastAppDiscoveryServiceImpl::CastAppDiscoveryServiceImpl(
26 CastPlatformClient* platform_client,
27 ClockNowFunctionPtr clock)
28 : platform_client_(platform_client), clock_(clock), weak_factory_(this) {
29 OSP_DCHECK(platform_client_);
30 OSP_DCHECK(clock_);
31 }
32
~CastAppDiscoveryServiceImpl()33 CastAppDiscoveryServiceImpl::~CastAppDiscoveryServiceImpl() {
34 OSP_CHECK_EQ(avail_queries_.size(), 0u);
35 }
36
37 CastAppDiscoveryService::Subscription
StartObservingAvailability(const CastMediaSource & source,AvailabilityCallback callback)38 CastAppDiscoveryServiceImpl::StartObservingAvailability(
39 const CastMediaSource& source,
40 AvailabilityCallback callback) {
41 const std::string& source_id = source.source_id();
42
43 // Return cached results immediately, if available.
44 std::vector<std::string> cached_device_ids =
45 availability_tracker_.GetAvailableDevices(source);
46 if (!cached_device_ids.empty()) {
47 callback(source, GetReceiversByIds(cached_device_ids));
48 }
49
50 auto& callbacks = avail_queries_[source_id];
51 uint32_t query_id = next_avail_query_id_++;
52 callbacks.push_back({query_id, std::move(callback)});
53 if (callbacks.size() == 1) {
54 // NOTE: Even though we retain availability results for an app unregistered
55 // from the tracker, we will refresh the results when the app is
56 // re-registered.
57 std::vector<std::string> new_app_ids =
58 availability_tracker_.RegisterSource(source);
59 for (const auto& app_id : new_app_ids) {
60 for (const auto& entry : receivers_by_id_) {
61 RequestAppAvailability(entry.first, app_id);
62 }
63 }
64 }
65
66 return Subscription(this, query_id);
67 }
68
Refresh()69 void CastAppDiscoveryServiceImpl::Refresh() {
70 const auto app_ids = availability_tracker_.GetRegisteredApps();
71 for (const auto& entry : receivers_by_id_) {
72 for (const auto& app_id : app_ids) {
73 RequestAppAvailability(entry.first, app_id);
74 }
75 }
76 }
77
AddOrUpdateReceiver(const ServiceInfo & receiver)78 void CastAppDiscoveryServiceImpl::AddOrUpdateReceiver(
79 const ServiceInfo& receiver) {
80 const std::string& device_id = receiver.unique_id;
81 receivers_by_id_[device_id] = receiver;
82
83 // Any queries that currently contain this receiver should be updated.
84 UpdateAvailabilityQueries(
85 availability_tracker_.GetSupportedSources(device_id));
86
87 for (const std::string& app_id : availability_tracker_.GetRegisteredApps()) {
88 RequestAppAvailability(device_id, app_id);
89 }
90 }
91
RemoveReceiver(const ServiceInfo & receiver)92 void CastAppDiscoveryServiceImpl::RemoveReceiver(const ServiceInfo& receiver) {
93 const std::string& device_id = receiver.unique_id;
94 receivers_by_id_.erase(device_id);
95 UpdateAvailabilityQueries(
96 availability_tracker_.RemoveResultsForDevice(device_id));
97 }
98
RequestAppAvailability(const std::string & device_id,const std::string & app_id)99 void CastAppDiscoveryServiceImpl::RequestAppAvailability(
100 const std::string& device_id,
101 const std::string& app_id) {
102 if (ShouldRefreshAppAvailability(device_id, app_id, clock_())) {
103 platform_client_->RequestAppAvailability(
104 device_id, app_id,
105 [self = weak_factory_.GetWeakPtr(), device_id](
106 const std::string& app_id, AppAvailabilityResult availability) {
107 if (self) {
108 self->UpdateAppAvailability(device_id, app_id, availability);
109 }
110 });
111 }
112 }
113
UpdateAppAvailability(const std::string & device_id,const std::string & app_id,AppAvailabilityResult availability)114 void CastAppDiscoveryServiceImpl::UpdateAppAvailability(
115 const std::string& device_id,
116 const std::string& app_id,
117 AppAvailabilityResult availability) {
118 if (receivers_by_id_.find(device_id) == receivers_by_id_.end()) {
119 return;
120 }
121
122 OSP_DVLOG << "App " << app_id << " on receiver " << device_id << " is "
123 << ToString(availability);
124
125 UpdateAvailabilityQueries(availability_tracker_.UpdateAppAvailability(
126 device_id, app_id, {availability, clock_()}));
127 }
128
UpdateAvailabilityQueries(const std::vector<CastMediaSource> & sources)129 void CastAppDiscoveryServiceImpl::UpdateAvailabilityQueries(
130 const std::vector<CastMediaSource>& sources) {
131 for (const auto& source : sources) {
132 const std::string& source_id = source.source_id();
133 auto it = avail_queries_.find(source_id);
134 if (it == avail_queries_.end())
135 continue;
136 std::vector<std::string> device_ids =
137 availability_tracker_.GetAvailableDevices(source);
138 std::vector<ServiceInfo> receivers = GetReceiversByIds(device_ids);
139 for (const auto& callback : it->second) {
140 callback.callback(source, receivers);
141 }
142 }
143 }
144
GetReceiversByIds(const std::vector<std::string> & device_ids) const145 std::vector<ServiceInfo> CastAppDiscoveryServiceImpl::GetReceiversByIds(
146 const std::vector<std::string>& device_ids) const {
147 std::vector<ServiceInfo> receivers;
148 for (const std::string& device_id : device_ids) {
149 auto entry = receivers_by_id_.find(device_id);
150 if (entry != receivers_by_id_.end()) {
151 receivers.push_back(entry->second);
152 }
153 }
154 return receivers;
155 }
156
ShouldRefreshAppAvailability(const std::string & device_id,const std::string & app_id,Clock::time_point now) const157 bool CastAppDiscoveryServiceImpl::ShouldRefreshAppAvailability(
158 const std::string& device_id,
159 const std::string& app_id,
160 Clock::time_point now) const {
161 // TODO(btolsch): Consider an exponential backoff mechanism instead.
162 // Receivers will typically respond with "unavailable" immediately after boot
163 // and then become available 10-30 seconds later.
164 auto availability = availability_tracker_.GetAvailability(device_id, app_id);
165 switch (availability.availability) {
166 case AppAvailabilityResult::kAvailable:
167 return false;
168 case AppAvailabilityResult::kUnavailable:
169 return (now - availability.time) > kRefreshThreshold;
170 // TODO(btolsch): Should there be a background task for periodically
171 // refreshing kUnknown (or even kUnavailable) results?
172 case AppAvailabilityResult::kUnknown:
173 return true;
174 }
175
176 OSP_NOTREACHED();
177 }
178
RemoveAvailabilityCallback(uint32_t id)179 void CastAppDiscoveryServiceImpl::RemoveAvailabilityCallback(uint32_t id) {
180 for (auto entry = avail_queries_.begin(); entry != avail_queries_.end();
181 ++entry) {
182 const std::string& source_id = entry->first;
183 auto& callbacks = entry->second;
184 auto it =
185 std::find_if(callbacks.begin(), callbacks.end(),
186 [id](const AvailabilityCallbackEntry& callback_entry) {
187 return callback_entry.id == id;
188 });
189 if (it != callbacks.end()) {
190 callbacks.erase(it);
191 if (callbacks.empty()) {
192 availability_tracker_.UnregisterSource(source_id);
193 avail_queries_.erase(entry);
194 }
195 return;
196 }
197 }
198 }
199
200 } // namespace cast
201 } // namespace openscreen
202