Commit 386ef311 authored by Evan Stade's avatar Evan Stade Committed by Commit Bot

WebLayer: Add basic test for MediaRouter.

The test is modeled after Chrome's MediaRouterIntegrationTest. Other
test cases will be added soon.

The cast emulator service is moved from //chrome to //components. This
service (and an APK wrapping it) is necessary for the media route
chooser dialog to have entries in it. The selection of these entries
(creation of presentation, etc) is handled by MockMediaRouteProvider.

RouterTestUtils is also moved to //components/media_router. This
facilitates finding views in the chooser dialog.

Bug: 1057099
Change-Id: I0e8b67f3286b21bec5bc0f4581975fc1cca95b53
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2488088Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarmark a. foltz <mfoltz@chromium.org>
Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Reviewed-by: default avatarThomas Guilbert <tguilbert@chromium.org>
Reviewed-by: default avatarClark DuVall <cduvall@chromium.org>
Commit-Queue: Evan Stade <estade@chromium.org>
Cr-Commit-Position: refs/heads/master@{#821028}
parent c14be4a3
......@@ -2545,6 +2545,7 @@ chrome_test_apk_tmpl("chrome_public_test_apk") {
"//chrome/android/webapk/shell_apk:javatests_webapk",
"//chrome/android/webapk/shell_apk/javatests/dex_optimizer:dex_optimizer_apk",
"//chrome/test/android/chrome_public_test_support:chrome_public_test_support_apk",
"//components/media_router/test/android/media_router_test_support:media_router_test_support_apk",
]
}
......
......@@ -265,7 +265,6 @@ chrome_test_java_sources = [
"javatests/src/org/chromium/chrome/browser/login/ChromeHttpAuthHandlerTest.java",
"javatests/src/org/chromium/chrome/browser/media/MediaLauncherActivityTest.java",
"javatests/src/org/chromium/chrome/browser/media/router/MediaRouterIntegrationTest.java",
"javatests/src/org/chromium/chrome/browser/media/router/RouterTestUtils.java",
"javatests/src/org/chromium/chrome/browser/media/ui/AutoplayMutedNotificationTest.java",
"javatests/src/org/chromium/chrome/browser/media/ui/MediaSessionTest.java",
"javatests/src/org/chromium/chrome/browser/media/ui/PictureInPictureControllerTest.java",
......
......@@ -29,6 +29,7 @@ import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.components.media_router.BrowserMediaRouter;
import org.chromium.components.media_router.MockMediaRouteProvider;
import org.chromium.components.media_router.RouterTestUtils;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_public.browser.test.util.ClickUtils;
import org.chromium.content_public.browser.test.util.Criteria;
......@@ -44,8 +45,6 @@ import java.io.StringWriter;
/**
* Integration tests for MediaRouter.
*
* TODO(jbudorick): Remove this when media_router_integration_browsertest runs on Android.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ContentSwitches.DISABLE_GESTURE_REQUIREMENT_FOR_PRESENTATION,
......@@ -74,8 +73,6 @@ public class MediaRouterIntegrationTest {
private static final String SEND_MESSAGE_AND_EXPECT_CONNECTION_CLOSE_ON_ERROR_SCRIPT =
"sendMessageAndExpectConnectionCloseOnError()";
private static final int VIEW_TIMEOUT_MS = 2000;
private static final int VIEW_RETRY_MS = 100;
private static final int SCRIPT_TIMEOUT_MS = 10000;
private static final int SCRIPT_RETRY_MS = 50;
......@@ -196,8 +193,7 @@ public class MediaRouterIntegrationTest {
WebContents webContents = mActivityTestRule.getWebContents();
executeJavaScriptApi(webContents, WAIT_DEVICE_SCRIPT);
executeJavaScriptApi(webContents, START_SESSION_SCRIPT);
View testRouteButton = RouterTestUtils.waitForRouteButton(
mActivityTestRule.getActivity(), TEST_SINK_NAME, VIEW_TIMEOUT_MS, VIEW_RETRY_MS);
View testRouteButton = waitForRouteButton();
ClickUtils.mouseSingleClickView(
InstrumentationRegistry.getInstrumentation(), testRouteButton);
executeJavaScriptApi(webContents, CHECK_SESSION_SCRIPT);
......@@ -218,8 +214,7 @@ public class MediaRouterIntegrationTest {
WebContents webContents = mActivityTestRule.getWebContents();
executeJavaScriptApi(webContents, WAIT_DEVICE_SCRIPT);
executeJavaScriptApi(webContents, START_SESSION_SCRIPT);
View testRouteButton = RouterTestUtils.waitForRouteButton(
mActivityTestRule.getActivity(), TEST_SINK_NAME, VIEW_TIMEOUT_MS, VIEW_RETRY_MS);
View testRouteButton = waitForRouteButton();
ClickUtils.mouseSingleClickView(
InstrumentationRegistry.getInstrumentation(), testRouteButton);
executeJavaScriptApi(webContents, CHECK_SESSION_SCRIPT);
......@@ -239,8 +234,7 @@ public class MediaRouterIntegrationTest {
WebContents webContents = mActivityTestRule.getWebContents();
executeJavaScriptApi(webContents, WAIT_DEVICE_SCRIPT);
executeJavaScriptApi(webContents, START_SESSION_SCRIPT);
View testRouteButton = RouterTestUtils.waitForRouteButton(
mActivityTestRule.getActivity(), TEST_SINK_NAME, VIEW_TIMEOUT_MS, VIEW_RETRY_MS);
View testRouteButton = waitForRouteButton();
ClickUtils.mouseSingleClickView(
InstrumentationRegistry.getInstrumentation(), testRouteButton);
executeJavaScriptApi(webContents, CHECK_SESSION_SCRIPT);
......@@ -259,8 +253,7 @@ public class MediaRouterIntegrationTest {
WebContents webContents = mActivityTestRule.getWebContents();
executeJavaScriptApi(webContents, WAIT_DEVICE_SCRIPT);
executeJavaScriptApi(webContents, START_SESSION_SCRIPT);
View testRouteButton = RouterTestUtils.waitForRouteButton(
mActivityTestRule.getActivity(), TEST_SINK_NAME, VIEW_TIMEOUT_MS, VIEW_RETRY_MS);
View testRouteButton = waitForRouteButton();
ClickUtils.mouseSingleClickView(
InstrumentationRegistry.getInstrumentation(), testRouteButton);
checkStartFailed(
......@@ -277,8 +270,7 @@ public class MediaRouterIntegrationTest {
WebContents webContents = mActivityTestRule.getWebContents();
executeJavaScriptApi(webContents, WAIT_DEVICE_SCRIPT);
executeJavaScriptApi(webContents, START_SESSION_SCRIPT);
View testRouteButton = RouterTestUtils.waitForRouteButton(
mActivityTestRule.getActivity(), TEST_SINK_NAME, VIEW_TIMEOUT_MS, VIEW_RETRY_MS);
View testRouteButton = waitForRouteButton();
ClickUtils.mouseSingleClickView(
InstrumentationRegistry.getInstrumentation(), testRouteButton);
checkStartFailed(webContents, "UnknownError", "Unknown sink");
......@@ -293,8 +285,7 @@ public class MediaRouterIntegrationTest {
WebContents webContents = mActivityTestRule.getWebContents();
executeJavaScriptApi(webContents, WAIT_DEVICE_SCRIPT);
executeJavaScriptApi(webContents, START_SESSION_SCRIPT);
View testRouteButton = RouterTestUtils.waitForRouteButton(
mActivityTestRule.getActivity(), TEST_SINK_NAME, VIEW_TIMEOUT_MS, VIEW_RETRY_MS);
View testRouteButton = waitForRouteButton();
ClickUtils.mouseSingleClickView(
InstrumentationRegistry.getInstrumentation(), testRouteButton);
executeJavaScriptApi(webContents, CHECK_SESSION_SCRIPT);
......@@ -319,8 +310,7 @@ public class MediaRouterIntegrationTest {
WebContents webContents = mActivityTestRule.getWebContents();
executeJavaScriptApi(webContents, WAIT_DEVICE_SCRIPT);
executeJavaScriptApi(webContents, START_SESSION_SCRIPT);
View testRouteButton = RouterTestUtils.waitForRouteButton(
mActivityTestRule.getActivity(), TEST_SINK_NAME, VIEW_TIMEOUT_MS, VIEW_RETRY_MS);
View testRouteButton = waitForRouteButton();
ClickUtils.mouseSingleClickView(
InstrumentationRegistry.getInstrumentation(), testRouteButton);
executeJavaScriptApi(webContents, CHECK_SESSION_SCRIPT);
......@@ -344,9 +334,14 @@ public class MediaRouterIntegrationTest {
executeJavaScriptApi(webContents, WAIT_DEVICE_SCRIPT);
executeJavaScriptApi(webContents, START_SESSION_SCRIPT);
final Dialog routeSelectionDialog = RouterTestUtils.waitForDialog(
mActivityTestRule.getActivity(), VIEW_TIMEOUT_MS, VIEW_RETRY_MS);
mActivityTestRule.getActivity().getSupportFragmentManager());
Assert.assertNotNull(routeSelectionDialog);
TestThreadUtils.runOnUiThreadBlocking(() -> { routeSelectionDialog.cancel(); });
checkStartFailed(webContents, "NotAllowedError", "Dialog closed.");
}
private View waitForRouteButton() {
return RouterTestUtils.waitForRouteButton(
mActivityTestRule.getActivity().getSupportFragmentManager(), TEST_SINK_NAME);
}
}
......@@ -8,15 +8,6 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application>
<service android:name="org.chromium.chrome.browser.media.TestMediaRouteProviderService"
android:label="testMediaRouteProviderService"
android:process=":mrp"
tools:ignore="ExportedService" >
<intent-filter>
<action android:name="android.media.MediaRouteProviderService" />
</intent-filter>
</service>
<activity android:name="org.chromium.chrome.browser.browserservices.ActivityWithDeepLink">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
......
......@@ -13,10 +13,7 @@ android_apk("chrome_public_test_support_apk") {
# just disable it.
enable_multidex = false
deps = [
"//chrome/test/android/cast_emulator:cast_emulator_java",
"//chrome/test/android/test_trusted_web_activity:test_trusted_web_activity_java",
]
deps = [ "//chrome/test/android/test_trusted_web_activity:test_trusted_web_activity_java" ]
apk_name = "ChromePublicTestSupport"
android_manifest = "AndroidManifest.xml"
......
......@@ -87,6 +87,7 @@ android_library("test_support_java") {
sources = [
"java/src/org/chromium/components/media_router/TestMediaRouterClient.java",
"javatests/src/org/chromium/components/media_router/MockMediaRouteProvider.java",
"javatests/src/org/chromium/components/media_router/RouterTestUtils.java",
]
deps = [
......@@ -95,8 +96,10 @@ android_library("test_support_java") {
"//base:base_java",
"//components/browser_ui/media/android:java",
"//content/public/android:content_java",
"//content/public/test/android:content_java_test_support",
"//third_party/android_deps:androidx_annotation_annotation_java",
"//third_party/android_deps:androidx_fragment_fragment_java",
"//third_party/hamcrest:hamcrest_java",
]
}
......
......@@ -2,7 +2,7 @@
// 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;
package org.chromium.components.media_router;
import android.app.Dialog;
import android.view.View;
......@@ -13,7 +13,6 @@ import androidx.fragment.app.FragmentManager;
import org.hamcrest.Matchers;
import org.chromium.base.Log;
import org.chromium.chrome.browser.app.ChromeActivity;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.CriteriaNotSatisfiedException;
......@@ -28,12 +27,15 @@ import java.util.concurrent.Callable;
public class RouterTestUtils {
private static final String TAG = "RouterTestUtils";
public static View waitForRouteButton(final ChromeActivity activity,
final String chromecastName, int maxTimeoutMs, int intervalMs) {
private static final int VIEW_TIMEOUT_MS = 2000;
private static final int VIEW_RETRY_MS = 100;
public static View waitForRouteButton(
final FragmentManager fragmentManager, final String chromecastName) {
return waitForView(new Callable<View>() {
@Override
public View call() {
Dialog mediaRouteListDialog = getDialog(activity);
Dialog mediaRouteListDialog = getDialog(fragmentManager);
if (mediaRouteListDialog == null) {
Log.w(TAG, "Cannot find device selection dialog");
return null;
......@@ -53,35 +55,32 @@ public class RouterTestUtils {
Log.i(TAG, "Found wanted device");
return routesWanted.get(0);
}
}, maxTimeoutMs, intervalMs);
});
}
public static Dialog waitForDialog(
final ChromeActivity activity, int maxTimeoutMs, int intervalMs) {
public static Dialog waitForDialog(final FragmentManager fragmentManager) {
try {
CriteriaHelper.pollUiThread(() -> {
try {
Criteria.checkThat(getDialog(activity), Matchers.notNullValue());
Criteria.checkThat(getDialog(fragmentManager), Matchers.notNullValue());
} catch (Exception e) {
throw new CriteriaNotSatisfiedException(e);
}
}, maxTimeoutMs, intervalMs);
return getDialog(activity);
}, VIEW_TIMEOUT_MS, VIEW_RETRY_MS);
return getDialog(fragmentManager);
} catch (Exception e) {
return null;
}
}
public static Dialog getDialog(ChromeActivity activity) {
FragmentManager fm = activity.getSupportFragmentManager();
if (fm == null) return null;
return ((DialogFragment) fm.findFragmentByTag(
"android.support.v7.mediarouter:MediaRouteChooserDialogFragment"))
.getDialog();
private static Dialog getDialog(FragmentManager fragmentManager) {
DialogFragment fragment = (DialogFragment) fragmentManager.findFragmentByTag(
"android.support.v7.mediarouter:MediaRouteChooserDialogFragment");
if (fragment == null) return null;
return fragment.getDialog();
}
public static View waitForView(
final Callable<View> getViewCallable, int maxTimeoutMs, int intervalMs) {
private static View waitForView(final Callable<View> getViewCallable) {
try {
CriteriaHelper.pollUiThread(() -> {
try {
......@@ -89,7 +88,7 @@ public class RouterTestUtils {
} catch (Exception e) {
throw new CriteriaNotSatisfiedException(e);
}
}, maxTimeoutMs, intervalMs);
}, VIEW_TIMEOUT_MS, VIEW_RETRY_MS);
return getViewCallable.call();
} catch (Exception e) {
return null;
......
file://components/media_router/browser/android/OWNERS
# TEAM: media-dev@chromium.org
# COMPONENT: Internals>Cast
......@@ -9,15 +9,15 @@ android_library("cast_emulator_java") {
chromium_code = true
sources = [
"src/org/chromium/chrome/browser/media/RoutePublisher.java",
"src/org/chromium/chrome/browser/media/TestMediaRouteProvider.java",
"src/org/chromium/chrome/browser/media/TestMediaRouteProviderService.java",
"src/org/chromium/chrome/browser/media/remote/DummyPlayer.java",
"src/org/chromium/chrome/browser/media/remote/LocalSessionManager.java",
"src/org/chromium/chrome/browser/media/remote/MediaItem.java",
"src/org/chromium/chrome/browser/media/remote/RemotePlaybackRoutePublisher.java",
"src/org/chromium/chrome/browser/media/remote/RemoteSessionManager.java",
"src/org/chromium/chrome/browser/media/router/DummyRoutePublisher.java",
"src/org/chromium/components/media_router/cast_emulator/RoutePublisher.java",
"src/org/chromium/components/media_router/cast_emulator/TestMediaRouteProvider.java",
"src/org/chromium/components/media_router/cast_emulator/TestMediaRouteProviderService.java",
"src/org/chromium/components/media_router/cast_emulator/remote/DummyPlayer.java",
"src/org/chromium/components/media_router/cast_emulator/remote/LocalSessionManager.java",
"src/org/chromium/components/media_router/cast_emulator/remote/MediaItem.java",
"src/org/chromium/components/media_router/cast_emulator/remote/RemotePlaybackRoutePublisher.java",
"src/org/chromium/components/media_router/cast_emulator/remote/RemoteSessionManager.java",
"src/org/chromium/components/media_router/cast_emulator/router/DummyRoutePublisher.java",
]
deps = [
"$google_play_services_package:google_play_services_cast_java",
......
......@@ -2,7 +2,7 @@
// 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;
package org.chromium.components.media_router.cast_emulator;
import androidx.mediarouter.media.MediaRouteProvider;
......
......@@ -2,7 +2,7 @@
// 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;
package org.chromium.components.media_router.cast_emulator;
import android.content.Context;
......@@ -11,8 +11,8 @@ import androidx.mediarouter.media.MediaRouteProvider;
import androidx.mediarouter.media.MediaRouteSelector;
import org.chromium.base.Log;
import org.chromium.chrome.browser.media.remote.RemotePlaybackRoutePublisher;
import org.chromium.chrome.browser.media.router.DummyRoutePublisher;
import org.chromium.components.media_router.cast_emulator.remote.RemotePlaybackRoutePublisher;
import org.chromium.components.media_router.cast_emulator.router.DummyRoutePublisher;
import java.util.ArrayList;
import java.util.List;
......
......@@ -2,7 +2,7 @@
// 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;
package org.chromium.components.media_router.cast_emulator;
import androidx.mediarouter.media.MediaRouteProvider;
import androidx.mediarouter.media.MediaRouteProviderService;
......
......@@ -2,7 +2,7 @@
// 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.remote;
package org.chromium.components.media_router.cast_emulator.remote;
import android.content.Context;
import android.media.MediaPlayer;
......@@ -20,8 +20,8 @@ import java.io.IOException;
* Handles playback of a single media item using MediaPlayer.
*/
public class DummyPlayer implements MediaPlayer.OnPreparedListener,
MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener,
MediaPlayer.OnSeekCompleteListener {
MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener,
MediaPlayer.OnSeekCompleteListener {
private static final String TAG = "CastEmulator";
private static final int STATE_IDLE = 0;
......
......@@ -2,7 +2,7 @@
// 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.remote;
package org.chromium.components.media_router.cast_emulator.remote;
import android.app.PendingIntent;
import android.content.Context;
......@@ -87,7 +87,8 @@ public class LocalSessionManager {
public MediaSessionStatus getSessionStatus(String sid) {
if (!hasSession()) {
return new MediaSessionStatus.Builder(MediaSessionStatus.SESSION_STATE_INVALIDATED)
.setQueuePaused(false).build();
.setQueuePaused(false)
.build();
}
return mRemoteManager.getSessionStatus(sid);
}
......@@ -196,5 +197,4 @@ public class LocalSessionManager {
public void updateStatus() {
if (hasSession()) mRemoteManager.updateStatus();
}
}
......@@ -2,7 +2,7 @@
// 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.remote;
package org.chromium.components.media_router.cast_emulator.remote;
import android.app.PendingIntent;
import android.net.Uri;
......@@ -95,14 +95,17 @@ final class MediaItem {
}
public MediaItemStatus getStatus() {
return new MediaItemStatus.Builder(mPlaybackState).setContentPosition(mContentPosition)
.setContentDuration(mContentDuration).setTimestamp(mTimestamp).build();
return new MediaItemStatus.Builder(mPlaybackState)
.setContentPosition(mContentPosition)
.setContentDuration(mContentDuration)
.setTimestamp(mTimestamp)
.build();
}
@Override
public String toString() {
String state[] = {"PENDING", "PLAYING", "PAUSED", "BUFFERING", "FINISHED", "CANCELED",
"INVALIDATED", "ERROR"};
"INVALIDATED", "ERROR"};
return "[" + mSessionId + "|" + mItemId + "|"
+ (mRemoteItemId != null ? mRemoteItemId : "-") + "|" + state[mPlaybackState] + "] "
+ mUri.toString();
......
......@@ -2,7 +2,7 @@
// 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.remote;
package org.chromium.components.media_router.cast_emulator.remote;
import android.app.PendingIntent;
import android.content.Context;
......@@ -28,7 +28,7 @@ import com.google.android.gms.cast.CastMediaControlIntent;
import org.chromium.base.Log;
import org.chromium.base.annotations.RemovableInRelease;
import org.chromium.chrome.browser.media.RoutePublisher;
import org.chromium.components.media_router.cast_emulator.RoutePublisher;
import java.util.ArrayList;
import java.util.List;
......@@ -105,16 +105,20 @@ public final class RemotePlaybackRoutePublisher implements RoutePublisher {
controlFilters.add(f1);
controlFilters.add(f2);
MediaRouteDescriptor testRouteDescriptor = new MediaRouteDescriptor.Builder(
VARIABLE_VOLUME_SESSION_ROUTE_ID, "Cast Test Route")
.setDescription("Cast Test Route").addControlFilters(controlFilters)
MediaRouteDescriptor testRouteDescriptor =
new MediaRouteDescriptor
.Builder(VARIABLE_VOLUME_SESSION_ROUTE_ID, "Cast Test Route")
.setDescription("Cast Test Route")
.addControlFilters(controlFilters)
.setPlaybackStream(AudioManager.STREAM_MUSIC)
.setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
.setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(VOLUME_MAX).setVolume(mVolume).build();
.setVolumeMax(VOLUME_MAX)
.setVolume(mVolume)
.build();
MediaRouteProviderDescriptor providerDescriptor = new MediaRouteProviderDescriptor.Builder()
.addRoute(testRouteDescriptor).build();
MediaRouteProviderDescriptor providerDescriptor =
new MediaRouteProviderDescriptor.Builder().addRoute(testRouteDescriptor).build();
mProvider.setDescriptor(providerDescriptor);
}
......@@ -232,8 +236,8 @@ public final class RemotePlaybackRoutePublisher implements RoutePublisher {
if (item != null) {
String iid = item.getItemId();
result.putString(MediaControlIntent.EXTRA_ITEM_ID, iid);
result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
item.getStatus().asBundle());
result.putBundle(
MediaControlIntent.EXTRA_ITEM_STATUS, item.getStatus().asBundle());
if (mMetadata != null) {
result.putBundle(MediaControlIntent.EXTRA_ITEM_METADATA, mMetadata);
}
......@@ -247,8 +251,8 @@ public final class RemotePlaybackRoutePublisher implements RoutePublisher {
if (volume >= 0 && volume <= VOLUME_MAX) {
mVolume = volume;
Log.v(TAG, "%s: New volume is %d", mRouteId, mVolume);
AudioManager audioManager = (AudioManager) mProvider.getContext()
.getSystemService(Context.AUDIO_SERVICE);
AudioManager audioManager = (AudioManager) mProvider.getContext().getSystemService(
Context.AUDIO_SERVICE);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
publishRoutes();
}
......@@ -285,8 +289,7 @@ public final class RemotePlaybackRoutePublisher implements RoutePublisher {
Bundle result = new Bundle();
result.putString(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId());
result.putString(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId());
result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
item.getStatus().asBundle());
result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS, item.getStatus().asBundle());
callback.onResult(result);
}
return true;
......@@ -318,8 +321,8 @@ public final class RemotePlaybackRoutePublisher implements RoutePublisher {
if (callback != null) {
if (item != null) {
Bundle result = new Bundle();
result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
item.getStatus().asBundle());
result.putBundle(
MediaControlIntent.EXTRA_ITEM_STATUS, item.getStatus().asBundle());
callback.onResult(result);
} else {
callback.onError("Failed to get status, sid=" + sid + ", iid=" + iid, null);
......@@ -420,10 +423,11 @@ public final class RemotePlaybackRoutePublisher implements RoutePublisher {
if (callback != null) {
if (success) {
Bundle result = new Bundle();
MediaSessionStatus sessionStatus = new MediaSessionStatus.Builder(
MediaSessionStatus.SESSION_STATE_ENDED).build();
result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS,
sessionStatus.asBundle());
MediaSessionStatus sessionStatus =
new MediaSessionStatus.Builder(MediaSessionStatus.SESSION_STATE_ENDED)
.build();
result.putBundle(
MediaControlIntent.EXTRA_SESSION_STATUS, sessionStatus.asBundle());
callback.onResult(result);
handleSessionStatusChange(sid);
mSessionReceiver = null;
......@@ -444,8 +448,8 @@ public final class RemotePlaybackRoutePublisher implements RoutePublisher {
Intent intent = new Intent();
intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId());
intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId());
intent.putExtra(MediaControlIntent.EXTRA_ITEM_STATUS,
item.getStatus().asBundle());
intent.putExtra(
MediaControlIntent.EXTRA_ITEM_STATUS, item.getStatus().asBundle());
try {
receiver.send(mProvider.getContext(), 0, intent);
Log.v(TAG, "%s: Sending status update from provider", mRouteId);
......@@ -474,14 +478,11 @@ public final class RemotePlaybackRoutePublisher implements RoutePublisher {
@RemovableInRelease
private String getMediaControlIntentDebugString(Intent intent) {
return "uri=" + intent.getData()
+ ", mime=" + intent.getType()
+ ", sid=" + intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID)
+ ", pos=" + intent.getLongExtra(
MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0)
return "uri=" + intent.getData() + ", mime=" + intent.getType()
+ ", sid=" + intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID)
+ ", pos=" + intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0)
+ ", metadata=" + intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_METADATA)
+ ", headers=" + intent.getBundleExtra(
MediaControlIntent.EXTRA_ITEM_HTTP_HEADERS);
+ ", headers=" + intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_HTTP_HEADERS);
}
private static void addDataTypeUnchecked(IntentFilter filter, String type) {
......@@ -491,5 +492,4 @@ public final class RemotePlaybackRoutePublisher implements RoutePublisher {
throw new RuntimeException(ex);
}
}
}
......@@ -2,7 +2,7 @@
// 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.remote;
package org.chromium.components.media_router.cast_emulator.remote;
import android.annotation.SuppressLint;
import android.app.PendingIntent;
......@@ -31,8 +31,8 @@ public class RemoteSessionManager implements DummyPlayer.Callback {
* @param player the player to use
* @return the remote session manager
*/
public static RemoteSessionManager connect(LocalSessionManager localSessionManager,
Context context) {
public static RemoteSessionManager connect(
LocalSessionManager localSessionManager, Context context) {
if (sInstance == null) {
sInstance = new RemoteSessionManager("remote", context);
}
......@@ -75,8 +75,8 @@ public class RemoteSessionManager implements DummyPlayer.Callback {
// create new item with initial status PLAYBACK_STATE_PENDING
mItemId++;
mCurrentItem = new MediaItem(Integer.toString(mSessionId), Integer.toString(mItemId), uri,
mime, receiver);
mCurrentItem = new MediaItem(
Integer.toString(mSessionId), Integer.toString(mItemId), uri, mime, receiver);
mCurrentItem.setPosition(contentPosition);
Log.v(TAG, "%s: add: new item id = %s", mName, mCurrentItem);
......@@ -116,8 +116,7 @@ public class RemoteSessionManager implements DummyPlayer.Callback {
*/
public MediaSessionStatus getSessionStatus(String sid) {
Log.v(TAG, "Getting session status for session %s", sid);
int sessionState =
(sid != null && sid.equals(Integer.toString(mSessionId)))
int sessionState = (sid != null && sid.equals(Integer.toString(mSessionId)))
? MediaSessionStatus.SESSION_STATE_ACTIVE
: MediaSessionStatus.SESSION_STATE_INVALIDATED;
......@@ -233,12 +232,12 @@ public class RemoteSessionManager implements DummyPlayer.Callback {
}
/**
* Start a new emulated Chromecast session if needed.
*
* @param relaunch relaunch the remote session (the emulation of the Chromecast app) even if it
* is already running.
* @return The new session id
*/
* Start a new emulated Chromecast session if needed.
*
* @param relaunch relaunch the remote session (the emulation of the Chromecast app) even if it
* is already running.
* @return The new session id
*/
public String startSession(boolean relaunch) {
if (!mSessionValid || relaunch) {
if (mPlayer != null) mPlayer.setCallback(null);
......@@ -306,8 +305,9 @@ public class RemoteSessionManager implements DummyPlayer.Callback {
private void finishItem(boolean error) {
if (mCurrentItem != null) {
removeItem(mCurrentItem.getItemId(), error ? MediaItemStatus.PLAYBACK_STATE_ERROR
: MediaItemStatus.PLAYBACK_STATE_FINISHED);
removeItem(mCurrentItem.getItemId(),
error ? MediaItemStatus.PLAYBACK_STATE_ERROR
: MediaItemStatus.PLAYBACK_STATE_FINISHED);
updateStatus();
}
}
......@@ -336,7 +336,7 @@ public class RemoteSessionManager implements DummyPlayer.Callback {
if (mCurrentItem != null) {
if (mCurrentItem.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
mCurrentItem.setState(mPaused ? MediaItemStatus.PLAYBACK_STATE_PAUSED
: MediaItemStatus.PLAYBACK_STATE_PLAYING);
: MediaItemStatus.PLAYBACK_STATE_PLAYING);
mPlayer.play(mCurrentItem);
} else if (mPaused
&& mCurrentItem.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
......@@ -358,5 +358,4 @@ public class RemoteSessionManager implements DummyPlayer.Callback {
mLocalSessionManager.onItemChanged(mCurrentItem);
}
}
}
......@@ -2,7 +2,7 @@
// 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;
package org.chromium.components.media_router.cast_emulator.router;
import android.content.IntentFilter;
......@@ -13,7 +13,7 @@ import androidx.mediarouter.media.MediaRouteProviderDescriptor;
import com.google.android.gms.cast.CastMediaControlIntent;
import org.chromium.base.Log;
import org.chromium.chrome.browser.media.RoutePublisher;
import org.chromium.components.media_router.cast_emulator.RoutePublisher;
import java.util.ArrayList;
......@@ -55,19 +55,21 @@ public final class DummyRoutePublisher implements RoutePublisher {
ArrayList<IntentFilter> controlFilters = new ArrayList<IntentFilter>();
controlFilters.add(filter);
MediaRouteDescriptor testRouteDescriptor1 = new MediaRouteDescriptor.Builder(
DUMMY_ROUTE_ID1, DUMMY_ROUTE_NAME1)
.setDescription(DUMMY_ROUTE_NAME1).addControlFilters(controlFilters)
MediaRouteDescriptor testRouteDescriptor1 =
new MediaRouteDescriptor.Builder(DUMMY_ROUTE_ID1, DUMMY_ROUTE_NAME1)
.setDescription(DUMMY_ROUTE_NAME1)
.addControlFilters(controlFilters)
.build();
MediaRouteDescriptor testRouteDescriptor2 = new MediaRouteDescriptor.Builder(
DUMMY_ROUTE_ID2, DUMMY_ROUTE_NAME2)
.setDescription(DUMMY_ROUTE_NAME2).addControlFilters(controlFilters)
MediaRouteDescriptor testRouteDescriptor2 =
new MediaRouteDescriptor.Builder(DUMMY_ROUTE_ID2, DUMMY_ROUTE_NAME2)
.setDescription(DUMMY_ROUTE_NAME2)
.addControlFilters(controlFilters)
.build();
MediaRouteProviderDescriptor providerDescriptor = new MediaRouteProviderDescriptor.Builder()
.addRoute(testRouteDescriptor1)
.addRoute(testRouteDescriptor2)
.build();
.addRoute(testRouteDescriptor1)
.addRoute(testRouteDescriptor2)
.build();
mProvider.setDescriptor(providerDescriptor);
}
......
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2020 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. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.chromium.components.media_router.test_support">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application>
<service android:name="org.chromium.components.media_router.cast_emulator.TestMediaRouteProviderService"
android:label="testMediaRouteProviderService"
android:process=":mrp"
tools:ignore="ExportedService" >
<intent-filter>
<action android:name="android.media.MediaRouteProviderService" />
</intent-filter>
</service>
</application>
</manifest>
# Copyright 2020 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.
import("//build/config/android/config.gni")
import("//build/config/android/rules.gni")
android_apk("media_router_test_support_apk") {
# Used as an additional_apk in test scripts.
never_incremental = true
# Multidex requires a custom Application class to initialize it. Simpler to
# just disable it.
enable_multidex = false
deps = [
"//components/media_router/test/android/cast_emulator:cast_emulator_java",
]
apk_name = "MediaRouterTestSupportApk"
android_manifest = "AndroidManifest.xml"
}
......@@ -75,6 +75,7 @@ android_library("weblayer_private_java_tests") {
"src/org/chromium/weblayer/test/GeolocationTest.java",
"src/org/chromium/weblayer/test/InfoBarTest.java",
"src/org/chromium/weblayer/test/MediaCaptureTest.java",
"src/org/chromium/weblayer/test/MediaRouterTest.java",
"src/org/chromium/weblayer/test/NetworkChangeNotifierTest.java",
"src/org/chromium/weblayer/test/PageInfoTest.java",
"src/org/chromium/weblayer/test/PopupTest.java",
......@@ -89,6 +90,7 @@ android_library("weblayer_private_java_tests") {
":weblayer_java_test_support",
"//base:base_java",
"//base:base_java_test_support",
"//content/public/android:content_java",
"//content/public/test/android:content_java_test_support",
"//net/android:net_java_test_support",
"//third_party/android_deps:android_support_v4_java",
......@@ -100,6 +102,7 @@ android_library("weblayer_private_java_tests") {
"//third_party/android_support_test_runner:runner_java",
"//third_party/hamcrest:hamcrest_java",
"//third_party/junit:junit",
"//ui/android:ui_java_test_support",
"//weblayer/public/java",
"//weblayer/public/javatestutil:test_java",
"//weblayer/shell/android:weblayer_shell_java",
......@@ -197,7 +200,10 @@ weblayer_instrumentation("weblayer_support_instrumentation_test_apk") {
weblayer_instrumentation("weblayer_private_instrumentation_test_apk") {
apk_name = "WebLayerPrivateInstrumentationTest"
apk_under_test = "//weblayer/shell/android:weblayer_shell_apk"
additional_apks = [ "//weblayer/shell/android:weblayer_support_apk" ]
additional_apks = [
"//weblayer/shell/android:weblayer_support_apk",
"//components/media_router/test/android/media_router_test_support:media_router_test_support_apk",
]
deps = [ ":weblayer_private_java_tests" ]
}
......
......@@ -222,9 +222,12 @@ public class InstrumentationActivityTestRule
}
public String executeScriptAndExtractString(String script) {
return executeScriptAndExtractString(script, true /* useSeparateIsolate */);
}
public String executeScriptAndExtractString(String script, boolean useSeparateIsolate) {
try {
return executeScriptSync(script, true /* useSeparateIsolate */)
.getString(Tab.SCRIPT_RESULT_KEY);
return executeScriptSync(script, useSeparateIsolate).getString(Tab.SCRIPT_RESULT_KEY);
} catch (JSONException e) {
throw new RuntimeException(e);
}
......
// Copyright 2020 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.weblayer.test;
import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
import android.support.test.InstrumentationRegistry;
import android.view.View;
import androidx.test.filters.LargeTest;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Restriction;
import org.chromium.content_public.browser.test.util.ClickUtils;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.common.ContentSwitches;
import org.chromium.ui.test.util.UiRestriction;
import org.chromium.weblayer.TestWebLayer;
import org.chromium.weblayer.shell.InstrumentationActivity;
/**
* Tests of the Presentation API.
*/
@MinWebLayerVersion(88)
@RunWith(WebLayerJUnit4ClassRunner.class)
@CommandLineFlags.Add({ContentSwitches.DISABLE_GESTURE_REQUIREMENT_FOR_PRESENTATION})
public class MediaRouterTest {
@Rule
public InstrumentationActivityTestRule mActivityTestRule =
new InstrumentationActivityTestRule();
private InstrumentationActivity mActivity;
private static final String TEST_PAGE = "media_router/basic_test.html";
private static final int SCRIPT_TIMEOUT_MS = 10000;
private static final int SCRIPT_RETRY_MS = 150;
private static final String TEST_SINK_NAME = "test-sink-1";
// The javascript snippets.
private static final String UNSET_RESULT_SCRIPT = "lastExecutionResult = null";
private static final String GET_RESULT_SCRIPT = "lastExecutionResult";
private static final String WAIT_DEVICE_SCRIPT = "waitUntilDeviceAvailable();";
private static final String START_PRESENTATION_SCRIPT = "startPresentation();";
private static final String CHECK_CONNECTION_SCRIPT = "checkConnection();";
private static final String TERMINATE_CONNECTION_SCRIPT =
"terminateConnectionAndWaitForStateChange();";
@Before
public void setUp() throws Exception {
mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
TestWebLayer.getTestWebLayer(mActivity.getApplicationContext())
.initializeMockMediaRouteProvider();
}
private void executeJavaScriptApi(String script) throws Exception {
mActivityTestRule.executeScriptSync(UNSET_RESULT_SCRIPT, false);
mActivityTestRule.executeScriptSync(script, false);
CriteriaHelper.pollInstrumentationThread(() -> {
String result =
mActivityTestRule.executeScriptAndExtractString(GET_RESULT_SCRIPT, false);
Criteria.checkThat(result, Matchers.is("passed"));
}, SCRIPT_TIMEOUT_MS, SCRIPT_RETRY_MS);
}
/**
* Basic test where the page requests a route, the user selects a route, and a connection is
* started.
*/
@Test
@Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
@Feature({"MediaRouter"})
@LargeTest
public void testBasic() throws Exception {
// Request a presentation.
mActivityTestRule.navigateAndWait(mActivityTestRule.getTestDataURL(TEST_PAGE));
executeJavaScriptApi(WAIT_DEVICE_SCRIPT);
executeJavaScriptApi(START_PRESENTATION_SCRIPT);
// Verify the route selection dialog is showing and make a selection.
View testRouteButton = TestWebLayer.getTestWebLayer(mActivity.getApplicationContext())
.getMediaRouteButton(TEST_SINK_NAME);
Assert.assertNotNull(testRouteButton);
ClickUtils.mouseSingleClickView(
InstrumentationRegistry.getInstrumentation(), testRouteButton);
// Verify in javascript that a presentation has started.
executeJavaScriptApi(CHECK_CONNECTION_SCRIPT);
String connectionId =
mActivityTestRule.executeScriptAndExtractString("startedConnection.id");
Assert.assertFalse(connectionId.isEmpty());
String defaultRequestConnectionId =
mActivityTestRule.executeScriptAndExtractString("defaultRequestConnectionId");
Assert.assertEquals(connectionId, defaultRequestConnectionId);
executeJavaScriptApi(TERMINATE_CONNECTION_SCRIPT);
}
}
......@@ -290,6 +290,8 @@ android_library("test_java") {
"//base:jni_java",
"//components/infobars/android:java",
"//components/location/android:location_java",
"//components/media_router/browser/android:java",
"//components/media_router/browser/android:test_support_java",
"//components/permissions/android:java",
"//content/public/test/android:content_java_test_support",
"//net/android:net_java",
......
......@@ -33,6 +33,7 @@ import org.chromium.weblayer_private.interfaces.IRemoteFragment;
import org.chromium.weblayer_private.interfaces.IRemoteFragmentClient;
import org.chromium.weblayer_private.interfaces.StrictModeWorkaround;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
/**
......@@ -53,6 +54,10 @@ public class MediaRouteDialogFragmentImpl extends RemoteFragmentImpl {
private boolean mStarted;
private FragmentController mFragmentController;
// The instance for the currently active dialog, if any. This is a WeakReference to get around
// StaticFieldLeak warnings.
private static WeakReference<MediaRouteDialogFragmentImpl> sInstanceForTest;
/**
* A fake FragmentActivity needed to make the Fragment system happy.
*
......@@ -195,6 +200,7 @@ public class MediaRouteDialogFragmentImpl extends RemoteFragmentImpl {
public MediaRouteDialogFragmentImpl(IRemoteFragmentClient remoteFragmentClient) {
super(remoteFragmentClient);
sInstanceForTest = new WeakReference<MediaRouteDialogFragmentImpl>(this);
}
@Override
......@@ -239,6 +245,7 @@ public class MediaRouteDialogFragmentImpl extends RemoteFragmentImpl {
StrictModeWorkaround.apply();
super.onDestroy();
mFragmentController.dispatchDestroy();
sInstanceForTest = null;
}
@Override
......@@ -300,4 +307,8 @@ public class MediaRouteDialogFragmentImpl extends RemoteFragmentImpl {
private Context getWebLayerContext() {
return mContext;
}
public static MediaRouteDialogFragmentImpl getInstanceForTest() {
return sInstanceForTest.get();
}
}
......@@ -9,12 +9,17 @@ import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.fragment.app.FragmentManager;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.annotations.UsedByReflection;
import org.chromium.components.infobars.InfoBarAnimationListener;
import org.chromium.components.infobars.InfoBarUiItem;
import org.chromium.components.location.LocationUtils;
import org.chromium.components.media_router.BrowserMediaRouter;
import org.chromium.components.media_router.MockMediaRouteProvider;
import org.chromium.components.media_router.RouterTestUtils;
import org.chromium.components.permissions.PermissionDialogController;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.device.geolocation.LocationProviderOverrider;
......@@ -27,6 +32,7 @@ import org.chromium.weblayer_private.WebLayerAccessibilityUtil;
import org.chromium.weblayer_private.interfaces.IObjectWrapper;
import org.chromium.weblayer_private.interfaces.ITab;
import org.chromium.weblayer_private.interfaces.ObjectWrapper;
import org.chromium.weblayer_private.media.MediaRouteDialogFragmentImpl;
import org.chromium.weblayer_private.test_interfaces.ITestWebLayer;
import java.util.concurrent.ExecutionException;
......@@ -191,4 +197,16 @@ public final class TestWebLayerImpl extends ITestWebLayer.Stub {
TabImpl tabImpl = (TabImpl) tab;
return tabImpl.didShowFullscreenToast();
}
@Override
public void initializeMockMediaRouteProvider() {
BrowserMediaRouter.setRouteProviderFactoryForTest(new MockMediaRouteProvider.Factory());
}
@Override
public IObjectWrapper getMediaRouteButton(String name) {
FragmentManager fm =
MediaRouteDialogFragmentImpl.getInstanceForTest().getSupportFragmentManager();
return ObjectWrapper.wrap(RouterTestUtils.waitForRouteButton(fm, name));
}
}
......@@ -38,7 +38,7 @@ interface ITestWebLayer {
void addInfoBar(in ITab tab, in IObjectWrapper runnable) = 10;
// Gets the infobar container view associated with |tab|.
IObjectWrapper getInfoBarContainerView(in ITab tab) = 11;
IObjectWrapper /* View */ getInfoBarContainerView(in ITab tab) = 11;
void setIgnoreMissingKeyForTranslateManager(in boolean ignore) = 12;
void forceNetworkConnectivityState(in boolean networkAvailable) = 13;
......@@ -53,4 +53,11 @@ interface ITestWebLayer {
// Returns true if a fullscreen toast was shown for |tab|.
boolean didShowFullscreenToast(in ITab tab) = 17;
// Does setup for MediaRouter tests, mocking out Chromecast devices.
void initializeMockMediaRouteProvider() = 18;
// Gets a button from the currently visible media route selection dialog. The button represents a
// route and contains the text |name|. Returns null if no such dialog or button exists.
IObjectWrapper /* View */ getMediaRouteButton(String name) = 19;
}
......@@ -138,4 +138,12 @@ public final class TestWebLayer {
public boolean didShowFullscreenToast(Tab tab) throws RemoteException {
return mITestWebLayer.didShowFullscreenToast(tab.getITab());
}
public void initializeMockMediaRouteProvider() throws RemoteException {
mITestWebLayer.initializeMockMediaRouteProvider();
}
public View getMediaRouteButton(String name) throws RemoteException {
return (View) ObjectWrapper.unwrap(mITestWebLayer.getMediaRouteButton(name), View.class);
}
}
<!DOCTYPE HTML>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>MR Integration Basic Test</title>
<script type="text/javascript" src="common.js"></script>
</head>
<body>
</body>
</html>
/**
* Copyright 2020 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.
*
* @fileoverview Test utilities for presentation integration tests.
*/
var startPresentationPromise = null;
var startedConnection = null;
var reconnectedConnection = null;
var presentationUrl = "cast:CCCCCCCC";
let params = (new URL(window.location.href)).searchParams;
var presentationRequest = new PresentationRequest([presentationUrl]);
var defaultRequestConnectionId = null;
var lastExecutionResult = null;
window.navigator.presentation.defaultRequest = presentationRequest;
window.navigator.presentation.defaultRequest.onconnectionavailable = function(
e) {
defaultRequestConnectionId = e.connection.id;
};
/**
* Waits until one sink is available.
*/
function waitUntilDeviceAvailable() {
presentationRequest.getAvailability(presentationUrl)
.then(function(availability) {
console.log('availability ' + availability.value + '\n');
if (availability.value) {
sendResult(true, '');
} else {
availability.onchange = function(newAvailability) {
if (newAvailability)
sendResult(true, '');
}
}
})
.catch(function(e) {
sendResult(false, 'got error: ' + e);
});
}
/**
* Starts presentation.
*/
function startPresentation() {
startPresentationPromise = presentationRequest.start();
console.log('start presentation');
sendResult(true, '');
}
/**
* Checks if the presentation has been started successfully.
*/
function checkConnection() {
if (!startPresentationPromise) {
sendResult(false, 'Did not attempt to start presentation');
} else {
startPresentationPromise
.then(function(connection) {
if (!connection) {
sendResult(
false, 'Failed to start presentation: connection is null');
} else {
// set the new connection
startedConnection = connection;
waitForConnectedStateAndSendResult(startedConnection);
}
})
.catch(function(e) {
// terminate old connection if exists
startedConnection && startedConnection.terminate();
sendResult(
false, 'Failed to start connection: encountered exception ' + e);
})
}
}
/**
* Asserts the current state of the connection is 'connected' or 'connecting'.
* If the current state is connecting, waits for it to become 'connected'.
* @param {!PresentationConnection} connection
*/
function waitForConnectedStateAndSendResult(connection) {
console.log('connection state is "' + connection.state + '"');
if (connection.state == 'connected') {
sendResult(true, '');
} else if (connection.state == 'connecting') {
connection.onconnect = () => {
sendResult(true, '');
};
} else {
sendResult(
false,
'Expect connection state to be "connecting" or "connected", actual: ' +
connection.state);
}
}
/**
* Checks the start() request fails with expected error and message substring.
* @param {!string} expectedErrorName
* @param {!string} expectedErrorMessageSubstring
*/
function checkStartFailed(expectedErrorName, expectedErrorMessageSubstring) {
if (!startPresentationPromise) {
sendResult(false, 'Did not attempt to start presentation');
} else {
startPresentationPromise
.then(function(connection) {
sendResult(false, 'start() unexpectedly succeeded.');
})
.catch(function(e) {
if (expectedErrorName != e.name) {
sendResult(
false, 'Got unexpected error. ' + e.name + ': ' + e.message);
} else if (e.message.indexOf(expectedErrorMessageSubstring) == -1) {
sendResult(
false,
'Error message is not correct, it should contain "' +
expectedErrorMessageSubstring + '"');
} else {
sendResult(true, '');
}
})
}
}
/**
* Terminates current presentation.
*/
function terminateConnectionAndWaitForStateChange() {
if (startedConnection) {
startedConnection.onterminate = function() {
sendResult(true, '');
};
startedConnection.terminate();
} else {
sendResult(false, 'startedConnection does not exist.');
}
}
/**
* Closes |startedConnection| and waits for its onclose event.
*/
function closeConnectionAndWaitForStateChange() {
if (startedConnection) {
if (startedConnection.state == 'closed') {
sendResult(false, 'startedConnection is unexpectedly closed.');
return;
}
startedConnection.onclose = function() {
sendResult(true, '');
};
startedConnection.close();
} else {
sendResult(false, 'startedConnection does not exist.');
}
}
/**
* Sends a message to |startedConnection| and expects InvalidStateError to be
* thrown. Requires |startedConnection.state| to not equal |initialState|.
*/
function checkSendMessageFailed(initialState) {
if (!startedConnection) {
sendResult(false, 'startedConnection does not exist.');
return;
}
if (startedConnection.state != initialState) {
sendResult(
false,
'startedConnection.state is "' + startedConnection.state +
'", but we expected "' + initialState + '".');
return;
}
try {
startedConnection.send('test message');
} catch (e) {
if (e.name == 'InvalidStateError') {
sendResult(true, '');
} else {
sendResult(false, 'Got an unexpected error: ' + e.name);
}
}
sendResult(false, 'Expected InvalidStateError but it was never thrown.');
}
/**
* Sends a message, and expects the connection to close on error.
*/
function sendMessageAndExpectConnectionCloseOnError() {
if (!startedConnection) {
sendResult(false, 'startedConnection does not exist.');
return;
}
startedConnection.onclose = function(event) {
var reason = event.reason;
if (reason != 'error') {
sendResult(false, 'Unexpected close reason: ' + reason);
return;
}
sendResult(true, '');
};
startedConnection.send('foo');
}
/**
* Sends the given message, and expects response from the receiver.
* @param {!string} message
*/
function sendMessageAndExpectResponse(message) {
if (!startedConnection) {
sendResult(false, 'startedConnection does not exist.');
return;
}
if (startedConnection.state != 'connected') {
sendResult(
false,
'Expected the connection state to be connected but it was ' +
startedConnection.state);
return;
}
startedConnection.onmessage = function(receivedMessage) {
var expectedResponse = 'Pong: ' + message;
var actualResponse = receivedMessage.data;
if (actualResponse != expectedResponse) {
sendResult(
false,
'Expected message: ' + expectedResponse +
', but got: ' + actualResponse);
return;
}
sendResult(true, '');
};
startedConnection.send(message);
}
/**
* Sends 'close' to receiver page, and expects receiver page closing
* the connection.
*/
function initiateCloseFromReceiverPage() {
if (!startedConnection) {
sendResult(false, 'startedConnection does not exist.');
return;
}
if (startedConnection.state != 'connected') {
sendResult(
false,
'Expected the connection state to be connected but it was ' +
startedConnection.state);
return;
}
startedConnection.onclose = (event) => {
const reason = event.reason;
if (reason != 'closed') {
sendResult(false, 'Unexpected close reason: ' + reason);
return;
}
sendResult(true, '');
};
startedConnection.send('close');
}
/**
* Reconnects to |connectionId| and verifies that it succeeds.
* @param {!string} connectionId ID of connection to reconnect.
*/
function reconnectConnection(connectionId) {
var reconnectConnectionRequest = new PresentationRequest(presentationUrl);
reconnectConnectionRequest.reconnect(connectionId)
.then(function(connection) {
if (!connection) {
sendResult(false, 'reconnectConnection returned null connection');
} else {
reconnectedConnection = connection;
waitForConnectedStateAndSendResult(reconnectedConnection);
}
})
.catch(function(error) {
sendResult(false, 'reconnectConnection failed: ' + error.message);
});
}
/**
* Calls reconnect(connectionId) and verifies that it fails.
* @param {!string} connectionId ID of connection to reconnect.
* @param {!string} expectedErrorMessage
*/
function reconnectConnectionAndExpectFailure(
connectionId, expectedErrorMessage) {
var reconnectConnectionRequest = new PresentationRequest(presentationUrl);
reconnectConnectionRequest.reconnect(connectionId)
.then(function(connection) {
sendResult(false, 'reconnect() unexpectedly succeeded.');
})
.catch(function(error) {
if (error.message.indexOf(expectedErrorMessage) > -1) {
sendResult(true, '');
} else {
sendResult(
false,
'Error message mismatch. Expected: ' + expectedErrorMessage +
', actual: ' + error.message);
}
});
}
/**
* Sends the test result back to browser test.
* @param passed true if test passes, otherwise false.
* @param errorMessage empty string if test passes, error message if test
* fails.
*/
function sendResult(passed, errorMessage) {
lastExecutionResult = passed ? 'passed' : errorMessage;
}
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