summaryrefslogtreecommitdiff
path: root/android/media/MediaLibraryService2.java
blob: f29d386c417da3d23e30cfda72a99b5b0ba71d2b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
/*
 * 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.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.media.MediaLibraryService2.MediaLibrarySession.Builder;
import android.media.MediaLibraryService2.MediaLibrarySession.MediaLibrarySessionCallback;
import android.media.MediaSession2.ControllerInfo;
import android.media.update.ApiLoader;
import android.media.update.MediaLibraryService2Provider.LibraryRootProvider;
import android.media.update.MediaLibraryService2Provider.MediaLibrarySessionProvider;
import android.media.update.MediaSessionService2Provider;
import android.os.Bundle;

import java.util.List;
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>
 * &lt;service android:name="component_name_of_your_implementation" &gt;
 *   &lt;intent-filter&gt;
 *     &lt;action android:name="android.media.MediaLibraryService2" /&gt;
 *   &lt;/intent-filter&gt;
 * &lt;/service&gt;</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
 */
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 {@link MediaLibraryService2}. Build this object with
     * {@link Builder} and return in {@link #onCreateSession(String)}.
     */
    public static final class MediaLibrarySession extends MediaSession2 {
        private final MediaLibrarySessionProvider mProvider;

        /**
         * Callback for the {@link MediaLibrarySession}.
         */
        public static class MediaLibrarySessionCallback extends MediaSession2.SessionCallback {
            public MediaLibrarySessionCallback() {
                super();
            }

            /**
             * 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.
             *
             * @param session the session for this event
             * @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 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
             */
            public @Nullable LibraryRoot onGetLibraryRoot(@NonNull MediaLibrarySession session,
                    @NonNull ControllerInfo controllerInfo, @Nullable Bundle rootHints) {
                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.
             */
            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}.
             */
            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
             */
            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
             */
            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.
             */
            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.
             */
            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 BuilderBase<MediaLibrarySession, Builder,
                MediaLibrarySessionCallback> {
            // 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 @CallbackExecutor Executor callbackExecutor,
                    @NonNull MediaLibrarySessionCallback callback) {
                super((instance) -> ApiLoader.getProvider().createMediaLibraryService2Builder(
                        service, (Builder) instance, callbackExecutor, callback));
            }

            @Override
            public Builder setPlayer(@NonNull MediaPlayerBase player) {
                return super.setPlayer(player);
            }

            @Override
            public Builder setPlaylistAgent(@NonNull MediaPlaylistAgent playlistAgent) {
                return super.setPlaylistAgent(playlistAgent);
            }

            @Override
            public Builder setVolumeProvider(@Nullable VolumeProvider2 volumeProvider) {
                return super.setVolumeProvider(volumeProvider);
            }

            @Override
            public Builder setSessionActivity(@Nullable PendingIntent pi) {
                return super.setSessionActivity(pi);
            }

            @Override
            public Builder setId(@NonNull String id) {
                return super.setId(id);
            }

            @Override
            public Builder setSessionCallback(@NonNull @CallbackExecutor Executor executor,
                    @NonNull MediaLibrarySessionCallback callback) {
                return super.setSessionCallback(executor, callback);
            }

            @Override
            public MediaLibrarySession build() {
                return super.build();
            }
        }

        /**
         * @hide
         */
        public MediaLibrarySession(MediaLibrarySessionProvider provider) {
            super(provider);
            mProvider = provider;
        }

        /**
         * 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) {
            mProvider.notifyChildrenChanged_impl(controller, parentId, itemCount, extras);
        }

        /**
         * 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) {
            mProvider.notifyChildrenChanged_impl(parentId, itemCount, extras);
        }

        /**
         * 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) {
            mProvider.notifySearchResultChanged_impl(controller, query, itemCount, extras);
        }
    }

    @Override
    MediaSessionService2Provider createProvider() {
        return ApiLoader.getProvider().createMediaLibraryService2(this);
    }

    /**
     * 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(MediaLibrarySession, 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.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(MediaLibrarySession, 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.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(MediaLibrarySession, 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.media.extra.SUGGESTED";

        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) {
            mProvider = ApiLoader.getProvider().createMediaLibraryService2LibraryRoot(
                    this, rootId, extras);
        }

        /**
         * Gets the root id for browsing.
         */
        public String getRootId() {
            return mProvider.getRootId_impl();
        }

        /**
         * Gets any extras about the library service.
         */
        public Bundle getExtras() {
            return mProvider.getExtras_impl();
        }
    }
}