Commit e6157156 authored by mark a. foltz's avatar mark a. foltz Committed by Commit Bot

[Media Router] Add Cast MRP state to chrome://media-router-internals.

This patch adds the following:

- GetState(), a new MediaRouteProvider API to allow the Media Router to
  query the internal state of MRPs
- GetProviderState(), a new MediaRouter API to expose this to chrome/
- Implementation of GetState() in the Cast MRP
- A call to GetState(CAST) from the Media Router Internals WebUI,
  which renders the results as JSON.

The initial use case is to allow Cast developers to access the Cast
session ID from a running session.  However, this can be extended in
the future for feedback reports and debugging.

This also adds some basic styling to the internals page for readability.
(Much more could be done here.)

Bug: 1046078
Change-Id: I20d1c2123486c70e0f5ec24243ea1c4ec016dc52
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2084675
Commit-Queue: mark a. foltz <mfoltz@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarTakumi Fujimoto <takumif@chromium.org>
Cr-Commit-Position: refs/heads/master@{#748313}
parent 819df6c0
......@@ -18,6 +18,7 @@
#include "chrome/browser/media/cast_remoting_connector.h"
#include "chrome/browser/media/router/route_message_observer.h"
#include "chrome/common/media_router/media_route.h"
#include "chrome/common/media_router/media_route_provider_helper.h"
#include "chrome/common/media_router/media_sink.h"
#include "chrome/common/media_router/media_source.h"
#include "chrome/common/media_router/mojom/media_router.mojom.h"
......@@ -216,10 +217,18 @@ class MediaRouter : public KeyedService {
CastRemotingConnector* remoting_source) = 0;
virtual void UnregisterRemotingSource(SessionID tab_id) = 0;
// Returns media router state as a JSON string represented by base::Vaule.
// Used by media-router-internals page.
// Returns media router state as a JSON string represented by base::Value.
// Includes known sinks and sink compatibility with media sources.
// Used by chrome://media-router-internals.
virtual base::Value GetState() const = 0;
// Returns the media route provider state for |provider_id| via |callback|.
// Includes details about routes/sessions owned by the MRP.
// Used by chrome://media-router-internals.
virtual void GetProviderState(
MediaRouteProviderId provider_id,
mojom::MediaRouteProvider::GetStateCallback callback) const = 0;
private:
friend class IssuesObserver;
friend class MediaSinksObserver;
......
......@@ -189,4 +189,11 @@ base::Value MediaRouterBase::GetState() const {
return base::Value(base::Value::Type::DICTIONARY);
}
void MediaRouterBase::GetProviderState(
MediaRouteProviderId provider_id,
mojom::MediaRouteProvider::GetStateCallback callback) const {
NOTREACHED() << "Should not invoke MediaRouterBase::GetProviderState()";
std::move(callback).Run(mojom::ProviderStatePtr());
}
} // namespace media_router
......@@ -52,6 +52,9 @@ class MediaRouterBase : public MediaRouter {
CastRemotingConnector* remoting_source) override;
void UnregisterRemotingSource(SessionID tab_id) override;
base::Value GetState() const override;
void GetProviderState(
MediaRouteProviderId provider_id,
mojom::MediaRouteProvider::GetStateCallback callback) const override;
protected:
FRIEND_TEST_ALL_PREFIXES(MediaRouterMojoImplTest,
......
......@@ -61,6 +61,17 @@ base::Value MediaRouterDesktop::GetState() const {
return media_sink_service_status_.GetStatusAsValue();
}
void MediaRouterDesktop::GetProviderState(
MediaRouteProviderId provider_id,
mojom::MediaRouteProvider::GetStateCallback callback) const {
if (provider_id == MediaRouteProviderId::CAST &&
CastMediaRouteProviderEnabled()) {
media_route_providers_.at(provider_id)->GetState(std::move(callback));
} else {
std::move(callback).Run(mojom::ProviderStatePtr());
}
}
base::Optional<MediaRouteProviderId>
MediaRouterDesktop::GetProviderIdForPresentation(
const std::string& presentation_id) {
......
......@@ -57,6 +57,9 @@ class MediaRouterDesktop : public MediaRouterMojoImpl {
// MediaRouter implementation.
void OnUserGesture() override;
base::Value GetState() const override;
void GetProviderState(
MediaRouteProviderId provider_id,
mojom::MediaRouteProvider::GetStateCallback callback) const override;
protected:
// MediaRouterMojoImpl override:
......
......@@ -107,6 +107,7 @@ class CastActivityManager : public CastActivityManagerBase,
const MediaRoute* GetRoute(const MediaRoute::Id& route_id) const;
std::vector<MediaRoute> GetRoutes() const;
CastSessionTracker* GetCastSessionTracker() const { return session_tracker_; }
// cast_channel::CastMessageHandler::Observer overrides.
void OnAppMessage(int channel_id,
......
......@@ -12,6 +12,7 @@
#include "base/task/post_task.h"
#include "chrome/browser/media/router/providers/cast/cast_activity_manager.h"
#include "chrome/browser/media/router/providers/cast/cast_internal_message_util.h"
#include "chrome/browser/media/router/providers/cast/cast_session_tracker.h"
#include "chrome/common/media_router/media_source.h"
#include "chrome/common/media_router/mojom/media_router.mojom.h"
#include "chrome/common/media_router/providers/cast/cast_media_source.h"
......@@ -268,6 +269,27 @@ void CastMediaRouteProvider::CreateMediaRouteController(
route_id, std::move(media_controller), std::move(observer)));
}
void CastMediaRouteProvider::GetState(GetStateCallback callback) {
if (!activity_manager_) {
std::move(callback).Run(mojom::ProviderState::New());
}
const CastSessionTracker::SessionMap& sessions =
activity_manager_->GetCastSessionTracker()->GetSessions();
mojom::CastProviderStatePtr cast_state(mojom::CastProviderState::New());
for (const auto& session : sessions) {
if (!session.second)
continue;
mojom::CastSessionStatePtr session_state(mojom::CastSessionState::New());
session_state->sink_id = session.first;
session_state->app_id = session.second->app_id();
session_state->session_id = session.second->session_id();
session_state->route_description = session.second->GetRouteDescription();
cast_state->session_state.emplace_back(std::move(session_state));
}
std::move(callback).Run(
mojom::ProviderState::NewCastProviderState(std::move(cast_state)));
}
void CastMediaRouteProvider::OnSinkQueryUpdated(
const MediaSource::Id& source_id,
const std::vector<MediaSinkInternal>& sinks) {
......
......@@ -98,6 +98,7 @@ class CastMediaRouteProvider : public mojom::MediaRouteProvider {
mojo::PendingReceiver<mojom::MediaController> media_controller,
mojo::PendingRemote<mojom::MediaStatusObserver> observer,
CreateMediaRouteControllerCallback callback) override;
void GetState(GetStateCallback callback) override;
private:
void Init(mojo::PendingReceiver<mojom::MediaRouteProvider> receiver,
......
......@@ -206,4 +206,38 @@ TEST_F(CastMediaRouteProviderTest, TerminateRoute) {
base::Unretained(this)));
}
TEST_F(CastMediaRouteProviderTest, GetState) {
MediaSinkInternal sink = CreateCastSink(1);
media_sink_service_.AddOrUpdateSink(sink);
session_tracker_->HandleReceiverStatusMessage(sink, base::test::ParseJson(R"({
"status": {
"applications": [{
"appId": "ABCDEFGH",
"displayName": "App display name",
"namespaces": [
{"name": "urn:x-cast:com.google.cast.media"},
{"name": "urn:x-cast:com.google.foo"}
],
"sessionId": "theSessionId",
"statusText":"App status",
"transportId":"theTransportId"
}]
}
})"));
provider_->GetState(base::BindOnce([](mojom::ProviderStatePtr state) {
ASSERT_TRUE(state);
ASSERT_TRUE(state->is_cast_provider_state());
const mojom::CastProviderState& cast_state =
*(state->get_cast_provider_state());
ASSERT_EQ(cast_state.session_state.size(), 1UL);
const mojom::CastSessionState& session_state =
*(cast_state.session_state[0]);
EXPECT_EQ(session_state.sink_id, "cast:<id1>");
EXPECT_EQ(session_state.app_id, "ABCDEFGH");
EXPECT_EQ(session_state.session_id, "theSessionId");
EXPECT_EQ(session_state.route_description, "App status");
}));
}
} // namespace media_router
......@@ -103,6 +103,7 @@ class CastSessionTracker : public MediaSinkServiceBase::Observer,
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(CastSessionTracker);
FRIEND_TEST_ALL_PREFIXES(CastActivityRecordTest, SendAppMessageToReceiver);
FRIEND_TEST_ALL_PREFIXES(CastMediaRouteProviderTest, GetState);
FRIEND_TEST_ALL_PREFIXES(CastSessionTrackerTest, RemoveSession);
FRIEND_TEST_ALL_PREFIXES(CastSessionTrackerTest,
HandleMediaStatusMessageBasic);
......
......@@ -517,6 +517,11 @@ void DialMediaRouteProvider::CreateMediaRouteController(
std::move(callback).Run(false);
}
void DialMediaRouteProvider::GetState(GetStateCallback callback) {
NOTIMPLEMENTED();
std::move(callback).Run(mojom::ProviderStatePtr());
}
void DialMediaRouteProvider::SetActivityManagerForTest(
std::unique_ptr<DialActivityManager> activity_manager) {
DCHECK(!activity_manager_);
......
......@@ -114,6 +114,7 @@ class DialMediaRouteProvider : public mojom::MediaRouteProvider,
mojo::PendingReceiver<mojom::MediaController> media_controller,
mojo::PendingRemote<mojom::MediaStatusObserver> observer,
CreateMediaRouteControllerCallback callback) override;
void GetState(GetStateCallback callback) override;
void SetActivityManagerForTest(
std::unique_ptr<DialActivityManager> activity_manager);
......
......@@ -221,6 +221,11 @@ void ExtensionMediaRouteProviderProxy::CreateMediaRouteController(
MediaRouteProviderWakeReason::CREATE_MEDIA_ROUTE_CONTROLLER);
}
void ExtensionMediaRouteProviderProxy::GetState(GetStateCallback callback) {
NOTIMPLEMENTED();
std::move(callback).Run(mojom::ProviderStatePtr());
}
void ExtensionMediaRouteProviderProxy::RegisterMediaRouteProvider(
mojo::PendingRemote<mojom::MediaRouteProvider> media_route_provider) {
media_route_provider_.reset();
......
......@@ -93,6 +93,7 @@ class ExtensionMediaRouteProviderProxy : public mojom::MediaRouteProvider {
mojo::PendingReceiver<mojom::MediaController> media_controller,
mojo::PendingRemote<mojom::MediaStatusObserver> observer,
CreateMediaRouteControllerCallback callback) override;
void GetState(GetStateCallback callback) override;
// Sets the MediaRouteProvider to forward calls to. Notifies
// |request_manager_| that Mojo connections are ready.
......
......@@ -286,6 +286,11 @@ void WiredDisplayMediaRouteProvider::CreateMediaRouteController(
std::move(callback).Run(true);
}
void WiredDisplayMediaRouteProvider::GetState(GetStateCallback callback) {
NOTIMPLEMENTED();
std::move(callback).Run(mojom::ProviderStatePtr());
}
void WiredDisplayMediaRouteProvider::OnDidProcessDisplayChanges() {
NotifySinkObservers();
}
......
......@@ -100,6 +100,7 @@ class WiredDisplayMediaRouteProvider : public mojom::MediaRouteProvider,
mojo::PendingReceiver<mojom::MediaController> media_controller,
mojo::PendingRemote<mojom::MediaStatusObserver> observer,
CreateMediaRouteControllerCallback callback) override;
void GetState(GetStateCallback callback) override;
// display::DisplayObserver:
void OnDidProcessDisplayChanges() override;
......
......@@ -155,6 +155,10 @@ class MockMediaRouteProvider : public mojom::MediaRouteProvider {
mojo::PendingReceiver<mojom::MediaController>& media_controller,
mojo::PendingRemote<mojom::MediaStatusObserver>& observer,
CreateMediaRouteControllerCallback& callback));
void GetState(GetStateCallback callback) override {
GetStateInternal(callback);
}
MOCK_METHOD1(GetStateInternal, void(const GetStateCallback& callback));
// These methods execute the callbacks with the success or timeout result
// code. If the callback takes a route, the route set in SetRouteToReturn() is
......
......@@ -2,6 +2,11 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
#sink-status-div {
body {
font-size: large;
}
.status {
font-family: monospace;
white-space: pre-wrap;
}
......@@ -2,10 +2,14 @@
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
<link rel="stylesheet" href="media_router_internals.css">
<script type="module" src="media_router_internals.js"></script>
</head>
<body>
<div id="sink-status-div"></div>
<h1>Media Router</h1>
<div id="sink-status-div" class="status"></div>
<h1>Cast Media Route Provider</h1>
<div id="cast-status-div" class="status"></div>
</body>
</html>
......@@ -7,8 +7,12 @@ import {$} from 'chrome://resources/js/util.m.js';
// Handles user events for the Media Router Internals UI.
document.addEventListener('DOMContentLoaded', function() {
sendWithPromise('getStatus').then(status => {
const jsonStatus = JSON.stringify(status, null, /* spacing level = */ 2);
$('sink-status-div').textContent = jsonStatus;
sendWithPromise('getState').then(status => {
$('sink-status-div').textContent =
JSON.stringify(status, null, /* spacing level = */ 2);
});
sendWithPromise('getProviderState', 'CAST').then(status => {
$('cast-status-div').textContent =
JSON.stringify(status, null, /* spacing level = */ 2);
});
});
......@@ -1446,6 +1446,7 @@ jumbo_static_library("ui") {
"//chrome/browser/ui/webui/app_management:mojo_bindings",
"//chrome/common:buildflags",
"//chrome/common:search_mojom",
"//chrome/common/media_router/mojom:media_router",
"//chrome/common/search:generate_chrome_colors_info",
"//chrome/common/themes:autogenerated_theme_util",
"//chrome/services/app_service/public/cpp:app_update",
......
......@@ -5,30 +5,84 @@
#include "chrome/browser/ui/webui/media_router/media_router_internals_webui_message_handler.h"
#include "base/bind.h"
#include "base/values.h"
#include "chrome/browser/media/router/media_router.h"
namespace media_router {
namespace {
base::Value CastProviderStateToValue(const mojom::CastProviderState& state) {
base::Value result(base::Value::Type::LIST);
for (const mojom::CastSessionStatePtr& session : state.session_state) {
base::Value session_value(base::Value::Type::DICTIONARY);
session_value.SetStringKey("sink_id", session->sink_id);
session_value.SetStringKey("app_id", session->app_id);
session_value.SetStringKey("session_id", session->session_id);
session_value.SetStringKey("route_description", session->route_description);
result.Append(std::move(session_value));
}
return result;
}
} // namespace
MediaRouterInternalsWebUIMessageHandler::
MediaRouterInternalsWebUIMessageHandler(const MediaRouter* router)
: router_(router) {
DCHECK(router_);
}
MediaRouterInternalsWebUIMessageHandler::
~MediaRouterInternalsWebUIMessageHandler() = default;
void MediaRouterInternalsWebUIMessageHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"getStatus",
"getState", base::BindRepeating(
&MediaRouterInternalsWebUIMessageHandler::HandleGetState,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getProviderState",
base::BindRepeating(
&MediaRouterInternalsWebUIMessageHandler::HandleGetStatus,
&MediaRouterInternalsWebUIMessageHandler::HandleGetProviderState,
base::Unretained(this)));
}
void MediaRouterInternalsWebUIMessageHandler::HandleGetStatus(
void MediaRouterInternalsWebUIMessageHandler::HandleGetState(
const base::ListValue* args) {
AllowJavascript();
const base::Value& callback_id = args->GetList()[0];
ResolveJavascriptCallback(callback_id, router_->GetState());
}
void MediaRouterInternalsWebUIMessageHandler::HandleGetProviderState(
const base::ListValue* args) {
AllowJavascript();
base::Value callback_id = args->GetList()[0].Clone();
if (args->GetList().size() != 2 || !args->GetList()[1].is_string()) {
RejectJavascriptCallback(callback_id, base::Value("Invalid arguments"));
}
MediaRouteProviderId provider_id =
ProviderIdFromString(args->GetList()[1].GetString());
if (provider_id == MediaRouteProviderId::UNKNOWN) {
RejectJavascriptCallback(callback_id,
base::Value("Unknown MediaRouteProviderId"));
}
router_->GetProviderState(
provider_id,
base::BindOnce(&MediaRouterInternalsWebUIMessageHandler::OnProviderState,
weak_factory_.GetWeakPtr(), std::move(callback_id)));
}
void MediaRouterInternalsWebUIMessageHandler::OnProviderState(
base::Value callback_id,
mojom::ProviderStatePtr state) {
base::Value result;
if (state && state->is_cast_provider_state() &&
state->get_cast_provider_state()) {
result = CastProviderStateToValue(*(state->get_cast_provider_state()));
}
ResolveJavascriptCallback(callback_id, result);
}
} // namespace media_router
......@@ -7,6 +7,9 @@
#include <string>
#include "base/memory/weak_ptr.h"
#include "base/values.h"
#include "chrome/common/media_router/mojom/media_router.mojom.h"
#include "content/public/browser/web_ui_message_handler.h"
namespace media_router {
......@@ -19,16 +22,23 @@ class MediaRouterInternalsWebUIMessageHandler
: public content::WebUIMessageHandler {
public:
explicit MediaRouterInternalsWebUIMessageHandler(const MediaRouter* router);
~MediaRouterInternalsWebUIMessageHandler() override;
private:
// WebUIMessageHandler implementation.
void RegisterMessages() override;
// Handlers for JavaScript messages.
void HandleGetStatus(const base::ListValue* args);
void HandleGetState(const base::ListValue* args);
void HandleGetProviderState(const base::ListValue* args);
void OnProviderState(base::Value callback_id, mojom::ProviderStatePtr state);
// Pointer to the MediaRouter.
const MediaRouter* router_;
const MediaRouter* const router_;
base::WeakPtrFactory<MediaRouterInternalsWebUIMessageHandler> weak_factory_{
this};
};
} // namespace media_router
......
......@@ -5,25 +5,46 @@
#include "chrome/common/media_router/media_route_provider_helper.h"
#include "base/logging.h"
#include "base/strings/string_piece.h"
constexpr const char kExtension[] = "EXTENSION";
constexpr const char kWiredDisplay[] = "WIRED_DISPLAY";
constexpr const char kDial[] = "DIAL";
constexpr const char kCast[] = "CAST";
constexpr const char kUnknown[] = "UNKNOWN";
namespace media_router {
const char* ProviderIdToString(MediaRouteProviderId provider_id) {
switch (provider_id) {
case EXTENSION:
return "EXTENSION";
return kExtension;
case WIRED_DISPLAY:
return "WIRED_DISPLAY";
return kWiredDisplay;
case CAST:
return "CAST";
return kCast;
case DIAL:
return "DIAL";
return kDial;
case UNKNOWN:
return "UNKNOWN";
return kUnknown;
}
NOTREACHED() << "Unknown provider_id " << static_cast<int>(provider_id);
return "Unknown provider_id";
}
MediaRouteProviderId ProviderIdFromString(base::StringPiece provider_id) {
if (provider_id == kExtension) {
return MediaRouteProviderId::EXTENSION;
} else if (provider_id == kWiredDisplay) {
return MediaRouteProviderId::WIRED_DISPLAY;
} else if (provider_id == kCast) {
return MediaRouteProviderId::CAST;
} else if (provider_id == kDial) {
return MediaRouteProviderId::DIAL;
} else {
return MediaRouteProviderId::UNKNOWN;
}
}
} // namespace media_router
......@@ -7,11 +7,14 @@
#include <string>
#include "base/strings/string_piece_forward.h"
namespace media_router {
// Each MediaRouteProvider is associated with a unique ID. This enum must be
// kept in sync with mojom::MediaRouteProvider::Id, except for |UNKNOWN|, which
// is not present in the Mojo enum.
// FIXME: Can we just use the mojo enum instead?
enum MediaRouteProviderId {
EXTENSION,
WIRED_DISPLAY,
......@@ -21,6 +24,7 @@ enum MediaRouteProviderId {
};
const char* ProviderIdToString(MediaRouteProviderId provider_id);
MediaRouteProviderId ProviderIdFromString(base::StringPiece provider_id);
} // namespace media_router
......
......@@ -255,6 +255,31 @@ struct RoutePresentationConnection {
pending_receiver<blink.mojom.PresentationConnection> connection_receiver;
};
// State of one active Cast session.
struct CastSessionState {
// The Media Sink ID for the session.
string sink_id;
// The Cast App ID that the session is currently running.
string app_id;
// The session ID assigned by the Cast Receiver.
string session_id;
// Human readable description of the app or session, e.g. "Netflix."
string route_description;
};
// The state of the Cast Media Route Provider.
struct CastProviderState {
// Includes all sessions running on known (local network) Cast receivers.
array<CastSessionState> session_state;
};
// Returned to the Media Router to reflect the state of a single route
// provider for chrome://media-router-internals.
union ProviderState {
// The state of the Cast MRP.
CastProviderState cast_provider_state;
};
// Modeled after the MediaRouter interface defined in
// chrome/browser/media/router/media_router.h
//
......@@ -489,6 +514,12 @@ interface MediaRouteProvider {
pending_receiver<MediaController> media_controller,
pending_remote<MediaStatusObserver> observer) =>
(bool success);
// Returns the state of this Media Route Provider for debugging; returns null
// if this MRP does not report any state.
//
// Currently, only the Cast MRP returns a state value.
GetState() => (ProviderState? state);
};
// Interface for a service which observes MediaRouteProviders for state changes
......
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