|  | /* | 
|  | * 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 androidx.media; | 
|  |  | 
|  | import static android.support.v4.media.MediaBrowserCompat.EXTRA_PAGE; | 
|  | import static android.support.v4.media.MediaBrowserCompat.EXTRA_PAGE_SIZE; | 
|  |  | 
|  | import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; | 
|  |  | 
|  | import android.app.PendingIntent; | 
|  | import android.content.Intent; | 
|  | import android.os.Bundle; | 
|  | import android.os.IBinder; | 
|  | import android.support.v4.media.MediaBrowserCompat.MediaItem; | 
|  |  | 
|  | import androidx.annotation.NonNull; | 
|  | import androidx.annotation.Nullable; | 
|  | import androidx.annotation.RestrictTo; | 
|  | import androidx.media.MediaLibraryService2.MediaLibrarySession.Builder; | 
|  | import androidx.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback; | 
|  | import androidx.media.MediaSession2.ControllerInfo; | 
|  |  | 
|  | import java.util.ArrayList; | 
|  | import java.util.List; | 
|  | import java.util.concurrent.CountDownLatch; | 
|  | import java.util.concurrent.Executor; | 
|  |  | 
|  | /** | 
|  | * @hide | 
|  | * Base class for media library services. | 
|  | * <p> | 
|  | * 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}. | 
|  | * <p> | 
|  | * When extending this class, also add the following to your {@code AndroidManifest.xml}. | 
|  | * <pre> | 
|  | * <service android:name="component_name_of_your_implementation" > | 
|  | *   <intent-filter> | 
|  | *     <action android:name="android.media.MediaLibraryService2" /> | 
|  | *   </intent-filter> | 
|  | * </service></pre> | 
|  | * <p> | 
|  | * The {@link MediaLibraryService2} class derives from {@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. | 
|  | * | 
|  | * @see MediaSessionService2 | 
|  | */ | 
|  | @RestrictTo(LIBRARY_GROUP) | 
|  | 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"; | 
|  |  | 
|  | // TODO: Revisit this value. | 
|  |  | 
|  | /** | 
|  | * Session for the {@link MediaLibraryService2}. Build this object with | 
|  | * {@link Builder} and return in {@link #onCreateSession(String)}. | 
|  | */ | 
|  | public static final class MediaLibrarySession extends MediaSession2 { | 
|  | /** | 
|  | * Callback for the {@link MediaLibrarySession}. | 
|  | */ | 
|  | public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback { | 
|  | /** | 
|  | * Called to get the root information for browsing by a particular client. | 
|  | * <p> | 
|  | * 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. | 
|  | * <p> | 
|  | * Note: this callback may be called on the main thread, regardless of the callback | 
|  | * executor. | 
|  | * | 
|  | * @param session the session for this event | 
|  | * @param controllerInfo information of the controller requesting access to browse | 
|  | *                       media. | 
|  | * @param extras An optional bundle of service-specific arguments to send | 
|  | * to the media library 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 LibraryRoot} for accessing this app's content or null. | 
|  | * @see LibraryRoot#EXTRA_RECENT | 
|  | * @see LibraryRoot#EXTRA_OFFLINE | 
|  | * @see LibraryRoot#EXTRA_SUGGESTED | 
|  | * @see SessionCommand2#COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT | 
|  | */ | 
|  | public @Nullable LibraryRoot onGetLibraryRoot(@NonNull MediaLibrarySession session, | 
|  | @NonNull ControllerInfo controllerInfo, @Nullable Bundle extras) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Called to get an item. Return result here for the browser. | 
|  | * <p> | 
|  | * Return {@code null} for no result or error. | 
|  | * | 
|  | * @param session the session for this event | 
|  | * @param mediaId item id to get media item. | 
|  | * @return a media item. {@code null} for no result or error. | 
|  | * @see SessionCommand2#COMMAND_CODE_LIBRARY_GET_ITEM | 
|  | */ | 
|  | public @Nullable MediaItem2 onGetItem(@NonNull MediaLibrarySession session, | 
|  | @NonNull ControllerInfo controllerInfo, @NonNull String mediaId) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Called to get children of given parent id. Return the children here for the browser. | 
|  | * <p> | 
|  | * Return an empty list for no children, and return {@code null} for the error. | 
|  | * | 
|  | * @param session the session for this event | 
|  | * @param parentId parent id to get children | 
|  | * @param page number of page | 
|  | * @param pageSize size of the page | 
|  | * @param extras extra bundle | 
|  | * @return list of children. Can be {@code null}. | 
|  | * @see SessionCommand2#COMMAND_CODE_LIBRARY_GET_CHILDREN | 
|  | */ | 
|  | public @Nullable List<MediaItem2> onGetChildren(@NonNull MediaLibrarySession session, | 
|  | @NonNull ControllerInfo controller, @NonNull String parentId, int page, | 
|  | int pageSize, @Nullable Bundle extras) { | 
|  | return null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Called when a controller subscribes to the parent. | 
|  | * <p> | 
|  | * It's your responsibility to keep subscriptions by your own and call | 
|  | * {@link MediaLibrarySession#notifyChildrenChanged(ControllerInfo, String, int, Bundle)} | 
|  | * when the parent is changed. | 
|  | * | 
|  | * @param session the session for this event | 
|  | * @param controller controller | 
|  | * @param parentId parent id | 
|  | * @param extras extra bundle | 
|  | * @see SessionCommand2#COMMAND_CODE_LIBRARY_SUBSCRIBE | 
|  | */ | 
|  | public void onSubscribe(@NonNull MediaLibrarySession session, | 
|  | @NonNull ControllerInfo controller, @NonNull String parentId, | 
|  | @Nullable Bundle extras) { | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Called when a controller unsubscribes to the parent. | 
|  | * | 
|  | * @param session the session for this event | 
|  | * @param controller controller | 
|  | * @param parentId parent id | 
|  | * @see SessionCommand2#COMMAND_CODE_LIBRARY_UNSUBSCRIBE | 
|  | */ | 
|  | // TODO: Make this to be called. | 
|  | public void onUnsubscribe(@NonNull MediaLibrarySession session, | 
|  | @NonNull ControllerInfo controller, @NonNull String parentId) { | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Called when a controller requests search. | 
|  | * | 
|  | * @param session the session for this event | 
|  | * @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. | 
|  | * @see SessionCommand2#COMMAND_CODE_LIBRARY_SEARCH | 
|  | */ | 
|  | public void onSearch(@NonNull MediaLibrarySession session, | 
|  | @NonNull ControllerInfo controllerInfo, @NonNull String query, | 
|  | @Nullable Bundle extras) { | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Called to get the search result. Return search result here for the browser which has | 
|  | * requested search previously. | 
|  | * <p> | 
|  | * Return an empty list for no search result, and return {@code null} for the error. | 
|  | * | 
|  | * @param session the session for this event | 
|  | * @param controllerInfo Information of the controller requesting the search result. | 
|  | * @param query The search query which was previously sent through | 
|  | *              {@link #onSearch(MediaLibrarySession, ControllerInfo, String, Bundle)}. | 
|  | * @param page page number. Starts from {@code 1}. | 
|  | * @param pageSize page size. Should be greater or equal to {@code 1}. | 
|  | * @param extras The bundle of service-specific arguments sent from the media browser. | 
|  | * @return search result. {@code null} for error. | 
|  | * @see SessionCommand2#COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT | 
|  | */ | 
|  | public @Nullable List<MediaItem2> onGetSearchResult( | 
|  | @NonNull MediaLibrarySession session, @NonNull ControllerInfo controllerInfo, | 
|  | @NonNull String query, int page, int pageSize, @Nullable Bundle extras) { | 
|  | return null; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Builder for {@link MediaLibrarySession}. | 
|  | */ | 
|  | // Override all methods just to show them with the type instead of generics in Javadoc. | 
|  | // This workarounds javadoc issue described in the MediaSession2.BuilderBase. | 
|  | public static final class Builder extends MediaSession2.BuilderBase<MediaLibrarySession, | 
|  | Builder, MediaLibrarySessionCallback> { | 
|  | private MediaLibrarySessionImplBase.Builder mImpl; | 
|  |  | 
|  | // Builder requires MediaLibraryService2 instead of Context just to ensure that the | 
|  | // builder can be only instantiated within the MediaLibraryService2. | 
|  | // Ideally it's better to make it inner class of service to enforce, it violates API | 
|  | // guideline that Builders should be the inner class of the building target. | 
|  | public Builder(@NonNull MediaLibraryService2 service, | 
|  | @NonNull Executor callbackExecutor, | 
|  | @NonNull MediaLibrarySessionCallback callback) { | 
|  | super(service); | 
|  | mImpl = new MediaLibrarySessionImplBase.Builder(service); | 
|  | setImpl(mImpl); | 
|  | setSessionCallback(callbackExecutor, callback); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public @NonNull Builder setPlayer(@NonNull MediaPlayerBase player) { | 
|  | return super.setPlayer(player); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public @NonNull Builder setPlaylistAgent(@NonNull MediaPlaylistAgent playlistAgent) { | 
|  | return super.setPlaylistAgent(playlistAgent); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public @NonNull Builder setVolumeProvider( | 
|  | @Nullable VolumeProviderCompat volumeProvider) { | 
|  | return super.setVolumeProvider(volumeProvider); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public @NonNull Builder setSessionActivity(@Nullable PendingIntent pi) { | 
|  | return super.setSessionActivity(pi); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public @NonNull Builder setId(@NonNull String id) { | 
|  | return super.setId(id); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public @NonNull Builder setSessionCallback(@NonNull Executor executor, | 
|  | @NonNull MediaLibrarySessionCallback callback) { | 
|  | return super.setSessionCallback(executor, callback); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public @NonNull MediaLibrarySession build() { | 
|  | return super.build(); | 
|  | } | 
|  | } | 
|  |  | 
|  | MediaLibrarySession(SupportLibraryImpl impl) { | 
|  | super(impl); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Notify the controller of the change in a parent's children. | 
|  | * <p> | 
|  | * If the controller hasn't subscribed to the parent, the API will do nothing. | 
|  | * <p> | 
|  | * Controllers will use {@link MediaBrowser2#getChildren(String, int, int, Bundle)} to get | 
|  | * the list of children. | 
|  | * | 
|  | * @param controller controller to notify | 
|  | * @param parentId parent id with changes in its children | 
|  | * @param itemCount number of children. | 
|  | * @param extras extra information from session to controller | 
|  | */ | 
|  | public void notifyChildrenChanged(@NonNull ControllerInfo controller, | 
|  | @NonNull String parentId, int itemCount, @Nullable Bundle extras) { | 
|  | Bundle options = new Bundle(extras); | 
|  | options.putInt(MediaBrowser2.EXTRA_ITEM_COUNT, itemCount); | 
|  | options.putBundle(MediaBrowser2.EXTRA_TARGET, controller.toBundle()); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Notify all controllers that subscribed to the parent about change in the parent's | 
|  | * children, regardless of the extra bundle supplied by | 
|  | * {@link MediaBrowser2#subscribe(String, Bundle)}. | 
|  | * | 
|  | * @param parentId parent id | 
|  | * @param itemCount number of children | 
|  | * @param extras extra information from session to controller | 
|  | */ | 
|  | // This is for the backward compatibility. | 
|  | public void notifyChildrenChanged(@NonNull String parentId, int itemCount, | 
|  | @Nullable Bundle extras) { | 
|  | Bundle options = new Bundle(extras); | 
|  | options.putInt(MediaBrowser2.EXTRA_ITEM_COUNT, itemCount); | 
|  | getServiceCompat().notifyChildrenChanged(parentId, options); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Notify controller about change in the search result. | 
|  | * | 
|  | * @param controller controller to notify | 
|  | * @param query previously sent search query from the controller. | 
|  | * @param itemCount the number of items that have been found in the search. | 
|  | * @param extras extra bundle | 
|  | */ | 
|  | public void notifySearchResultChanged(@NonNull ControllerInfo controller, | 
|  | @NonNull String query, int itemCount, @NonNull Bundle extras) { | 
|  | // TODO: Implement | 
|  | } | 
|  |  | 
|  | private MediaLibraryService2 getService() { | 
|  | return (MediaLibraryService2) getContext(); | 
|  | } | 
|  |  | 
|  | private MediaBrowserServiceCompat getServiceCompat() { | 
|  | return getService().getServiceCompat(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | MediaLibrarySessionCallback getCallback() { | 
|  | return (MediaLibrarySessionCallback) super.getCallback(); | 
|  | } | 
|  | } | 
|  |  | 
|  | @Override | 
|  | MediaBrowserServiceCompat createBrowserServiceCompat() { | 
|  | return new MyBrowserService(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | int getSessionType() { | 
|  | return SessionToken2.TYPE_LIBRARY_SERVICE; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onCreate() { | 
|  | super.onCreate(); | 
|  |  | 
|  | MediaSession2 session = getSession(); | 
|  | if (!(session instanceof MediaLibrarySession)) { | 
|  | throw new RuntimeException("Expected MediaLibrarySession, but returned MediaSession2"); | 
|  | } | 
|  | } | 
|  |  | 
|  | private MediaLibrarySession getLibrarySession() { | 
|  | return (MediaLibrarySession) getSession(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public IBinder onBind(Intent intent) { | 
|  | return super.onBind(intent); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Called when another app requested to start this service. | 
|  | * <p> | 
|  | * Library service will accept or reject the connection with the | 
|  | * {@link MediaLibrarySessionCallback} in the created session. | 
|  | * <p> | 
|  | * 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. | 
|  | * <p> | 
|  | * This method will be called on the main thread. | 
|  | * | 
|  | * @param sessionId session id written in the AndroidManifest.xml. | 
|  | * @return a new library session | 
|  | * @see Builder | 
|  | * @see #getSession() | 
|  | * @throws RuntimeException if returned session is invalid | 
|  | */ | 
|  | @Override | 
|  | public @NonNull abstract MediaLibrarySession onCreateSession(String sessionId); | 
|  |  | 
|  | /** | 
|  | * Contains information that the library service needs to send to the client when | 
|  | * {@link MediaBrowser2#getLibraryRoot(Bundle)} is called. | 
|  | */ | 
|  | public static final class LibraryRoot { | 
|  | /** | 
|  | * The lookup key for a boolean that indicates whether the library service should return a | 
|  | * librar root for recently played media items. | 
|  | * | 
|  | * <p>When creating a media browser for a given media library service, this key can be | 
|  | * supplied as a root hint for retrieving media items that are recently played. | 
|  | * If the media library service can provide such media items, the implementation must return | 
|  | * the key in the root hint when | 
|  | * {@link MediaLibrarySessionCallback#onGetLibraryRoot} | 
|  | * is called back. | 
|  | * | 
|  | * <p>The root hint may contain multiple keys. | 
|  | * | 
|  | * @see #EXTRA_OFFLINE | 
|  | * @see #EXTRA_SUGGESTED | 
|  | */ | 
|  | public static final String EXTRA_RECENT = "android.media.extra.RECENT"; | 
|  |  | 
|  | /** | 
|  | * The lookup key for a boolean that indicates whether the library service should return a | 
|  | * library root for offline media items. | 
|  | * | 
|  | * <p>When creating a media browser for a given media library 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 library service can provide such media items, the implementation must return | 
|  | * the key in the root hint when | 
|  | * {@link MediaLibrarySessionCallback#onGetLibraryRoot} | 
|  | * is called back. | 
|  | * | 
|  | * <p>The root hint may contain multiple keys. | 
|  | * | 
|  | * @see #EXTRA_RECENT | 
|  | * @see #EXTRA_SUGGESTED | 
|  | */ | 
|  | public static final String EXTRA_OFFLINE = "android.media.extra.OFFLINE"; | 
|  |  | 
|  | /** | 
|  | * The lookup key for a boolean that indicates whether the library service should return a | 
|  | * library root for suggested media items. | 
|  | * | 
|  | * <p>When creating a media browser for a given media library service, this key can be | 
|  | * supplied as a root hint for retrieving the media items suggested by the media library | 
|  | * service. The list of media items is considered ordered by relevance, first being the top | 
|  | * suggestion. | 
|  | * If the media library service can provide such media items, the implementation must return | 
|  | * the key in the root hint when | 
|  | * {@link MediaLibrarySessionCallback#onGetLibraryRoot} | 
|  | * is called back. | 
|  | * | 
|  | * <p>The root hint may contain multiple keys. | 
|  | * | 
|  | * @see #EXTRA_RECENT | 
|  | * @see #EXTRA_OFFLINE | 
|  | */ | 
|  | public static final String EXTRA_SUGGESTED = "android.media.extra.SUGGESTED"; | 
|  |  | 
|  | private final String mRootId; | 
|  | private final Bundle mExtras; | 
|  |  | 
|  | //private final LibraryRootProvider mProvider; | 
|  |  | 
|  | /** | 
|  | * Constructs a library root. | 
|  | * @param rootId The root id for browsing. | 
|  | * @param extras Any extras about the library service. | 
|  | */ | 
|  | public LibraryRoot(@NonNull String rootId, @Nullable Bundle extras) { | 
|  | if (rootId == null) { | 
|  | throw new IllegalArgumentException("rootId shouldn't be null"); | 
|  | } | 
|  | mRootId = rootId; | 
|  | mExtras = extras; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gets the root id for browsing. | 
|  | */ | 
|  | public String getRootId() { | 
|  | return mRootId; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gets any extras about the library service. | 
|  | */ | 
|  | public Bundle getExtras() { | 
|  | return mExtras; | 
|  | } | 
|  | } | 
|  |  | 
|  | private class MyBrowserService extends MediaBrowserServiceCompat { | 
|  | @Override | 
|  | public BrowserRoot onGetRoot(String clientPackageName, int clientUid, | 
|  | final Bundle extras) { | 
|  | if (MediaUtils2.isDefaultLibraryRootHint(extras)) { | 
|  | // For connection request from the MediaController2. accept the connection from | 
|  | // here, and let MediaLibrarySession decide whether to accept or reject the | 
|  | // controller. | 
|  | return sDefaultBrowserRoot; | 
|  | } | 
|  | final CountDownLatch latch = new CountDownLatch(1); | 
|  | // TODO: Revisit this when we support caller information. | 
|  | final ControllerInfo info = new ControllerInfo(MediaLibraryService2.this, clientUid, -1, | 
|  | clientPackageName, null); | 
|  | MediaLibrarySession session = getLibrarySession(); | 
|  | // Call onGetLibraryRoot() directly instead of execute on the executor. Here's the | 
|  | // reason. | 
|  | // We need to return browser root here. So if we run the callback on the executor, we | 
|  | // should wait for the completion. | 
|  | // However, we cannot wait if the callback executor is the main executor, which posts | 
|  | // the runnable to the main thread's. In that case, since this onGetRoot() always runs | 
|  | // on the main thread, the posted runnable for calling onGetLibraryRoot() wouldn't run | 
|  | // in here. Even worse, we cannot know whether it would be run on the main thread or | 
|  | // not. | 
|  | // Because of the reason, just call onGetLibraryRoot directly here. onGetLibraryRoot() | 
|  | // has documentation that it may be called on the main thread. | 
|  | LibraryRoot libraryRoot = session.getCallback().onGetLibraryRoot( | 
|  | session, info, extras); | 
|  | if (libraryRoot == null) { | 
|  | return null; | 
|  | } | 
|  | return new BrowserRoot(libraryRoot.getRootId(), libraryRoot.getExtras()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onLoadChildren(String parentId, Result<List<MediaItem>> result) { | 
|  | onLoadChildren(parentId, result, null); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onLoadChildren(final String parentId, final Result<List<MediaItem>> result, | 
|  | final Bundle options) { | 
|  | final ControllerInfo controller = getController(); | 
|  | getLibrarySession().getCallbackExecutor().execute(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | int page = options.getInt(EXTRA_PAGE, -1); | 
|  | int pageSize = options.getInt(EXTRA_PAGE_SIZE, -1); | 
|  | if (page >= 0 && pageSize >= 0) { | 
|  | // Requesting the list of children through the pagenation. | 
|  | List<MediaItem2> children = getLibrarySession().getCallback().onGetChildren( | 
|  | getLibrarySession(), controller, parentId, page, pageSize, options); | 
|  | if (children == null) { | 
|  | result.sendError(null); | 
|  | } else { | 
|  | List<MediaItem> list = new ArrayList<>(); | 
|  | for (int i = 0; i < children.size(); i++) { | 
|  | list.add(MediaUtils2.createMediaItem(children.get(i))); | 
|  | } | 
|  | result.sendResult(list); | 
|  | } | 
|  | } else { | 
|  | // Only wants to register callbacks | 
|  | getLibrarySession().getCallback().onSubscribe(getLibrarySession(), | 
|  | controller, parentId, options); | 
|  | } | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onLoadItem(final String itemId, final Result<MediaItem> result) { | 
|  | final ControllerInfo controller = getController(); | 
|  | getLibrarySession().getCallbackExecutor().execute(new Runnable() { | 
|  | @Override | 
|  | public void run() { | 
|  | MediaItem2 item = getLibrarySession().getCallback().onGetItem( | 
|  | getLibrarySession(), controller, itemId); | 
|  | if (item == null) { | 
|  | result.sendError(null); | 
|  | } else { | 
|  | result.sendResult(MediaUtils2.createMediaItem(item)); | 
|  | } | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onSearch(String query, Bundle extras, Result<List<MediaItem>> result) { | 
|  | // TODO: Implement | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void onCustomAction(String action, Bundle extras, Result<Bundle> result) { | 
|  | // TODO: Implement | 
|  | } | 
|  |  | 
|  | private ControllerInfo getController() { | 
|  | // TODO: Implement, by using getBrowserRootHints() / getCurrentBrowserInfo() / ... | 
|  | return null; | 
|  | } | 
|  | } | 
|  | } |