1/*
2 * Copyright 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.media;
18
19import android.annotation.CallbackExecutor;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.app.PendingIntent;
23import android.media.MediaLibraryService2.MediaLibrarySession.Builder;
24import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
25import android.media.MediaSession2.ControllerInfo;
26import android.media.update.ApiLoader;
27import android.media.update.MediaLibraryService2Provider.LibraryRootProvider;
28import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
29import android.media.update.MediaSessionService2Provider;
30import android.os.Bundle;
31
32import java.util.List;
33import java.util.concurrent.Executor;
34
35/**
36 * @hide
37 * Base class for media library services.
38 * <p>
39 * Media library services enable applications to browse media content provided by an application
40 * and ask the application to start playing it. They may also be used to control content that
41 * is already playing by way of a {@link MediaSession2}.
42 * <p>
43 * When extending this class, also add the following to your {@code AndroidManifest.xml}.
44 * <pre>
45 * &lt;service android:name="component_name_of_your_implementation" &gt;
46 *   &lt;intent-filter&gt;
47 *     &lt;action android:name="android.media.MediaLibraryService2" /&gt;
48 *   &lt;/intent-filter&gt;
49 * &lt;/service&gt;</pre>
50 * <p>
51 * The {@link MediaLibraryService2} class derives from {@link MediaSessionService2}. IDs shouldn't
52 * be shared between the {@link MediaSessionService2} and {@link MediaSession2}. By
53 * default, an empty string will be used for ID of the service. If you want to specify an ID,
54 * declare metadata in the manifest as follows.
55 *
56 * @see MediaSessionService2
57 */
58public abstract class MediaLibraryService2 extends MediaSessionService2 {
59    /**
60     * This is the interface name that a service implementing a session service should say that it
61     * support -- that is, this is the action it uses for its intent filter.
62     */
63    public static final String SERVICE_INTERFACE = "android.media.MediaLibraryService2";
64
65    /**
66     * Session for the {@link MediaLibraryService2}. Build this object with
67     * {@link Builder} and return in {@link #onCreateSession(String)}.
68     */
69    public static final class MediaLibrarySession extends MediaSession2 {
70        private final MediaLibrarySessionProvider mProvider;
71
72        /**
73         * Callback for the {@link MediaLibrarySession}.
74         */
75        public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
76            public MediaLibrarySessionCallback() {
77                super();
78            }
79
80            /**
81             * Called to get the root information for browsing by a particular client.
82             * <p>
83             * The implementation should verify that the client package has permission
84             * to access browse media information before returning the root id; it
85             * should return null if the client is not allowed to access this
86             * information.
87             *
88             * @param session the session for this event
89             * @param controllerInfo information of the controller requesting access to browse media.
90             * @param rootHints An optional bundle of service-specific arguments to send
91             * to the media library service when connecting and retrieving the
92             * root id for browsing, or null if none. The contents of this
93             * bundle may affect the information returned when browsing.
94             * @return The {@link LibraryRoot} for accessing this app's content or null.
95             * @see LibraryRoot#EXTRA_RECENT
96             * @see LibraryRoot#EXTRA_OFFLINE
97             * @see LibraryRoot#EXTRA_SUGGESTED
98             */
99            public @Nullable LibraryRoot onGetLibraryRoot(@NonNull MediaLibrarySession session,
100                    @NonNull ControllerInfo controllerInfo, @Nullable Bundle rootHints) {
101                return null;
102            }
103
104            /**
105             * Called to get an item. Return result here for the browser.
106             * <p>
107             * Return {@code null} for no result or error.
108             *
109             * @param session the session for this event
110             * @param mediaId item id to get media item.
111             * @return a media item. {@code null} for no result or error.
112             */
113            public @Nullable MediaItem2 onGetItem(@NonNull MediaLibrarySession session,
114                    @NonNull ControllerInfo controllerInfo, @NonNull String mediaId) {
115                return null;
116            }
117
118            /**
119             * Called to get children of given parent id. Return the children here for the browser.
120             * <p>
121             * Return an empty list for no children, and return {@code null} for the error.
122             *
123             * @param session the session for this event
124             * @param parentId parent id to get children
125             * @param page number of page
126             * @param pageSize size of the page
127             * @param extras extra bundle
128             * @return list of children. Can be {@code null}.
129             */
130            public @Nullable List<MediaItem2> onGetChildren(@NonNull MediaLibrarySession session,
131                    @NonNull ControllerInfo controller, @NonNull String parentId, int page,
132                    int pageSize, @Nullable Bundle extras) {
133                return null;
134            }
135
136            /**
137             * Called when a controller subscribes to the parent.
138             * <p>
139             * It's your responsibility to keep subscriptions by your own and call
140             * {@link MediaLibrarySession#notifyChildrenChanged(ControllerInfo, String, int, Bundle)}
141             * when the parent is changed.
142             *
143             * @param session the session for this event
144             * @param controller controller
145             * @param parentId parent id
146             * @param extras extra bundle
147             */
148            public void onSubscribe(@NonNull MediaLibrarySession session,
149                    @NonNull ControllerInfo controller, @NonNull String parentId,
150                    @Nullable Bundle extras) {
151            }
152
153            /**
154             * Called when a controller unsubscribes to the parent.
155             *
156             * @param session the session for this event
157             * @param controller controller
158             * @param parentId parent id
159             */
160            public void onUnsubscribe(@NonNull MediaLibrarySession session,
161                    @NonNull ControllerInfo controller, @NonNull String parentId) {
162            }
163
164            /**
165             * Called when a controller requests search.
166             *
167             * @param session the session for this event
168             * @param query The search query sent from the media browser. It contains keywords
169             *              separated by space.
170             * @param extras The bundle of service-specific arguments sent from the media browser.
171             */
172            public void onSearch(@NonNull MediaLibrarySession session,
173                    @NonNull ControllerInfo controllerInfo, @NonNull String query,
174                    @Nullable Bundle extras) {
175            }
176
177            /**
178             * Called to get the search result. Return search result here for the browser which has
179             * requested search previously.
180             * <p>
181             * Return an empty list for no search result, and return {@code null} for the error.
182             *
183             * @param session the session for this event
184             * @param controllerInfo Information of the controller requesting the search result.
185             * @param query The search query which was previously sent through
186             *              {@link #onSearch(MediaLibrarySession, ControllerInfo, String, Bundle)}.
187             * @param page page number. Starts from {@code 1}.
188             * @param pageSize page size. Should be greater or equal to {@code 1}.
189             * @param extras The bundle of service-specific arguments sent from the media browser.
190             * @return search result. {@code null} for error.
191             */
192            public @Nullable List<MediaItem2> onGetSearchResult(
193                    @NonNull MediaLibrarySession session, @NonNull ControllerInfo controllerInfo,
194                    @NonNull String query, int page, int pageSize, @Nullable Bundle extras) {
195                return null;
196            }
197        }
198
199        /**
200         * Builder for {@link MediaLibrarySession}.
201         */
202        // Override all methods just to show them with the type instead of generics in Javadoc.
203        // This workarounds javadoc issue described in the MediaSession2.BuilderBase.
204        public static final class Builder extends BuilderBase<MediaLibrarySession, Builder,
205                MediaLibrarySessionCallback> {
206            // Builder requires MediaLibraryService2 instead of Context just to ensure that the
207            // builder can be only instantiated within the MediaLibraryService2.
208            // Ideally it's better to make it inner class of service to enforce, it violates API
209            // guideline that Builders should be the inner class of the building target.
210            public Builder(@NonNull MediaLibraryService2 service,
211                    @NonNull @CallbackExecutor Executor callbackExecutor,
212                    @NonNull MediaLibrarySessionCallback callback) {
213                super((instance) -> ApiLoader.getProvider().createMediaLibraryService2Builder(
214                        service, (Builder) instance, callbackExecutor, callback));
215            }
216
217            @Override
218            public Builder setPlayer(@NonNull MediaPlayerBase player) {
219                return super.setPlayer(player);
220            }
221
222            @Override
223            public Builder setPlaylistAgent(@NonNull MediaPlaylistAgent playlistAgent) {
224                return super.setPlaylistAgent(playlistAgent);
225            }
226
227            @Override
228            public Builder setVolumeProvider(@Nullable VolumeProvider2 volumeProvider) {
229                return super.setVolumeProvider(volumeProvider);
230            }
231
232            @Override
233            public Builder setSessionActivity(@Nullable PendingIntent pi) {
234                return super.setSessionActivity(pi);
235            }
236
237            @Override
238            public Builder setId(@NonNull String id) {
239                return super.setId(id);
240            }
241
242            @Override
243            public Builder setSessionCallback(@NonNull @CallbackExecutor Executor executor,
244                    @NonNull MediaLibrarySessionCallback callback) {
245                return super.setSessionCallback(executor, callback);
246            }
247
248            @Override
249            public MediaLibrarySession build() {
250                return super.build();
251            }
252        }
253
254        /**
255         * @hide
256         */
257        public MediaLibrarySession(MediaLibrarySessionProvider provider) {
258            super(provider);
259            mProvider = provider;
260        }
261
262        /**
263         * Notify the controller of the change in a parent's children.
264         * <p>
265         * If the controller hasn't subscribed to the parent, the API will do nothing.
266         * <p>
267         * Controllers will use {@link MediaBrowser2#getChildren(String, int, int, Bundle)} to get
268         * the list of children.
269         *
270         * @param controller controller to notify
271         * @param parentId parent id with changes in its children
272         * @param itemCount number of children.
273         * @param extras extra information from session to controller
274         */
275        public void notifyChildrenChanged(@NonNull ControllerInfo controller,
276                @NonNull String parentId, int itemCount, @Nullable Bundle extras) {
277            mProvider.notifyChildrenChanged_impl(controller, parentId, itemCount, extras);
278        }
279
280        /**
281         * Notify all controllers that subscribed to the parent about change in the parent's
282         * children, regardless of the extra bundle supplied by
283         * {@link MediaBrowser2#subscribe(String, Bundle)}.
284         *
285         * @param parentId parent id
286         * @param itemCount number of children
287         * @param extras extra information from session to controller
288         */
289        // This is for the backward compatibility.
290        public void notifyChildrenChanged(@NonNull String parentId, int itemCount,
291                @Nullable Bundle extras) {
292            mProvider.notifyChildrenChanged_impl(parentId, itemCount, extras);
293        }
294
295        /**
296         * Notify controller about change in the search result.
297         *
298         * @param controller controller to notify
299         * @param query previously sent search query from the controller.
300         * @param itemCount the number of items that have been found in the search.
301         * @param extras extra bundle
302         */
303        public void notifySearchResultChanged(@NonNull ControllerInfo controller,
304                @NonNull String query, int itemCount, @NonNull Bundle extras) {
305            mProvider.notifySearchResultChanged_impl(controller, query, itemCount, extras);
306        }
307    }
308
309    @Override
310    MediaSessionService2Provider createProvider() {
311        return ApiLoader.getProvider().createMediaLibraryService2(this);
312    }
313
314    /**
315     * Called when another app requested to start this service.
316     * <p>
317     * Library service will accept or reject the connection with the
318     * {@link MediaLibrarySessionCallback} in the created session.
319     * <p>
320     * Service wouldn't run if {@code null} is returned or session's ID doesn't match with the
321     * expected ID that you've specified through the AndroidManifest.xml.
322     * <p>
323     * This method will be called on the main thread.
324     *
325     * @param sessionId session id written in the AndroidManifest.xml.
326     * @return a new library session
327     * @see Builder
328     * @see #getSession()
329     * @throws RuntimeException if returned session is invalid
330     */
331    @Override
332    public @NonNull abstract MediaLibrarySession onCreateSession(String sessionId);
333
334    /**
335     * Contains information that the library service needs to send to the client when
336     * {@link MediaBrowser2#getLibraryRoot(Bundle)} is called.
337     */
338    public static final class LibraryRoot {
339        /**
340         * The lookup key for a boolean that indicates whether the library service should return a
341         * librar root for recently played media items.
342         *
343         * <p>When creating a media browser for a given media library service, this key can be
344         * supplied as a root hint for retrieving media items that are recently played.
345         * If the media library service can provide such media items, the implementation must return
346         * the key in the root hint when
347         * {@link MediaLibrarySessionCallback#onGetLibraryRoot(MediaLibrarySession, ControllerInfo, Bundle)}
348         * is called back.
349         *
350         * <p>The root hint may contain multiple keys.
351         *
352         * @see #EXTRA_OFFLINE
353         * @see #EXTRA_SUGGESTED
354         */
355        public static final String EXTRA_RECENT = "android.media.extra.RECENT";
356
357        /**
358         * The lookup key for a boolean that indicates whether the library service should return a
359         * library root for offline media items.
360         *
361         * <p>When creating a media browser for a given media library service, this key can be
362         * supplied as a root hint for retrieving media items that are can be played without an
363         * internet connection.
364         * If the media library service can provide such media items, the implementation must return
365         * the key in the root hint when
366         * {@link MediaLibrarySessionCallback#onGetLibraryRoot(MediaLibrarySession, ControllerInfo, Bundle)}
367         * is called back.
368         *
369         * <p>The root hint may contain multiple keys.
370         *
371         * @see #EXTRA_RECENT
372         * @see #EXTRA_SUGGESTED
373         */
374        public static final String EXTRA_OFFLINE = "android.media.extra.OFFLINE";
375
376        /**
377         * The lookup key for a boolean that indicates whether the library service should return a
378         * library root for suggested media items.
379         *
380         * <p>When creating a media browser for a given media library service, this key can be
381         * supplied as a root hint for retrieving the media items suggested by the media library
382         * service. The list of media items is considered ordered by relevance, first being the top
383         * suggestion.
384         * If the media library service can provide such media items, the implementation must return
385         * the key in the root hint when
386         * {@link MediaLibrarySessionCallback#onGetLibraryRoot(MediaLibrarySession, ControllerInfo, Bundle)}
387         * is called back.
388         *
389         * <p>The root hint may contain multiple keys.
390         *
391         * @see #EXTRA_RECENT
392         * @see #EXTRA_OFFLINE
393         */
394        public static final String EXTRA_SUGGESTED = "android.media.extra.SUGGESTED";
395
396        private final LibraryRootProvider mProvider;
397
398        /**
399         * Constructs a library root.
400         * @param rootId The root id for browsing.
401         * @param extras Any extras about the library service.
402         */
403        public LibraryRoot(@NonNull String rootId, @Nullable Bundle extras) {
404            mProvider = ApiLoader.getProvider().createMediaLibraryService2LibraryRoot(
405                    this, rootId, extras);
406        }
407
408        /**
409         * Gets the root id for browsing.
410         */
411        public String getRootId() {
412            return mProvider.getRootId_impl();
413        }
414
415        /**
416         * Gets any extras about the library service.
417         */
418        public Bundle getExtras() {
419            return mProvider.getExtras_impl();
420        }
421    }
422}
423