Commit 8979a892 authored by Zhiqiang Zhang's avatar Zhiqiang Zhang Committed by Commit Bot

Unit test for CafMediaRouteProvider

This CL adds unit test for CafMediaRouteProvider. Also to clean
up, the provider reads the sink directly from the session
controller when onSessionStarted().

Bug: 711860
Change-Id: I9c4001bc4d6a59f764b71bf90f9fd96761454963
Reviewed-on: https://chromium-review.googlesource.com/c/1339461
Commit-Queue: Zhiqiang Zhang <zqzhang@chromium.org>
Reviewed-by: default avatarThomas Guilbert <tguilbert@chromium.org>
Cr-Commit-Position: refs/heads/master@{#609625}
parent 645a4900
...@@ -205,4 +205,12 @@ public class BaseSessionController { ...@@ -205,4 +205,12 @@ public class BaseSessionController {
public FlingingController getFlingingController() { public FlingingController getFlingingController() {
return null; return null;
} }
/**
* Helper message to get the session ID of the attached session. For stubbing in tests as
* {@link CastSession#getSessionId()} is final.
*/
public String getSessionId() {
return isConnected() ? getSession().getSessionId() : null;
}
} }
...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.media.router.caf; ...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.media.router.caf;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v7.media.MediaRouteSelector; import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter; import android.support.v7.media.MediaRouter;
import android.support.v7.media.MediaRouter.RouteInfo; import android.support.v7.media.MediaRouter.RouteInfo;
...@@ -43,7 +44,8 @@ public abstract class CafBaseMediaRouteProvider ...@@ -43,7 +44,8 @@ public abstract class CafBaseMediaRouteProvider
protected final MediaRouteManager mManager; protected final MediaRouteManager mManager;
protected final Map<String, DiscoveryCallback> mDiscoveryCallbacks = protected final Map<String, DiscoveryCallback> mDiscoveryCallbacks =
new HashMap<String, DiscoveryCallback>(); new HashMap<String, DiscoveryCallback>();
protected final Map<String, MediaRoute> mRoutes = new HashMap<String, MediaRoute>(); @VisibleForTesting
final Map<String, MediaRoute> mRoutes = new HashMap<String, MediaRoute>();
protected Handler mHandler = new Handler(); protected Handler mHandler = new Handler();
private CreateRouteRequestInfo mPendingCreateRouteRequestInfo; private CreateRouteRequestInfo mPendingCreateRouteRequestInfo;
...@@ -199,6 +201,8 @@ public abstract class CafBaseMediaRouteProvider ...@@ -199,6 +201,8 @@ public abstract class CafBaseMediaRouteProvider
return; return;
} }
// Don't remove the route while the session is still active. All the routes will be removed
// upon session end.
sessionController().endSession(); sessionController().endSession();
} }
......
...@@ -8,6 +8,7 @@ import static org.chromium.chrome.browser.media.router.caf.CastUtils.isSameOrigi ...@@ -8,6 +8,7 @@ import static org.chromium.chrome.browser.media.router.caf.CastUtils.isSameOrigi
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v7.media.MediaRouter; import android.support.v7.media.MediaRouter;
import com.google.android.gms.cast.framework.CastSession; import com.google.android.gms.cast.framework.CastSession;
...@@ -36,12 +37,14 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider { ...@@ -36,12 +37,14 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider {
private CreateRouteRequestInfo mPendingCreateRouteRequestInfo; private CreateRouteRequestInfo mPendingCreateRouteRequestInfo;
private ClientRecord mLastRemovedRouteRecord; @VisibleForTesting
ClientRecord mLastRemovedRouteRecord;
// The records for clients, which must match mRoutes. This is used for the saving last record // The records for clients, which must match mRoutes. This is used for the saving last record
// for autojoin. // for autojoin.
private final Map<String, ClientRecord> mClientIdToRecords = private final Map<String, ClientRecord> mClientIdToRecords =
new HashMap<String, ClientRecord>(); new HashMap<String, ClientRecord>();
private CafMessageHandler mMessageHandler; @VisibleForTesting
CafMessageHandler mMessageHandler;
// The session controller which is always attached to the current CastSession. // The session controller which is always attached to the current CastSession.
private final CastSessionController mSessionController; private final CastSessionController mSessionController;
...@@ -75,7 +78,6 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider { ...@@ -75,7 +78,6 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider {
MediaRoute route = MediaRoute route =
new MediaRoute(sessionController().getSink().getId(), sourceId, presentationId); new MediaRoute(sessionController().getSink().getId(), sourceId, presentationId);
addRoute(route, origin, tabId, nativeRequestId, /* wasLaunched= */ false); addRoute(route, origin, tabId, nativeRequestId, /* wasLaunched= */ false);
mManager.onRouteCreated(route.id, route.sinkId, nativeRequestId, this, false);
} }
// TODO(zqzhang): the clientRecord/route management is not clean and the logic seems to be // TODO(zqzhang): the clientRecord/route management is not clean and the logic seems to be
...@@ -90,8 +92,7 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider { ...@@ -90,8 +92,7 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider {
ClientRecord client = getClientRecordByRouteId(routeId); ClientRecord client = getClientRecordByRouteId(routeId);
if (client != null) { if (client != null) {
MediaSink sink = MediaSink.fromSinkId( MediaSink sink = sessionController().getSink();
sessionController().getSink().getId(), getAndroidMediaRouter());
if (sink != null) { if (sink != null) {
mMessageHandler.sendReceiverActionToClient(routeId, sink, client.clientId, "stop"); mMessageHandler.sendReceiverActionToClient(routeId, sink, client.clientId, "stop");
} }
...@@ -153,10 +154,8 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider { ...@@ -153,10 +154,8 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider {
for (ClientRecord clientRecord : mClientIdToRecords.values()) { for (ClientRecord clientRecord : mClientIdToRecords.values()) {
// Should be exactly one instance of MediaRoute/ClientRecord at this moment. // Should be exactly one instance of MediaRoute/ClientRecord at this moment.
MediaRoute route = mRoutes.get(clientRecord.routeId); mMessageHandler.sendReceiverActionToClient(clientRecord.routeId,
MediaSink sink = MediaSink.fromSinkId(route.sinkId, getAndroidMediaRouter()); sessionController().getSink(), clientRecord.clientId, "cast");
mMessageHandler.sendReceiverActionToClient(
clientRecord.routeId, sink, clientRecord.clientId, "cast");
} }
mMessageHandler.onSessionStarted(); mMessageHandler.onSessionStarted();
...@@ -200,14 +199,15 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider { ...@@ -200,14 +199,15 @@ public class CafMediaRouteProvider extends CafBaseMediaRouteProvider {
mMessageHandler = new CafMessageHandler(this, mSessionController); mMessageHandler = new CafMessageHandler(this, mSessionController);
} }
private boolean canJoinExistingSession( @VisibleForTesting
boolean canJoinExistingSession(
String presentationId, String origin, int tabId, CastMediaSource source) { String presentationId, String origin, int tabId, CastMediaSource source) {
if (AUTO_JOIN_PRESENTATION_ID.equals(presentationId)) { if (AUTO_JOIN_PRESENTATION_ID.equals(presentationId)) {
return canAutoJoin(source, origin, tabId); return canAutoJoin(source, origin, tabId);
} }
if (presentationId.startsWith(PRESENTATION_ID_SESSION_ID_PREFIX)) { if (presentationId.startsWith(PRESENTATION_ID_SESSION_ID_PREFIX)) {
String sessionId = presentationId.substring(PRESENTATION_ID_SESSION_ID_PREFIX.length()); String sessionId = presentationId.substring(PRESENTATION_ID_SESSION_ID_PREFIX.length());
return sessionController().getSession().getSessionId().equals(sessionId); return sessionId != null && sessionId.equals(sessionController().getSessionId());
} }
for (MediaRoute route : mRoutes.values()) { for (MediaRoute route : mRoutes.values()) {
if (route.presentationId.equals(presentationId)) return true; if (route.presentationId.equals(presentationId)) return true;
......
...@@ -2378,9 +2378,11 @@ chrome_junit_test_java_sources = [ ...@@ -2378,9 +2378,11 @@ chrome_junit_test_java_sources = [
"junit/src/org/chromium/chrome/browser/media/router/caf/MediaRouterTestHelper.java", "junit/src/org/chromium/chrome/browser/media/router/caf/MediaRouterTestHelper.java",
"junit/src/org/chromium/chrome/browser/media/router/caf/ShadowMediaRouter.java", "junit/src/org/chromium/chrome/browser/media/router/caf/ShadowMediaRouter.java",
"junit/src/org/chromium/chrome/browser/media/router/caf/ShadowCastContext.java", "junit/src/org/chromium/chrome/browser/media/router/caf/ShadowCastContext.java",
"junit/src/org/chromium/chrome/browser/media/router/caf/ShadowCastMediaSource.java",
"junit/src/org/chromium/chrome/browser/media/router/caf/BaseSessionControllerTest.java", "junit/src/org/chromium/chrome/browser/media/router/caf/BaseSessionControllerTest.java",
"junit/src/org/chromium/chrome/browser/media/router/caf/CastSessionControllerTest.java", "junit/src/org/chromium/chrome/browser/media/router/caf/CastSessionControllerTest.java",
"junit/src/org/chromium/chrome/browser/media/router/caf/CafBaseMediaRouteProviderTest.java", "junit/src/org/chromium/chrome/browser/media/router/caf/CafBaseMediaRouteProviderTest.java",
"junit/src/org/chromium/chrome/browser/media/router/caf/CafMediaRouteProviderTest.java",
"junit/src/org/chromium/chrome/browser/media/ui/MediaImageManagerTest.java", "junit/src/org/chromium/chrome/browser/media/ui/MediaImageManagerTest.java",
"junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationActionsUpdatedTest.java", "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationActionsUpdatedTest.java",
"junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationButtonComputationTest.java", "junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationButtonComputationTest.java",
......
...@@ -50,7 +50,6 @@ import org.chromium.chrome.browser.media.router.MediaSource; ...@@ -50,7 +50,6 @@ import org.chromium.chrome.browser.media.router.MediaSource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* Robolectric tests for CafBaseMediaRouteProvider. * Robolectric tests for CafBaseMediaRouteProvider.
...@@ -394,9 +393,9 @@ public class CafBaseMediaRouteProviderTest { ...@@ -394,9 +393,9 @@ public class CafBaseMediaRouteProviderTest {
inOrder.verify(mSessionController).attachToCastSession(mCastSession); inOrder.verify(mSessionController).attachToCastSession(mCastSession);
inOrder.verify(mSessionController).onSessionStarted(); inOrder.verify(mSessionController).onSessionStarted();
assertEquals(mProvider.getRoutes().size(), 1); assertEquals(mProvider.mRoutes.size(), 1);
MediaRoute route = (MediaRoute) (mProvider.getRoutes().values().toArray()[0]); MediaRoute route = (MediaRoute) (mProvider.mRoutes.values().toArray()[0]);
assertEquals(route.sinkId, "cast-route"); assertEquals(route.sinkId, "cast-route");
assertEquals(route.sourceId, "source-id"); assertEquals(route.sourceId, "source-id");
assertEquals(route.presentationId, "presentation-id"); assertEquals(route.presentationId, "presentation-id");
...@@ -586,10 +585,10 @@ public class CafBaseMediaRouteProviderTest { ...@@ -586,10 +585,10 @@ public class CafBaseMediaRouteProviderTest {
} }
private void assertContainsRoutes(MediaRoute... routes) { private void assertContainsRoutes(MediaRoute... routes) {
assertEquals(mProvider.getRoutes().size(), routes.length); assertEquals(mProvider.mRoutes.size(), routes.length);
for (MediaRoute route : routes) { for (MediaRoute route : routes) {
assertEquals(mProvider.getRoutes().get(route.id), route); assertEquals(mProvider.mRoutes.get(route.id), route);
} }
} }
...@@ -614,9 +613,5 @@ public class CafBaseMediaRouteProviderTest { ...@@ -614,9 +613,5 @@ public class CafBaseMediaRouteProviderTest {
@Override @Override
public void joinRoute(String routeId, String presentationId, String origin, int tabId, public void joinRoute(String routeId, String presentationId, String origin, int tabId,
int nativeRequestId) {} int nativeRequestId) {}
public Map<String, MediaRoute> getRoutes() {
return mRoutes;
}
} }
} }
// 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.
package org.chromium.chrome.browser.media.router.caf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.support.v7.media.MediaRouter;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.cast.framework.CastSession;
import com.google.android.gms.cast.framework.SessionManager;
import com.google.android.gms.cast.framework.media.RemoteMediaClient;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.media.router.ClientRecord;
import org.chromium.chrome.browser.media.router.MediaRoute;
import org.chromium.chrome.browser.media.router.MediaRouteManager;
import org.chromium.chrome.browser.media.router.MediaSink;
import org.chromium.chrome.browser.media.router.cast.CastMediaSource;
/**
* Robolectric tests for CafMediaRouteProvider.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE,
shadows = {ShadowMediaRouter.class, ShadowCastContext.class, ShadowLooper.class,
ShadowCastMediaSource.class})
public class CafMediaRouteProviderTest {
private static final String SUPPORTED_SOURCE = "cast:DEADBEEF";
private static final String SUPPORTED_AUTOJOIN_SOURCE = "cast:DEADBEEF"
+ "?clientId=12345&autoJoinPolicy=" + CastMediaSource.AUTOJOIN_TAB_AND_ORIGIN_SCOPED;
private Context mContext;
private CafMediaRouteProvider mProvider;
private MediaRouterTestHelper mMediaRouterHelper;
private MediaRouter mMediaRouter;
private MediaRoute mRoute1;
private MediaRoute mRoute2;
@Mock
private MediaRouteManager mManager;
@Mock
private CastContext mCastContext;
@Mock
private CastSession mCastSession;
@Mock
private SessionManager mSessionManager;
@Mock
private RemoteMediaClient mRemoteMediaClient;
@Mock
private BaseSessionController mSessionController;
@Mock
private ShadowCastMediaSource.ShadowImplementation mShadowCastMediaSource;
@Mock
private CafMessageHandler mMessageHandler;
@Mock
private CastMediaSource mSource1;
@Mock
private CastMediaSource mSource2;
@Mock
private MediaSink mSink;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
ShadowCastContext.setInstance(mCastContext);
ShadowCastMediaSource.setImplementation(mShadowCastMediaSource);
mMediaRouterHelper = new MediaRouterTestHelper();
mMediaRouter = MediaRouter.getInstance(mContext);
mProvider = spy(CafMediaRouteProvider.create(mManager));
mProvider.mMessageHandler = mMessageHandler;
mRoute1 = new MediaRoute("sink-id", "source-id-1", "presentation-id-1");
mRoute2 = new MediaRoute("sink-id", "source-id-2", "presentation-id-2");
doReturn(mSource1).when(mShadowCastMediaSource).from("source-id-1");
doReturn(mSource2).when(mShadowCastMediaSource).from("source-id-2");
doReturn("client-id-1").when(mSource1).getClientId();
doReturn("client-id-2").when(mSource2).getClientId();
doReturn("app-id-1").when(mSource1).getApplicationId();
doReturn("app-id-2").when(mSource2).getApplicationId();
doReturn("sink-id").when(mSink).getId();
doReturn(mSessionController).when(mProvider).sessionController();
doReturn(mSessionManager).when(mCastContext).getSessionManager();
doReturn(mCastSession).when(mSessionController).getSession();
doReturn(mRemoteMediaClient).when(mCastSession).getRemoteMediaClient();
}
@Test
public void testJoinRoute() {
InOrder inOrder = inOrder(mManager);
doReturn(mSource1).when(mShadowCastMediaSource).from("source-id-1");
doReturn(mSink).when(mSessionController).getSink();
doReturn(true).when(mSessionController).isConnected();
doReturn(true).when(mProvider).canJoinExistingSession(
anyString(), anyString(), anyInt(), any(CastMediaSource.class));
// Regular case.
mProvider.joinRoute("source-id-1", "presentation-id-1", "origin", 1, 1);
inOrder.verify(mManager, never()).onRouteRequestError(anyString(), anyInt());
inOrder.verify(mManager).onRouteCreated(
anyString(), eq("sink-id"), eq(1), eq(mProvider), eq(false));
assertEquals(mProvider.mRoutes.size(), 1);
MediaRoute route = (MediaRoute) (mProvider.mRoutes.values().toArray()[0]);
assertEquals(route.sinkId, "sink-id");
assertEquals(route.sourceId, "source-id-1");
assertEquals(route.presentationId, "presentation-id-1");
// No source.
mProvider.mRoutes.clear();
doReturn(null).when(mShadowCastMediaSource).from("source-id-1");
mProvider.joinRoute("source-id-1", "presentation-id-1", "origin", 1, 1);
verifyRouteRequestError(inOrder, "Unsupported presentation URL", 1);
assertTrue(mProvider.mRoutes.isEmpty());
// No client ID.
doReturn(mSource1).when(mShadowCastMediaSource).from("source-id-1");
doReturn(null).when(mSource1).getClientId();
mProvider.joinRoute("source-id-1", "presentation-id-1", "origin", 1, 1);
verifyRouteRequestError(inOrder, "Unsupported presentation URL", 1);
assertTrue(mProvider.mRoutes.isEmpty());
// No session.
doReturn("client-id-1").when(mSource1).getClientId();
doReturn(false).when(mSessionController).isConnected();
mProvider.joinRoute("source-id-1", "presentation-id-1", "origin", 1, 1);
verifyRouteRequestError(inOrder, "No presentation", 1);
assertTrue(mProvider.mRoutes.isEmpty());
// No matching route.
doReturn(true).when(mSessionController).isConnected();
doReturn(false).when(mProvider).canJoinExistingSession(
anyString(), anyString(), anyInt(), any(CastMediaSource.class));
mProvider.joinRoute("source-id-1", "presentation-id-1", "origin", 1, 1);
verifyRouteRequestError(inOrder, "No matching route", 1);
assertTrue(mProvider.mRoutes.isEmpty());
}
@Test
public void testCloseRoute() {
InOrder inOrder = inOrder(mMessageHandler);
doReturn(mSink).when(mSessionController).getSink();
// Regular case when there is active session.
mProvider.addRoute(mRoute1, "origin", 1, 1, false);
doReturn(true).when(mSessionController).isConnected();
mProvider.closeRoute(mRoute1.id);
inOrder.verify(mMessageHandler)
.sendReceiverActionToClient(mRoute1.id, mSink, "client-id-1", "stop");
assertEquals(mProvider.mRoutes.size(), 1);
assertEquals(mProvider.getClientIdToRecords().size(), 1);
// Abnormal case when the session controller doesn't have a sink.
doReturn(null).when(mSessionController).getSink();
mProvider.closeRoute(mRoute1.id);
inOrder.verify(mMessageHandler, never())
.sendReceiverActionToClient(
anyString(), any(MediaSink.class), anyString(), anyString());
assertEquals(mProvider.mRoutes.size(), 1);
assertEquals(mProvider.getClientIdToRecords().size(), 1);
// Abnormal case when there is no session.
doReturn(mSink).when(mSessionController).getSink();
doReturn(false).when(mSessionController).isConnected();
mProvider.closeRoute(mRoute1.id);
inOrder.verify(mMessageHandler, never())
.sendReceiverActionToClient(
anyString(), any(MediaSink.class), anyString(), anyString());
assertTrue(mProvider.mRoutes.isEmpty());
assertTrue(mProvider.getClientIdToRecords().isEmpty());
}
@Test
public void testSendStringMessage() {
InOrder inOrder = inOrder(mMessageHandler);
mProvider.addRoute(mRoute1, "origin", 1, 1, false);
// A client in record sends a message.
mProvider.sendStringMessage(mRoute1.id, "message");
inOrder.verify(mMessageHandler).handleMessageFromClient("message");
// An unknown client sends a mesasge.
mProvider.sendStringMessage("other-route-id", "message");
inOrder.verify(mMessageHandler, never()).handleMessageFromClient(anyString());
}
@Test
public void testSendMessageToClient() {
InOrder inOrder = inOrder(mManager);
mProvider.addRoute(mRoute1, "origin", 1, 1, false);
mProvider.getClientIdToRecords().get("client-id-1").isConnected = true;
// Normal case.
mProvider.sendMessageToClient("client-id-1", "message");
inOrder.verify(mManager).onMessage(mRoute1.id, "message");
// Client is not in record.
mProvider.sendMessageToClient("client-id-unkonwn", "message");
inOrder.verify(mManager, never()).onMessage(anyString(), anyString());
// Message enqueued while client is not connected.
mProvider.getClientIdToRecords().get("client-id-1").isConnected = false;
mProvider.sendMessageToClient("client-id-1", "message");
inOrder.verify(mManager, never()).onMessage(anyString(), anyString());
// Flush message
mProvider.flushPendingMessagesToClient(mProvider.getClientIdToRecords().get("client-id-1"));
inOrder.verify(mManager).onMessage(mRoute1.id, "message");
}
@Test
public void testOnSessionStarted() {
InOrder inOrder = inOrder(mSessionController, mRemoteMediaClient);
doReturn(mSink).when(mSessionController).getSink();
// Prepare the pending create route request so super.onSessionStarted() behaves correctly.
mProvider.createRoute(
"source-id-1", "cast-route", "presentation-id", "origin", 1, false, 1);
mProvider.addRoute(mRoute1, "origin", 1, 1, false);
mProvider.addRoute(mRoute2, "origin", 1, 1, false);
// Skip adding route when the super.onSessionStarted() is called.
doNothing().when(mProvider).addRoute(
any(MediaRoute.class), anyString(), anyInt(), anyInt(), anyBoolean());
mProvider.onSessionStarted(mCastSession, "session-id");
// Verify super.onSessionStarted() is called.
inOrder.verify(mSessionController).attachToCastSession(mCastSession);
inOrder.verify(mRemoteMediaClient).requestStatus();
verify(mMessageHandler)
.sendReceiverActionToClient(mRoute1.id, mSink, "client-id-1", "cast");
verify(mMessageHandler)
.sendReceiverActionToClient(mRoute2.id, mSink, "client-id-2", "cast");
}
@Test
public void testRouteManagement() {
// Add the first route.
mProvider.addRoute(mRoute1, "origin-1", 1, 1, false);
assertEquals(mProvider.mRoutes.size(), 1);
assertEquals(mProvider.getClientIdToRecords().size(), 1);
ClientRecord record = mProvider.getClientIdToRecords().get("client-id-1");
verifyClientRecord(record, mRoute1.id, "client-id-1", "app-id-1", "origin-1", 1, false);
// Add the second route.
mProvider.addRoute(mRoute2, "origin-2", 2, 2, false);
assertEquals(mProvider.mRoutes.size(), 2);
assertEquals(mProvider.getClientIdToRecords().size(), 2);
record = mProvider.getClientIdToRecords().get("client-id-2");
verifyClientRecord(record, mRoute2.id, "client-id-2", "app-id-2", "origin-2", 2, false);
// Add a duplicate route. This addition will be ignored as `mRoute2` is already in record.
// This should never happen in production.
mProvider.addRoute(mRoute2, "origin-3", 3, 3, false);
assertEquals(mProvider.mRoutes.size(), 2);
assertEquals(mProvider.getClientIdToRecords().size(), 2);
record = mProvider.getClientIdToRecords().get("client-id-2");
verifyClientRecord(record, mRoute2.id, "client-id-2", "app-id-2", "origin-2", 2, false);
// Remove a route.
ClientRecord lastRecord = mProvider.getClientIdToRecords().get("client-id-1");
mProvider.removeRoute(mRoute1.id, null);
assertEquals(mProvider.mRoutes.size(), 1);
assertEquals(mProvider.getClientIdToRecords().size(), 1);
record = mProvider.getClientIdToRecords().get("client-id-2");
verifyClientRecord(record, mRoute2.id, "client-id-2", "app-id-2", "origin-2", 2, false);
assertEquals(mProvider.mLastRemovedRouteRecord, lastRecord);
// Remove a non-existing route.
mProvider.removeRoute(mRoute1.id, null);
assertEquals(mProvider.mRoutes.size(), 1);
assertEquals(mProvider.getClientIdToRecords().size(), 1);
record = mProvider.getClientIdToRecords().get("client-id-2");
verifyClientRecord(record, mRoute2.id, "client-id-2", "app-id-2", "origin-2", 2, false);
lastRecord = record;
// Remove the last route.
mProvider.removeRoute(mRoute2.id, null);
assertTrue(mProvider.mRoutes.isEmpty());
assertTrue(mProvider.getClientIdToRecords().isEmpty());
assertEquals(mProvider.mLastRemovedRouteRecord, lastRecord);
}
@Test
public void testCanJoin_matchingSessionId() {
// Regular case.
doReturn("session-id").when(mSessionController).getSessionId();
assertTrue(mProvider.canJoinExistingSession(
"cast-session_session-id", "origin", 1, mock(CastMediaSource.class)));
// The current session ID is null.
doReturn(null).when(mSessionController).getSessionId();
assertFalse(mProvider.canJoinExistingSession(
"cast-session_session-id", "origin", 1, mock(CastMediaSource.class)));
// Mismatching session ID.
doReturn("session-id").when(mSessionController).getSessionId();
assertFalse(mProvider.canJoinExistingSession(
"cast-session_other-session-id", "origin", 1, mock(CastMediaSource.class)));
}
@Test
public void testAutoJoin_usingLastRemovedRouteRecord() {
doReturn("app-id-1").when(mSource1).getApplicationId();
doReturn("app-id-1").when(mSource2).getApplicationId();
doReturn("tab_and_origin_scoped").when(mSource2).getAutoJoinPolicy();
doReturn(mSource1).when(mSessionController).getSource();
mProvider.addRoute(mRoute1, "origin-1", 1, 1, false);
mProvider.removeRoute(mRoute1.id, null);
// Regular case.
assertTrue(mProvider.canJoinExistingSession("auto-join", "origin-1", 1, mSource2));
// Mismatching origin.
assertFalse(mProvider.canJoinExistingSession("auto-join", "origin-2", 1, mSource2));
// Mismatching tab id.
assertFalse(mProvider.canJoinExistingSession("auto-join", "origin-1", 2, mSource2));
}
@Test
public void testAutoJoin_mismatchingSources() {
doReturn("app-id-1").when(mSource1).getApplicationId();
doReturn("app-id-1").when(mSource2).getApplicationId();
doReturn("tab_and_origin_scoped").when(mSource2).getAutoJoinPolicy();
doReturn(mSource1).when(mSessionController).getSource();
mProvider.addRoute(mRoute1, "origin-1", 1, 1, false);
mProvider.removeRoute(mRoute1.id, null);
// Page scoped auto-join policy.
doReturn("page_scoped").when(mSource2).getAutoJoinPolicy();
assertFalse(mProvider.canJoinExistingSession("auto-join", "origin-1", 1, mSource2));
// Mismatching app ID.
doReturn("tab_and_origin_scoped").when(mSource2).getAutoJoinPolicy();
doReturn("app-id-2").when(mSource2).getApplicationId();
assertFalse(mProvider.canJoinExistingSession("auto-join", "origin-1", 1, mSource2));
}
@Test
public void testAutoJoin_originScoped() {
doReturn("app-id-1").when(mSource1).getApplicationId();
doReturn("app-id-1").when(mSource2).getApplicationId();
doReturn("origin_scoped").when(mSource2).getAutoJoinPolicy();
doReturn(mSource1).when(mSessionController).getSource();
mProvider.addRoute(mRoute1, "origin-1", 1, 1, false);
// Normal case.
assertTrue(mProvider.canJoinExistingSession("auto-join", "origin-1", 1, mSource2));
// Mismatching tab ID is allowed.
assertTrue(mProvider.canJoinExistingSession("auto-join", "origin-1", 2, mSource2));
// Mismatching origin is not allowed.
assertFalse(mProvider.canJoinExistingSession("auto-join", "origin-2", 1, mSource2));
}
@Test
public void testAutoJoin_tabAndOriginScoped() {
doReturn("app-id-1").when(mSource1).getApplicationId();
doReturn("app-id-1").when(mSource2).getApplicationId();
doReturn("tab_and_origin_scoped").when(mSource2).getAutoJoinPolicy();
doReturn(mSource1).when(mSessionController).getSource();
mProvider.addRoute(mRoute1, "origin-1", 1, 1, false);
// Normal case.
assertTrue(mProvider.canJoinExistingSession("auto-join", "origin-1", 1, mSource2));
// Mismatching tab ID is not allowed.
assertFalse(mProvider.canJoinExistingSession("auto-join", "origin-1", 2, mSource2));
// Mismatching origin is not allowed.
assertFalse(mProvider.canJoinExistingSession("auto-join", "origin-2", 1, mSource2));
}
private void verifyRouteRequestError(InOrder inOrder, String error, int nativeRequestId) {
inOrder.verify(mManager).onRouteRequestError(error, nativeRequestId);
inOrder.verify(mManager, never())
.onRouteCreated(anyString(), anyString(), anyInt(),
any(CafBaseMediaRouteProvider.class), anyBoolean());
}
private void verifyClientRecord(ClientRecord record, String routeId, String clientId,
String appId, String origin, int tabId, boolean isConnected) {
assertEquals(record.routeId, routeId);
assertEquals(record.clientId, clientId);
assertEquals(record.appId, appId);
assertEquals(record.origin, origin);
assertEquals(record.tabId, tabId);
assertEquals(record.isConnected, isConnected);
}
}
// 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.
package org.chromium.chrome.browser.media.router.caf;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.chromium.chrome.browser.media.router.cast.CastMediaSource;
/** Shadow implementation for {@link MediaRouter}. */
@Implements(CastMediaSource.class)
public class ShadowCastMediaSource {
private static ShadowImplementation sImpl;
@Implementation
public static CastMediaSource from(String sourceId) {
return sImpl.from(sourceId);
}
public static void setImplementation(ShadowImplementation impl) {
sImpl = impl;
}
/** The implementation skeleton for the implementation backing the shadow. */
public static class ShadowImplementation {
public CastMediaSource from(String sourceId) {
return null;
}
}
}
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