diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/CastNotificationControl.java b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/CastNotificationControl.java index 9864d7b4a7c49..bb8ab9b481e78 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/media/remote/CastNotificationControl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/remote/CastNotificationControl.java @@ -16,6 +16,7 @@ import org.chromium.chrome.browser.media.ui.MediaNotificationInfo; import org.chromium.chrome.browser.media.ui.MediaNotificationListener; import org.chromium.chrome.browser.media.ui.MediaNotificationManager; import org.chromium.chrome.browser.tab.Tab; +import org.chromium.content_public.common.MediaMetadata; import javax.annotation.Nullable; @@ -117,7 +118,7 @@ public class CastNotificationControl implements MediaRouteController.UiListener, } private void updateNotification() { - mNotificationBuilder.setTitle(mTitle); + mNotificationBuilder.setMetadata(new MediaMetadata(mTitle, "", "")); if (mState == PlayerState.PAUSED || mState == PlayerState.PLAYING) { mNotificationBuilder.setPaused(mState != PlayerState.PLAYING); mNotificationBuilder.setActions(MediaNotificationInfo.ACTION_STOP @@ -208,4 +209,4 @@ public class CastNotificationControl implements MediaRouteController.UiListener, boolean isShowingForTests() { return mIsShowing; } -} \ No newline at end of file +} diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionImpl.java b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionImpl.java index 47cdc4db30a65..2427eca5c2fec 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionImpl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/router/cast/CastSessionImpl.java @@ -22,6 +22,8 @@ import org.chromium.chrome.browser.media.ui.MediaNotificationInfo; import org.chromium.chrome.browser.media.ui.MediaNotificationListener; import org.chromium.chrome.browser.media.ui.MediaNotificationManager; import org.chromium.chrome.browser.tab.Tab; +import org.chromium.content_public.common.MediaMetadata; + import org.json.JSONException; import org.json.JSONObject; @@ -128,7 +130,7 @@ public class CastSessionImpl implements MediaNotificationListener, CastSession { } mNotificationBuilder = new MediaNotificationInfo.Builder() - .setTitle(mCastDevice.getFriendlyName()) + .setMetadata(new MediaMetadata(mCastDevice.getFriendlyName(), "", "")) .setPaused(false) .setOrigin(origin) // TODO(avayvod): the same session might have more than one tab id. Should we track diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationInfo.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationInfo.java index 4269336e414fb..e55bc122f40b8 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationInfo.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationInfo.java @@ -9,6 +9,7 @@ import android.graphics.Bitmap; import android.text.TextUtils; import org.chromium.chrome.browser.tab.Tab; +import org.chromium.content_public.common.MediaMetadata; /** * Exposes information about the current media notification to the external clients. @@ -42,7 +43,7 @@ public class MediaNotificationInfo { */ public static final class Builder { - private String mTitle = ""; + private MediaMetadata mMetadata; private boolean mIsPaused = false; private String mOrigin = ""; private int mTabId = Tab.INVALID_TAB_ID; @@ -61,12 +62,12 @@ public class MediaNotificationInfo { } public MediaNotificationInfo build() { - assert mTitle != null; + assert mMetadata != null; assert mOrigin != null; assert mListener != null; return new MediaNotificationInfo( - mTitle, + mMetadata, mIsPaused, mOrigin, mTabId, @@ -79,8 +80,8 @@ public class MediaNotificationInfo { mListener); } - public Builder setTitle(String title) { - mTitle = title; + public Builder setMetadata(MediaMetadata metadata) { + mMetadata = metadata; return this; } @@ -141,9 +142,9 @@ public class MediaNotificationInfo { private final int mActions; /** - * The title of the media. + * The metadata associated with the media. */ - public final String title; + public final MediaMetadata metadata; /** * The current state of the media, paused or not. @@ -213,7 +214,7 @@ public class MediaNotificationInfo { /** * Create a new MediaNotificationInfo. - * @param title The title of the media. + * @param metadata The metadata associated with the media. * @param isPaused The current state of the media, paused or not. * @param origin The origin of the tab containing the media. * @param tabId The id of the tab containing the media. @@ -223,7 +224,7 @@ public class MediaNotificationInfo { * @param listener The listener for the control events. */ private MediaNotificationInfo( - String title, + MediaMetadata metadata, boolean isPaused, String origin, int tabId, @@ -234,7 +235,7 @@ public class MediaNotificationInfo { Bitmap image, Intent contentIntent, MediaNotificationListener listener) { - this.title = title; + this.metadata = metadata; this.isPaused = isPaused; this.origin = origin; this.tabId = tabId; @@ -259,7 +260,7 @@ public class MediaNotificationInfo { && icon == other.icon && mActions == other.mActions && id == other.id - && TextUtils.equals(title, other.title) + && metadata.equals(other.metadata) && TextUtils.equals(origin, other.origin) && image == other.image || (image != null && image.sameAs(other.image)) && contentIntent.equals(other.contentIntent) @@ -270,7 +271,7 @@ public class MediaNotificationInfo { public int hashCode() { int result = isPaused ? 1 : 0; result = 31 * result + (isPrivate ? 1 : 0); - result = 31 * result + (title == null ? 0 : title.hashCode()); + result = 31 * result + (metadata == null ? 0 : metadata.hashCode()); result = 31 * result + (origin == null ? 0 : origin.hashCode()); result = 31 * result + (image == null ? 0 : image.hashCode()); result = 31 * result + (contentIntent == null ? 0 : contentIntent.hashCode()); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java index 67cd06f5aa0b8..fa664a7eda125 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaNotificationManager.java @@ -24,6 +24,7 @@ import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; import android.support.v7.media.MediaRouter; +import android.text.TextUtils; import android.util.SparseArray; import android.view.KeyEvent; import android.view.View; @@ -502,7 +503,7 @@ public class MediaNotificationManager { playPauseButtonId = R.id.button2; } - contentView.setTextViewText(R.id.title, mMediaNotificationInfo.title); + contentView.setTextViewText(R.id.title, mMediaNotificationInfo.metadata.getTitle()); contentView.setTextViewText(R.id.status, mMediaNotificationInfo.origin); if (mNotificationIcon != null) { contentView.setImageViewBitmap(R.id.icon, mNotificationIcon); @@ -541,7 +542,7 @@ public class MediaNotificationManager { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, - mMediaNotificationInfo.title); + mMediaNotificationInfo.metadata.getTitle()); metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, mMediaNotificationInfo.origin); metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, @@ -554,12 +555,21 @@ public class MediaNotificationManager { } } else { metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_TITLE, - mMediaNotificationInfo.title); + mMediaNotificationInfo.metadata.getTitle()); metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, mMediaNotificationInfo.origin); metadataBuilder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, mediaSessionImage); } + if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getArtist())) { + metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, + mMediaNotificationInfo.metadata.getArtist()); + } + if (!TextUtils.isEmpty(mMediaNotificationInfo.metadata.getAlbum())) { + metadataBuilder.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, + mMediaNotificationInfo.metadata.getAlbum()); + } + return metadataBuilder.build(); } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java index db674b73dd62c..657410fef750a 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/media/ui/MediaSessionTabHelper.java @@ -6,6 +6,7 @@ package org.chromium.chrome.browser.media.ui; import android.app.Activity; import android.media.AudioManager; +import android.text.TextUtils; import org.chromium.base.ApplicationStatus; import org.chromium.base.Log; @@ -17,6 +18,7 @@ import org.chromium.chrome.browser.tab.TabObserver; import org.chromium.chrome.browser.util.UrlUtilities; import org.chromium.content_public.browser.WebContents; import org.chromium.content_public.browser.WebContentsObserver; +import org.chromium.content_public.common.MediaMetadata; import org.chromium.ui.base.WindowAndroid; import java.net.URI; @@ -36,6 +38,7 @@ public class MediaSessionTabHelper { private WebContentsObserver mWebContentsObserver; private int mPreviousVolumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE; private MediaNotificationInfo.Builder mNotificationInfoBuilder = null; + private MediaMetadata mFallbackMetadata; private MediaNotificationListener mControlsListener = new MediaNotificationListener() { @Override @@ -84,7 +87,8 @@ public class MediaSessionTabHelper { } @Override - public void mediaSessionStateChanged(boolean isControllable, boolean isPaused) { + public void mediaSessionStateChanged(boolean isControllable, boolean isPaused, + MediaMetadata metadata) { if (!isControllable) { hideNotification(); return; @@ -97,8 +101,20 @@ public class MediaSessionTabHelper { + "Showing the full URL instead."); } + mFallbackMetadata = null; + + // The page's title is used as a placeholder if no title is specified in the + // metadata. + if (TextUtils.isEmpty(metadata.getTitle())) { + mFallbackMetadata = new MediaMetadata( + sanitizeMediaTitle(mTab.getTitle()), + metadata.getArtist(), + metadata.getAlbum()); + metadata = mFallbackMetadata; + } + mNotificationInfoBuilder = new MediaNotificationInfo.Builder() - .setTitle(sanitizeMediaTitle(mTab.getTitle())) + .setMetadata(metadata) .setPaused(isPaused) .setOrigin(origin) .setTabId(mTab.getId()) @@ -145,9 +161,11 @@ public class MediaSessionTabHelper { @Override public void onTitleUpdated(Tab tab) { assert tab == mTab; - if (mNotificationInfoBuilder == null) return; + if (mNotificationInfoBuilder == null || mFallbackMetadata == null) return; + + mFallbackMetadata.setTitle(sanitizeMediaTitle(mTab.getTitle())); + mNotificationInfoBuilder.setMetadata(mFallbackMetadata); - mNotificationInfoBuilder.setTitle(sanitizeMediaTitle(mTab.getTitle())); MediaNotificationManager.show(ApplicationStatus.getApplicationContext(), mNotificationInfoBuilder.build()); } diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/NotificationTitleUpdatedTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/NotificationTitleUpdatedTest.java index 3838d5db66885..58ab5f77f38bb 100644 --- a/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/NotificationTitleUpdatedTest.java +++ b/chrome/android/javatests/src/org/chromium/chrome/browser/media/ui/NotificationTitleUpdatedTest.java @@ -19,6 +19,7 @@ import org.chromium.chrome.test.util.ChromeRestriction; import org.chromium.chrome.test.util.browser.TabTitleObserver; import org.chromium.content.browser.test.util.JavaScriptUtils; import org.chromium.content_public.browser.WebContentsObserver; +import org.chromium.content_public.common.MediaMetadata; /** * Test of media notifications to see whether the text updates when the tab title changes @@ -102,7 +103,7 @@ public class NotificationTitleUpdatedTest extends ChromeActivityTestCaseBase<Chr tab.getWebContents().getObserversForTesting(); while (observers.hasNext()) { observers.next().mediaSessionStateChanged( - isControllable, isSuspended); + isControllable, isSuspended, new MediaMetadata("", "", "")); } } }); @@ -125,7 +126,7 @@ public class NotificationTitleUpdatedTest extends ChromeActivityTestCaseBase<Chr @Override public void run() { assertEquals(title, MediaNotificationManager - .getNotificationInfoForTesting(NOTIFICATION_ID).title); + .getNotificationInfoForTesting(NOTIFICATION_ID).metadata.getTitle()); } }); } diff --git a/content/browser/android/web_contents_observer_proxy.cc b/content/browser/android/web_contents_observer_proxy.cc index 0483fd867f004..dfa52cfb2112a 100644 --- a/content/browser/android/web_contents_observer_proxy.cc +++ b/content/browser/android/web_contents_observer_proxy.cc @@ -11,6 +11,7 @@ #include "base/android/scoped_java_ref.h" #include "content/browser/renderer_host/render_widget_host_impl.h" #include "content/browser/web_contents/web_contents_impl.h" +#include "content/common/android/media_metadata_android.h" #include "content/public/browser/navigation_details.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/navigation_handle.h" @@ -307,14 +308,18 @@ void WebContentsObserverProxy::DidStartNavigationToPendingEntry( env, obj.obj(), jstring_url.obj()); } -void WebContentsObserverProxy::MediaSessionStateChanged(bool is_controllable, - bool is_suspended) { +void WebContentsObserverProxy::MediaSessionStateChanged( + bool is_controllable, + bool is_suspended, + const MediaMetadata& metadata) { JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef<jobject> obj(java_observer_); + ScopedJavaLocalRef<jobject> j_metadata = + MediaMetadataAndroid::CreateJavaObject(env, metadata); Java_WebContentsObserverProxy_mediaSessionStateChanged( - env, obj.obj(), is_controllable, is_suspended); + env, obj.obj(), is_controllable, is_suspended, j_metadata.obj()); } void WebContentsObserverProxy::SetToBaseURLForDataURLIfNeeded( diff --git a/content/browser/android/web_contents_observer_proxy.h b/content/browser/android/web_contents_observer_proxy.h index d37f718c64d04..af0895dc6693f 100644 --- a/content/browser/android/web_contents_observer_proxy.h +++ b/content/browser/android/web_contents_observer_proxy.h @@ -73,7 +73,8 @@ class WebContentsObserverProxy : public WebContentsObserver { const GURL& url, NavigationController::ReloadType reload_type) override; void MediaSessionStateChanged(bool is_controllable, - bool is_suspended) override; + bool is_suspended, + const MediaMetadata& metadata) override; void SetToBaseURLForDataURLIfNeeded(std::string* url); void DidFailLoadInternal(bool is_provisional_load, diff --git a/content/browser/media/session/media_session.h b/content/browser/media/session/media_session.h index 4fb5d42af49f5..2553c6d7885d5 100644 --- a/content/browser/media/session/media_session.h +++ b/content/browser/media/session/media_session.h @@ -13,6 +13,7 @@ #include "content/common/content_export.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" +#include "content/public/common/media_metadata.h" class MediaSessionBrowserTest; @@ -58,6 +59,11 @@ class MediaSession : public WebContentsObserver, ~MediaSession() override; + void setMetadata(const MediaMetadata& metadata) { + metadata_ = metadata; + } + const MediaMetadata& metadata() const { return metadata_; } + // Adds the given player to the current media session. Returns whether the // player was successfully added. If it returns false, AddPlayer() should be // called again later. @@ -184,6 +190,8 @@ class MediaSession : public WebContentsObserver, // multiply their volume with this multiplier to get the effective volume. double volume_multiplier_; + MediaMetadata metadata_; + DISALLOW_COPY_AND_ASSIGN(MediaSession); }; diff --git a/content/browser/media/session/media_session_browsertest.cc b/content/browser/media/session/media_session_browsertest.cc index f75342d7bfd8f..c8388f733ef6f 100644 --- a/content/browser/media/session/media_session_browsertest.cc +++ b/content/browser/media/session/media_session_browsertest.cc @@ -48,8 +48,9 @@ class MockWebContentsObserver : public WebContentsObserver { MockWebContentsObserver(WebContents* web_contents) : WebContentsObserver(web_contents) {} - MOCK_METHOD2(MediaSessionStateChanged, - void(bool is_controllable, bool is_suspended)); + MOCK_METHOD3(MediaSessionStateChanged, + void(bool is_controllable, bool is_suspended, + const content::MediaMetadata& metadata)); }; } // namespace @@ -488,7 +489,7 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, MediaSessionType) { IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsShowForContent) { EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); scoped_ptr<MockMediaSessionObserver> media_session_observer( new MockMediaSessionObserver); @@ -502,7 +503,7 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsShowForContent) { IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsNoShowForTransient) { EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(false, false)); + MediaSessionStateChanged(false, false, testing::_)); scoped_ptr<MockMediaSessionObserver> media_session_observer( new MockMediaSessionObserver); @@ -516,9 +517,9 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsNoShowForTransient) { IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsHideWhenStopped) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(false, true)) + MediaSessionStateChanged(false, true, testing::_)) .After(showControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( @@ -534,7 +535,7 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsHideWhenStopped) { IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsShownAcceptTransient) { EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); scoped_ptr<MockMediaSessionObserver> media_session_observer( new MockMediaSessionObserver); @@ -550,10 +551,10 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsShownAcceptTransient) { IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsShownAfterContentAdded) { - Expectation dontShowControls = EXPECT_CALL( - *mock_web_contents_observer(), MediaSessionStateChanged(false, false)); + Expectation dontShowControls = EXPECT_CALL(*mock_web_contents_observer(), + MediaSessionStateChanged(false, false, testing::_)); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)) + MediaSessionStateChanged(true, false, testing::_)) .After(dontShowControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( @@ -571,7 +572,7 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsStayIfOnlyOnePlayerHasBeenPaused) { EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); scoped_ptr<MockMediaSessionObserver> media_session_observer( new MockMediaSessionObserver); @@ -590,9 +591,9 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsHideWhenTheLastPlayerIsRemoved) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(false, true)) + MediaSessionStateChanged(false, true, testing::_)) .After(showControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( new MockMediaSessionObserver); @@ -614,9 +615,9 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsHideWhenAllThePlayersAreRemoved) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(false, true)) + MediaSessionStateChanged(false, true, testing::_)) .After(showControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( @@ -634,9 +635,9 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsNotHideWhenTheLastPlayerIsPaused) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, true)) + MediaSessionStateChanged(true, true, testing::_)) .After(showControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( @@ -659,9 +660,9 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, SuspendTemporaryUpdatesControls) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, true)) + MediaSessionStateChanged(true, true, testing::_)) .After(showControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( @@ -677,12 +678,11 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsUpdatedWhenResumed) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, true)) - .After(showControls); + MediaSessionStateChanged(true, true, testing::_)).After(showControls); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)) + MediaSessionStateChanged(true, false, testing::_)) .After(pauseControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( @@ -699,9 +699,9 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsUpdatedWhenResumed) { IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsHideWhenSessionSuspendedPermanently) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(false, true)) + MediaSessionStateChanged(false, true, testing::_)) .After(showControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( @@ -718,12 +718,11 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ConstrolsHideWhenSessionStops) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, true)) - .After(showControls); + MediaSessionStateChanged(true, true, testing::_)).After(showControls); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(false, true)) + MediaSessionStateChanged(false, true, testing::_)) .After(pauseControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( @@ -740,12 +739,11 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsHideWhenSessionChangesFromContentToTransient) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, true)) - .After(showControls); + MediaSessionStateChanged(true, true, testing::_)).After(showControls); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(false, false)) + MediaSessionStateChanged(false, false, testing::_)) .After(pauseControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( @@ -765,12 +763,11 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsUpdatedWhenNewPlayerResetsSession) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, true)) - .After(showControls); + MediaSessionStateChanged(true, true, testing::_)).After(showControls); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)) + MediaSessionStateChanged(true, false, testing::_)) .After(pauseControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( @@ -789,12 +786,11 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsResumedWhenPlayerIsResumed) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, true)) - .After(showControls); + MediaSessionStateChanged(true, true, testing::_)).After(showControls); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)) + MediaSessionStateChanged(true, false, testing::_)) .After(pauseControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( @@ -813,9 +809,10 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsUpdatedDueToResumeSessionAction) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, true)).After(showControls); + MediaSessionStateChanged(true, true, testing::_)) + .After(showControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( new MockMediaSessionObserver); @@ -830,12 +827,11 @@ IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, IN_PROC_BROWSER_TEST_F(MediaSessionBrowserTest, ControlsUpdatedDueToSuspendSessionAction) { Expectation showControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)); + MediaSessionStateChanged(true, false, testing::_)); Expectation pauseControls = EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, true)) - .After(showControls); + MediaSessionStateChanged(true, true, testing::_)).After(showControls); EXPECT_CALL(*mock_web_contents_observer(), - MediaSessionStateChanged(true, false)) + MediaSessionStateChanged(true, false, testing::_)) .After(pauseControls); scoped_ptr<MockMediaSessionObserver> media_session_observer( diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc index 83c786214b17d..0a748f1078548 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -3586,7 +3586,8 @@ void WebContentsImpl::OnMediaSessionStateChanged() { MediaSession* session = MediaSession::Get(this); FOR_EACH_OBSERVER(WebContentsObserver, observers_, MediaSessionStateChanged(session->IsControllable(), - session->IsSuspended())); + session->IsSuspended(), + session->metadata())); } void WebContentsImpl::ResumeMediaSession() { diff --git a/content/common/android/common_jni_registrar.cc b/content/common/android/common_jni_registrar.cc index 2ba3387c98db5..0feaf381eca85 100644 --- a/content/common/android/common_jni_registrar.cc +++ b/content/common/android/common_jni_registrar.cc @@ -8,10 +8,12 @@ #include "base/android/jni_registrar.h" #include "base/macros.h" #include "content/common/android/hash_set.h" +#include "content/common/android/media_metadata_android.h" namespace { base::android::RegistrationMethod kContentRegisteredMethods[] = { { "HashSet", content::RegisterHashSet }, + { "MediaMetadataAndroid", content::MediaMetadataAndroid::Register }, }; } // namespace diff --git a/content/common/android/media_metadata_android.cc b/content/common/android/media_metadata_android.cc new file mode 100644 index 0000000000000..0bd1563175891 --- /dev/null +++ b/content/common/android/media_metadata_android.cc @@ -0,0 +1,33 @@ +// Copyright 2016 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. + +#include "content/common/android/media_metadata_android.h" + +#include "base/android/jni_string.h" +#include "content/public/common/media_metadata.h" +#include "jni/MediaMetadata_jni.h" + +namespace content { + +// static +base::android::ScopedJavaLocalRef<jobject> +MediaMetadataAndroid::CreateJavaObject( + JNIEnv* env, const MediaMetadata& metadata) { + ScopedJavaLocalRef<jstring> j_title( + base::android::ConvertUTF16ToJavaString(env, metadata.title)); + ScopedJavaLocalRef<jstring> j_artist( + base::android::ConvertUTF16ToJavaString(env, metadata.artist)); + ScopedJavaLocalRef<jstring> j_album( + base::android::ConvertUTF16ToJavaString(env, metadata.album)); + + return Java_MediaMetadata_create( + env, j_title.obj(), j_artist.obj(), j_album.obj()); +} + +// static +bool MediaMetadataAndroid::Register(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace content diff --git a/content/common/android/media_metadata_android.h b/content/common/android/media_metadata_android.h new file mode 100644 index 0000000000000..e580e53af0fca --- /dev/null +++ b/content/common/android/media_metadata_android.h @@ -0,0 +1,29 @@ +// Copyright 2016 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. + +#ifndef CONTENT_COMMON_ANDROID_MEDIA_METADATA_ANDROID_H_ +#define CONTENT_COMMON_ANDROID_MEDIA_METADATA_ANDROID_H_ + +#include <jni.h> + +#include "base/android/scoped_java_ref.h" + +namespace content { + +struct MediaMetadata; + +class MediaMetadataAndroid { + public: + static base::android::ScopedJavaLocalRef<jobject> CreateJavaObject( + JNIEnv* env, const MediaMetadata& metadata); + + static bool Register(JNIEnv* env); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(MediaMetadataAndroid); +}; + +} // namespace content + +#endif // CONTENT_COMMON_ANDROID_MEDIA_METADATA_ANDROID_H_ diff --git a/content/content_common.gypi b/content/content_common.gypi index 94fc7a47ce975..75fbe5f53d3db 100644 --- a/content/content_common.gypi +++ b/content/content_common.gypi @@ -234,6 +234,8 @@ 'common/android/gin_java_bridge_value.h', 'common/android/hash_set.cc', 'common/android/hash_set.h', + 'common/android/media_metadata_android.cc', + 'common/android/media_metadata_android.h', 'common/android/sync_compositor_messages.cc', 'common/android/sync_compositor_messages.h', 'common/appcache_interfaces.cc', diff --git a/content/content_jni.gypi b/content/content_jni.gypi index a34d63b5a330a..2f1ccf829ffe6 100644 --- a/content/content_jni.gypi +++ b/content/content_jni.gypi @@ -42,6 +42,7 @@ 'public/android/java/src/org/chromium/content/browser/webcontents/WebContentsImpl.java', 'public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java', 'public/android/java/src/org/chromium/content_public/browser/LoadUrlParams.java', + 'public/android/java/src/org/chromium/content_public/common/MediaMetadata.java', ], 'variables': { 'jni_gen_package': 'content', diff --git a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java index 6a0ed1ffaf406..6919298023a23 100644 --- a/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java +++ b/content/public/android/java/src/org/chromium/content/browser/webcontents/WebContentsObserverProxy.java @@ -12,6 +12,7 @@ import org.chromium.base.annotations.CalledByNative; import org.chromium.base.annotations.JNINamespace; import org.chromium.base.annotations.MainDex; import org.chromium.content_public.browser.WebContentsObserver; +import org.chromium.content_public.common.MediaMetadata; /** * Serves as a compound observer proxy for dispatching WebContentsObserver callbacks, @@ -233,9 +234,11 @@ class WebContentsObserverProxy extends WebContentsObserver { @Override @CalledByNative - public void mediaSessionStateChanged(boolean isControllable, boolean isSuspended) { + public void mediaSessionStateChanged( + boolean isControllable, boolean isSuspended, MediaMetadata metadata) { for (mObserversIterator.rewind(); mObserversIterator.hasNext();) { - mObserversIterator.next().mediaSessionStateChanged(isControllable, isSuspended); + mObserversIterator.next().mediaSessionStateChanged( + isControllable, isSuspended, metadata); } } diff --git a/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java b/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java index 158ddf99f76ec..85c8819efb1b6 100644 --- a/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java +++ b/content/public/android/java/src/org/chromium/content_public/browser/WebContentsObserver.java @@ -4,6 +4,8 @@ package org.chromium.content_public.browser; +import org.chromium.content_public.common.MediaMetadata; + import java.lang.ref.WeakReference; /** @@ -163,9 +165,11 @@ public abstract class WebContentsObserver { /** * Called when the media session state changed. * @param isControllable if the session can be resumed or suspended. - * @param isSuspended if the session currently suspended or not + * @param isSuspended if the session currently suspended or not. + * @param metadata of the media session. */ - public void mediaSessionStateChanged(boolean isControllable, boolean isSuspended) {} + public void mediaSessionStateChanged( + boolean isControllable, boolean isSuspended, MediaMetadata metadata) {} /** * Stop observing the web contents and clean up associated references. diff --git a/content/public/android/java/src/org/chromium/content_public/common/MediaMetadata.java b/content/public/android/java/src/org/chromium/content_public/common/MediaMetadata.java new file mode 100644 index 0000000000000..fca2464e27e64 --- /dev/null +++ b/content/public/android/java/src/org/chromium/content_public/common/MediaMetadata.java @@ -0,0 +1,111 @@ +// Copyright 2016 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.content_public.common; + +import android.support.annotation.NonNull; +import android.text.TextUtils; + +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; + +/** + * The MediaMetadata class carries information related to a media session. It is + * the Java counterpart of content::MediaMetadata. + */ +@JNINamespace("content") +public class MediaMetadata { + + @NonNull + private String mTitle; + + @NonNull + private String mArtist; + + @NonNull + private String mAlbum; + + /** + * Returns the title associated with the media session. + */ + public String getTitle() { + return mTitle; + } + + /** + * Returns the artist name associated with the media session. + */ + public String getArtist() { + return mArtist; + } + + /** + * Returns the album name associated with the media session. + */ + public String getAlbum() { + return mAlbum; + } + + /** + * Sets the title associated with the media session. + * @param title The title to use for the media session. + */ + public void setTitle(String title) { + mTitle = title; + } + + /** + * Sets the arstist name associated with the media session. + * @param arstist The artist name to use for the media session. + */ + public void setArtist(String artist) { + mArtist = artist; + } + + /** + * Sets the album name associated with the media session. + * @param album The album name to use for the media session. + */ + public void setAlbum(String album) { + mAlbum = album; + } + + /** + * Creates a new MediaMetadata from the C++ code. This is exactly like the + * constructor below apart that it can be called by native code. + */ + @CalledByNative + private static MediaMetadata create(String title, String artist, String album) { + return new MediaMetadata(title == null ? "" : title, artist == null ? "" : artist, + album == null ? "" : album); + } + + /** + * Creates a new MediaMetadata. + */ + public MediaMetadata(@NonNull String title, @NonNull String artist, @NonNull String album) { + mTitle = title; + mArtist = artist; + mAlbum = album; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (!(obj instanceof MediaMetadata)) return false; + + MediaMetadata other = (MediaMetadata) obj; + return TextUtils.equals(mTitle, other.mTitle) + && TextUtils.equals(mArtist, other.mArtist) + && TextUtils.equals(mAlbum, other.mAlbum); + } + + @Override + public int hashCode() { + int result = mTitle.hashCode(); + result = 31 * result + mArtist.hashCode(); + result = 31 * result + mAlbum.hashCode(); + return result; + } +} diff --git a/content/public/browser/web_contents_observer.h b/content/public/browser/web_contents_observer.h index 0d7148c37782f..226f309d05121 100644 --- a/content/public/browser/web_contents_observer.h +++ b/content/public/browser/web_contents_observer.h @@ -13,6 +13,7 @@ #include "content/common/content_export.h" #include "content/public/browser/navigation_controller.h" #include "content/public/common/frame_navigate_params.h" +#include "content/public/common/media_metadata.h" #include "content/public/common/security_style.h" #include "ipc/ipc_listener.h" #include "ipc/ipc_sender.h" @@ -445,7 +446,8 @@ class CONTENT_EXPORT WebContentsObserver : public IPC::Listener, // Invoked when media session has changed its state. virtual void MediaSessionStateChanged(bool is_controllable, - bool is_suspended) {} + bool is_suspended, + const MediaMetadata& metadata) {} // Invoked when the renderer process changes the page scale factor. virtual void OnPageScaleFactorChanged(float page_scale_factor) {}