blob: 8c54cc3c7ab7065c473ae9e17f56a91309dcbf42 [file] [log] [blame]
// 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.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.content.Context;
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.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.media.router.CastSessionUtil;
import org.chromium.chrome.browser.media.router.MediaSink;
import org.chromium.chrome.browser.media.router.MediaSource;
import java.util.ArrayList;
import java.util.List;
/**
* Robolectric tests for CastSessionController.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
public class CastSessionControllerTest {
private static final String PRESENTATION_ID = "presentation-id";
private static final String ORIGIN = "https://example.com/";
private static final int TAB_ID = 1;
private static final String APP_ID = "12345678";
@Mock
private CastDevice mCastDevice;
@Mock
private CafMediaRouteProvider mProvider;
@Mock
private MediaSource mSource;
@Mock
private MediaSink mSink;
@Mock
private CastSession mCastSession;
@Mock
private RemoteMediaClient mRemoteMediaClient;
@Mock
private CafMessageHandler mMessageHandler;
@Mock
private ApplicationMetadata mApplicationMetadata;
private CastSessionController mController;
private CreateRouteRequestInfo mRequestInfo;
private MediaRouterTestHelper mMediaRouterHelper;
private Context mContext;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application;
mMediaRouterHelper = new MediaRouterTestHelper();
mController = spy(new CastSessionController(mProvider));
mController.initNestedFieldsForTesting();
doReturn(mRemoteMediaClient).when(mCastSession).getRemoteMediaClient();
doReturn(true).when(mCastSession).isConnected();
doReturn(mApplicationMetadata).when(mCastSession).getApplicationMetadata();
doReturn(mCastDevice).when(mCastSession).getCastDevice();
doReturn(mMessageHandler).when(mProvider).getMessageHandler();
doReturn("session_message").when(mMessageHandler).buildSessionMessage();
}
@Test
public void testSessionAttachment() {
// Attaching to a session.
mController.attachToCastSession(mCastSession);
verify(mCastSession).addCastListener(any(Cast.Listener.class));
assertSame(mController.getSession(), mCastSession);
// Detaching from a session.
mController.detachFromCastSession();
verify(mCastSession).removeCastListener(any(Cast.Listener.class));
assertNull(mController.getSession());
}
@Test
public void testOnSessionEnded() {
mController.attachToCastSession(mCastSession);
mController.onSessionEnded();
verify(mMessageHandler).onSessionEnded();
}
@Test
public void testCastListener() {
InOrder inOrder = inOrder(mController, mMessageHandler);
ArgumentCaptor<Cast.Listener> castListenerCaptor =
ArgumentCaptor.forClass(Cast.Listener.class);
mController.attachToCastSession(mCastSession);
verify(mCastSession).addCastListener(castListenerCaptor.capture());
// When the application status is changed, the namespaces should be updated and a session
// message needs to be broadcasted.
castListenerCaptor.getValue().onApplicationStatusChanged();
inOrder.verify(mController).updateNamespaces();
inOrder.verify(mMessageHandler).broadcastClientMessage("update_session", "session_message");
// When the application metadata is changed, the namespaces should be updated and a session
// message needs to be broadcasted.
castListenerCaptor.getValue().onApplicationMetadataChanged(mApplicationMetadata);
inOrder.verify(mController).updateNamespaces();
inOrder.verify(mMessageHandler).broadcastClientMessage("update_session", "session_message");
// When the volume is changed, the namespaces should be updated and a session message and a
// volume change message needs to be broadcasted.
castListenerCaptor.getValue().onVolumeChanged();
inOrder.verify(mController).updateNamespaces();
inOrder.verify(mMessageHandler).broadcastClientMessage("update_session", "session_message");
inOrder.verify(mMessageHandler).onVolumeChanged();
}
@Test
public void testUpdateNamespaces() throws Exception {
org.robolectric.shadows.ShadowLog.stream = System.out;
InOrder inOrder = inOrder(mCastSession);
mController.attachToCastSession(mCastSession);
List<String> namespaces = new ArrayList<>();
List<String> observedNamespaces = new ArrayList<>();
doReturn(namespaces).when(mApplicationMetadata).getSupportedNamespaces();
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
observedNamespaces.add((String) (invocation.getArguments()[0]));
return null;
}
})
.when(mCastSession)
.setMessageReceivedCallbacks(
any(String.class), any(Cast.MessageReceivedCallback.class));
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
observedNamespaces.remove((String) (invocation.getArguments()[0]));
return null;
}
})
.when(mCastSession)
.removeMessageReceivedCallbacks(any(String.class));
// New namespaces added.
namespaces.add("namespace1");
namespaces.add("namespace2");
mController.updateNamespaces();
assertNamespacesContainsExactly(observedNamespaces, "namespace1", "namespace2");
// Namespaces unchanged.
mController.updateNamespaces();
assertNamespacesContainsExactly(observedNamespaces, "namespace1", "namespace2");
// New namespace added.
namespaces.add("namespace3");
mController.updateNamespaces();
assertNamespacesContainsExactly(
observedNamespaces, "namespace1", "namespace2", "namespace3");
// Namespaces removed.
namespaces.remove("namespace1");
namespaces.remove("namespace3");
mController.updateNamespaces();
assertNamespacesContainsExactly(observedNamespaces, "namespace2");
}
@Test
public void testOnMessageReceived() {
mController.attachToCastSession(mCastSession);
// Non-media namespaces should be forwarded to the message handler. RemoteMediaClient only
// listens to the media namespace thus it shouldn't be notified.
mController.onMessageReceived(mCastDevice, "namespace", "message");
verify(mRemoteMediaClient, never())
.onMessageReceived(any(CastDevice.class), anyString(), anyString());
verify(mMessageHandler).onMessageReceived("namespace", "message");
// Media namespaces should be both forwarded to the message handler and RemoteMediaClient.
mController.onMessageReceived(mCastDevice, CastSessionUtil.MEDIA_NAMESPACE, "message");
verify(mRemoteMediaClient)
.onMessageReceived(mCastDevice, CastSessionUtil.MEDIA_NAMESPACE, "message");
verify(mMessageHandler).onMessageReceived(CastSessionUtil.MEDIA_NAMESPACE, "message");
}
private void assertNamespacesContainsExactly(
List<String> namespaces, String... expectedNamespaces) {
assertEquals(namespaces.size(), expectedNamespaces.length);
for (String namespace : expectedNamespaces) {
assertTrue(namespaces.contains(namespace));
}
}
}