Commit d9ca8389 authored by Zhiqiang Zhang's avatar Zhiqiang Zhang Committed by Commit Bot

[Android MR] Misc fixes for getting Cast to work

This CL includes the following changes:

* Register SessionManagerListener properly so CafMRP and the page can
  get the correct state.
* Store the pending request only in CafBaseMRP and CastSessionController
  will get it from the provider.
* When starting a session, only re-select a route after the route has
  been unselected. This is to avoid rapid selection/unselection to cause
  Android MediaRouter to work improperly.
* Several minor fixes to avoid crashing in the code flow.

Bug: 711860
Change-Id: I8e4e46abe9421908a5d6c7e221a68feb891bbfa3
Reviewed-on: https://chromium-review.googlesource.com/1196166
Commit-Queue: Zhiqiang Zhang <zqzhang@chromium.org>
Reviewed-by: default avatarThomas Guilbert <tguilbert@chromium.org>
Cr-Commit-Position: refs/heads/master@{#587914}
parent 366cb525
......@@ -10,6 +10,7 @@ import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.RouteInfo;
import com.google.android.gms.cast.CastStatusCodes;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.SessionManagerListener;
......@@ -149,6 +150,8 @@ public abstract class CafBaseMediaRouteProvider
public final void createRoute(String sourceId, String sinkId, String presentationId,
String origin, int tabId, boolean isIncognito, int nativeRequestId) {
Log.d(TAG, "createRoute");
CastUtils.getCastContext().getSessionManager().addSessionManagerListener(
this, CastSession.class);
if (mPendingCreateRouteRequestInfo != null) {
// TODO(zqzhang): do something.
}
......@@ -165,10 +168,21 @@ public abstract class CafBaseMediaRouteProvider
return;
}
mPendingCreateRouteRequestInfo = new CreateRouteRequestInfo(
source, sink, presentationId, origin, tabId, isIncognito, nativeRequestId);
MediaRouter.RouteInfo targetRouteInfo = null;
for (MediaRouter.RouteInfo routeInfo : getAndroidMediaRouter().getRoutes()) {
if (routeInfo.getId().equals(sink.getId())) {
targetRouteInfo = routeInfo;
break;
}
}
if (targetRouteInfo == null) {
mManager.onRouteRequestError("The sink does not exist", nativeRequestId);
}
mSessionController.requestSessionLaunch(mPendingCreateRouteRequestInfo);
mPendingCreateRouteRequestInfo = new CreateRouteRequestInfo(source, sink, presentationId,
origin, tabId, isIncognito, nativeRequestId, targetRouteInfo);
mSessionController.requestSessionLaunch();
}
@Override
......@@ -226,23 +240,33 @@ public abstract class CafBaseMediaRouteProvider
@Override
public final void onSessionEnding(CastSession session) {
mSessionController.detachFromCastSession(session);
getAndroidMediaRouter().selectRoute(getAndroidMediaRouter().getDefaultRoute());
removeAllRoutesWithError(/* error= */ null);
handleSessionEnd(/* error= */ null);
}
@Override
public final void onSessionEnded(CastSession session, int error) {}
public final void onSessionEnded(CastSession session, int error) {
handleSessionEnd((error == CastStatusCodes.SUCCESS)
? null
: ("Session ended with error code " + error));
}
@Override
public final void onSessionSuspended(CastSession session, int reason) {
mSessionController.detachFromCastSession(session);
mSessionController.detachFromCastSession();
}
///////////////////////////////////////////////////////
// SessionManagerListener implementation end
///////////////////////////////////////////////////////
private void handleSessionEnd(String error) {
mSessionController.detachFromCastSession();
getAndroidMediaRouter().selectRoute(getAndroidMediaRouter().getDefaultRoute());
removeAllRoutesWithError(error);
CastUtils.getCastContext().getSessionManager().removeSessionManagerListener(
this, CastSession.class);
}
public @NonNull MediaRouter getAndroidMediaRouter() {
return mAndroidMediaRouter;
}
......@@ -290,4 +314,8 @@ public abstract class CafBaseMediaRouteProvider
public FlingingController getFlingingController(String routeId) {
return null;
}
public CreateRouteRequestInfo getPendingCreateRouteRequestInfo() {
return mPendingCreateRouteRequestInfo;
}
}
......@@ -157,7 +157,7 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider {
clientRecord.routeId, sink, clientRecord.clientId, "cast");
}
mMessageHandler.onSessionStarted(sessionController());
mMessageHandler.onSessionStarted();
sessionController().getSession().getRemoteMediaClient().requestStatus();
}
......@@ -194,7 +194,7 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider {
private CafMediaRouteProvider(MediaRouter androidMediaRouter, MediaRouteManager manager) {
super(androidMediaRouter, manager);
mMessageHandler = new CafMessageHandler(this);
mMessageHandler = new CafMessageHandler(this, sessionController());
}
private boolean canJoinExistingSession(
......
......@@ -72,7 +72,7 @@ public class CafMessageHandler {
// The reference to CastSession, only valid after calling {@link onSessionCreated}, and will be
// reset to null when calling {@link onApplicationStopped}.
private CastSessionController mSessionController;
private final CastSessionController mSessionController;
private final CafMediaRouteProvider mRouteProvider;
private Handler mHandler;
......@@ -95,10 +95,12 @@ public class CafMessageHandler {
* @param session The {@link CastSession} for communicating with the Cast SDK.
* @param provider The {@link CafMediaRouteProvider} for communicating with the page.
*/
public CafMessageHandler(CafMediaRouteProvider provider) {
public CafMessageHandler(
CafMediaRouteProvider provider, CastSessionController sessionController) {
mRouteProvider = provider;
mRequests = new SparseArray<RequestRecord>();
mStopRequests = new ArrayMap<String, Queue<Integer>>();
mSessionController = sessionController;
mVolumeRequests = new ArrayDeque<RequestRecord>();
mHandler = new Handler();
......@@ -141,15 +143,12 @@ public class CafMessageHandler {
* Set the session when a session is started, and notify all clients that are not connected.
* @param session The newly created session.
*/
public void onSessionStarted(CastSessionController sessionController) {
mSessionController = sessionController;
public void onSessionStarted() {
for (ClientRecord client : mRouteProvider.getClientIdToRecords().values()) {
if (!client.isConnected) continue;
notifySessionConnectedToClient(client.clientId);
}
// TODO(zqzhang): Register namespaces.
// TODO(zqzhang): Request media status.
}
/** Notify a client that a session has connected. */
......@@ -438,7 +437,7 @@ public class CafMessageHandler {
@VisibleForTesting
boolean sendJsonCastMessage(JSONObject message, final String namespace, final String clientId,
final int sequenceNumber) throws JSONException {
if (mSessionController == null || !mSessionController.isConnected()) return false;
if (!mSessionController.isConnected()) return false;
removeNullFields(message);
......@@ -553,7 +552,6 @@ public class CafMessageHandler {
}
mStopRequests.remove(clientId);
}
mSessionController = null;
}
/**
......@@ -659,7 +657,7 @@ public class CafMessageHandler {
* @return A message containing the information of the {@link CastSession}.
*/
public String buildSessionMessage() {
if (mSessionController == null || !mSessionController.isConnected()) return "{}";
if (!mSessionController.isConnected()) return "{}";
try {
// "volume" is a part of "receiver" initialized below.
......@@ -778,7 +776,7 @@ public class CafMessageHandler {
private boolean sendStringCastMessage(
String message, String namespace, String clientId, int sequenceNumber) {
if (mSessionController == null || !mSessionController.isConnected()) return false;
if (!mSessionController.isConnected()) return false;
PendingResult<Status> pendingResult =
mSessionController.getSession().sendMessage(namespace, message);
......
......@@ -19,6 +19,7 @@ public class CastOptionsProvider implements OptionsProvider {
return new CastOptions.Builder()
.setCastMediaOptions(null)
.setEnableReconnectionService(false)
.setResumeSavedSession(false)
.build();
}
......
......@@ -10,6 +10,7 @@ import com.google.android.gms.cast.ApplicationMetadata;
import com.google.android.gms.cast.Cast;
import com.google.android.gms.cast.CastDevice;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import org.chromium.base.Log;
import org.chromium.chrome.browser.media.router.CastSessionUtil;
......@@ -33,26 +34,35 @@ public class CastSessionController {
private final CafBaseMediaRouteProvider mProvider;
private MediaSink mSink;
private MediaSource mSource;
private List<String> mNamespaces;
private List<String> mNamespaces = new ArrayList<String>();
private final CastListener mCastListener;
private final MediaRouter.Callback mMediaRouterCallbackForSessionLaunch;
public CastSessionController(CafBaseMediaRouteProvider provider) {
mProvider = provider;
mCastListener = new CastListener();
mMediaRouterCallbackForSessionLaunch = new MediaRouterCallbackForSessionLaunch();
}
public void requestSessionLaunch(CreateRouteRequestInfo request) {
public void requestSessionLaunch() {
CreateRouteRequestInfo request = mProvider.getPendingCreateRouteRequestInfo();
mSource = request.source;
mSink = request.sink;
CastUtils.getCastContext().setReceiverApplicationId(request.source.getApplicationId());
for (MediaRouter.RouteInfo routeInfo : mProvider.getAndroidMediaRouter().getRoutes()) {
if (routeInfo.getId().equals(request.sink.getId())) {
// Unselect and then select so that CAF will get notified of the selection.
mProvider.getAndroidMediaRouter().unselect(MediaRouter.UNSELECT_REASON_UNKNOWN);
routeInfo.select();
break;
}
if (request.routeInfo.isSelected()) {
// If a route has just been selected, CAF might not be ready yet before setting the app
// ID. So unselect and select the route will let CAF be aware that the route has been
// selected thus it can start the session.
//
// An issue of this workaround is that if a route is unselected and selected in a very
// short time, the selection might be ignored by MediaRouter, so put the reselection in
// a callback.
mProvider.getAndroidMediaRouter().addCallback(
mSource.buildRouteSelector(), mMediaRouterCallbackForSessionLaunch);
mProvider.getAndroidMediaRouter().unselect(MediaRouter.UNSELECT_REASON_UNKNOWN);
} else {
request.routeInfo.select();
}
}
......@@ -68,6 +78,10 @@ public class CastSessionController {
return mCastSession;
}
public RemoteMediaClient getRemoteMediaClient() {
return mCastSession.getRemoteMediaClient();
}
public void endSession() {
MediaRouter mediaRouter = mProvider.getAndroidMediaRouter();
mediaRouter.selectRoute(mediaRouter.getDefaultRoute());
......@@ -111,9 +125,13 @@ public class CastSessionController {
public void attachToCastSession(CastSession session) {
mCastSession = session;
mCastSession.addCastListener(mCastListener);
updateNamespaces();
}
public void detachFromCastSession(CastSession session) {
public void detachFromCastSession() {
if (mCastSession == null) return;
mNamespaces.clear();
mCastSession.removeCastListener(mCastListener);
mCastSession = null;
}
......@@ -194,4 +212,18 @@ public class CastSessionController {
CafMessageHandler messageHandler = mProvider.getMessageHandler();
messageHandler.onMessageReceived(namespace, message);
}
private class MediaRouterCallbackForSessionLaunch extends MediaRouter.Callback {
@Override
public void onRouteUnselected(MediaRouter mediaRouter, MediaRouter.RouteInfo routeInfo) {
if (mProvider.getPendingCreateRouteRequestInfo() == null) return;
if (routeInfo.getId().equals(
mProvider.getPendingCreateRouteRequestInfo().routeInfo.getId())) {
routeInfo.select();
mProvider.getAndroidMediaRouter().removeCallback(
mMediaRouterCallbackForSessionLaunch);
}
}
}
}
......@@ -4,6 +4,8 @@
package org.chromium.chrome.browser.media.router.caf;
import android.support.v7.media.MediaRouter;
import org.chromium.chrome.browser.media.router.MediaSink;
import org.chromium.chrome.browser.media.router.MediaSource;
......@@ -16,9 +18,11 @@ public class CreateRouteRequestInfo {
public final int tabId;
public final boolean isIncognito;
public final int nativeRequestId;
public final MediaRouter.RouteInfo routeInfo;
public CreateRouteRequestInfo(MediaSource source, MediaSink sink, String presentationId,
String origin, int tabId, boolean isIncognito, int nativeRequestId) {
String origin, int tabId, boolean isIncognito, int nativeRequestId,
MediaRouter.RouteInfo routeInfo) {
this.source = source;
this.sink = sink;
this.presentationId = presentationId;
......@@ -26,5 +30,6 @@ public class CreateRouteRequestInfo {
this.tabId = tabId;
this.isIncognito = isIncognito;
this.nativeRequestId = nativeRequestId;
this.routeInfo = routeInfo;
}
}
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