From ec877287fb061d641c55d6413915c68a8b8c9215 Mon Sep 17 00:00:00 2001 From: Jaewan Kim Date: Mon, 22 Jan 2018 20:52:21 +0900 Subject: [PATCH 1/2] MediaSession2: Initial commit of MediaBrowser2 Test: Run all MediaComponents tests once Change-Id: I284806ff97da4ba53d7e4de9a39864f70f69a942 --- media/java/android/media/IMediaSession2.aidl | 5 ++ media/java/android/media/MediaBrowser2.java | 69 +++++++++++++++++++ .../java/android/media/MediaController2.java | 8 ++- .../media/update/MediaBrowser2Provider.java | 26 +++++++ .../android/media/update/StaticProvider.java | 8 ++- 5 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 media/java/android/media/MediaBrowser2.java create mode 100644 media/java/android/media/update/MediaBrowser2Provider.java diff --git a/media/java/android/media/IMediaSession2.aidl b/media/java/android/media/IMediaSession2.aidl index bc8121ef80433..0c273f983a996 100644 --- a/media/java/android/media/IMediaSession2.aidl +++ b/media/java/android/media/IMediaSession2.aidl @@ -46,6 +46,11 @@ interface IMediaSession2 { PlaybackState getPlaybackState(); + ////////////////////////////////////////////////////////////////////////////////////////////// + // Get browser service specific + ////////////////////////////////////////////////////////////////////////////////////////////// + oneway void getBrowserRoot(IMediaSession2Callback callback, in Bundle rootHints); + ////////////////////////////////////////////////////////////////////////////////////////////// // Callbacks -- remove them ////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/media/java/android/media/MediaBrowser2.java b/media/java/android/media/MediaBrowser2.java new file mode 100644 index 0000000000000..fa00902077491 --- /dev/null +++ b/media/java/android/media/MediaBrowser2.java @@ -0,0 +1,69 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.annotation.Nullable; +import android.content.Context; +import android.media.update.ApiLoader; +import android.media.update.MediaBrowser2Provider; +import android.os.Bundle; + +import java.util.concurrent.Executor; + +/** + * Browses media content offered by a {@link MediaLibraryService2}. + * @hide + */ +public class MediaBrowser2 extends MediaController2 { + // Equals to the ((MediaBrowser2Provider) getProvider()) + private final MediaBrowser2Provider mProvider; + + /** + * Callback to listen events from {@link MediaLibraryService2}. + */ + public abstract static class BrowserCallback extends MediaController2.ControllerCallback { + /** + * Called with the result of {@link #getBrowserRoot(Bundle)}. + *

+ * {@code rootMediaId} and {@code rootExtra} can be {@code null} if the browser root isn't + * available. + * + * @param rootHints rootHints that you previously requested. + * @param rootMediaId media id of the browser root. Can be {@code null} + * @param rootExtra extra of the browser root. Can be {@code null} + */ + public abstract void onGetRootResult(Bundle rootHints, @Nullable String rootMediaId, + @Nullable Bundle rootExtra); + } + + public MediaBrowser2(Context context, SessionToken token, BrowserCallback callback, + Executor executor) { + super(context, token, callback, executor); + mProvider = (MediaBrowser2Provider) getProvider(); + } + + @Override + MediaBrowser2Provider createProvider(Context context, SessionToken token, + ControllerCallback callback, Executor executor) { + return ApiLoader.getProvider(context) + .createMediaBrowser2(this, context, token, (BrowserCallback) callback, executor); + } + + public void getBrowserRoot(Bundle rootHints) { + mProvider.getBrowserRoot_impl(rootHints); + } +} diff --git a/media/java/android/media/MediaController2.java b/media/java/android/media/MediaController2.java index 550eee2fd47c0..911245043e0b7 100644 --- a/media/java/android/media/MediaController2.java +++ b/media/java/android/media/MediaController2.java @@ -104,7 +104,13 @@ public class MediaController2 extends MediaPlayerBase { // session whose session binder is only valid while it's active. // prevent a controller from reusable after the // session is released and recreated. - mProvider = ApiLoader.getProvider(context) + mProvider = createProvider(context, token, callback, executor); + } + + MediaController2Provider createProvider(@NonNull Context context, + @NonNull SessionToken token, @NonNull ControllerCallback callback, + @NonNull Executor executor) { + return ApiLoader.getProvider(context) .createMediaController2(this, context, token, callback, executor); } diff --git a/media/java/android/media/update/MediaBrowser2Provider.java b/media/java/android/media/update/MediaBrowser2Provider.java new file mode 100644 index 0000000000000..355dbc9f4aefc --- /dev/null +++ b/media/java/android/media/update/MediaBrowser2Provider.java @@ -0,0 +1,26 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.update; + +import android.os.Bundle; + +/** + * @hide + */ +public interface MediaBrowser2Provider extends MediaController2Provider { + void getBrowserRoot_impl(Bundle rootHints); +} diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 91c9c660876ba..2d15444a3a70f 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -19,7 +19,10 @@ package android.media.update; import android.annotation.Nullable; import android.content.Context; import android.media.IMediaSession2Callback; +import android.media.MediaBrowser2; +import android.media.MediaBrowser2.BrowserCallback; import android.media.MediaController2; +import android.media.MediaController2.ControllerCallback; import android.media.MediaPlayerBase; import android.media.MediaSession2; import android.media.MediaSessionService2; @@ -53,7 +56,10 @@ public interface StaticProvider { String packageName, IMediaSession2Callback callback); MediaController2Provider createMediaController2( MediaController2 instance, Context context, SessionToken token, - MediaController2.ControllerCallback callback, Executor executor); + ControllerCallback callback, Executor executor); + MediaBrowser2Provider createMediaBrowser2( + MediaBrowser2 instance, Context context, SessionToken token, + BrowserCallback callback, Executor executor); MediaSessionService2Provider createMediaSessionService2( MediaSessionService2 instance); } From bcecf31a977b4a3cf77f1ab76264b817d5a29fe2 Mon Sep 17 00:00:00 2001 From: Jaewan Kim Date: Tue, 23 Jan 2018 19:30:42 +0900 Subject: [PATCH 2/2] MediaSession2: Initial commit of MediaLibraryService2 MediaLibraryService2 is the new name for the MediaBrowserService Test: Run all MediaComponents tests once Change-Id: I7a1ae20ff59aa4714cff08e43cdabb5b8c557b98 --- media/java/android/media/IMediaSession2.aidl | 2 +- .../android/media/IMediaSession2Callback.aidl | 5 + .../android/media/MediaLibraryService2.java | 144 ++++++++++++++++++ media/java/android/media/MediaSession2.java | 77 +++++----- .../android/media/MediaSessionService2.java | 43 ++---- media/java/android/media/SessionToken.java | 4 +- .../update/MediaLibraryService2Provider.java | 24 +++ .../android/media/update/StaticProvider.java | 3 + .../server/media/MediaSessionService.java | 21 ++- 9 files changed, 252 insertions(+), 71 deletions(-) create mode 100644 media/java/android/media/MediaLibraryService2.java create mode 100644 media/java/android/media/update/MediaLibraryService2Provider.java diff --git a/media/java/android/media/IMediaSession2.aidl b/media/java/android/media/IMediaSession2.aidl index 0c273f983a996..078b611bae4cf 100644 --- a/media/java/android/media/IMediaSession2.aidl +++ b/media/java/android/media/IMediaSession2.aidl @@ -47,7 +47,7 @@ interface IMediaSession2 { PlaybackState getPlaybackState(); ////////////////////////////////////////////////////////////////////////////////////////////// - // Get browser service specific + // Get library service specific ////////////////////////////////////////////////////////////////////////////////////////////// oneway void getBrowserRoot(IMediaSession2Callback callback, in Bundle rootHints); diff --git a/media/java/android/media/IMediaSession2Callback.aidl b/media/java/android/media/IMediaSession2Callback.aidl index 0058e79eab157..a63b6bf4fcab4 100644 --- a/media/java/android/media/IMediaSession2Callback.aidl +++ b/media/java/android/media/IMediaSession2Callback.aidl @@ -44,4 +44,9 @@ oneway interface IMediaSession2Callback { // Follow-up TODO: Add similar functions to the session. // TODO(jaewan): Is term 'accepted/rejected' correct? For permission, 'grant' is used. void onConnectionChanged(IMediaSession2 sessionBinder, in Bundle commandGroup); + + ////////////////////////////////////////////////////////////////////////////////////////////// + // Browser sepcific + ////////////////////////////////////////////////////////////////////////////////////////////// + void onGetRootResult(in Bundle rootHints, String rootMediaId, in Bundle rootExtra); } diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java new file mode 100644 index 0000000000000..bbc940799926e --- /dev/null +++ b/media/java/android/media/MediaLibraryService2.java @@ -0,0 +1,144 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.media.MediaSession2.BuilderBase; +import android.media.MediaSession2.ControllerInfo; +import android.media.update.ApiLoader; +import android.media.update.MediaSessionService2Provider; +import android.os.Bundle; +import android.service.media.MediaBrowserService.BrowserRoot; + +/** + * Base class for media library services. + *

+ * Media library services enable applications to browse media content provided by an application + * and ask the application to start playing it. They may also be used to control content that + * is already playing by way of a {@link MediaSession2}. + *

+ * To extend this class, adding followings directly to your {@code AndroidManifest.xml}. + *

+ * <service android:name="component_name_of_your_implementation" >
+ *   <intent-filter>
+ *     <action android:name="android.media.MediaLibraryService2" />
+ *   </intent-filter>
+ * </service>
+ *

+ * A {@link MediaLibraryService2} is extension of {@link MediaSessionService2}. IDs shouldn't + * be shared between the {@link MediaSessionService2} and {@link MediaSession2}. By + * default, an empty string will be used for ID of the service. If you want to specify an ID, + * declare metadata in the manifest as follows. + * @hide + */ +// TODO(jaewan): Unhide +public abstract class MediaLibraryService2 extends MediaSessionService2 { + /** + * This is the interface name that a service implementing a session service should say that it + * support -- that is, this is the action it uses for its intent filter. + */ + public static final String SERVICE_INTERFACE = "android.media.MediaLibraryService2"; + + /** + * Session for the media library service. + */ + public class MediaLibrarySession extends MediaSession2 { + MediaLibrarySession(Context context, MediaPlayerBase player, String id, + SessionCallback callback) { + super(context, player, id, callback); + } + // TODO(jaewan): Place public methods here. + } + + public static abstract class MediaLibrarySessionCallback extends MediaSession2.SessionCallback { + /** + * Called to get the root information for browsing by a particular client. + *

+ * The implementation should verify that the client package has permission + * to access browse media information before returning the root id; it + * should return null if the client is not allowed to access this + * information. + * + * @param controllerInfo information of the controller requesting access to browse media. + * @param rootHints An optional bundle of service-specific arguments to send + * to the media browser service when connecting and retrieving the + * root id for browsing, or null if none. The contents of this + * bundle may affect the information returned when browsing. + * @return The {@link BrowserRoot} for accessing this app's content or null. + * @see BrowserRoot#EXTRA_RECENT + * @see BrowserRoot#EXTRA_OFFLINE + * @see BrowserRoot#EXTRA_SUGGESTED + */ + public abstract @Nullable BrowserRoot onGetRoot( + @NonNull ControllerInfo controllerInfo, @Nullable Bundle rootHints); + } + + /** + * Builder for {@link MediaLibrarySession}. + */ + // TODO(jaewan): Move this to updatable. + public class MediaLibrarySessionBuilder + extends BuilderBase { + public MediaLibrarySessionBuilder( + @NonNull Context context, @NonNull MediaPlayerBase player, + @NonNull MediaLibrarySessionCallback callback) { + super(context, player); + setSessionCallback(callback); + } + + @Override + public MediaLibrarySessionBuilder setSessionCallback( + @NonNull MediaLibrarySessionCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("MediaLibrarySessionCallback cannot be null"); + } + return super.setSessionCallback(callback); + } + + @Override + public MediaLibrarySession build() throws IllegalStateException { + return new MediaLibrarySession(mContext, mPlayer, mId, mCallback); + } + } + + @Override + MediaSessionService2Provider createProvider() { + return ApiLoader.getProvider(this).createMediaLibraryService2(this); + } + + /** + * Called when another app requested to start this service. + *

+ * Library service will accept or reject the connection with the + * {@link MediaLibrarySessionCallback} in the created session. + *

+ * Service wouldn't run if {@code null} is returned or session's ID doesn't match with the + * expected ID that you've specified through the AndroidManifest.xml. + *

+ * This method will be called on the main thread. + * + * @param sessionId session id written in the AndroidManifest.xml. + * @return a new browser session + * @see MediaLibrarySessionBuilder + * @see #getSession() + * @throws RuntimeException if returned session is invalid + */ + @Override + public @NonNull abstract MediaLibrarySession onCreateSession(String sessionId); +} diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java index 807535bed967a..33a5807d13137 100644 --- a/media/java/android/media/MediaSession2.java +++ b/media/java/android/media/MediaSession2.java @@ -16,7 +16,6 @@ package android.media; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; @@ -29,12 +28,9 @@ import android.media.update.MediaSession2Provider.ControllerInfoProvider; import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; -import android.os.Process; import android.text.TextUtils; import android.util.ArraySet; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; @@ -55,20 +51,17 @@ import java.util.List; * instead. With it, your playback can be revived even after you've finished playback. See * {@link MediaSessionService2} for details. *

- * A session can be obtained by {@link #getInstance(Context, Handler)}. The owner of the session may - * pass its session token to other processes to allow them to create a {@link MediaController2} - * to interact with the session. + * A session can be obtained by {@link Builder}. The owner of the session may pass its session token + * to other processes to allow them to create a {@link MediaController2} to interact with the + * session. *

- * To receive transport control commands, an underlying media player must be set with - * {@link #setPlayer(MediaPlayerBase)}. Commands will be sent to the underlying player directly - * on the thread that had been specified by {@link #getInstance(Context, Handler)}. + * When a session receive transport control commands, the session sends the commands directly to + * the the underlying media player set by {@link Builder} or {@link #setPlayer(MediaPlayerBase)}. *

- * When an app is finished performing playback it must call - * {@link #setPlayer(MediaPlayerBase)} with {@code null} to clean up the session and notify any - * controllers. It's developers responsibility of cleaning the session and releasing resources. + * When an app is finished performing playback it must call {@link #close()} to clean up the session + * and notify any controllers. *

- * MediaSession2 objects should be used on the handler's thread that is initially given by - * {@link #getInstance(Context, Handler)}. + * {@link MediaSession2} objects should be used on the thread on the looper. * * @see MediaSessionService2 * @hide @@ -81,7 +74,7 @@ import java.util.List; // TODO(jaewan): Should we make APIs for MediaSessionService2 public? It's helpful for // developers that doesn't want to override from Browser, but user may not use this // correctly. -public final class MediaSession2 extends MediaPlayerBase { +public class MediaSession2 extends MediaPlayerBase { private final MediaSession2Provider mProvider; // Note: Do not define IntDef because subclass can add more command code on top of these. @@ -321,19 +314,16 @@ public final class MediaSession2 extends MediaPlayerBase { }; /** - * Builder for {@link MediaSession2}. - *

- * Any incoming event from the {@link MediaController2} will be handled on the thread - * that created session with the {@link Builder#build()}. + * Base builder class for MediaSession2 and its subclass. + * + * @hide */ - // TODO(jaewan): Move this to updatable - // TODO(jaewan): Add setRatingType() - // TODO(jaewan): Add setSessionActivity() - public final static class Builder { - private final Context mContext; - private final MediaPlayerBase mPlayer; - private String mId; - private SessionCallback mCallback; + static abstract class BuilderBase + , C extends SessionCallback> { + final Context mContext; + final MediaPlayerBase mPlayer; + String mId; + C mCallback; /** * Constructor. @@ -343,7 +333,8 @@ public final class MediaSession2 extends MediaPlayerBase { * @throws IllegalArgumentException if any parameter is null, or the player is a * {@link MediaSession2} or {@link MediaController2}. */ - public Builder(@NonNull Context context, @NonNull MediaPlayerBase player) { + // TODO(jaewan): Also need executor + public BuilderBase(@NonNull Context context, @NonNull MediaPlayerBase player) { if (context == null) { throw new IllegalArgumentException("context shouldn't be null"); } @@ -370,12 +361,12 @@ public final class MediaSession2 extends MediaPlayerBase { * @throws IllegalArgumentException if id is {@code null} * @return */ - public Builder setId(@NonNull String id) { + public T setId(@NonNull String id) { if (id == null) { throw new IllegalArgumentException("id shouldn't be null"); } mId = id; - return this; + return (T) this; } /** @@ -384,9 +375,9 @@ public final class MediaSession2 extends MediaPlayerBase { * @param callback session callback. * @return */ - public Builder setSessionCallback(@Nullable SessionCallback callback) { + public T setSessionCallback(@Nullable C callback) { mCallback = callback; - return this; + return (T) this; } /** @@ -396,6 +387,24 @@ public final class MediaSession2 extends MediaPlayerBase { * @throws IllegalStateException if the session with the same id is already exists for the * package. */ + public abstract MediaSession2 build() throws IllegalStateException; + } + + /** + * Builder for {@link MediaSession2}. + *

+ * Any incoming event from the {@link MediaController2} will be handled on the thread + * that created session with the {@link Builder#build()}. + */ + // TODO(jaewan): Move this to updatable + // TODO(jaewan): Add setRatingType() + // TODO(jaewan): Add setSessionActivity() + public static final class Builder extends BuilderBase { + public Builder(Context context, @NonNull MediaPlayerBase player) { + super(context, player); + } + + @Override public MediaSession2 build() throws IllegalStateException { if (mCallback == null) { mCallback = new SessionCallback(); @@ -492,7 +501,7 @@ public final class MediaSession2 extends MediaPlayerBase { * framework had to add heuristics to figure out if an app is * @hide */ - private MediaSession2(Context context, MediaPlayerBase player, String id, + MediaSession2(Context context, MediaPlayerBase player, String id, SessionCallback callback) { super(); mProvider = ApiLoader.getProvider(context) diff --git a/media/java/android/media/MediaSessionService2.java b/media/java/android/media/MediaSessionService2.java index f1f546732daea..1a12d6806c057 100644 --- a/media/java/android/media/MediaSessionService2.java +++ b/media/java/android/media/MediaSessionService2.java @@ -29,7 +29,7 @@ import android.media.update.MediaSessionService2Provider; import android.os.IBinder; /** - * Service version of the {@link MediaSession2}. + * Base class for media session services, which is the service version of the {@link MediaSession2}. *

* It's highly recommended for an app to use this instead of {@link MediaSession2} if it wants * to keep media playback in the background. @@ -43,11 +43,11 @@ import android.os.IBinder; * * For example, user's voice command can start playback of your app even when it's not running. *

- * To use this class, adding followings directly to your {@code AndroidManifest.xml}. + * To extend this class, adding followings directly to your {@code AndroidManifest.xml}. *

  * <service android:name="component_name_of_your_implementation" >
  *   <intent-filter>
- *     <action android:name="android.media.session.MediaSessionService2" />
+ *     <action android:name="android.media.MediaSessionService2" />
  *   </intent-filter>
  * </service>
*

@@ -58,7 +58,7 @@ import android.os.IBinder; *

  * <service android:name="component_name_of_your_implementation" >
  *   <intent-filter>
- *     <action android:name="android.media.session.MediaSessionService2" />
+ *     <action android:name="android.media.MediaSessionService2" />
  *   </intent-filter>
  *   <meta-data android:name="android.media.session"
  *       android:value="session_id"/>
@@ -120,8 +120,7 @@ public abstract class MediaSessionService2 extends Service {
      * This is the interface name that a service implementing a session service should say that it
      * support -- that is, this is the action it uses for its intent filter.
      */
-    public static final String SERVICE_INTERFACE =
-            "android.media.session.MediaSessionService2";
+    public static final String SERVICE_INTERFACE = "android.media.MediaSessionService2";
 
     /**
      * Name under which a MediaSessionService2 component publishes information about itself.
@@ -129,21 +128,13 @@ public abstract class MediaSessionService2 extends Service {
      */
     public static final String SERVICE_META_DATA = "android.media.session";
 
-    /**
-     * Default notification channel ID used by {@link #onUpdateNotification(PlaybackState)}.
-     *
-     */
-    public static final String DEFAULT_MEDIA_NOTIFICATION_CHANNEL_ID = "media_session_service";
-
-    /**
-     * Default notification channel ID used by {@link #onUpdateNotification(PlaybackState)}.
-     *
-     */
-    public static final int DEFAULT_MEDIA_NOTIFICATION_ID = 1001;
-
     public MediaSessionService2() {
         super();
-        mProvider = ApiLoader.getProvider(this).createMediaSessionService2(this);
+        mProvider = createProvider();
+    }
+
+    MediaSessionService2Provider createProvider() {
+        return ApiLoader.getProvider(this).createMediaSessionService2(this);
     }
 
     /**
@@ -168,32 +159,24 @@ public abstract class MediaSessionService2 extends Service {
      * Service wouldn't run if {@code null} is returned or session's ID doesn't match with the
      * expected ID that you've specified through the AndroidManifest.xml.
      * 

- * This method will be call on the main thread. + * This method will be called on the main thread. * * @param sessionId session id written in the AndroidManifest.xml. * @return a new session * @see MediaSession2.Builder * @see #getSession() */ - // TODO(jaewan): Replace this with onCreateSession(). Its sesssion callback will replace - // this abstract method. - // TODO(jaewan): Should we also include/documents notification listener access? - // TODO(jaewan): Is term accepted/rejected correct? For permission, granted is more common. - // TODO(jaewan): Return ConnectResult() that encapsulate supported action and player. public @NonNull abstract MediaSession2 onCreateSession(String sessionId); /** * Called when the playback state of this session is changed, and notification needs update. - *

- * Default media style notification will be shown if you don't override this or return - * {@code null}. Override this method to show your own notification UI. + * Override this method to show your own notification UI. *

* With the notification returned here, the service become foreground service when the playback * is started. It becomes background service after the playback is stopped. * * @param state playback state - * @return a {@link MediaNotification}. If it's {@code null}, default notification will be shown - * instead. + * @return a {@link MediaNotification}. If it's {@code null}, notification wouldn't be shown. */ // TODO(jaewan): Also add metadata public MediaNotification onUpdateNotification(PlaybackState state) { diff --git a/media/java/android/media/SessionToken.java b/media/java/android/media/SessionToken.java index b470dea4fee38..53fc24afea2ce 100644 --- a/media/java/android/media/SessionToken.java +++ b/media/java/android/media/SessionToken.java @@ -42,12 +42,13 @@ import java.lang.annotation.RetentionPolicy; // TODO(jaewan): Find better name for this (SessionToken or Session2Token) public final class SessionToken { @Retention(RetentionPolicy.SOURCE) - @IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE}) + @IntDef(value = {TYPE_SESSION, TYPE_SESSION_SERVICE, TYPE_LIBRARY_SERVICE}) public @interface TokenType { } public static final int TYPE_SESSION = 0; public static final int TYPE_SESSION_SERVICE = 1; + public static final int TYPE_LIBRARY_SERVICE = 2; private static final String KEY_TYPE = "android.media.token.type"; private static final String KEY_PACKAGE_NAME = "android.media.token.package_name"; @@ -73,6 +74,7 @@ public final class SessionToken { * @hide */ // TODO(jaewan): UID is also needed. + // TODO(jaewan): Unhide public SessionToken(@TokenType int type, @NonNull String packageName, @NonNull String id, @Nullable String serviceName, @Nullable IMediaSession2 sessionBinder) { // TODO(jaewan): Add sanity check. diff --git a/media/java/android/media/update/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java new file mode 100644 index 0000000000000..7e3444f4d9efd --- /dev/null +++ b/media/java/android/media/update/MediaLibraryService2Provider.java @@ -0,0 +1,24 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.update; + +/** + * @hide + */ +public interface MediaLibraryService2Provider extends MediaSessionService2Provider { + // Nothing new for now +} diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java index 2d15444a3a70f..fef2cbb432817 100644 --- a/media/java/android/media/update/StaticProvider.java +++ b/media/java/android/media/update/StaticProvider.java @@ -23,6 +23,7 @@ import android.media.MediaBrowser2; import android.media.MediaBrowser2.BrowserCallback; import android.media.MediaController2; import android.media.MediaController2.ControllerCallback; +import android.media.MediaLibraryService2; import android.media.MediaPlayerBase; import android.media.MediaSession2; import android.media.MediaSessionService2; @@ -62,4 +63,6 @@ public interface StaticProvider { BrowserCallback callback, Executor executor); MediaSessionService2Provider createMediaSessionService2( MediaSessionService2 instance); + MediaSessionService2Provider createMediaLibraryService2( + MediaLibraryService2 instance); } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index 6812778ee0953..c9c7d04e6e141 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -38,6 +38,7 @@ import android.media.AudioSystem; import android.media.IAudioService; import android.media.IMediaSession2; import android.media.IRemoteVolumeController; +import android.media.MediaLibraryService2; import android.media.MediaSessionService2; import android.media.SessionToken; import android.media.session.IActiveSessionsListener; @@ -445,9 +446,18 @@ public class MediaSessionService extends SystemService implements Monitor { } // TODO(jaewan): Query per users. - List services = getContext().getPackageManager().queryIntentServices( - new Intent(MediaSessionService2.SERVICE_INTERFACE), - PackageManager.GET_META_DATA); + List services = new ArrayList<>(); + // If multiple actions are declared for a service, browser gets higher priority. + List libraryServices = getContext().getPackageManager().queryIntentServices( + new Intent(MediaLibraryService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA); + if (libraryServices != null) { + services.addAll(libraryServices); + } + List sessionServices = getContext().getPackageManager().queryIntentServices( + new Intent(MediaSessionService2.SERVICE_INTERFACE), PackageManager.GET_META_DATA); + if (sessionServices != null) { + services.addAll(sessionServices); + } synchronized (mLock) { mSessions.clear(); if (services == null) { @@ -470,10 +480,11 @@ public class MediaSessionService extends SystemService implements Monitor { + " the same ID=" + id + ". Ignoring " + serviceInfo.packageName + "/" + serviceInfo.name); } else { + int type = (libraryServices.contains(services.get(i))) + ? SessionToken.TYPE_LIBRARY_SERVICE : SessionToken.TYPE_SESSION_SERVICE; MediaSessionService2Record record = new MediaSessionService2Record(getContext(), mSessionDestroyedListener, - SessionToken.TYPE_SESSION_SERVICE, - serviceInfo.packageName, serviceInfo.name, id); + type, serviceInfo.packageName, serviceInfo.name, id); mSessions.add(record); } }