summaryrefslogtreecommitdiffstats
path: root/chrome/test
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/test')
-rw-r--r--chrome/test/android/cast_emulator/BUILD.gn26
-rw-r--r--chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/DummyPlayer.java236
-rw-r--r--chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/LocalSessionManager.java199
-rw-r--r--chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/MediaItem.java109
-rw-r--r--chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/RemoteSessionManager.java360
-rw-r--r--chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/TestMediaRouteProvider.java467
-rw-r--r--chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/TestMediaRouteProviderService.java21
-rw-r--r--chrome/test/android/chrome_public_test_support/AndroidManifest.xml20
-rw-r--r--chrome/test/android/chrome_public_test_support/BUILD.gn16
9 files changed, 1454 insertions, 0 deletions
diff --git a/chrome/test/android/cast_emulator/BUILD.gn b/chrome/test/android/cast_emulator/BUILD.gn
new file mode 100644
index 0000000..9545047
--- /dev/null
+++ b/chrome/test/android/cast_emulator/BUILD.gn
@@ -0,0 +1,26 @@
+# Copyright 2015 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")
+
+# GYP: //clank/native/clank.gyp:cast_emulator
+android_library("cast_emulator") {
+ chromium_code = true
+
+ java_files = [
+ "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/RemoteSessionManager.java",
+ "src/org/chromium/chrome/browser/media/remote/TestMediaRouteProvider.java",
+ "src/org/chromium/chrome/browser/media/remote/TestMediaRouteProviderService.java",
+ ]
+ deps = [
+ "//base:base_java",
+ "//third_party/android_tools:android_support_v7_appcompat_java",
+ "//third_party/android_tools:android_support_v7_mediarouter_java",
+ google_play_services_library,
+ ]
+}
diff --git a/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/DummyPlayer.java b/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/DummyPlayer.java
new file mode 100644
index 0000000..1267317
--- /dev/null
+++ b/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/DummyPlayer.java
@@ -0,0 +1,236 @@
+// Copyright 2014 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.remote;
+
+import android.content.Context;
+import android.media.MediaPlayer;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.support.v7.media.MediaItemStatus;
+import android.support.v7.media.MediaRouter.RouteInfo;
+import android.util.Log;
+
+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 {
+ private static final String TAG = "CastDummyPlayer";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private static final int STATE_IDLE = 0;
+ private static final int STATE_PLAY_PENDING = 1;
+ private static final int STATE_READY = 2;
+ private static final int STATE_PLAYING = 3;
+ private static final int STATE_PAUSED = 4;
+
+ private final Context mContext;
+ private final Handler mHandler = new Handler();
+ private MediaPlayer mMediaPlayer;
+ private int mState = STATE_IDLE;
+ private int mSeekToPos;
+ private Callback mCallback;
+
+ /**
+ * Callback interface for the session manager
+ */
+ public static interface Callback {
+ void onError();
+ void onCompletion();
+ void onSeekComplete();
+ void onPrepared();
+ }
+
+ public DummyPlayer(Context context) {
+ // reset media player
+ reset();
+ mContext = context;
+ }
+
+ public void connect(RouteInfo route) {
+ if (DEBUG) Log.d(TAG, "connecting to: " + route);
+ }
+
+ public void release() {
+ if (DEBUG) Log.d(TAG, "releasing");
+ // release media player
+ if (mMediaPlayer != null) {
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+ }
+
+ // Player
+ public void play(final MediaItem item) {
+ if (DEBUG) Log.d(TAG, "play: item=" + item);
+ reset();
+ mSeekToPos = (int) item.getPosition();
+ try {
+ mMediaPlayer.setDataSource(mContext, item.getUri());
+ mMediaPlayer.prepare();
+ } catch (IllegalStateException e) {
+ Log.e(TAG, "MediaPlayer throws IllegalStateException, uri=" + item.getUri());
+ } catch (IOException e) {
+ Log.e(TAG, "MediaPlayer throws IOException, uri=" + item.getUri());
+ } catch (IllegalArgumentException e) {
+ Log.e(TAG, "MediaPlayer throws IllegalArgumentException, uri=" + item.getUri());
+ } catch (SecurityException e) {
+ Log.e(TAG, "MediaPlayer throws SecurityException, uri=" + item.getUri());
+ }
+ if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
+ resume();
+ } else {
+ pause();
+ }
+ }
+
+ public void seek(final MediaItem item) {
+ if (DEBUG) Log.d(TAG, "seek: item=" + item);
+ int pos = (int) item.getPosition();
+ if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
+ mMediaPlayer.seekTo(pos);
+ mSeekToPos = pos;
+ } else if (mState == STATE_IDLE || mState == STATE_PLAY_PENDING) {
+ // Seek before onPrepared() arrives,
+ // need to performed delayed seek in onPrepared()
+ mSeekToPos = pos;
+ }
+ }
+
+ public void getStatus(final MediaItem item, final boolean update) {
+ if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
+ // use mSeekToPos if we're currently seeking (mSeekToPos is reset
+ // when seeking is completed)
+ item.setDuration(mMediaPlayer.getDuration());
+ item.setPosition(mSeekToPos > 0 ? mSeekToPos : mMediaPlayer.getCurrentPosition());
+ item.setTimestamp(SystemClock.uptimeMillis());
+ }
+ }
+
+ public void pause() {
+ if (DEBUG) Log.d(TAG, "pause");
+ if (mState == STATE_PLAYING) {
+ mMediaPlayer.pause();
+ mState = STATE_PAUSED;
+ }
+ }
+
+ public void resume() {
+ if (DEBUG) Log.d(TAG, "resume");
+ if (mState == STATE_READY || mState == STATE_PAUSED) {
+ mMediaPlayer.start();
+ mState = STATE_PLAYING;
+ } else if (mState == STATE_IDLE) {
+ mState = STATE_PLAY_PENDING;
+ }
+ }
+
+ public void stop() {
+ if (DEBUG) Log.d(TAG, "stop");
+ if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
+ mMediaPlayer.stop();
+ mState = STATE_IDLE;
+ }
+ }
+
+ // MediaPlayer Listeners
+ @Override
+ public void onPrepared(MediaPlayer mp) {
+ if (DEBUG) Log.d(TAG, "onPrepared");
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mState == STATE_IDLE) {
+ mState = STATE_READY;
+ } else if (mState == STATE_PLAY_PENDING) {
+ mState = STATE_PLAYING;
+ if (mSeekToPos > 0) {
+ if (DEBUG) Log.d(TAG, "seek to initial pos: " + mSeekToPos);
+ mMediaPlayer.seekTo(mSeekToPos);
+ }
+ mMediaPlayer.start();
+ }
+ if (mCallback != null) {
+ mCallback.onPrepared();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onCompletion(MediaPlayer mp) {
+ if (DEBUG) Log.d(TAG, "onCompletion");
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mCallback != null) {
+ mCallback.onCompletion();
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean onError(MediaPlayer mp, int what, int extra) {
+ if (DEBUG) Log.d(TAG, "onError");
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mCallback != null) {
+ mCallback.onError();
+ }
+ }
+ });
+ // return true so that onCompletion is not called
+ return true;
+ }
+
+ @Override
+ public void onSeekComplete(MediaPlayer mp) {
+ if (DEBUG) Log.d(TAG, "onSeekComplete");
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mSeekToPos = 0;
+ if (mCallback != null) {
+ mCallback.onSeekComplete();
+ }
+ }
+ });
+ }
+
+ protected Context getContext() {
+ return mContext;
+ }
+
+ private void reset() {
+ if (mMediaPlayer != null) {
+ mMediaPlayer.stop();
+ mMediaPlayer.release();
+ mMediaPlayer = null;
+ }
+ mMediaPlayer = new MediaPlayer();
+ mMediaPlayer.setOnPreparedListener(this);
+ mMediaPlayer.setOnCompletionListener(this);
+ mMediaPlayer.setOnErrorListener(this);
+ mMediaPlayer.setOnSeekCompleteListener(this);
+ mState = STATE_IDLE;
+ mSeekToPos = 0;
+ }
+
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ public static DummyPlayer create(Context context, RouteInfo route) {
+ DummyPlayer player = new DummyPlayer(context);
+ player.connect(route);
+ return player;
+ }
+}
diff --git a/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/LocalSessionManager.java b/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/LocalSessionManager.java
new file mode 100644
index 0000000..e66d2e1
--- /dev/null
+++ b/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/LocalSessionManager.java
@@ -0,0 +1,199 @@
+// Copyright 2014 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.remote;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.net.Uri;
+import android.support.v7.media.MediaSessionStatus;
+
+/**
+ * LocalSessionManager emulates the local management of the playback of media items on Chromecast.
+ * It only handles one item at a time, and does not support queuing.
+ *
+ * Most members simply forward their calls to a RemoteSessionManager, which emulates the session
+ * management on the Chromecast, however this class also controls connection to and disconnection
+ * from the RemoteSessionManager.
+ */
+public class LocalSessionManager {
+ /**
+ * Callbacks for MediaRouteProvider object.
+ */
+ public interface Callback {
+ void onItemChanged(MediaItem item);
+ }
+
+ private Callback mCallback;
+ private RemoteSessionManager mRemoteManager;
+
+ private final Context mContext;
+
+ /**
+ * @param context
+ */
+ public LocalSessionManager(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Add a video we want to play
+ * @param uri the URI of the video
+ * @param mime the mime type
+ * @param receiver the pending intent to use to send state changes
+ * @return the new media item
+ */
+ public MediaItem add(Uri uri, String mime, PendingIntent receiver) {
+ if (!hasSession()) mRemoteManager = RemoteSessionManager.connect(this, mContext);
+ return mRemoteManager.add(uri, mime, receiver);
+ }
+
+ /**
+ * End the current session
+ * @return whether there was a current session
+ */
+ public boolean endSession() {
+ if (hasSession()) {
+ mRemoteManager.disconnect();
+ mRemoteManager = null;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Get the currently playing item
+ * @return the currently playing item, or null if none.
+ */
+ public MediaItem getCurrentItem() {
+ return hasSession() ? mRemoteManager.getCurrentItem() : null;
+ }
+
+ /**
+ * Get the session id of the current session
+ * @return the session id, or null if none.
+ */
+ public String getSessionId() {
+ return hasSession() ? mRemoteManager.getSessionId() : null;
+ }
+
+ /**
+ * Get the status of a session
+ * @param sid the session id of session being asked about
+ * @return the status
+ */
+ public MediaSessionStatus getSessionStatus(String sid) {
+ if (!hasSession()) {
+ return new MediaSessionStatus.Builder(MediaSessionStatus.SESSION_STATE_INVALIDATED)
+ .setQueuePaused(false).build();
+ }
+ return mRemoteManager.getSessionStatus(sid);
+ }
+
+ /**
+ * Get a printable string describing the status of the session
+ * @return the string
+ */
+ public String getSessionStatusString() {
+ if (hasSession()) {
+ return mRemoteManager.getSessionStatusString();
+ } else {
+ return "No remote session connection";
+ }
+ }
+
+ /**
+ * Get the status of a media item
+ * @param iid - the id of the item
+ * @return the MediaItem, from which its status can be read.
+ */
+ public MediaItem getStatus(String iid) {
+ if (!hasSession()) {
+ throw new IllegalStateException("Session not set!");
+ }
+ return mRemoteManager.getStatus(iid);
+ }
+
+ /**
+ * @return whether there is a current session
+ */
+ public boolean hasSession() {
+ return mRemoteManager != null;
+ }
+
+ /**
+ * @return whether the current video is paused
+ */
+ public boolean isPaused() {
+ return hasSession() && mRemoteManager.isPaused();
+ }
+
+ /**
+ * Forward the item changed callback to the UI
+ * @param item the item that has changed.
+ */
+ public void onItemChanged(MediaItem item) {
+ if (mCallback != null) mCallback.onItemChanged(item);
+ }
+
+ /**
+ * Pause the current video
+ */
+ public void pause() {
+ if (hasSession()) mRemoteManager.pause();
+ }
+
+ /**
+ * Resume the current video
+ */
+ public void resume() {
+ if (hasSession()) mRemoteManager.resume();
+ }
+
+ /**
+ * Seek to a position in a video
+ * @param iid the id of the video
+ * @param pos the position in ms
+ * @return the Media item.
+ */
+ public MediaItem seek(String iid, long pos) {
+ return hasSession() ? mRemoteManager.seek(iid, pos) : null;
+ }
+
+ /**
+ * provide a callback interface to tell the UI when significant state changes occur
+ * @param callback the callback object
+ */
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ /**
+ * Start a new local session
+ * @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 (!relaunch) endSession();
+ if (!hasSession()) mRemoteManager = RemoteSessionManager.connect(this, mContext);
+ return mRemoteManager.startSession(relaunch);
+ }
+
+ /**
+ * Stop the current video
+ */
+ public void stop() {
+ if (hasSession()) mRemoteManager.stop();
+ endSession();
+ }
+
+ /**
+ * Updates the session status.
+ */
+ public void updateStatus() {
+ if (hasSession()) mRemoteManager.updateStatus();
+ }
+
+}
diff --git a/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/MediaItem.java b/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/MediaItem.java
new file mode 100644
index 0000000..732d9df
--- /dev/null
+++ b/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/MediaItem.java
@@ -0,0 +1,109 @@
+// Copyright 2014 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.remote;
+
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.support.v7.media.MediaItemStatus;
+import android.util.Log;
+
+/**
+ * PlaylistItem helps keep track of the current status of an media item.
+ */
+final class MediaItem {
+ // immutables
+ private final String mSessionId;
+ private final String mItemId;
+ private final Uri mUri;
+ private final PendingIntent mUpdateReceiver;
+ // changeable states
+ private int mPlaybackState = MediaItemStatus.PLAYBACK_STATE_PENDING;
+ private long mContentPosition;
+ private long mContentDuration;
+ private long mTimestamp;
+ private String mRemoteItemId;
+ private static final String TAG = "MediaItem";
+
+
+ public MediaItem(String qid, String iid, Uri uri, String mime, PendingIntent pi) {
+ mSessionId = qid;
+ mItemId = iid;
+ mUri = uri;
+ mUpdateReceiver = pi;
+ setTimestamp(SystemClock.uptimeMillis());
+ }
+
+ public void setRemoteItemId(String riid) {
+ mRemoteItemId = riid;
+ }
+
+ public void setState(int state) {
+ mPlaybackState = state;
+ Log.d(TAG, "State set to " + state);
+ }
+
+ public void setPosition(long pos) {
+ mContentPosition = pos;
+ }
+
+ public void setTimestamp(long ts) {
+ mTimestamp = ts;
+ }
+
+ public void setDuration(long duration) {
+ mContentDuration = duration;
+ }
+
+ public String getSessionId() {
+ return mSessionId;
+ }
+
+ public String getItemId() {
+ return mItemId;
+ }
+
+ public String getRemoteItemId() {
+ return mRemoteItemId;
+ }
+
+ public Uri getUri() {
+ return mUri;
+ }
+
+ public PendingIntent getUpdateReceiver() {
+ return mUpdateReceiver;
+ }
+
+ public int getState() {
+ return mPlaybackState;
+ }
+
+ public long getPosition() {
+ return mContentPosition;
+ }
+
+ public long getDuration() {
+ return mContentDuration;
+ }
+
+ public long getTimestamp() {
+ return mTimestamp;
+ }
+
+ public MediaItemStatus getStatus() {
+ 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"};
+ return "[" + mSessionId + "|" + mItemId + "|"
+ + (mRemoteItemId != null ? mRemoteItemId : "-") + "|" + state[mPlaybackState] + "] "
+ + mUri.toString();
+ }
+}
diff --git a/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/RemoteSessionManager.java b/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/RemoteSessionManager.java
new file mode 100644
index 0000000..a0835fb
--- /dev/null
+++ b/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/RemoteSessionManager.java
@@ -0,0 +1,360 @@
+// Copyright 2014 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.remote;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.net.Uri;
+import android.support.v7.media.MediaItemStatus;
+import android.support.v7.media.MediaSessionStatus;
+import android.util.Log;
+
+/**
+ * RemoteSessionManager emulates the session management of the playback of media items on
+ * Chromecast. This can be seen as emulating the cast receiver application. It only handles one item
+ * at a time, and does not support queuing.
+ *
+ * Actual playback of a single media item is abstracted into the DummyPlayer class, and is handled
+ * outside this class.
+ */
+public class RemoteSessionManager implements DummyPlayer.Callback {
+ private static final String TAG = "RemoteSessionManager";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ /**
+ * Connect a local session manager to the unique remote session manager, creating it if needed.
+ * @param localSessionManager the local session manager being connected
+ * @param player the player to use
+ * @return the remote session manager
+ */
+ public static RemoteSessionManager connect(LocalSessionManager localSessionManager,
+ Context context) {
+ if (sInstance == null) {
+ sInstance = new RemoteSessionManager("remote", context);
+ }
+ sInstance.mLocalSessionManager = localSessionManager;
+ return sInstance;
+ }
+
+ private String mName;
+ private int mSessionId;
+ private int mItemId;
+ private boolean mPaused;
+ private boolean mSessionValid;
+ private DummyPlayer mPlayer;
+ private MediaItem mCurrentItem;
+ private static RemoteSessionManager sInstance;
+
+ private LocalSessionManager mLocalSessionManager;
+ private Context mContext;
+
+ private RemoteSessionManager(String name, Context context) {
+ mName = name;
+ mContext = context;
+ }
+
+ /**
+ * Add a video we want to play
+ *
+ * @param uri the URI of the video
+ * @param mime the mime type
+ * @param receiver the pending intent to use to send state changes
+ * @return the new media item
+ */
+ public MediaItem add(Uri uri, String mime, PendingIntent receiver) {
+ if (DEBUG) log("add: uri=" + uri + ", receiver=" + receiver);
+ // create new session if needed
+ startSession(false);
+ checkPlayerAndSession();
+
+ // create new item with initial status PLAYBACK_STATE_PENDING
+ mItemId++;
+ mCurrentItem = new MediaItem(Integer.toString(mSessionId), Integer.toString(mItemId), uri,
+ mime, receiver);
+ if (DEBUG) log("add: new item id = " + mCurrentItem);
+ return mCurrentItem;
+ }
+
+ /**
+ * Disconnect from the local session
+ */
+ public void disconnect() {
+ mLocalSessionManager = null;
+ }
+
+ /**
+ * Get the currently playing item
+ *
+ * @return the currently playing item, or null if none.
+ */
+ public MediaItem getCurrentItem() {
+ return mCurrentItem;
+ }
+
+ /**
+ * Get the session id of the current session
+ *
+ * @return the session id, or null if none.
+ */
+ public String getSessionId() {
+ return mSessionValid ? Integer.toString(mSessionId) : null;
+ }
+
+ /**
+ * Get the status of a session
+ *
+ * @param sid the session id of session being asked about
+ * @return the status
+ */
+ public MediaSessionStatus getSessionStatus(String sid) {
+ Log.d(TAG, "Getting session status for session " + sid);
+ int sessionState =
+ (sid != null && sid.equals(Integer.toString(mSessionId)))
+ ? MediaSessionStatus.SESSION_STATE_ACTIVE
+ : MediaSessionStatus.SESSION_STATE_INVALIDATED;
+
+ Log.d(TAG, "Session state is " + sessionState);
+
+ return new MediaSessionStatus.Builder(sessionState).setQueuePaused(mPaused).build();
+ }
+
+ /**
+ * Get a printable string describing the status of the session
+ * @return the string
+ */
+ public String getSessionStatusString() {
+ if (mCurrentItem != null) {
+ return "Current media item: " + mCurrentItem.toString();
+ } else {
+ return "No current media item";
+ }
+ }
+
+ /**
+ * Get the status of a media item
+ *
+ * @param iid - the id of the item
+ * @return the MediaItem, from which its status can be read.
+ */
+ public MediaItem getStatus(String iid) {
+ checkPlayerAndSession();
+ checkItemCurrent(iid);
+
+ mPlayer.getStatus(mCurrentItem, false);
+ return mCurrentItem;
+ }
+
+ /**
+ * @return whether the current video is paused
+ */
+ public boolean isPaused() {
+ return mSessionValid && mPaused;
+ }
+
+ @Override
+ public void onCompletion() {
+ finishItem(false);
+ }
+
+ // Player.Callback
+ @Override
+ public void onError() {
+ finishItem(true);
+ }
+
+ @Override
+ public void onSeekComplete() {
+ // Playlist has changed, update the cached playlist
+ updateStatus();
+ }
+
+ @Override
+ public void onPrepared() {
+ // Item is ready to play, update the status.
+ updateStatus();
+ // Send the new status to the local session manager.
+ onItemChanged();
+ }
+
+ /**
+ * Pause the current video
+ */
+ public void pause() {
+ if (DEBUG) log("pause");
+ if (!mSessionValid) {
+ return;
+ }
+ checkPlayer();
+ mPaused = true;
+ updatePlaybackState();
+ }
+
+ /**
+ * Resume the current video
+ */
+ public void resume() {
+ if (DEBUG) log("resume");
+ if (!mSessionValid) {
+ return;
+ }
+ checkPlayer();
+ mPaused = false;
+ updatePlaybackState();
+ }
+
+ /**
+ * Seek to a position in a video
+ *
+ * @param iid the id of the video
+ * @param pos the position in ms
+ * @return the Media item.
+ */
+ public MediaItem seek(String iid, long pos) {
+ if (DEBUG) log("seek: iid=" + iid + ", pos=" + pos);
+ checkPlayerAndSession();
+ checkItemCurrent(iid);
+
+ if (pos != mCurrentItem.getPosition()) {
+ mCurrentItem.setPosition(pos);
+ if (mCurrentItem.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
+ || mCurrentItem.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+ mPlayer.seek(mCurrentItem);
+ }
+ }
+ return mCurrentItem;
+ }
+
+ /**
+ * 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);
+ finishItem(false);
+ if (mPlayer != null) mPlayer.release();
+ mSessionId++;
+ mItemId = 0;
+ mPaused = false;
+ mSessionValid = true;
+ mPlayer = DummyPlayer.create(mContext, null);
+ mPlayer.setCallback(this);
+ mCurrentItem = null;
+ Log.d(TAG, "Starting session " + mSessionId);
+ }
+ return Integer.toString(mSessionId);
+ }
+
+ /**
+ * Stop the current video
+ */
+ public void stop() {
+ if (DEBUG) log("stop");
+ if (!mSessionValid) {
+ return;
+ }
+ checkPlayer();
+ mPlayer.stop();
+ mCurrentItem = null;
+ mPaused = false;
+ updateStatus();
+ }
+
+ // Updates the playlist.
+ public void updateStatus() {
+ if (DEBUG) log("updateStatus");
+ checkPlayer();
+
+ if (mCurrentItem != null) {
+ mPlayer.getStatus(mCurrentItem, true /* update */);
+ }
+ }
+
+ private void checkItemCurrent(String iid) {
+ if (mCurrentItem == null || !mCurrentItem.getItemId().equals(iid)) {
+ throw new IllegalArgumentException("Item is not current!");
+ }
+ }
+
+ private void checkPlayer() {
+ if (mPlayer == null) {
+ throw new IllegalStateException("Player not set!");
+ }
+ }
+
+ private void checkPlayerAndSession() {
+ checkPlayer();
+ checkSession();
+ }
+
+ private void checkSession() {
+ if (!mSessionValid) {
+ throw new IllegalStateException("Session not set!");
+ }
+ }
+
+ private void finishItem(boolean error) {
+ if (mCurrentItem != null) {
+ removeItem(mCurrentItem.getItemId(), error ? MediaItemStatus.PLAYBACK_STATE_ERROR
+ : MediaItemStatus.PLAYBACK_STATE_FINISHED);
+ updateStatus();
+ }
+ }
+
+ private void log(String message) {
+ Log.d(TAG, mName + ": " + message);
+ }
+
+ private MediaItem removeItem(String iid, int state) {
+ checkPlayerAndSession();
+
+ checkItemCurrent(iid);
+
+ if (mCurrentItem.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
+ || mCurrentItem.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+ mPlayer.stop();
+ }
+ mCurrentItem.setState(state);
+ onItemChanged();
+ updatePlaybackState();
+
+ MediaItem item = mCurrentItem;
+
+ mCurrentItem = null;
+
+ return item;
+ }
+
+ private void updatePlaybackState() {
+ if (mCurrentItem != null) {
+ if (mCurrentItem.getState() == MediaItemStatus.PLAYBACK_STATE_PENDING) {
+ mCurrentItem.setState(mPaused ? MediaItemStatus.PLAYBACK_STATE_PAUSED
+ : MediaItemStatus.PLAYBACK_STATE_PLAYING);
+ mPlayer.play(mCurrentItem);
+ } else if (mPaused
+ && mCurrentItem.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING) {
+ mPlayer.pause();
+ mCurrentItem.setState(MediaItemStatus.PLAYBACK_STATE_PAUSED);
+ } else if (!mPaused
+ && mCurrentItem.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
+ mPlayer.resume();
+ mCurrentItem.setState(MediaItemStatus.PLAYBACK_STATE_PLAYING);
+ }
+ // notify client that item playback status has changed
+ onItemChanged();
+ }
+ updateStatus();
+ }
+
+ private void onItemChanged() {
+ if (mLocalSessionManager != null) {
+ mLocalSessionManager.onItemChanged(mCurrentItem);
+ }
+ }
+
+}
diff --git a/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/TestMediaRouteProvider.java b/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/TestMediaRouteProvider.java
new file mode 100644
index 0000000..e05586b
--- /dev/null
+++ b/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/TestMediaRouteProvider.java
@@ -0,0 +1,467 @@
+// Copyright 2014 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.remote;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentFilter.MalformedMimeTypeException;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.media.AudioManager;
+import android.media.MediaRouter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaRouteDescriptor;
+import android.support.v7.media.MediaRouteProvider;
+import android.support.v7.media.MediaRouteProviderDescriptor;
+import android.support.v7.media.MediaRouter.ControlRequestCallback;
+import android.support.v7.media.MediaSessionStatus;
+import android.util.Log;
+
+import com.google.android.gms.cast.CastMediaControlIntent;
+
+import org.chromium.base.annotations.SuppressFBWarnings;
+
+import java.util.ArrayList;
+
+/**
+ * Dummy media route provider for testing casting from Chrome.
+ *
+ * @see TestMediaRouteProviderService
+ */
+final class TestMediaRouteProvider extends MediaRouteProvider {
+ private static final String MANIFEST_CAST_KEY =
+ "com.google.android.apps.chrome.tests.support.CAST_ID";
+
+ private static final String TAG = "TestMediaRouteProvider";
+
+ private static final String VARIABLE_VOLUME_SESSION_ROUTE_ID = "variable_session";
+ private static final int VOLUME_MAX = 10;
+
+ private int mVolume = 5;
+
+ public TestMediaRouteProvider(Context context) {
+ super(context);
+
+ publishRoutes();
+ }
+
+ @Override
+ public RouteController onCreateRouteController(String routeId) {
+ return new TestMediaController(routeId);
+ }
+
+ private void publishRoutes() {
+
+ String castId = CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID;
+ try {
+ // Downstream cast uses a different, private, castId; so read this from
+ // the manifest.
+ ApplicationInfo ai;
+ ai = getContext().getPackageManager().getApplicationInfo(
+ getContext().getPackageName(), PackageManager.GET_META_DATA);
+ Bundle bundle = ai.metaData;
+ if (bundle != null) {
+ castId = bundle.getString(MANIFEST_CAST_KEY, castId);
+ }
+ } catch (NameNotFoundException e) {
+ // Should never happen, do nothing - use default
+ }
+
+ IntentFilter f1 = new IntentFilter();
+ f1.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ f1.addCategory(CastMediaControlIntent.categoryForRemotePlayback(castId));
+ f1.addAction(MediaControlIntent.ACTION_PLAY);
+ f1.addDataScheme("http");
+ f1.addDataScheme("https");
+ addDataTypeUnchecked(f1, "video/*");
+
+ IntentFilter f2 = new IntentFilter();
+ f2.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
+ f2.addCategory(CastMediaControlIntent.categoryForRemotePlayback());
+ f2.addAction(MediaControlIntent.ACTION_SEEK);
+ f2.addAction(MediaControlIntent.ACTION_GET_STATUS);
+ f2.addAction(MediaControlIntent.ACTION_PAUSE);
+ f2.addAction(MediaControlIntent.ACTION_RESUME);
+ f2.addAction(MediaControlIntent.ACTION_STOP);
+ f2.addAction(MediaControlIntent.ACTION_START_SESSION);
+ f2.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS);
+ f2.addAction(MediaControlIntent.ACTION_END_SESSION);
+ f2.addAction(CastMediaControlIntent.ACTION_SYNC_STATUS);
+
+ ArrayList<IntentFilter> controlFilters = new ArrayList<IntentFilter>();
+ 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)
+ .setPlaybackStream(AudioManager.STREAM_MUSIC)
+ .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
+ .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
+ .setVolumeMax(VOLUME_MAX).setVolume(mVolume).build();
+
+ MediaRouteProviderDescriptor providerDescriptor = new MediaRouteProviderDescriptor.Builder()
+ .addRoute(testRouteDescriptor).build();
+ setDescriptor(providerDescriptor);
+ }
+
+ private final class TestMediaController extends MediaRouteProvider.RouteController {
+ private final String mRouteId;
+ private final LocalSessionManager mSessionManager = new LocalSessionManager(getContext());
+ private PendingIntent mSessionReceiver;
+ private Bundle mMetadata;
+
+ public TestMediaController(String routeId) {
+ mRouteId = routeId;
+ mSessionManager.setCallback(new LocalSessionManager.Callback() {
+ @Override
+ public void onItemChanged(MediaItem item) {
+ handleStatusChange(item);
+ }
+ });
+ setVolumeInternal(mVolume);
+ Log.d(TAG, mRouteId + ": Controller created");
+ }
+
+ @Override
+ public void onRelease() {
+ Log.d(TAG, mRouteId + ": Controller released");
+ }
+
+ @Override
+ public void onSelect() {
+ Log.d(TAG, mRouteId + ": Selected");
+ }
+
+ @Override
+ public void onUnselect() {
+ Log.d(TAG, mRouteId + ": Unselected");
+ }
+
+ @Override
+ public void onSetVolume(int volume) {
+ Log.d(TAG, mRouteId + ": Set volume to " + volume);
+ setVolumeInternal(volume);
+ }
+
+ @Override
+ public void onUpdateVolume(int delta) {
+ Log.d(TAG, mRouteId + ": Update volume by " + delta);
+ setVolumeInternal(mVolume + delta);
+ }
+
+ @Override
+ public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
+ Log.d(TAG, mRouteId + ": Received control request " + intent);
+ String action = intent.getAction();
+ if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+ || intent.hasCategory(CastMediaControlIntent.categoryForRemotePlayback())) {
+ boolean success = false;
+ if (action.equals(MediaControlIntent.ACTION_PLAY)) {
+ success = handlePlay(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_SEEK)) {
+ success = handleSeek(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_GET_STATUS)) {
+ success = handleGetStatus(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_PAUSE)) {
+ success = handlePause(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_RESUME)) {
+ success = handleResume(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_STOP)) {
+ success = handleStop(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_START_SESSION)) {
+ success = handleStartSession(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_GET_SESSION_STATUS)) {
+ success = handleGetSessionStatus(intent, callback);
+ } else if (action.equals(MediaControlIntent.ACTION_END_SESSION)) {
+ success = handleEndSession(intent, callback);
+ } else if (action.equals(CastMediaControlIntent.ACTION_SYNC_STATUS)) {
+ success = handleSyncStatus(intent, callback);
+ }
+ Log.d(TAG, mSessionManager.getSessionStatusString());
+ return success;
+ }
+
+ return false;
+ }
+
+ /**
+ * @param intent
+ * @param callback
+ * @return
+ */
+ private boolean handleSyncStatus(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ Log.d(TAG, mRouteId + ": Received syncStatus request, sid=" + sid);
+
+ MediaItem item = mSessionManager.getCurrentItem();
+ if (callback != null) {
+ Bundle result = new Bundle();
+ if (item != null) {
+ String iid = item.getItemId();
+ result.putString(MediaControlIntent.EXTRA_ITEM_ID, iid);
+ result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
+ item.getStatus().asBundle());
+ if (mMetadata != null) {
+ result.putBundle(MediaControlIntent.EXTRA_ITEM_METADATA, mMetadata);
+ }
+ }
+ callback.onResult(result);
+ }
+ return true;
+ }
+
+ private void setVolumeInternal(int volume) {
+ if (volume >= 0 && volume <= VOLUME_MAX) {
+ mVolume = volume;
+ Log.d(TAG, mRouteId + ": New volume is " + mVolume);
+ AudioManager audioManager = (AudioManager) getContext()
+ .getSystemService(Context.AUDIO_SERVICE);
+ audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
+ publishRoutes();
+ }
+ }
+
+ private boolean handlePlay(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ if (sid != null && !sid.equals(mSessionManager.getSessionId())) {
+ Log.d(TAG, "handlePlay fails because of bad sid=" + sid);
+ return false;
+ }
+ if (mSessionManager.hasSession()) {
+ mSessionManager.stop();
+ }
+
+ Uri uri = intent.getData();
+ if (uri == null) {
+ Log.d(TAG, "handlePlay fails because of null uri");
+ return false;
+ }
+
+ String mime = intent.getType();
+ long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
+ mMetadata = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_METADATA);
+ Bundle headers = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_HTTP_HEADERS);
+ PendingIntent receiver = (PendingIntent) intent.getParcelableExtra(
+ MediaControlIntent.EXTRA_ITEM_STATUS_UPDATE_RECEIVER);
+
+ Log.d(TAG, mRouteId + ": Received play request, uri=" + uri + ", mime=" + mime
+ + ", sid=" + sid + ", pos=" + pos + ", metadata=" + mMetadata + ", headers="
+ + headers + ", receiver=" + receiver);
+ // Add the video to the session manager.
+ MediaItem item = mSessionManager.add(uri, mime, receiver);
+ // And start it playing.
+ mSessionManager.resume();
+ if (callback != null) {
+ if (item != null) {
+ 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());
+ callback.onResult(result);
+ } else {
+ callback.onError("Failed to open " + uri.toString(), null);
+ }
+ }
+ return true;
+ }
+
+ private boolean handleSeek(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ if (sid == null || !sid.equals(mSessionManager.getSessionId())) {
+ return false;
+ }
+
+ String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
+ long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
+ Log.d(TAG, mRouteId + ": Received seek request, pos=" + pos);
+ MediaItem item = mSessionManager.seek(iid, pos);
+ if (callback != null) {
+ Bundle result = new Bundle();
+ result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS, item.getStatus().asBundle());
+ callback.onResult(result);
+ }
+ return true;
+ }
+
+ private boolean handleGetStatus(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
+ Log.d(TAG, mRouteId + ": Received getStatus request, sid=" + sid + ", iid=" + iid);
+ MediaItem item = mSessionManager.getStatus(iid);
+ if (callback != null) {
+ if (item != null) {
+ Bundle result = new Bundle();
+ result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
+ item.getStatus().asBundle());
+ callback.onResult(result);
+ } else {
+ callback.onError("Failed to get status" + ", sid=" + sid + ", iid=" + iid,
+ null);
+ }
+ }
+ return (item != null);
+ }
+
+ private boolean handlePause(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
+ mSessionManager.pause();
+ if (callback != null) {
+ if (success) {
+ callback.onResult(new Bundle());
+ handleSessionStatusChange(sid);
+ } else {
+ callback.onError("Failed to pause, sid=" + sid, null);
+ }
+ }
+ return success;
+ }
+
+ private boolean handleResume(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
+ mSessionManager.resume();
+ if (callback != null) {
+ if (success) {
+ callback.onResult(new Bundle());
+ handleSessionStatusChange(sid);
+ } else {
+ callback.onError("Failed to resume, sid=" + sid, null);
+ }
+ }
+ return success;
+ }
+
+ private boolean handleStop(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
+ mSessionManager.stop();
+ if (callback != null) {
+ if (success) {
+ callback.onResult(new Bundle());
+ handleSessionStatusChange(sid);
+ } else {
+ callback.onError("Failed to stop, sid=" + sid, null);
+ }
+ }
+ return success;
+ }
+
+ @SuppressFBWarnings("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
+ private boolean handleStartSession(Intent intent, ControlRequestCallback callback) {
+ boolean relaunch = intent.getBooleanExtra(
+ CastMediaControlIntent.EXTRA_CAST_RELAUNCH_APPLICATION, true);
+ String sid = mSessionManager.startSession(relaunch);
+ Log.d(TAG, "StartSession returns sessionId " + sid);
+ if (callback != null) {
+ if (sid != null) {
+ Bundle result = new Bundle();
+ result.putString(MediaControlIntent.EXTRA_SESSION_ID, sid);
+ result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS,
+ mSessionManager.getSessionStatus(sid).asBundle());
+ Log.d(TAG, "StartSession sends result of " + result);
+ callback.onResult(result);
+ mSessionReceiver = (PendingIntent) intent.getParcelableExtra(
+ MediaControlIntent.EXTRA_SESSION_STATUS_UPDATE_RECEIVER);
+ handleSessionStatusChange(sid);
+ } else {
+ callback.onError("Failed to start session.", null);
+ }
+ }
+ return (sid != null);
+ }
+
+ private boolean handleGetSessionStatus(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+
+ MediaSessionStatus sessionStatus = mSessionManager.getSessionStatus(sid);
+ if (callback != null) {
+ if (sessionStatus != null) {
+ Bundle result = new Bundle();
+ result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS,
+ mSessionManager.getSessionStatus(sid).asBundle());
+ callback.onResult(result);
+ } else {
+ callback.onError("Failed to get session status, sid=" + sid, null);
+ }
+ }
+ return (sessionStatus != null);
+ }
+
+ private boolean handleEndSession(Intent intent, ControlRequestCallback callback) {
+ String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
+ boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId())
+ && mSessionManager.endSession();
+ 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());
+ callback.onResult(result);
+ handleSessionStatusChange(sid);
+ mSessionReceiver = null;
+ } else {
+ callback.onError("Failed to end session, sid=" + sid, null);
+ }
+ }
+ return success;
+ }
+
+ private void handleStatusChange(MediaItem item) {
+ if (item == null) {
+ item = mSessionManager.getCurrentItem();
+ }
+ if (item != null) {
+ PendingIntent receiver = item.getUpdateReceiver();
+ if (receiver != null) {
+ 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());
+ try {
+ receiver.send(getContext(), 0, intent);
+ Log.d(TAG, mRouteId + ": Sending status update from provider");
+ } catch (PendingIntent.CanceledException e) {
+ Log.d(TAG, mRouteId + ": Failed to send status update!");
+ }
+ }
+ }
+ }
+
+ private void handleSessionStatusChange(String sid) {
+ if (mSessionReceiver != null) {
+ Intent intent = new Intent();
+ intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sid);
+ intent.putExtra(MediaControlIntent.EXTRA_SESSION_STATUS,
+ mSessionManager.getSessionStatus(sid).asBundle());
+ try {
+ mSessionReceiver.send(getContext(), 0, intent);
+ Log.d(TAG, mRouteId + ": Sending session status update from provider");
+ } catch (PendingIntent.CanceledException e) {
+ Log.d(TAG, mRouteId + ": Failed to send session status update!");
+ }
+ }
+ }
+ }
+
+ private static void addDataTypeUnchecked(IntentFilter filter, String type) {
+ try {
+ filter.addDataType(type);
+ } catch (MalformedMimeTypeException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+}
diff --git a/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/TestMediaRouteProviderService.java b/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/TestMediaRouteProviderService.java
new file mode 100644
index 0000000..2867ab9
--- /dev/null
+++ b/chrome/test/android/cast_emulator/src/org/chromium/chrome/browser/media/remote/TestMediaRouteProviderService.java
@@ -0,0 +1,21 @@
+// Copyright 2014 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.remote;
+
+import android.support.v7.media.MediaRouteProvider;
+import android.support.v7.media.MediaRouteProviderService;
+
+/**
+ * Demonstrates how to register a custom media route provider service
+ * using the support library.
+ *
+ * @see TestMediaRouteProvider
+ */
+public class TestMediaRouteProviderService extends MediaRouteProviderService {
+ @Override
+ public MediaRouteProvider onCreateMediaRouteProvider() {
+ return new TestMediaRouteProvider(this);
+ }
+}
diff --git a/chrome/test/android/chrome_public_test_support/AndroidManifest.xml b/chrome/test/android/chrome_public_test_support/AndroidManifest.xml
new file mode 100644
index 0000000..0d60c71
--- /dev/null
+++ b/chrome/test/android/chrome_public_test_support/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+ <!-- Copyright (c) 2014 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.chrome.tests.support">
+ <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="22" />
+ <uses-permission android:name="android.permission.INTERNET" />
+ <application>
+ <service android:name="org.chromium.chrome.browser.media.remote.TestMediaRouteProviderService"
+ android:label="testMediaRouteProviderService"
+ android:process=":mrp"
+ tools:ignore="ExportedService" >
+ <intent-filter>
+ <action android:name="android.media.MediaRouteProviderService" />
+ </intent-filter>
+ </service>
+ </application>
+</manifest>
diff --git a/chrome/test/android/chrome_public_test_support/BUILD.gn b/chrome/test/android/chrome_public_test_support/BUILD.gn
new file mode 100644
index 0000000..d65584b
--- /dev/null
+++ b/chrome/test/android/chrome_public_test_support/BUILD.gn
@@ -0,0 +1,16 @@
+# Copyright 2015 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")
+
+# GYP: //clank/native/clank.gyp:chrome_public_test_support_apk
+android_apk("chrome_public_test_support_apk") {
+ deps = [
+ "//chrome/test/android/cast_emulator:cast_emulator",
+ ]
+
+ apk_name = "ChromePublicTestSupport"
+ android_manifest = "AndroidManifest.xml"
+}