Commit 9163b64f authored by avayvod's avatar avayvod Committed by Commit bot

[Cast, Android] Reimplement ReceiverAction.CAST message for the new cast_sender.js

BUG=580113
TEST=e2e tests with replacing the current script with the new one.

For Chrome 50, the new version of cast_sender.js will be served that doesn't
use PresentationRequest.join("_receiver-action") to get the receiver action
event. Instead, the PresentationConnection is returned immediately after
the user picked a device for PresentationRequest.start() and the receiver
action message is sent back to the page via the corresponding MediaRoute.

This in particular means the route will exist without the CastSession and
some messages need to be handled by CastMediaRouteProvider, as well as queued
until "client_connected" is sent to the route.

Review URL: https://codereview.chromium.org/1614023002

Cr-Commit-Position: refs/heads/master@{#371780}
parent b2d90a34
......@@ -37,7 +37,6 @@ public class CastMediaRouteProvider implements MediaRouteProvider, DiscoveryDele
private static final String AUTO_JOIN_PRESENTATION_ID = "auto-join";
private static final String PRESENTATION_ID_SESSION_ID_PREFIX = "cast-session_";
private static final String RECEIVER_ACTION_PRESENTATION_ID = "_receiver-action";
private final Context mApplicationContext;
private final MediaRouter mAndroidMediaRouter;
......@@ -90,34 +89,26 @@ public class CastMediaRouteProvider implements MediaRouteProvider, DiscoveryDele
return new CastMediaRouteProvider(applicationContext, androidMediaRouter, manager);
}
public void onRouteCreated(
int requestId, MediaRoute route, CastSession session, String origin, int tabId) {
mSession = session;
addRoute(route, origin, tabId);
mManager.onRouteCreated(route.id, route.sinkId, requestId, this, true);
}
public void onRouteRequestError(String message, int requestId) {
mManager.onRouteRequestError(message, requestId);
}
public void onClientDisconnected(String clientId) {
ClientRecord client = mClientRecords.get(clientId);
assert client != null;
mRoutes.remove(client.routeId);
removeClient(client);
mManager.onRouteClosed(client.routeId);
}
public void onSessionStopAction() {
if (mSession == null) return;
for (String routeId : mRoutes.keySet()) closeRoute(routeId);
}
public void onSessionCreated(CastSession session) {
mSession = session;
for (ClientRecord client : mClientRecords.values()) {
if (!client.isConnected) continue;
mSession.onClientConnected(client.clientId);
}
}
public void onSessionClosed() {
if (mSession == null) return;
......@@ -136,7 +127,7 @@ public class CastMediaRouteProvider implements MediaRouteProvider, DiscoveryDele
mSession = null;
if (mPendingCreateRouteRequest != null) {
mPendingCreateRouteRequest.start(mApplicationContext);
launchSession(mPendingCreateRouteRequest);
mPendingCreateRouteRequest = null;
} else if (mAndroidMediaRouter != null) {
mAndroidMediaRouter.selectRoute(mAndroidMediaRouter.getDefaultRoute());
......@@ -149,11 +140,15 @@ public class CastMediaRouteProvider implements MediaRouteProvider, DiscoveryDele
public void onMessage(String clientId, String message) {
ClientRecord clientRecord = mClientRecords.get(clientId);
if (clientRecord == null
|| clientRecord.clientId.endsWith(RECEIVER_ACTION_PRESENTATION_ID)) {
if (clientRecord == null) return;
if (!clientRecord.isConnected) {
Log.d(TAG, "Queueing message to client %s: %s", clientId, message);
clientRecord.pendingMessages.add(message);
return;
}
Log.d(TAG, "Sending message to client %s: %s", clientId, message);
mManager.onMessage(clientRecord.routeId, message);
}
......@@ -249,16 +244,6 @@ public class CastMediaRouteProvider implements MediaRouteProvider, DiscoveryDele
return;
}
if (source.getClientId() != null) {
String receiverActionClientId = source.getClientId() + RECEIVER_ACTION_PRESENTATION_ID;
ClientRecord clientRecord = mClientRecords.get(receiverActionClientId);
if (clientRecord != null) {
sendReceiverAction(clientRecord.routeId, sink, receiverActionClientId, "cast");
detachRoute(clientRecord.routeId);
mManager.onRouteClosed(clientRecord.routeId);
}
}
CreateRouteRequest createRouteRequest = new CreateRouteRequest(
source, sink, presentationId, origin, tabId, nativeRequestId, this);
......@@ -269,7 +254,25 @@ public class CastMediaRouteProvider implements MediaRouteProvider, DiscoveryDele
return;
}
createRouteRequest.start(mApplicationContext);
launchSession(createRouteRequest);
}
private void launchSession(CreateRouteRequest request) {
MediaSink sink = request.getSink();
MediaSource source = request.getSource();
MediaRoute route = new MediaRoute(
sink.getId(), source.getUrn(), request.getPresentationId());
addRoute(route, request.getOrigin(), request.getTabId());
mManager.onRouteCreated(route.id, route.sinkId, request.getNativeRequestId(), this, true);
if (source.getClientId() != null) {
ClientRecord clientRecord = mClientRecords.get(source.getClientId());
if (clientRecord != null) {
sendReceiverAction(clientRecord.routeId, sink, source.getClientId(), "cast");
}
}
request.start(mApplicationContext);
}
@Override
......@@ -281,14 +284,6 @@ public class CastMediaRouteProvider implements MediaRouteProvider, DiscoveryDele
return;
}
// For the ReceiverAction presentation id there's no need to have a session or a route.
if (RECEIVER_ACTION_PRESENTATION_ID.equals(presentationId)) {
MediaRoute route = new MediaRoute("", sourceId, presentationId);
addRoute(route, origin, tabId);
mManager.onRouteCreated(route.id, route.sinkId, nativeRequestId, this, true);
return;
}
if (mSession == null) {
mManager.onRouteRequestError("No presentation", nativeRequestId);
return;
......@@ -333,19 +328,31 @@ public class CastMediaRouteProvider implements MediaRouteProvider, DiscoveryDele
@Override
public void sendStringMessage(String routeId, String message, int nativeCallbackId) {
ClientRecord clientRecord = getClientRecordByRouteId(routeId);
if (clientRecord != null
&& clientRecord.clientId.endsWith(RECEIVER_ACTION_PRESENTATION_ID)) {
mManager.onMessageSentResult(true, nativeCallbackId);
return;
}
Log.d(TAG, "Received message from client: %s", message);
if (mSession == null || !mRoutes.containsKey(routeId)) {
if (!mRoutes.containsKey(routeId)) {
mManager.onMessageSentResult(false, nativeCallbackId);
return;
}
mSession.sendStringMessage(message, nativeCallbackId);
boolean success = false;
try {
JSONObject jsonMessage = new JSONObject(message);
String messageType = jsonMessage.getString("type");
if ("client_connect".equals(messageType)) {
success = handleClientConnectMessage(jsonMessage);
} else if ("client_disconnect".equals(messageType)) {
success = handleClientDisconnectMessage(jsonMessage);
} else if (mSession != null) {
success = mSession.handleSessionMessage(jsonMessage, messageType);
}
} catch (JSONException e) {
Log.e(TAG, "JSONException while handling internal message: " + e);
success = false;
}
mManager.onMessageSentResult(success, nativeCallbackId);
}
@Override
......@@ -357,6 +364,41 @@ public class CastMediaRouteProvider implements MediaRouteProvider, DiscoveryDele
mManager.onMessageSentResult(false, nativeCallbackId);
}
private boolean handleClientConnectMessage(JSONObject jsonMessage) throws JSONException {
String clientId = jsonMessage.getString("clientId");
if (clientId == null) return false;
ClientRecord clientRecord = mClientRecords.get(clientId);
if (clientRecord == null) return false;
clientRecord.isConnected = true;
if (mSession != null) mSession.onClientConnected(clientId);
if (clientRecord.pendingMessages.size() == 0) return true;
for (String message : clientRecord.pendingMessages) {
Log.d(TAG, "Deqeueing message for client %s: %s", clientId, message);
mManager.onMessage(clientRecord.routeId, message);
}
clientRecord.pendingMessages.clear();
return true;
}
private boolean handleClientDisconnectMessage(JSONObject jsonMessage) throws JSONException {
String clientId = jsonMessage.getString("clientId");
if (clientId == null) return false;
ClientRecord client = mClientRecords.get(clientId);
if (client == null) return false;
mRoutes.remove(client.routeId);
removeClient(client);
mManager.onRouteClosed(client.routeId);
return true;
}
private CastMediaRouteProvider(
Context applicationContext, MediaRouter androidMediaRouter, MediaRouteManager manager) {
mApplicationContext = applicationContext;
......@@ -455,8 +497,7 @@ public class CastMediaRouteProvider implements MediaRouteProvider, DiscoveryDele
json.put("clientId", clientId);
json.put("message", jsonReceiverAction);
Log.d(TAG, "Sending receiver action to %s: %s", routeId, json.toString());
mManager.onMessage(routeId, json.toString());
onMessage(clientId, json.toString());
} catch (JSONException e) {
Log.e(TAG, "Failed to send receiver action message", e);
}
......
......@@ -306,13 +306,6 @@ public class CastSession implements MediaNotificationListener {
});
}
public void sendStringMessage(String message, int callbackId) {
if (handleInternalMessage(message, callbackId)) return;
// TODO(avayvod): figure out what to do with custom namespace messages.
mRouteProvider.onMessageSentResult(false, callbackId);
}
public String getSourceId() {
return mSource.getUrn();
}
......@@ -321,6 +314,13 @@ public class CastSession implements MediaNotificationListener {
return mCastDevice.getDeviceId();
}
public void onClientConnected(String clientId) {
sendClientMessageTo(
clientId, "new_session", buildSessionMessage(), INVALID_SEQUENCE_NUMBER);
if (mMediaPlayer != null && !isApiClientInvalid()) mMediaPlayer.requestStatus(mApiClient);
}
/////////////////////////////////////////////////////////////////////////////////////////////
// MediaNotificationListener implementation.
......@@ -432,55 +432,18 @@ public class CastSession implements MediaNotificationListener {
}
}
private boolean handleInternalMessage(String message, int callbackId) {
Log.d(TAG, "Received message from client: %s", message);
boolean success = true;
try {
JSONObject jsonMessage = new JSONObject(message);
String messageType = jsonMessage.getString("type");
if ("client_connect".equals(messageType)) {
success = handleClientConnectMessage(jsonMessage);
} else if ("client_disconnect".equals(messageType)) {
success = handleClientDisconnectMessage(jsonMessage);
} else if ("leave_session".equals(messageType)) {
success = handleLeaveSessionMessage(jsonMessage);
} else if ("v2_message".equals(messageType)) {
success = handleCastV2Message(jsonMessage);
} else if ("app_message".equals(messageType)) {
success = handleAppMessage(jsonMessage);
} else {
Log.e(TAG, "Unsupported message: %s", message);
return false;
}
} catch (JSONException e) {
Log.e(TAG, "JSONException while handling internal message: " + e);
public boolean handleSessionMessage(
JSONObject message, String messageType) throws JSONException {
if ("leave_session".equals(messageType)) {
return handleLeaveSessionMessage(message);
} else if ("v2_message".equals(messageType)) {
return handleCastV2Message(message);
} else if ("app_message".equals(messageType)) {
return handleAppMessage(message);
} else {
Log.e(TAG, "Unsupported message: %s", message);
return false;
}
mRouteProvider.onMessageSentResult(success, callbackId);
return true;
}
private boolean handleClientConnectMessage(JSONObject jsonMessage) throws JSONException {
String clientId = jsonMessage.getString("clientId");
if (clientId == null || !mRouteProvider.getClients().contains(clientId)) return false;
sendClientMessageTo(
clientId, "new_session", buildSessionMessage(), INVALID_SEQUENCE_NUMBER);
if (mMediaPlayer != null && !isApiClientInvalid()) mMediaPlayer.requestStatus(mApiClient);
return true;
}
private boolean handleClientDisconnectMessage(JSONObject jsonMessage) throws JSONException {
String clientId = jsonMessage.getString("clientId");
if (clientId == null || !mRouteProvider.getClients().contains(clientId)) return false;
mRouteProvider.onClientDisconnected(clientId);
return true;
}
// An example of the leave_session message.
......@@ -766,8 +729,6 @@ public class CastSession implements MediaNotificationListener {
Log.e(TAG, "Failed to build the reply: " + e);
}
Log.d(TAG, "Sending message to client: " + json);
return json.toString();
}
......
......@@ -6,6 +6,9 @@ package org.chromium.chrome.browser.media.router.cast;
import org.chromium.chrome.browser.media.router.MediaRoute;
import java.util.ArrayList;
import java.util.List;
/**
* Contains information about a single client connection to the {@link MediaRoute}.
*/
......@@ -40,6 +43,16 @@ public class ClientRecord {
*/
public final int tabId;
/**
* Whether the client is ready to receive messages.
*/
public boolean isConnected = false;
/**
* The pending messages for the client.
*/
public List<String> pendingMessages = new ArrayList<String>();
ClientRecord(
String routeId,
String clientId,
......
......@@ -133,6 +133,30 @@ public class CreateRouteRequest implements GoogleApiClient.ConnectionCallbacks,
mRouteProvider = routeProvider;
}
public MediaSource getSource() {
return mSource;
}
public MediaSink getSink() {
return mSink;
}
public String getPresentationId() {
return mPresentationId;
}
public String getOrigin() {
return mOrigin;
}
public int getTabId() {
return mTabId;
}
public int getNativeRequestId() {
return mRequestId;
}
/**
* Starts the process of launching the application on the Cast device.
* @param applicationContext application context
......@@ -234,7 +258,6 @@ public class CreateRouteRequest implements GoogleApiClient.ConnectionCallbacks,
private void reportSuccess(Cast.ApplicationConnectionResult result) {
if (mState != STATE_LAUNCH_SUCCEEDED) throwInvalidState();
MediaRoute route = new MediaRoute(mSink.getId(), mSource.getUrn(), mPresentationId);
CastSession session = new CastSession(
mApiClient,
result.getSessionId(),
......@@ -246,7 +269,7 @@ public class CreateRouteRequest implements GoogleApiClient.ConnectionCallbacks,
mSource,
mRouteProvider);
mCastListener.setSession(session);
mRouteProvider.onRouteCreated(mRequestId, route, session, mOrigin, mTabId);
mRouteProvider.onSessionCreated(session);
terminate();
}
......
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