Commit cacba2dc authored by Derek Cheng's avatar Derek Cheng Committed by Commit Bot

[DIAL MRP] DialActivityManager and DialInternalMessageUtil.

Part 3 of Custom DIAL launch patches. (Diffbase = 1014722)

Added the following classes:

DialActivityManager - handles custom DIAL launch session management
DialInternalMessageUtil - utility functions for generating/parsing
different types of Cast messages for Custom DIAL launch for
communicating with Cast SDK.

Bug: 816628
Change-Id: Id9eb4fdd393677771c9a278d78584e88339d21fa
Reviewed-on: https://chromium-review.googlesource.com/1018281
Commit-Queue: Derek Cheng <imcheng@chromium.org>
Reviewed-by: default avatarBrandon Tolsch <btolsch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#554871}
parent cb4431a3
......@@ -99,6 +99,10 @@ static_library("router") {
"providers/cast/chrome_cast_message_handler.h",
"providers/cast/dual_media_sink_service.cc",
"providers/cast/dual_media_sink_service.h",
"providers/dial/dial_activity_manager.cc",
"providers/dial/dial_activity_manager.h",
"providers/dial/dial_internal_message_util.cc",
"providers/dial/dial_internal_message_util.h",
"providers/dial/dial_media_route_provider.cc",
"providers/dial/dial_media_route_provider.h",
"providers/extension/extension_media_route_provider_proxy.cc",
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_DIAL_DIAL_ACTIVITY_MANAGER_H_
#define CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_DIAL_DIAL_ACTIVITY_MANAGER_H_
#include <memory>
#include <string>
#include "base/containers/flat_map.h"
#include "base/sequence_checker.h"
#include "chrome/browser/media/router/discovery/dial/dial_url_fetcher.h"
#include "chrome/common/media_router/discovery/media_sink_internal.h"
#include "chrome/common/media_router/media_route.h"
#include "chrome/common/media_router/media_source.h"
#include "chrome/common/media_router/mojo/media_router.mojom.h"
#include "chrome/common/media_router/route_request_result.h"
#include "url/gurl.h"
namespace media_router {
struct CustomDialLaunchMessageBody;
// Represents parameters used to complete a custom DIAL launch sequence.
struct DialLaunchInfo {
DialLaunchInfo(const std::string& app_name,
const base::Optional<std::string>& post_data,
const std::string& client_id,
const GURL& app_launch_url);
DialLaunchInfo(const DialLaunchInfo& other);
~DialLaunchInfo();
std::string app_name;
// The data to include with the app launch POST request. Note this may be
// overridden by the launchParameter in the CUSTOM_DIAL_LAUNCH response.
base::Optional<std::string> post_data;
// Cast SDK client ID.
std::string client_id;
// The URL to use to launch the app.
GURL app_launch_url;
};
// Represents custom DIAL launch activity that is to be or has been initiated
// on a DIAL MediaSink.
struct DialActivity {
public:
// Returns a DialActivity object created from the given parameters. Returns
// nullptr if |source_id| does not represent a valid DIAL MediaSource.
static std::unique_ptr<DialActivity> From(const std::string& presentation_id,
const MediaSinkInternal& sink,
const MediaSource::Id& source_id,
bool incognito);
DialActivity(const DialLaunchInfo& launch_info, const MediaRoute& route);
~DialActivity();
DialLaunchInfo launch_info;
// TODO(https://crbug.com/816628): The MediaRoute itself does not contain
// sufficient information to tell the current state of the activity (launching
// vs. launched). Because of this, the route is rendered in the Media Router
// UI the same way for both states. Consider introducing a state property in
// MediaRoute so that the UI can render them differently.
MediaRoute route;
};
template <typename CallbackType>
struct DialPendingRequest {
public:
DialPendingRequest(std::unique_ptr<DialURLFetcher> fetcher,
CallbackType callback)
: fetcher(std::move(fetcher)), callback(std::move(callback)) {}
~DialPendingRequest() = default;
std::unique_ptr<DialURLFetcher> fetcher;
CallbackType callback;
DISALLOW_COPY_AND_ASSIGN(DialPendingRequest);
};
// Keeps track of all ongoing custom DIAL launch activities, and is the source
// of truth for DialActivity and MediaRoutes for the DIAL MRP.
// Custom DIAL launch consists of a sequence of message excahnges between the
// MRP and the Cast SDK client:
// (1) When the user requests a custom DIAL launch, |AddActivity()| is called
// with a DialActivity pertaining to the launch. This creates a MediaRoute and
// a connection between the MRP and the SDK client to begin message exchanges.
// (2) The SDK client sends back a custom DIAL launch response. |LaunchApp()|
// is called with the response message which contains additional launch
// parameters.
// (3) After the app is launched, |StopApp()| may be called to terminate the
// app on the receiver.
// All methods on this class must run on the same sequence.
// TODO(crbug.com/816628): We should be able to simplify the interaction
// between DialMediaRouteProvider and this class once PresentationConnection is
// available to this class to communicate with the page directly.
class DialActivityManager {
public:
using LaunchAppCallback = base::OnceCallback<void(bool)>;
DialActivityManager();
virtual ~DialActivityManager();
// Adds |activity| to the manager. This call is valid only if there is no
// existing activity with the same MediaRoute ID.
void AddActivity(const DialActivity& activity);
// Returns the DialActivity associated with |route_id| or nullptr if not
// found.
const DialActivity* GetActivity(const MediaRoute::Id& route_id) const;
// Returns the DialActivity associated with |sink_id| or nullptr if not
// found.
const DialActivity* GetActivityBySinkId(const MediaSink::Id& sink_id) const;
// Launches the app specified in the activity associated with |route_id|.
// If |message.launch_parameter| is set, then it overrides the post data
// originally specified in the activity's DialLaunchInfo.
// |callback| will be invoked with |true| if the launch succeeded, or |false|
// if the launch failed.
// If |message.do_launch| is |false|, then app launch will be skipped, and
// |calback| will be invoked with |true|.
// This method is only valid to call if there is an activity for |route_id|.
// This method is a no-op if there is already a pending launch request, or
// if the app is already launched.
void LaunchApp(const MediaRoute::Id& route_id,
const CustomDialLaunchMessageBody& message,
LaunchAppCallback callback);
// Stops the app that is currently active on |route_id|.
// On success, the associated DialActivity and MediaRoute will be removed
// before |callback| is invoked. On failure, the DialActivity and MediaRoute
// will not be removed.
void StopApp(const MediaRoute::Id& route_id,
mojom::MediaRouteProvider::TerminateRouteCallback callback);
std::vector<MediaRoute> GetRoutes() const;
private:
using DialLaunchRequest =
DialPendingRequest<DialActivityManager::LaunchAppCallback>;
using DialStopRequest =
DialPendingRequest<mojom::MediaRouteProvider::TerminateRouteCallback>;
// Represents the state of a launch activity.
struct Record {
explicit Record(const DialActivity& activity);
~Record();
enum State { kLaunching, kLaunched };
const DialActivity activity;
GURL app_instance_url;
std::unique_ptr<DialLaunchRequest> pending_launch_request;
std::unique_ptr<DialStopRequest> pending_stop_request;
State state = kLaunching;
DISALLOW_COPY_AND_ASSIGN(Record);
};
// Marked virtual for tests.
virtual std::unique_ptr<DialURLFetcher> CreateFetcher(
DialURLFetcher::SuccessCallback success_cb,
DialURLFetcher::ErrorCallback error_cb);
void OnLaunchSuccess(const MediaRoute::Id& route_id,
const std::string& response);
void OnLaunchError(const MediaRoute::Id& route_id,
int response_code,
const std::string& message);
void OnStopSuccess(const MediaRoute::Id& route_id,
const std::string& response);
void OnStopError(const MediaRoute::Id& route_id,
int response_code,
const std::string& message);
base::flat_map<MediaRoute::Id, std::unique_ptr<Record>> records_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(DialActivityManager);
};
} // namespace media_router
#endif // CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_DIAL_DIAL_ACTIVITY_MANAGER_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/media/router/providers/dial/dial_activity_manager.h"
#include "base/test/scoped_task_environment.h"
#include "chrome/browser/media/router/providers/dial/dial_internal_message_util.h"
#include "chrome/browser/media/router/test/test_helper.h"
#include "net/http/http_status_code.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::Not;
namespace media_router {
TEST(DialActivityTest, From) {
std::string presentation_id = "presentationId";
MediaSinkInternal sink = CreateDialSink(1);
MediaSource::Id source_id =
"cast-dial:YouTube?clientId=152127444812943594&dialPostData=foo";
auto activity =
DialActivity::From(presentation_id, sink, source_id, /*incognito*/ true);
ASSERT_TRUE(activity);
GURL expected_app_launch_url(sink.dial_data().app_url.spec() + "/YouTube");
const DialLaunchInfo& launch_info = activity->launch_info;
EXPECT_EQ("YouTube", launch_info.app_name);
EXPECT_EQ("152127444812943594", launch_info.client_id);
EXPECT_EQ("foo", launch_info.post_data);
EXPECT_EQ(expected_app_launch_url, launch_info.app_launch_url);
const MediaRoute& route = activity->route;
EXPECT_EQ(presentation_id, route.presentation_id());
EXPECT_EQ(source_id, route.media_source().id());
EXPECT_EQ(sink.sink().id(), route.media_sink_id());
EXPECT_EQ("YouTube", route.description());
EXPECT_TRUE(route.is_local());
EXPECT_TRUE(route.is_incognito());
EXPECT_FALSE(route.is_local_presentation());
EXPECT_EQ(RouteControllerType::kNone, route.controller_type());
}
class DialActivityManagerTest : public testing::Test {
public:
DialActivityManagerTest() : manager_(&loader_factory_) {}
~DialActivityManagerTest() override = default;
void TestLaunchApp(const DialActivity& activity,
const base::Optional<std::string>& launch_parameter,
const base::Optional<GURL>& app_instance_url) {
manager_.SetExpectedRequest(activity.launch_info.app_launch_url, "POST",
launch_parameter ? *launch_parameter : "foo");
LaunchApp(activity.route.media_route_id(), launch_parameter);
// |GetRoutes()| returns the route even though it is still launching.
EXPECT_FALSE(manager_.GetRoutes().empty());
// Pending launch request, no-op.
EXPECT_CALL(manager_, OnFetcherCreated()).Times(0);
LaunchApp(activity.route.media_route_id(), base::nullopt);
LaunchApp(activity.route.media_route_id(), "bar");
network::ResourceResponseHead response_head;
if (app_instance_url) {
response_head.headers =
base::MakeRefCounted<net::HttpResponseHeaders>("");
response_head.headers->AddHeader("LOCATION: " + app_instance_url->spec());
}
loader_factory_.AddResponse(activity.launch_info.app_launch_url,
response_head, "",
network::URLLoaderCompletionStatus());
EXPECT_CALL(*this, OnAppLaunchResult(true));
base::RunLoop().RunUntilIdle();
auto routes = manager_.GetRoutes();
EXPECT_EQ(1u, routes.size());
EXPECT_TRUE(routes[0].Equals(activity.route));
// App already launched, no-op.
EXPECT_CALL(manager_, OnFetcherCreated()).Times(0);
LaunchApp(activity.route.media_route_id(), base::nullopt);
}
void LaunchApp(const MediaRoute::Id& route_id,
const base::Optional<std::string>& launch_parameter) {
CustomDialLaunchMessageBody message(true, launch_parameter);
manager_.LaunchApp(
route_id, message,
base::BindOnce(&DialActivityManagerTest::OnAppLaunchResult,
base::Unretained(this)));
}
MOCK_METHOD1(OnAppLaunchResult, void(bool));
void StopApp(const MediaRoute::Id& route_id) {
manager_.StopApp(route_id,
base::BindOnce(&DialActivityManagerTest::OnStopAppResult,
base::Unretained(this)));
}
MOCK_METHOD2(OnStopAppResult,
void(const base::Optional<std::string>&,
RouteRequestResult::ResultCode));
protected:
base::test::ScopedTaskEnvironment environment_;
std::string presentation_id_ = "presentationId";
MediaSinkInternal sink_ = CreateDialSink(1);
MediaSource::Id source_id_ =
"cast-dial:YouTube?clientId=152127444812943594&dialPostData=foo";
network::TestURLLoaderFactory loader_factory_;
TestDialActivityManager manager_;
DISALLOW_COPY_AND_ASSIGN(DialActivityManagerTest);
};
TEST_F(DialActivityManagerTest, AddActivity) {
auto activity = DialActivity::From(presentation_id_, sink_, source_id_,
/*incognito*/ false);
ASSERT_TRUE(activity);
EXPECT_TRUE(manager_.GetRoutes().empty());
manager_.AddActivity(*activity);
// |GetRoutes()| returns the route even though it is still launching.
EXPECT_FALSE(manager_.GetRoutes().empty());
EXPECT_TRUE(manager_.GetActivity(activity->route.media_route_id()));
}
TEST_F(DialActivityManagerTest, LaunchApp) {
auto activity = DialActivity::From(presentation_id_, sink_, source_id_,
/*incognito*/ false);
ASSERT_TRUE(activity);
manager_.AddActivity(*activity);
GURL app_instance_url =
GURL(sink_.dial_data().app_url.spec() + "/YouTube/app_instance");
TestLaunchApp(*activity, base::nullopt, app_instance_url);
}
TEST_F(DialActivityManagerTest, LaunchAppLaunchParameter) {
auto activity = DialActivity::From(presentation_id_, sink_, source_id_,
/*incognito*/ false);
ASSERT_TRUE(activity);
manager_.AddActivity(*activity);
GURL app_instance_url =
GURL(sink_.dial_data().app_url.spec() + "/YouTube/app_instance");
TestLaunchApp(*activity, "bar", app_instance_url);
}
TEST_F(DialActivityManagerTest, LaunchAppFails) {
auto activity = DialActivity::From(presentation_id_, sink_, source_id_,
/*incognito*/ false);
ASSERT_TRUE(activity);
manager_.AddActivity(*activity);
manager_.SetExpectedRequest(activity->launch_info.app_launch_url, "POST",
"foo");
LaunchApp(activity->route.media_route_id(), base::nullopt);
network::ResourceResponseHead response_head;
loader_factory_.AddResponse(
activity->launch_info.app_launch_url, response_head, "",
network::URLLoaderCompletionStatus(net::HTTP_SERVICE_UNAVAILABLE));
EXPECT_CALL(*this, OnAppLaunchResult(false));
base::RunLoop().RunUntilIdle();
// Activity is removed on failure.
EXPECT_TRUE(manager_.GetRoutes().empty());
EXPECT_FALSE(manager_.GetActivity(activity->route.media_route_id()));
}
TEST_F(DialActivityManagerTest, StopApp) {
auto activity = DialActivity::From(presentation_id_, sink_, source_id_,
/*incognito*/ false);
ASSERT_TRUE(activity);
manager_.AddActivity(*activity);
GURL app_instance_url =
GURL(sink_.dial_data().app_url.spec() + "/YouTube/app_instance");
TestLaunchApp(*activity, base::nullopt, app_instance_url);
manager_.SetExpectedRequest(app_instance_url, "DELETE", base::nullopt);
StopApp(activity->route.media_route_id());
// Pending stop request.
EXPECT_CALL(*this, OnStopAppResult(_, Not(RouteRequestResult::OK)));
EXPECT_CALL(manager_, OnFetcherCreated()).Times(0);
StopApp(activity->route.media_route_id());
loader_factory_.AddResponse(app_instance_url, network::ResourceResponseHead(),
"", network::URLLoaderCompletionStatus());
EXPECT_CALL(*this, OnStopAppResult(testing::Eq(base::nullopt),
RouteRequestResult::OK));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(manager_.GetRoutes().empty());
}
TEST_F(DialActivityManagerTest, StopAppUseFallbackURL) {
auto activity = DialActivity::From(presentation_id_, sink_, source_id_,
/*incognito*/ false);
ASSERT_TRUE(activity);
manager_.AddActivity(*activity);
TestLaunchApp(*activity, base::nullopt, base::nullopt);
GURL app_instance_url =
GURL(activity->launch_info.app_launch_url.spec() + "/run");
manager_.SetExpectedRequest(app_instance_url, "DELETE", base::nullopt);
StopApp(activity->route.media_route_id());
loader_factory_.AddResponse(app_instance_url, network::ResourceResponseHead(),
"", network::URLLoaderCompletionStatus());
EXPECT_CALL(*this, OnStopAppResult(testing::Eq(base::nullopt),
RouteRequestResult::OK));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(manager_.GetRoutes().empty());
}
TEST_F(DialActivityManagerTest, StopAppFails) {
auto activity = DialActivity::From(presentation_id_, sink_, source_id_,
/*incognito*/ false);
ASSERT_TRUE(activity);
manager_.AddActivity(*activity);
TestLaunchApp(*activity, base::nullopt, base::nullopt);
GURL app_instance_url =
GURL(activity->launch_info.app_launch_url.spec() + "/run");
manager_.SetExpectedRequest(app_instance_url, "DELETE", base::nullopt);
StopApp(activity->route.media_route_id());
loader_factory_.AddResponse(
app_instance_url, network::ResourceResponseHead(), "",
network::URLLoaderCompletionStatus(net::HTTP_SERVICE_UNAVAILABLE));
EXPECT_CALL(*this, OnStopAppResult(_, Not(RouteRequestResult::OK)));
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(manager_.GetActivity(activity->route.media_route_id()));
EXPECT_FALSE(manager_.GetRoutes().empty());
}
} // namespace media_router
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_DIAL_DIAL_INTERNAL_MESSAGE_UTIL_H_
#define CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_DIAL_DIAL_INTERNAL_MESSAGE_UTIL_H_
#include <memory>
#include <string>
#include "base/values.h"
#include "chrome/browser/media/router/discovery/dial/parsed_dial_app_info.h"
#include "content/public/common/presentation_connection_message.h"
namespace media_router {
struct DialLaunchInfo;
class MediaSinkInternal;
// Types of internal messages that are used in a custom DIAL launch workflow.
enum class DialInternalMessageType {
// Cast SDK -> MR
kClientConnect,
kV2Message,
// MR -> Cast SDK
kNewSession,
kReceiverAction,
// MR <-> Cast SDK
kCustomDialLaunch,
kOther
};
// Possible types of ReceiverAction taken by the user on a receiver.
enum class DialReceiverAction {
// The user selected a receiver with the intent of casting to it with the
// sender application.
kCast,
// The user requested to stop the session running on a receiver.
kStop
};
// Parsed custom DIAL launch internal message coming from a Cast SDK client.
struct DialInternalMessage {
// Returns a DialInternalMessage for |message|, or nullptr is |message| is not
// a valid custom DIAL launch internal message.
static std::unique_ptr<DialInternalMessage> From(const std::string& message);
DialInternalMessage(DialInternalMessageType type,
base::Optional<base::Value> body,
const std::string& client_id,
int sequence_number);
~DialInternalMessage();
DialInternalMessageType type;
base::Optional<base::Value> body;
std::string client_id;
int sequence_number;
DISALLOW_COPY_AND_ASSIGN(DialInternalMessage);
};
// Parsed CUSTOM_DIAL_LAUNCH response from the Cast SDK client.
struct CustomDialLaunchMessageBody {
// Returns a CustomDialLaunchMessageBody for |message|.
// This method is only valid to call if |message.type| == |kCustomDialLaunch|.
static CustomDialLaunchMessageBody From(const DialInternalMessage& message);
CustomDialLaunchMessageBody();
CustomDialLaunchMessageBody(
bool do_launch,
const base::Optional<std::string>& launch_parameter);
CustomDialLaunchMessageBody(const CustomDialLaunchMessageBody& other);
~CustomDialLaunchMessageBody();
// If |true|, the DialMediaRouteProvider should handle the app launch.
bool do_launch = true;
// If |do_launch| is |true|, optional launch parameter to include with the
// launch (POST) request. This overrides the launch parameter that was
// specified in the MediaSource (if any).
base::Optional<std::string> launch_parameter;
};
class DialInternalMessageUtil {
public:
// Returns |true| if |message| is a valid STOP_SESSION message.
static bool IsStopSessionMessage(const DialInternalMessage& message);
// Returns a NEW_SESSION message to be sent to the page when the user requests
// an app launch.
static content::PresentationConnectionMessage CreateNewSessionMessage(
const DialLaunchInfo& launch_info,
const MediaSinkInternal& sink);
// Returns a RECEIVER_ACTION / CAST message to be sent to the page when the
// user requests an app launch.
static content::PresentationConnectionMessage CreateReceiverActionCastMessage(
const DialLaunchInfo& launch_info,
const MediaSinkInternal& sink);
// Returns a RECEIVER_ACTION / STOP message to be sent to the page when an app
// is stopped by DialMediaRouteProvider.
static content::PresentationConnectionMessage CreateReceiverActionStopMessage(
const DialLaunchInfo& launch_info,
const MediaSinkInternal& sink);
// Returns a CUSTOM_DIAL_LAUNCH request message to be sent to the page.
// Generates and returns the next number to associate a DIAL launch sequence
// with.
static std::pair<content::PresentationConnectionMessage, int>
CreateCustomDialLaunchMessage(const DialLaunchInfo& launch_info,
const MediaSinkInternal& sink,
const ParsedDialAppInfo& app_info);
};
} // namespace media_router
#endif // CHROME_BROWSER_MEDIA_ROUTER_PROVIDERS_DIAL_DIAL_INTERNAL_MESSAGE_UTIL_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/media/router/providers/dial/dial_internal_message_util.h"
#include "base/json/json_reader.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/media/router/providers/dial/dial_activity_manager.h"
#include "chrome/browser/media/router/test/test_helper.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace media_router {
class DialInternalMessageUtilTest : public ::testing::Test {
public:
DialInternalMessageUtilTest()
: launch_info_("YouTube",
base::nullopt,
"152127444812943594",
GURL("http://172.17.32.151/app/YouTube")) {
MediaSink sink("dial:<29a400068c051073801508058128105d>", "Lab Roku",
SinkIconType::GENERIC);
DialSinkExtraData extra_data;
extra_data.ip_address = net::IPAddress(172, 17, 32, 151);
sink_ = MediaSinkInternal(sink, extra_data);
}
void ExpectMessagesEqual(const std::string& expected_message,
const std::string& message) {
auto expected_message_value = base::JSONReader::Read(expected_message);
ASSERT_TRUE(expected_message_value);
auto message_value = base::JSONReader::Read(message);
ASSERT_TRUE(message_value);
EXPECT_EQ(*expected_message_value, *message_value);
}
protected:
DialLaunchInfo launch_info_;
MediaSinkInternal sink_;
};
TEST_F(DialInternalMessageUtilTest, ParseClientConnectMessage) {
const char kClientConnectMessage[] = R"(
{
"type":"client_connect",
"message":"15212681945883010",
"sequenceNumber":-1,
"timeoutMillis":0,
"clientId":"15212681945883010"
})";
auto message = DialInternalMessage::From(kClientConnectMessage);
ASSERT_TRUE(message);
EXPECT_EQ(DialInternalMessageType::kClientConnect, message->type);
EXPECT_EQ(base::Value("15212681945883010"), message->body);
EXPECT_EQ("15212681945883010", message->client_id);
EXPECT_EQ(-1, message->sequence_number);
}
TEST_F(DialInternalMessageUtilTest, ParseCustomDialLaunchMessage) {
const char kCustomDialLaunchMessage[] = R"(
{
"type":"custom_dial_launch",
"message": {
"doLaunch":true,
"launchParameter":"pairingCode=foo"
},
"sequenceNumber":12345,
"timeoutMillis":3000,
"clientId":"152127444812943594"
})";
auto message = DialInternalMessage::From(kCustomDialLaunchMessage);
ASSERT_TRUE(message);
EXPECT_EQ(DialInternalMessageType::kCustomDialLaunch, message->type);
EXPECT_EQ("152127444812943594", message->client_id);
EXPECT_EQ(12345, message->sequence_number);
CustomDialLaunchMessageBody body =
CustomDialLaunchMessageBody::From(*message);
EXPECT_TRUE(body.do_launch);
EXPECT_EQ("pairingCode=foo", body.launch_parameter);
}
TEST_F(DialInternalMessageUtilTest, ParseV2StopSessionMessage) {
const char kV2StopSessionMessage[] = R"(
{
"type":"v2_message",
"message": {
"type":"STOP"
},
"sequenceNumber":-1,
"timeoutMillis":0,
"clientId":"152127444812943594"
})";
auto message = DialInternalMessage::From(kV2StopSessionMessage);
ASSERT_TRUE(message);
EXPECT_EQ(DialInternalMessageType::kV2Message, message->type);
EXPECT_EQ("152127444812943594", message->client_id);
EXPECT_EQ(-1, message->sequence_number);
EXPECT_TRUE(DialInternalMessageUtil::IsStopSessionMessage(*message));
}
TEST_F(DialInternalMessageUtilTest, CreateReceiverActionCastMessage) {
const char kReceiverActionCastMessage[] = R"(
{
"clientId":"152127444812943594",
"message": {
"action":"cast",
"receiver": {
"capabilities":[],
"displayStatus":null,
"friendlyName":"Lab Roku",
"ipAddress":"172.17.32.151",
"isActiveInput":null,
"label":"vSzzcOE6bD_NrLSbPN-qswEktGk",
"receiverType":"dial",
"volume":null
}
},
"sequenceNumber":-1,
"timeoutMillis":0,
"type":"receiver_action"
})";
content::PresentationConnectionMessage message =
DialInternalMessageUtil::CreateReceiverActionCastMessage(launch_info_,
sink_);
ASSERT_TRUE(message.message);
ExpectMessagesEqual(kReceiverActionCastMessage, *message.message);
}
TEST_F(DialInternalMessageUtilTest, CreateReceiverActionStopMessage) {
const char kReceiverActionStopMessage[] = R"(
{
"clientId":"152127444812943594",
"message": {
"action":"stop",
"receiver": {
"capabilities":[],
"displayStatus":null,
"friendlyName":"Lab Roku",
"ipAddress":"172.17.32.151",
"isActiveInput":null,
"label":"vSzzcOE6bD_NrLSbPN-qswEktGk",
"receiverType":"dial",
"volume":null
}
},
"sequenceNumber":-1,
"timeoutMillis":0,
"type":"receiver_action"
})";
content::PresentationConnectionMessage message =
DialInternalMessageUtil::CreateReceiverActionStopMessage(launch_info_,
sink_);
ASSERT_TRUE(message.message);
ExpectMessagesEqual(kReceiverActionStopMessage, *message.message);
}
TEST_F(DialInternalMessageUtilTest, CreateNewSessionMessage) {
const char kNewSessionMessage[] = R"(
{
"clientId":"152127444812943594",
"message": {
"appId":"",
"appImages":[],
"displayName":"YouTube",
"media":[],
"namespaces":[],
"receiver": {
"capabilities":[],
"displayStatus":null,
"friendlyName":"Lab Roku",
"ipAddress":"172.17.32.151",
"isActiveInput":null,
"label":"vSzzcOE6bD_NrLSbPN-qswEktGk",
"receiverType":"dial",
"volume":null
},
"senderApps":[],
"sessionId":"1",
"status":"connected",
"statusText":"",
"transportId":""
},
"sequenceNumber":-1,
"timeoutMillis":0,
"type":"new_session"
})";
content::PresentationConnectionMessage message =
DialInternalMessageUtil::CreateNewSessionMessage(launch_info_, sink_);
ASSERT_TRUE(message.message);
ExpectMessagesEqual(kNewSessionMessage, *message.message);
}
TEST_F(DialInternalMessageUtilTest, CreateCustomDialLaunchMessage) {
const char kCustomDialLaunchMessage[] = R"(
{
"clientId":"152127444812943594",
"message": {
"appState":"stopped",
"receiver": {
"capabilities":[],
"displayStatus":null,
"friendlyName":"Lab Roku",
"ipAddress":"172.17.32.151",
"isActiveInput":null,
"label":"vSzzcOE6bD_NrLSbPN-qswEktGk",
"receiverType":"dial",
"volume":null
}
},
"sequenceNumber":%d,
"timeoutMillis":0,
"type":"custom_dial_launch"
})";
ParsedDialAppInfo app_info =
CreateParsedDialAppInfo("YouTube", DialAppState::kStopped);
auto message_and_seq_num =
DialInternalMessageUtil::CreateCustomDialLaunchMessage(launch_info_,
sink_, app_info);
const auto& message = message_and_seq_num.first;
int seq_num = message_and_seq_num.second;
ASSERT_TRUE(message.message);
ExpectMessagesEqual(base::StringPrintf(kCustomDialLaunchMessage, seq_num),
*message.message);
}
} // namespace media_router
......@@ -101,6 +101,32 @@ void TestDialURLFetcher::StartDownload() {
256 * 1024);
}
TestDialActivityManager::TestDialActivityManager(
network::TestURLLoaderFactory* factory)
: DialActivityManager(), factory_(factory) {}
TestDialActivityManager::~TestDialActivityManager() = default;
std::unique_ptr<DialURLFetcher> TestDialActivityManager::CreateFetcher(
DialURLFetcher::SuccessCallback success_cb,
DialURLFetcher::ErrorCallback error_cb) {
OnFetcherCreated();
auto fetcher = std::make_unique<TestDialURLFetcher>(
std::move(success_cb), std::move(error_cb), factory_);
EXPECT_CALL(*fetcher, DoStart(expected_url_, expected_method_,
expected_post_data_, testing::_));
return fetcher;
}
void TestDialActivityManager::SetExpectedRequest(
const GURL& url,
const std::string& method,
const base::Optional<std::string>& post_data) {
EXPECT_CALL(*this, OnFetcherCreated());
expected_url_ = url;
expected_method_ = method;
expected_post_data_ = post_data;
}
net::IPEndPoint CreateIPEndPoint(int num) {
net::IPAddress ip_address;
CHECK(ip_address.AssignFromIPLiteral(
......
......@@ -26,6 +26,7 @@
#include "chrome/browser/media/router/discovery/dial/dial_url_fetcher.h"
#include "chrome/browser/media/router/discovery/mdns/cast_media_sink_service.h"
#include "chrome/browser/media/router/discovery/mdns/cast_media_sink_service_impl.h"
#include "chrome/browser/media/router/providers/dial/dial_activity_manager.h"
#include "chrome/common/media_router/discovery/media_sink_internal.h"
#include "net/base/ip_endpoint.h"
#include "services/network/test/test_url_loader_factory.h"
......@@ -178,6 +179,31 @@ class TestDialURLFetcher : public DialURLFetcher {
network::TestURLLoaderFactory* const factory_;
};
class TestDialActivityManager : public DialActivityManager {
public:
explicit TestDialActivityManager(network::TestURLLoaderFactory* factory);
~TestDialActivityManager() override;
std::unique_ptr<DialURLFetcher> CreateFetcher(
DialURLFetcher::SuccessCallback success_cb,
DialURLFetcher::ErrorCallback error_cb) override;
void SetExpectedRequest(const GURL& url,
const std::string& method,
const base::Optional<std::string>& post_data);
MOCK_METHOD0(OnFetcherCreated, void());
private:
network::TestURLLoaderFactory* const factory_;
GURL expected_url_;
std::string expected_method_;
base::Optional<std::string> expected_post_data_;
DISALLOW_COPY_AND_ASSIGN(TestDialActivityManager);
};
// Helper function to create an IP endpoint object.
// If |num| is 1, returns 192.168.0.101:8009;
// If |num| is 2, returns 192.168.0.102:8009.
......
......@@ -3124,6 +3124,8 @@ test("unit_tests") {
"../browser/media/router/providers/cast/cast_app_discovery_service_unittest.cc",
"../browser/media/router/providers/cast/cast_media_route_provider_metrics_unittest.cc",
"../browser/media/router/providers/cast/dual_media_sink_service_unittest.cc",
"../browser/media/router/providers/dial/dial_activity_manager_unittest.cc",
"../browser/media/router/providers/dial/dial_internal_message_util_unittest.cc",
"../browser/media/router/providers/dial/dial_media_route_provider_unittest.cc",
"../browser/media/router/providers/extension/extension_media_route_provider_proxy_unittest.cc",
"../browser/media/router/providers/wired_display/wired_display_media_route_provider_unittest.cc",
......
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