chromium/chromecast/browser/cast_session_id_map.cc

// 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