MediaSession2: Public APIs for MediaBrowser2 and MediaLibraryService2

Test: Run MediaComponents test once
Change-Id: Icea9f7db7f13ed8354c7ce36b069f45ad7ddbfa8
diff --git a/media/java/android/media/MediaBrowser2.java b/media/java/android/media/MediaBrowser2.java
index fa00902..33377bc 100644
--- a/media/java/android/media/MediaBrowser2.java
+++ b/media/java/android/media/MediaBrowser2.java
@@ -16,12 +16,14 @@
 
 package android.media;
 
+import android.annotation.NonNull;
 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.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -35,7 +37,7 @@
     /**
      * Callback to listen events from {@link MediaLibraryService2}.
      */
-    public abstract static class BrowserCallback extends MediaController2.ControllerCallback {
+    public static class BrowserCallback extends MediaController2.ControllerCallback {
         /**
          * Called with the result of {@link #getBrowserRoot(Bundle)}.
          * <p>
@@ -46,8 +48,55 @@
          * @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 void onGetRootResult(Bundle rootHints, @Nullable String rootMediaId,
+                @Nullable Bundle rootExtra) { }
+
+        /**
+         * Called when the item has been returned by the library service for the previous
+         * {@link MediaBrowser2#getItem} call.
+         * <p>
+         * Result can be null if there had been error.
+         *
+         * @param mediaId media id
+         * @param result result. Can be {@code null}
+         */
+        public void onItemLoaded(@NonNull String mediaId, @Nullable MediaItem2 result) { }
+
+        /**
+         * Called when the list of items has been returned by the library service for the previous
+         * {@link MediaBrowser2#getChildren(String, int, int, Bundle)}.
+         *
+         * @param parentId parent id
+         * @param page page number that you've specified
+         * @param pageSize page size that you've specified
+         * @param options optional bundle that you've specified
+         * @param result result. Can be {@code null}
+         */
+        public void onChildrenLoaded(@NonNull String parentId, int page, int pageSize,
+                @Nullable Bundle options, @Nullable List<MediaItem2> result) { }
+
+        /**
+         * Called when there's change in the parent's children.
+         *
+         * @param parentId parent id that you've specified with subscribe
+         * @param options optional bundle that you've specified with subscribe
+         */
+        public void onChildrenChanged(@NonNull String parentId, @Nullable Bundle options) { }
+
+        /**
+         * Called when the search result has been returned by the library service for the previous
+         * {@link MediaBrowser2#search(String, int, int, Bundle)}.
+         * <p>
+         * Result can be null if there had been error.
+         *
+         * @param query query string that you've specified
+         * @param page page number that you've specified
+         * @param pageSize page size that you've specified
+         * @param options optional bundle that you've specified
+         * @param result result. Can be {@code null}
+         */
+        public void onSearchResult(@NonNull String query, int page, int pageSize,
+                @Nullable Bundle options, @Nullable List<MediaItem2> result) { }
     }
 
     public MediaBrowser2(Context context, SessionToken token, BrowserCallback callback,
@@ -66,4 +115,62 @@
     public void getBrowserRoot(Bundle rootHints) {
         mProvider.getBrowserRoot_impl(rootHints);
     }
+
+    /**
+     * Subscribe to a parent id for the change in its children. When there's a change,
+     * {@link BrowserCallback#onChildrenChanged(String, Bundle)} will be called with the bundle
+     * that you've specified. You should call {@link #getChildren(String, int, int, Bundle)} to get
+     * the actual contents for the parent.
+     *
+     * @param parentId parent id
+     * @param options optional bundle
+     */
+    public void subscribe(String parentId, @Nullable Bundle options) {
+        mProvider.subscribe_impl(parentId, options);
+    }
+
+    /**
+     * Unsubscribe for changes to the children of the parent, which was previously subscribed with
+     * {@link #subscribe(String, Bundle)}.
+     *
+     * @param parentId parent id
+     * @param options optional bundle
+     */
+    public void unsubscribe(String parentId, @Nullable Bundle options) {
+        mProvider.unsubscribe_impl(parentId, options);
+    }
+
+    /**
+     * Get the media item with the given media id. Result would be sent back asynchronously with the
+     * {@link BrowserCallback#onItemLoaded(String, MediaItem2)}.
+     *
+     * @param mediaId media id
+     */
+    public void getItem(String mediaId) {
+        mProvider.getItem_impl(mediaId);
+    }
+
+    /**
+     * Get list of children under the parent. Result would be sent back asynchronously with the
+     * {@link BrowserCallback#onChildrenLoaded(String, int, int, Bundle, List)}.
+     *
+     * @param parentId
+     * @param page
+     * @param pageSize
+     * @param options
+     */
+    public void getChildren(String parentId, int page, int pageSize, @Nullable Bundle options) {
+        mProvider.getChildren_impl(parentId, page, pageSize, options);
+    }
+
+    /**
+     *
+     * @param query search query deliminated by string
+     * @param page page number to get search result. Starts from {@code 1}
+     * @param pageSize page size. Should be greater or equal to {@code 1}
+     * @param extras extra bundle
+     */
+    public void search(String query, int page, int pageSize, Bundle extras) {
+        mProvider.search_impl(query, page, pageSize, extras);
+    }
 }
diff --git a/media/java/android/media/MediaLibraryService2.java b/media/java/android/media/MediaLibraryService2.java
index 44b1917..b98936e 100644
--- a/media/java/android/media/MediaLibraryService2.java
+++ b/media/java/android/media/MediaLibraryService2.java
@@ -23,10 +23,14 @@
 import android.media.MediaSession2.BuilderBase;
 import android.media.MediaSession2.ControllerInfo;
 import android.media.update.ApiLoader;
+import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
+import android.media.update.MediaSession2Provider;
 import android.media.update.MediaSessionService2Provider;
 import android.os.Bundle;
 import android.service.media.MediaBrowserService.BrowserRoot;
 
+import java.util.List;
+
 /**
  * Base class for media library services.
  * <p>
@@ -60,16 +64,50 @@
      * Session for the media library service.
      */
     public class MediaLibrarySession extends MediaSession2 {
+        private final MediaLibrarySessionProvider mProvider;
 
         MediaLibrarySession(Context context, MediaPlayerBase player, String id,
                 SessionCallback callback, VolumeProvider volumeProvider,
                 int ratingType, PendingIntent sessionActivity) {
             super(context, player, id, callback, volumeProvider, ratingType, sessionActivity);
+            mProvider = (MediaLibrarySessionProvider) getProvider();
         }
-        // TODO(jaewan): Place public methods here.
+
+        @Override
+        MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id,
+                SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+                PendingIntent sessionActivity) {
+            return ApiLoader.getProvider(context)
+                    .createMediaLibraryService2MediaLibrarySession(this, context, player, id,
+                            (MediaLibrarySessionCallback) callback, volumeProvider, ratingType,
+                            sessionActivity);
+        }
+
+        /**
+         * Notify subscribed controller about change in a parent's children.
+         *
+         * @param controller controller to notify
+         * @param parentId
+         * @param options
+         */
+        public void notifyChildrenChanged(@NonNull ControllerInfo controller,
+                @NonNull String parentId, @NonNull Bundle options) {
+            mProvider.notifyChildrenChanged_impl(controller, parentId, options);
+        }
+
+        /**
+         * Notify subscribed controller about change in a parent's children.
+         *
+         * @param parentId parent id
+         * @param options optional bundle
+         */
+        // This is for the backward compatibility.
+        public void notifyChildrenChanged(@NonNull String parentId, @Nullable Bundle options) {
+            mProvider.notifyChildrenChanged_impl(parentId, options);
+        }
     }
 
-    public static abstract class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
+    public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
         /**
          * Called to get the root information for browsing by a particular client.
          * <p>
@@ -88,8 +126,76 @@
          * @see BrowserRoot#EXTRA_OFFLINE
          * @see BrowserRoot#EXTRA_SUGGESTED
          */
-        public abstract @Nullable BrowserRoot onGetRoot(
-                @NonNull ControllerInfo controllerInfo, @Nullable Bundle rootHints);
+        public @Nullable BrowserRoot onGetRoot(@NonNull ControllerInfo controllerInfo,
+                @Nullable Bundle rootHints) {
+            return null;
+        }
+
+        /**
+         * Called to get the search result. Return search result here for the browser.
+         * <p>
+         * Return an empty list for no search result, and return {@code null} for the error.
+         *
+         * @param query The search query sent from the media browser. It contains keywords separated
+         *            by space.
+         * @param extras The bundle of service-specific arguments sent from the media browser.
+         * @return search result. {@code null} for error.
+         */
+        public @Nullable List<MediaItem2> onSearch(@NonNull ControllerInfo controllerInfo,
+                @NonNull String query, @Nullable Bundle extras) {
+            return null;
+        }
+
+        /**
+         * Called to get the search result . Return result here for the browser.
+         * <p>
+         * Return an empty list for no search result, and return {@code null} for the error.
+         *
+         * @param itemId item id to get media item.
+         * @return media item2. {@code null} for error.
+         */
+        public @Nullable MediaItem2 onLoadItem(@NonNull ControllerInfo controllerInfo,
+                @NonNull String itemId) {
+            return null;
+        }
+
+        /**
+         * Called to get the search result. Return search result here for the browser.
+         * <p>
+         * Return an empty list for no search result, and return {@code null} for the error.
+         *
+         * @param parentId parent id to get children
+         * @param page number of page
+         * @param pageSize size of the page
+         * @param options
+         * @return list of children. Can be {@code null}.
+         */
+        public @Nullable List<MediaItem2> onLoadChildren(@NonNull ControllerInfo controller,
+                @NonNull String parentId, int page, int pageSize, @Nullable Bundle options) {
+            return null;
+        }
+
+        /**
+         * Called when a controller subscribes to the parent.
+         *
+         * @param controller controller
+         * @param parentId parent id
+         * @param options optional bundle
+         */
+        public void onSubscribed(@NonNull ControllerInfo controller,
+                String parentId, @Nullable Bundle options) {
+        }
+
+        /**
+         * Called when a controller unsubscribes to the parent.
+         *
+         * @param controller controller
+         * @param parentId parent id
+         * @param options optional bundle
+         */
+        public void onUnsubscribed(@NonNull ControllerInfo controller,
+                String parentId, @Nullable Bundle options) {
+        }
     }
 
     /**
@@ -145,4 +251,95 @@
      */
     @Override
     public @NonNull abstract MediaLibrarySession onCreateSession(String sessionId);
+
+    /**
+     * Contains information that the browser service needs to send to the client
+     * when first connected.
+     */
+    public static final class BrowserRoot {
+        /**
+         * The lookup key for a boolean that indicates whether the browser service should return a
+         * browser root for recently played media items.
+         *
+         * <p>When creating a media browser for a given media browser service, this key can be
+         * supplied as a root hint for retrieving media items that are recently played.
+         * If the media browser service can provide such media items, the implementation must return
+         * the key in the root hint when
+         * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
+         *
+         * <p>The root hint may contain multiple keys.
+         *
+         * @see #EXTRA_OFFLINE
+         * @see #EXTRA_SUGGESTED
+         */
+        public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+
+        /**
+         * The lookup key for a boolean that indicates whether the browser service should return a
+         * browser root for offline media items.
+         *
+         * <p>When creating a media browser for a given media browser service, this key can be
+         * supplied as a root hint for retrieving media items that are can be played without an
+         * internet connection.
+         * If the media browser service can provide such media items, the implementation must return
+         * the key in the root hint when
+         * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
+         *
+         * <p>The root hint may contain multiple keys.
+         *
+         * @see #EXTRA_RECENT
+         * @see #EXTRA_SUGGESTED
+         */
+        public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+
+        /**
+         * The lookup key for a boolean that indicates whether the browser service should return a
+         * browser root for suggested media items.
+         *
+         * <p>When creating a media browser for a given media browser service, this key can be
+         * supplied as a root hint for retrieving the media items suggested by the media browser
+         * service. The list of media items passed in {@link android.media.browse.MediaBrowser.SubscriptionCallback#onChildrenLoaded(String, List)}
+         * is considered ordered by relevance, first being the top suggestion.
+         * If the media browser service can provide such media items, the implementation must return
+         * the key in the root hint when
+         * {@link MediaLibrarySessionCallback#onGetRoot(ControllerInfo, Bundle)} is called back.
+         *
+         * <p>The root hint may contain multiple keys.
+         *
+         * @see #EXTRA_RECENT
+         * @see #EXTRA_OFFLINE
+         */
+        public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+
+        final private String mRootId;
+        final private Bundle mExtras;
+
+        /**
+         * Constructs a browser root.
+         * @param rootId The root id for browsing.
+         * @param extras Any extras about the browser service.
+         */
+        public BrowserRoot(@NonNull String rootId, @Nullable Bundle extras) {
+            if (rootId == null) {
+                throw new IllegalArgumentException("The root id in BrowserRoot cannot be null. " +
+                        "Use null for BrowserRoot instead.");
+            }
+            mRootId = rootId;
+            mExtras = extras;
+        }
+
+        /**
+         * Gets the root id for browsing.
+         */
+        public String getRootId() {
+            return mRootId;
+        }
+
+        /**
+         * Gets any extras about the browser service.
+         */
+        public Bundle getExtras() {
+            return mExtras;
+        }
+    }
 }
diff --git a/media/java/android/media/MediaSession2.java b/media/java/android/media/MediaSession2.java
index eeac75a..6fdf7a8 100644
--- a/media/java/android/media/MediaSession2.java
+++ b/media/java/android/media/MediaSession2.java
@@ -889,7 +889,14 @@
             SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
             PendingIntent sessionActivity) {
         super();
-        mProvider = ApiLoader.getProvider(context)
+        mProvider = createProvider(context, player, id, callback,
+                volumeProvider, ratingType, sessionActivity);
+    }
+
+    MediaSession2Provider createProvider(Context context, MediaPlayerBase player, String id,
+            SessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+            PendingIntent sessionActivity) {
+        return ApiLoader.getProvider(context)
                 .createMediaSession2(this, context, player, id, callback,
                         volumeProvider, ratingType, sessionActivity);
     }
diff --git a/media/java/android/media/update/MediaBrowser2Provider.java b/media/java/android/media/update/MediaBrowser2Provider.java
index 355dbc9..e48711d 100644
--- a/media/java/android/media/update/MediaBrowser2Provider.java
+++ b/media/java/android/media/update/MediaBrowser2Provider.java
@@ -23,4 +23,11 @@
  */
 public interface MediaBrowser2Provider extends MediaController2Provider {
     void getBrowserRoot_impl(Bundle rootHints);
+
+    void subscribe_impl(String parentId, Bundle options);
+    void unsubscribe_impl(String parentId, Bundle options);
+
+    void getItem_impl(String mediaId);
+    void getChildren_impl(String parentId, int page, int pageSize, Bundle options);
+    void search_impl(String query, int page, int pageSize, Bundle extras);
 }
diff --git a/media/java/android/media/update/MediaLibraryService2Provider.java b/media/java/android/media/update/MediaLibraryService2Provider.java
index 7e3444f..dac5784 100644
--- a/media/java/android/media/update/MediaLibraryService2Provider.java
+++ b/media/java/android/media/update/MediaLibraryService2Provider.java
@@ -16,9 +16,15 @@
 
 package android.media.update;
 
-/**
+import android.media.MediaSession2.ControllerInfo;
+import android.os.Bundle; /**
  * @hide
  */
 public interface MediaLibraryService2Provider extends MediaSessionService2Provider {
     // Nothing new for now
+
+    interface MediaLibrarySessionProvider extends MediaSession2Provider {
+        void notifyChildrenChanged_impl(ControllerInfo controller, String parentId, Bundle options);
+        void notifyChildrenChanged_impl(String parentId, Bundle options);
+    }
 }
diff --git a/media/java/android/media/update/StaticProvider.java b/media/java/android/media/update/StaticProvider.java
index 80e2402..64968d6 100644
--- a/media/java/android/media/update/StaticProvider.java
+++ b/media/java/android/media/update/StaticProvider.java
@@ -25,12 +25,16 @@
 import android.media.MediaController2;
 import android.media.MediaController2.ControllerCallback;
 import android.media.MediaLibraryService2;
+import android.media.MediaLibraryService2.MediaLibrarySession;
+import android.media.MediaLibraryService2.MediaLibrarySessionCallback;
 import android.media.MediaPlayerBase;
 import android.media.MediaSession2;
 import android.media.MediaSession2.SessionCallback;
 import android.media.MediaSessionService2;
 import android.media.SessionToken;
 import android.media.VolumeProvider;
+import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
+import android.media.update.MediaSession2Provider.ControllerInfoProvider;
 import android.util.AttributeSet;
 import android.widget.MediaControlView2;
 import android.widget.VideoView2;
@@ -57,7 +61,7 @@
             MediaPlayerBase player, String id, SessionCallback callback,
             VolumeProvider volumeProvider, int ratingType,
             PendingIntent sessionActivity);
-    MediaSession2Provider.ControllerInfoProvider createMediaSession2ControllerInfoProvider(
+    ControllerInfoProvider createMediaSession2ControllerInfoProvider(
             MediaSession2.ControllerInfo instance, Context context, int uid, int pid,
             String packageName, IMediaSession2Callback callback);
     MediaController2Provider createMediaController2(
@@ -70,4 +74,8 @@
             MediaSessionService2 instance);
     MediaSessionService2Provider createMediaLibraryService2(
             MediaLibraryService2 instance);
+    MediaLibrarySessionProvider createMediaLibraryService2MediaLibrarySession(
+            MediaLibrarySession instance, Context context, MediaPlayerBase player, String id,
+            MediaLibrarySessionCallback callback, VolumeProvider volumeProvider, int ratingType,
+            PendingIntent sessionActivity);
 }