// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromecast/browser/cast_session_id_map.h"
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "chromecast/base/bind_to_task_runner.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
namespace chromecast {
namespace shell {
// A small class that listens for the destruction of a WebContents, and forwards
// the event to the CastSessionIdMap with the appropriate group_id.
class CastSessionIdMap::GroupObserver : content::WebContentsObserver {
public:
using GroupDestroyedCallback =
base::OnceCallback<void(base::UnguessableToken)>;
GroupObserver(content::WebContents* web_contents,
GroupDestroyedCallback destroyed_callback)
: destroyed_callback_(std::move(destroyed_callback)),
group_id_(web_contents->GetAudioGroupId()) {
content::WebContentsObserver::Observe(web_contents);
}
private:
// content::WebContentsObserver implementation:
void WebContentsDestroyed() override {
DCHECK(destroyed_callback_);
content::WebContentsObserver::Observe(nullptr);
std::move(destroyed_callback_).Run(group_id_);
}
GroupDestroyedCallback destroyed_callback_;
base::UnguessableToken group_id_;
};
// static
CastSessionIdMap* CastSessionIdMap::GetInstance(
base::SequencedTaskRunner* task_runner) {
static base::NoDestructor<CastSessionIdMap> map(task_runner);
return map.get();
}
void CastSessionIdMap::SetAppProperties(std::string session_id,
bool is_audio_app,
content::WebContents* web_contents) {
base::UnguessableToken group_id = web_contents->GetAudioGroupId();
// Unretained is safe here, because the singleton CastSessionIdMap never gets
// destroyed.
auto destroyed_callback = base::BindOnce(&CastSessionIdMap::OnGroupDestroyed,
base::Unretained(GetInstance()));
auto group_observer = std::make_unique<GroupObserver>(
web_contents, std::move(destroyed_callback));
SetAppPropertiesInternal(session_id, is_audio_app, group_id,
std::move(group_observer));
}
void CastSessionIdMap::SetGroupInfo(std::string session_id, bool is_group) {
if (!task_runner_->RunsTasksInCurrentSequence()) {
// Unretained is safe here, because the singleton CastSessionIdMap never
// gets destroyed.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CastSessionIdMap::SetGroupInfo, base::Unretained(this),
std::move(session_id), is_group));
return;
}
group_info_mapping_.emplace(session_id, is_group);
}
CastSessionIdMap::CastSessionIdMap(base::SequencedTaskRunner* task_runner)
: task_runner_(task_runner) {
DCHECK(task_runner_);
DETACH_FROM_SEQUENCE(sequence_checker_);
}
CastSessionIdMap::~CastSessionIdMap() = default;
void CastSessionIdMap::SetAppPropertiesInternal(
std::string session_id,
bool is_audio_app,
base::UnguessableToken group_id,
std::unique_ptr<GroupObserver> group_observer) {
if (!task_runner_->RunsTasksInCurrentSequence()) {
// Unretained is safe here, because the singleton CastSessionIdMap never
// gets destroyed.
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&CastSessionIdMap::SetAppPropertiesInternal,
base::Unretained(this), std::move(session_id),
is_audio_app, group_id, std::move(group_observer)));
return;
}
// This check is required to bind to the current sequence.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(GetSessionId(group_id.ToString()).empty());
DCHECK(group_observer);
DVLOG(1) << "Mapping session_id=" << session_id
<< " to group_id=" << group_id.ToString();
auto group_data = std::make_pair(session_id, std::move(group_observer));
mapping_.emplace(group_id.ToString(), std::move(group_data));
application_capability_mapping_.emplace(session_id, is_audio_app);
}
std::string CastSessionIdMap::GetSessionId(const std::string& group_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = mapping_.find(group_id);
if (it != mapping_.end())
return it->second.first;
return std::string();
}
bool CastSessionIdMap::IsAudioOnlySession(const std::string& session_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = application_capability_mapping_.find(session_id);
if (it != application_capability_mapping_.end())
return it->second;
return false;
}
bool CastSessionIdMap::IsGroup(const std::string& session_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = group_info_mapping_.find(session_id);
if (it != group_info_mapping_.end())
return it->second;
return false;
}
void CastSessionIdMap::IsAudioOnlySessionAsync(
const std::string& session_id,
IsAudioOnlySessionAsyncCallback callback) {
if (!task_runner_->RunsTasksInCurrentSequence()) {
// Unretained is safe here, because the singleton CastSessionIdMap never
// gets destroyed.
task_runner_->PostTask(
FROM_HERE, base::BindOnce(&CastSessionIdMap::IsAudioOnlySessionAsync,
base::Unretained(this), session_id,
BindToCurrentSequence(std::move(callback))));
return;
}
std::move(callback).Run(IsAudioOnlySession(session_id));
}
void CastSessionIdMap::OnGroupDestroyed(base::UnguessableToken group_id) {
// Unretained is safe here, because the singleton CastSessionIdMap never gets
// destroyed.
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&CastSessionIdMap::RemoveGroupId,
base::Unretained(this), group_id));
}
void CastSessionIdMap::RemoveGroupId(base::UnguessableToken group_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = mapping_.find(group_id.ToString());
if (it != mapping_.end()) {
DVLOG(1) << "Removing mapping for session_id=" << it->second.first
<< " to group_id=" << group_id.ToString();
auto it_app = application_capability_mapping_.find(it->second.first);
if (it_app != application_capability_mapping_.end()) {
application_capability_mapping_.erase(it_app);
}
auto it_group = group_info_mapping_.find(it->second.first);
if (it_group != group_info_mapping_.end()) {
group_info_mapping_.erase(it_group);
}
mapping_.erase(it);
}
}
} // namespace shell
} // namespace chromecast