MediaSessionCompat.java revision bbcdf78e350d58ecd6baa75e282d4908d3129fe2
1
2/*
3 * Copyright (C) 2014 The Android Open Source Project
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *      http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package android.support.v4.media.session;
19
20import android.content.Context;
21import android.content.Intent;
22import android.media.AudioManager;
23import android.os.Bundle;
24import android.os.Handler;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.os.ResultReceiver;
28import android.support.v4.media.MediaMetadataCompat;
29import android.support.v4.media.RatingCompat;
30import android.support.v4.media.VolumeProviderCompat;
31import android.text.TextUtils;
32
33/**
34 * Allows interaction with media controllers, volume keys, media buttons, and
35 * transport controls.
36 * <p>
37 * A MediaSession should be created when an app wants to publish media playback
38 * information or handle media keys. In general an app only needs one session
39 * for all playback, though multiple sessions can be created to provide finer
40 * grain controls of media.
41 * <p>
42 * Once a session is created the owner of the session may pass its
43 * {@link #getSessionToken() session token} to other processes to allow them to
44 * create a {@link MediaControllerCompat} to interact with the session.
45 * <p>
46 * To receive commands, media keys, and other events a {@link Callback} must be
47 * set with {@link #setCallback(Callback)}.
48 * <p>
49 * When an app is finished performing playback it must call {@link #release()}
50 * to clean up the session and notify any controllers.
51 * <p>
52 * MediaSession objects are thread safe.
53 * <p>
54 * This is a helper for accessing features in
55 * {@link android.media.session.MediaSession} introduced after API level 4 in a
56 * backwards compatible fashion.
57 */
58public class MediaSessionCompat {
59    private final MediaSessionImpl mImpl;
60
61    /**
62     * Set this flag on the session to indicate that it can handle media button
63     * events.
64     */
65    public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
66
67    /**
68     * Set this flag on the session to indicate that it handles transport
69     * control commands through its {@link Callback}.
70     */
71    public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
72
73    /**
74     * Creates a new session.
75     *
76     * @param context The context.
77     * @param tag A short name for debugging purposes.
78     */
79    public MediaSessionCompat(Context context, String tag) {
80        if (context == null) {
81            throw new IllegalArgumentException("context must not be null");
82        }
83        if (TextUtils.isEmpty(tag)) {
84            throw new IllegalArgumentException("tag must not be null or empty");
85        }
86
87        if (android.os.Build.VERSION.SDK_INT >= 21) {
88            mImpl = new MediaSessionImplApi21(context, tag);
89        } else {
90            mImpl = new MediaSessionImplBase();
91        }
92    }
93
94    private MediaSessionCompat(MediaSessionImpl impl) {
95        mImpl = impl;
96    }
97
98    /**
99     * Add a callback to receive updates on for the MediaSession. This includes
100     * media button and volume events. The caller's thread will be used to post
101     * events.
102     *
103     * @param callback The callback object
104     */
105    public void setCallback(Callback callback) {
106        setCallback(callback, null);
107    }
108
109    /**
110     * Set the callback to receive updates for the MediaSession. This includes
111     * media button and volume events. Set the callback to null to stop
112     * receiving events.
113     *
114     * @param callback The callback to receive updates on.
115     * @param handler The handler that events should be posted on.
116     */
117    public void setCallback(Callback callback, Handler handler) {
118        mImpl.setCallback(callback, handler != null ? handler : new Handler());
119    }
120
121    /**
122     * Set any flags for the session.
123     *
124     * @param flags The flags to set for this session.
125     */
126    public void setFlags(int flags) {
127        mImpl.setFlags(flags);
128    }
129
130    /**
131     * Set the stream this session is playing on. This will affect the system's
132     * volume handling for this session. If {@link #setPlaybackToRemote} was
133     * previously called it will stop receiving volume commands and the system
134     * will begin sending volume changes to the appropriate stream.
135     * <p>
136     * By default sessions are on {@link AudioManager#STREAM_MUSIC}.
137     *
138     * @param stream The {@link AudioManager} stream this session is playing on.
139     */
140    public void setPlaybackToLocal(int stream) {
141        mImpl.setPlaybackToLocal(stream);
142    }
143
144    /**
145     * Configure this session to use remote volume handling. This must be called
146     * to receive volume button events, otherwise the system will adjust the
147     * current stream volume for this session. If {@link #setPlaybackToLocal}
148     * was previously called that stream will stop receiving volume changes for
149     * this session.
150     *
151     * @param volumeProvider The provider that will handle volume changes. May
152     *            not be null.
153     */
154    public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
155        if (volumeProvider == null) {
156            throw new IllegalArgumentException("volumeProvider may not be null!");
157        }
158        mImpl.setPlaybackToRemote(volumeProvider);
159    }
160
161    /**
162     * Set if this session is currently active and ready to receive commands. If
163     * set to false your session's controller may not be discoverable. You must
164     * set the session to active before it can start receiving media button
165     * events or transport commands.
166     *
167     * @param active Whether this session is active or not.
168     */
169    public void setActive(boolean active) {
170        mImpl.setActive(active);
171    }
172
173    /**
174     * Get the current active state of this session.
175     *
176     * @return True if the session is active, false otherwise.
177     */
178    public boolean isActive() {
179        return mImpl.isActive();
180    }
181
182    /**
183     * Send a proprietary event to all MediaControllers listening to this
184     * Session. It's up to the Controller/Session owner to determine the meaning
185     * of any events.
186     *
187     * @param event The name of the event to send
188     * @param extras Any extras included with the event
189     */
190    public void sendSessionEvent(String event, Bundle extras) {
191        if (TextUtils.isEmpty(event)) {
192            throw new IllegalArgumentException("event cannot be null or empty");
193        }
194        mImpl.sendSessionEvent(event, extras);
195    }
196
197    /**
198     * This must be called when an app has finished performing playback. If
199     * playback is expected to start again shortly the session can be left open,
200     * but it must be released if your activity or service is being destroyed.
201     */
202    public void release() {
203        mImpl.release();
204    }
205
206    /**
207     * Retrieve a token object that can be used by apps to create a
208     * {@link MediaControllerCompat} for interacting with this session. The owner of
209     * the session is responsible for deciding how to distribute these tokens.
210     *
211     * @return A token that can be used to create a MediaController for this
212     *         session.
213     */
214    public Token getSessionToken() {
215        return mImpl.getSessionToken();
216    }
217
218    /**
219     * Update the current playback state.
220     *
221     * @param state The current state of playback
222     */
223    public void setPlaybackState(PlaybackStateCompat state) {
224        mImpl.setPlaybackState(state);
225    }
226
227    /**
228     * Update the current metadata. New metadata can be created using
229     * {@link android.media.MediaMetadata.Builder}.
230     *
231     * @param metadata The new metadata
232     */
233    public void setMetadata(MediaMetadataCompat metadata) {
234        mImpl.setMetadata(metadata);
235    }
236
237    /**
238     * Gets the underlying framework {@link android.media.session.MediaSession} object.
239     * <p>
240     * This method is only supported on API 21+.
241     * </p>
242     *
243     * @return The underlying {@link android.media.session.MediaSession} object,
244     * or null if none.
245     */
246    public Object getMediaSession() {
247        return mImpl.getMediaSession();
248    }
249
250    /**
251     * Obtain a compat wrapper for an existing MediaSession.
252     *
253     * @param mediaSession The {@link android.media.session.MediaSession} to
254     *            wrap.
255     * @return A compat wrapper for the provided session.
256     */
257    public static MediaSessionCompat obtain(Object mediaSession) {
258        return new MediaSessionCompat(new MediaSessionImplApi21(mediaSession));
259    }
260
261    /**
262     * Receives transport controls, media buttons, and commands from controllers
263     * and the system. The callback may be set using {@link #setCallback}.
264     */
265    public abstract static class Callback {
266        final Object mCallbackObj;
267
268        public Callback() {
269            if (android.os.Build.VERSION.SDK_INT >= 21) {
270                mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21());
271            } else {
272                mCallbackObj = null;
273            }
274        }
275
276        /**
277         * Called when a controller has sent a custom command to this session.
278         * The owner of the session may handle custom commands but is not
279         * required to.
280         *
281         * @param command The command name.
282         * @param extras Optional parameters for the command, may be null.
283         * @param cb A result receiver to which a result may be sent by the command, may be null.
284         */
285        public void onCommand(String command, Bundle extras, ResultReceiver cb) {
286        }
287
288        /**
289         * Override to handle media button events.
290         *
291         * @param mediaButtonEvent The media button event intent.
292         * @return True if the event was handled, false otherwise.
293         */
294        public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
295            return false;
296        }
297
298        /**
299         * Override to handle requests to begin playback.
300         */
301        public void onPlay() {
302        }
303
304        /**
305         * Override to handle requests to pause playback.
306         */
307        public void onPause() {
308        }
309
310        /**
311         * Override to handle requests to skip to the next media item.
312         */
313        public void onSkipToNext() {
314        }
315
316        /**
317         * Override to handle requests to skip to the previous media item.
318         */
319        public void onSkipToPrevious() {
320        }
321
322        /**
323         * Override to handle requests to fast forward.
324         */
325        public void onFastForward() {
326        }
327
328        /**
329         * Override to handle requests to rewind.
330         */
331        public void onRewind() {
332        }
333
334        /**
335         * Override to handle requests to stop playback.
336         */
337        public void onStop() {
338        }
339
340        /**
341         * Override to handle requests to seek to a specific position in ms.
342         *
343         * @param pos New position to move to, in milliseconds.
344         */
345        public void onSeekTo(long pos) {
346        }
347
348        /**
349         * Override to handle the item being rated.
350         *
351         * @param rating
352         */
353        public void onSetRating(RatingCompat rating) {
354        }
355
356        private class StubApi21 implements MediaSessionCompatApi21.Callback {
357
358            @Override
359            public void onCommand(String command, Bundle extras, ResultReceiver cb) {
360                Callback.this.onCommand(command, extras, cb);
361            }
362
363            @Override
364            public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
365                return Callback.this.onMediaButtonEvent(mediaButtonIntent);
366            }
367
368            @Override
369            public void onPlay() {
370                Callback.this.onPlay();
371            }
372
373            @Override
374            public void onPause() {
375                Callback.this.onPause();
376            }
377
378            @Override
379            public void onSkipToNext() {
380                Callback.this.onSkipToNext();
381            }
382
383            @Override
384            public void onSkipToPrevious() {
385                Callback.this.onSkipToPrevious();
386            }
387
388            @Override
389            public void onFastForward() {
390                Callback.this.onFastForward();
391            }
392
393            @Override
394            public void onRewind() {
395                Callback.this.onRewind();
396            }
397
398            @Override
399            public void onStop() {
400                Callback.this.onStop();
401            }
402
403            @Override
404            public void onSeekTo(long pos) {
405                Callback.this.onSeekTo(pos);
406            }
407
408            @Override
409            public void onSetRating(Object ratingObj) {
410                Callback.this.onSetRating(RatingCompat.fromRating(ratingObj));
411            }
412        }
413    }
414
415    /**
416     * Represents an ongoing session. This may be passed to apps by the session
417     * owner to allow them to create a {@link MediaControllerCompat} to communicate with
418     * the session.
419     */
420    public static final class Token implements Parcelable {
421        private final Parcelable mInner;
422
423        Token(Parcelable inner) {
424            mInner = inner;
425        }
426
427        @Override
428        public int describeContents() {
429            return mInner.describeContents();
430        }
431
432        @Override
433        public void writeToParcel(Parcel dest, int flags) {
434            dest.writeParcelable(mInner, flags);
435        }
436
437        /**
438         * Gets the underlying framework {@link android.media.session.MediaSession.Token} object.
439         * <p>
440         * This method is only supported on API 21+.
441         * </p>
442         *
443         * @return The underlying {@link android.media.session.MediaSession.Token} object,
444         * or null if none.
445         */
446        public Object getToken() {
447            return mInner;
448        }
449
450        public static final Parcelable.Creator<Token> CREATOR
451                = new Parcelable.Creator<Token>() {
452            @Override
453            public Token createFromParcel(Parcel in) {
454                return new Token(in.readParcelable(null));
455            }
456
457            @Override
458            public Token[] newArray(int size) {
459                return new Token[size];
460            }
461        };
462    }
463
464    interface MediaSessionImpl {
465        void setCallback(Callback callback, Handler handler);
466        void setFlags(int flags);
467        void setPlaybackToLocal(int stream);
468        void setPlaybackToRemote(VolumeProviderCompat volumeProvider);
469        void setActive(boolean active);
470        boolean isActive();
471        void sendSessionEvent(String event, Bundle extras);
472        void release();
473        Token getSessionToken();
474        void setPlaybackState(PlaybackStateCompat state);
475        void setMetadata(MediaMetadataCompat metadata);
476        Object getMediaSession();
477    }
478
479    // TODO: compatibility implementation
480    static class MediaSessionImplBase implements MediaSessionImpl {
481        @Override
482        public void setCallback(Callback callback, Handler handler) {
483        }
484
485        @Override
486        public void setFlags(int flags) {
487        }
488
489        @Override
490        public void setPlaybackToLocal(int stream) {
491        }
492
493        @Override
494        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
495        }
496
497        @Override
498        public void setActive(boolean active) {
499        }
500
501        @Override
502        public boolean isActive() {
503            return false;
504        }
505
506        @Override
507        public void sendSessionEvent(String event, Bundle extras) {
508        }
509
510        @Override
511        public void release() {
512        }
513
514        @Override
515        public Token getSessionToken() {
516            return null;
517        }
518
519        @Override
520        public void setPlaybackState(PlaybackStateCompat state) {
521        }
522
523        @Override
524        public void setMetadata(MediaMetadataCompat metadata) {
525        }
526
527        @Override
528        public Object getMediaSession() {
529            return null;
530        }
531    }
532
533    static class MediaSessionImplApi21 implements MediaSessionImpl {
534        private final Object mSessionObj;
535        private final Token mToken;
536
537        public MediaSessionImplApi21(Context context, String tag) {
538            mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
539            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
540        }
541
542        public MediaSessionImplApi21(Object mediaSession) {
543            mSessionObj = MediaSessionCompatApi21.verifySession(mediaSession);
544            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
545        }
546
547        @Override
548        public void setCallback(Callback callback, Handler handler) {
549            MediaSessionCompatApi21.setCallback(mSessionObj, callback.mCallbackObj, handler);
550        }
551
552        @Override
553        public void setFlags(int flags) {
554            MediaSessionCompatApi21.setFlags(mSessionObj, flags);
555        }
556
557        @Override
558        public void setPlaybackToLocal(int stream) {
559            MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream);
560        }
561
562        @Override
563        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
564            MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj,
565                    volumeProvider.getVolumeProvider());
566        }
567
568        @Override
569        public void setActive(boolean active) {
570            MediaSessionCompatApi21.setActive(mSessionObj, active);
571        }
572
573        @Override
574        public boolean isActive() {
575            return MediaSessionCompatApi21.isActive(mSessionObj);
576        }
577
578        @Override
579        public void sendSessionEvent(String event, Bundle extras) {
580            MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras);
581        }
582
583        @Override
584        public void release() {
585            MediaSessionCompatApi21.release(mSessionObj);
586        }
587
588        @Override
589        public Token getSessionToken() {
590            return mToken;
591        }
592
593        @Override
594        public void setPlaybackState(PlaybackStateCompat state) {
595            MediaSessionCompatApi21.setPlaybackState(mSessionObj, state.getPlaybackState());
596        }
597
598        @Override
599        public void setMetadata(MediaMetadataCompat metadata) {
600            MediaSessionCompatApi21.setMetadata(mSessionObj, metadata.getMediaMetadata());
601        }
602
603        @Override
604        public Object getMediaSession() {
605            return mSessionObj;
606        }
607    }
608}
609