MediaSessionCompat.java revision 24fa6c0dd42df057729e1a258388183f94da7f82
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 #addCallback(Callback)}. To receive transport control
48 * commands a {@link TransportControlsCallback} must be set with
49 * {@link #addTransportControlsCallback}.
50 * <p>
51 * When an app is finished performing playback it must call {@link #release()}
52 * to clean up the session and notify any controllers.
53 * <p>
54 * MediaSession objects are thread safe.
55 * <p>
56 * This is a helper for accessing features in {@link android.media.session.MediaSession}
57 * introduced after API level 4 in a backwards compatible fashion.
58 */
59public class MediaSessionCompat {
60    private final MediaSessionImpl mImpl;
61
62    /**
63     * Set this flag on the session to indicate that it can handle media button
64     * events.
65     */
66    public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
67
68    /**
69     * Set this flag on the session to indicate that it handles transport
70     * control commands through a {@link TransportControlsCallback}.
71     * The callback can be retrieved by calling {@link #addTransportControlsCallback}.
72     */
73    public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
74
75    /**
76     * The session uses local playback.
77     */
78    public static final int VOLUME_TYPE_LOCAL = 1;
79
80    /**
81     * The session uses remote playback.
82     */
83    public static final int VOLUME_TYPE_REMOTE = 2;
84
85    /**
86     * Creates a new session.
87     *
88     * @param context The context.
89     * @param tag A short name for debugging purposes.
90     */
91    public MediaSessionCompat(Context context, String tag) {
92        if (context == null) {
93            throw new IllegalArgumentException("context must not be null");
94        }
95        if (TextUtils.isEmpty(tag)) {
96            throw new IllegalArgumentException("tag must not be null or empty");
97        }
98
99        if (android.os.Build.VERSION.SDK_INT >= 21) {
100            mImpl = new MediaSessionImplApi21(context, tag);
101        } else {
102            mImpl = new MediaSessionImplBase();
103        }
104    }
105
106    /**
107     * Add a callback to receive updates on for the MediaSession. This includes
108     * media button and volume events. The caller's thread will be used to post
109     * events.
110     *
111     * @param callback The callback object
112     */
113    public void addCallback(Callback callback) {
114        addCallback(callback, null);
115    }
116
117    /**
118     * Add a callback to receive updates for the MediaSession. This includes
119     * media button and volume events.
120     *
121     * @param callback The callback to receive updates on.
122     * @param handler The handler that events should be posted on.
123     */
124    public void addCallback(Callback callback, Handler handler) {
125        if (callback == null) {
126            throw new IllegalArgumentException("callback must not be null");
127        }
128        mImpl.addCallback(callback, handler != null ? handler : new Handler());
129    }
130
131    /**
132     * Remove a callback. It will no longer receive updates.
133     *
134     * @param callback The callback to remove.
135     */
136    public void removeCallback(Callback callback) {
137        if (callback == null) {
138            throw new IllegalArgumentException("callback must not be null");
139        }
140        mImpl.removeCallback(callback);
141    }
142
143    /**
144     * Set any flags for the session.
145     *
146     * @param flags The flags to set for this session.
147     */
148    public void setFlags(int flags) {
149        mImpl.setFlags(flags);
150    }
151
152    /**
153     * Set the stream this session is playing on. This will affect the system's
154     * volume handling for this session. If {@link #setPlaybackToRemote} was
155     * previously called it will stop receiving volume commands and the system
156     * will begin sending volume changes to the appropriate stream.
157     * <p>
158     * By default sessions are on {@link AudioManager#STREAM_MUSIC}.
159     *
160     * @param stream The {@link AudioManager} stream this session is playing on.
161     */
162    public void setPlaybackToLocal(int stream) {
163        mImpl.setPlaybackToLocal(stream);
164    }
165
166    /**
167     * Configure this session to use remote volume handling. This must be called
168     * to receive volume button events, otherwise the system will adjust the
169     * current stream volume for this session. If {@link #setPlaybackToLocal}
170     * was previously called that stream will stop receiving volume changes for
171     * this session.
172     *
173     * @param volumeProvider The provider that will handle volume changes. May
174     *            not be null.
175     */
176    public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
177        if (volumeProvider == null) {
178            throw new IllegalArgumentException("volumeProvider may not be null!");
179        }
180        mImpl.setPlaybackToRemote(volumeProvider);
181    }
182
183    /**
184     * Set if this session is currently active and ready to receive commands. If
185     * set to false your session's controller may not be discoverable. You must
186     * set the session to active before it can start receiving media button
187     * events or transport commands.
188     *
189     * @param active Whether this session is active or not.
190     */
191    public void setActive(boolean active) {
192        mImpl.setActive(active);
193    }
194
195    /**
196     * Get the current active state of this session.
197     *
198     * @return True if the session is active, false otherwise.
199     */
200    public boolean isActive() {
201        return mImpl.isActive();
202    }
203
204    /**
205     * Send a proprietary event to all MediaControllers listening to this
206     * Session. It's up to the Controller/Session owner to determine the meaning
207     * of any events.
208     *
209     * @param event The name of the event to send
210     * @param extras Any extras included with the event
211     */
212    public void sendSessionEvent(String event, Bundle extras) {
213        if (TextUtils.isEmpty(event)) {
214            throw new IllegalArgumentException("event cannot be null or empty");
215        }
216        mImpl.sendSessionEvent(event, extras);
217    }
218
219    /**
220     * This must be called when an app has finished performing playback. If
221     * playback is expected to start again shortly the session can be left open,
222     * but it must be released if your activity or service is being destroyed.
223     */
224    public void release() {
225        mImpl.release();
226    }
227
228    /**
229     * Retrieve a token object that can be used by apps to create a
230     * {@link MediaControllerCompat} for interacting with this session. The owner of
231     * the session is responsible for deciding how to distribute these tokens.
232     *
233     * @return A token that can be used to create a MediaController for this
234     *         session.
235     */
236    public Token getSessionToken() {
237        return mImpl.getSessionToken();
238    }
239
240    /**
241     * Add a callback to receive transport controls on, such as play, rewind, or
242     * fast forward.
243     *
244     * @param callback The callback object.
245     */
246    public void addTransportControlsCallback(TransportControlsCallback callback) {
247        addTransportControlsCallback(callback, null);
248    }
249
250    /**
251     * Add a callback to receive transport controls on, such as play, rewind, or
252     * fast forward. The updates will be posted to the specified handler. If no
253     * handler is provided they will be posted to the caller's thread.
254     *
255     * @param callback The callback to receive updates on.
256     * @param handler The handler to post the updates on.
257     */
258    public void addTransportControlsCallback(TransportControlsCallback callback,
259            Handler handler) {
260        if (callback == null) {
261            throw new IllegalArgumentException("Callback cannot be null");
262        }
263        mImpl.addTransportControlsCallback(callback, handler != null ? handler : new Handler());
264    }
265
266    /**
267     * Stop receiving transport controls on the specified callback. If an update
268     * has already been posted you may still receive it after this call returns.
269     *
270     * @param callback The callback to stop receiving updates on.
271     */
272    public void removeTransportControlsCallback(TransportControlsCallback callback) {
273        if (callback == null) {
274            throw new IllegalArgumentException("Callback cannot be null");
275        }
276        mImpl.removeTransportControlsCallback(callback);
277    }
278
279    /**
280     * Update the current playback state.
281     *
282     * @param state The current state of playback
283     */
284    public void setPlaybackState(PlaybackStateCompat state) {
285        mImpl.setPlaybackState(state);
286    }
287
288    /**
289     * Update the current metadata. New metadata can be created using
290     * {@link android.media.MediaMetadata.Builder}.
291     *
292     * @param metadata The new metadata
293     */
294    public void setMetadata(MediaMetadataCompat metadata) {
295        mImpl.setMetadata(metadata);
296    }
297
298    /**
299     * Gets the underlying framework {@link android.media.session.MediaSession} object.
300     * <p>
301     * This method is only supported on API 21+.
302     * </p>
303     *
304     * @return The underlying {@link android.media.session.MediaSession} object,
305     * or null if none.
306     */
307    public Object getMediaSession() {
308        return mImpl.getMediaSession();
309    }
310
311    /**
312     * Receives generic commands or updates from controllers and the system.
313     * Callbacks may be registered using {@link #addCallback}.
314     */
315    public abstract static class Callback {
316        final Object mCallbackObj;
317
318        public Callback() {
319            if (android.os.Build.VERSION.SDK_INT >= 21) {
320                mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21());
321            } else {
322                mCallbackObj = null;
323            }
324        }
325
326        /**
327         * Called when a media button is pressed and this session has the
328         * highest priority or a controller sends a media button event to the
329         * session. TODO determine if using Intents identical to the ones
330         * RemoteControlClient receives is useful
331         * <p>
332         * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
333         * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
334         *
335         * @param mediaButtonIntent an intent containing the KeyEvent as an
336         *            extra
337         */
338        public void onMediaButtonEvent(Intent mediaButtonIntent) {
339        }
340
341        /**
342         * Called when a controller has sent a custom command to this session.
343         * The owner of the session may handle custom commands but is not
344         * required to.
345         *
346         * @param command The command name.
347         * @param extras Optional parameters for the command, may be null.
348         * @param cb A result receiver to which a result may be sent by the command, may be null.
349         */
350        public void onControlCommand(String command, Bundle extras, ResultReceiver cb) {
351        }
352
353        private class StubApi21 implements MediaSessionCompatApi21.Callback {
354            @Override
355            public void onMediaButtonEvent(Intent mediaButtonIntent) {
356                Callback.this.onMediaButtonEvent(mediaButtonIntent);
357            }
358
359            @Override
360            public void onControlCommand(
361                    String command, Bundle extras, ResultReceiver cb) {
362                Callback.this.onControlCommand(command, extras, cb);
363            }
364        }
365    }
366
367    /**
368     * Receives transport control commands. Callbacks may be registered using
369     * {@link #addTransportControlsCallback}.
370     */
371    public static abstract class TransportControlsCallback {
372        final Object mCallbackObj;
373
374        public TransportControlsCallback() {
375            if (android.os.Build.VERSION.SDK_INT >= 21) {
376                mCallbackObj = MediaSessionCompatApi21.createTransportControlsCallback(
377                        new StubApi21());
378            } else {
379                mCallbackObj = null;
380            }
381        }
382
383        /**
384         * Override to handle requests to begin playback.
385         */
386        public void onPlay() {
387        }
388
389        /**
390         * Override to handle requests to pause playback.
391         */
392        public void onPause() {
393        }
394
395        /**
396         * Override to handle requests to skip to the next media item.
397         */
398        public void onSkipToNext() {
399        }
400
401        /**
402         * Override to handle requests to skip to the previous media item.
403         */
404        public void onSkipToPrevious() {
405        }
406
407        /**
408         * Override to handle requests to fast forward.
409         */
410        public void onFastForward() {
411        }
412
413        /**
414         * Override to handle requests to rewind.
415         */
416        public void onRewind() {
417        }
418
419        /**
420         * Override to handle requests to stop playback.
421         */
422        public void onStop() {
423        }
424
425        /**
426         * Override to handle requests to seek to a specific position in ms.
427         *
428         * @param pos New position to move to, in milliseconds.
429         */
430        public void onSeekTo(long pos) {
431        }
432
433        /**
434         * Override to handle the item being rated.
435         *
436         * @param rating
437         */
438        public void onSetRating(RatingCompat rating) {
439        }
440
441        private class StubApi21 implements MediaSessionCompatApi21.TransportControlsCallback {
442            @Override
443            public void onPlay() {
444                TransportControlsCallback.this.onPlay();
445            }
446
447            @Override
448            public void onPause() {
449                TransportControlsCallback.this.onPause();
450            }
451
452            @Override
453            public void onSkipToNext() {
454                TransportControlsCallback.this.onSkipToNext();
455            }
456
457            @Override
458            public void onSkipToPrevious() {
459                TransportControlsCallback.this.onSkipToPrevious();
460            }
461
462            @Override
463            public void onFastForward() {
464                TransportControlsCallback.this.onFastForward();
465            }
466
467            @Override
468            public void onRewind() {
469                TransportControlsCallback.this.onRewind();
470            }
471
472            @Override
473            public void onStop() {
474                TransportControlsCallback.this.onStop();
475            }
476
477            @Override
478            public void onSeekTo(long pos) {
479                TransportControlsCallback.this.onSeekTo(pos);
480            }
481
482            @Override
483            public void onSetRating(Object ratingObj) {
484                TransportControlsCallback.this.onSetRating(RatingCompat.fromRating(ratingObj));
485            }
486        }
487    }
488
489    /**
490     * Represents an ongoing session. This may be passed to apps by the session
491     * owner to allow them to create a {@link MediaControllerCompat} to communicate with
492     * the session.
493     */
494    public static final class Token implements Parcelable {
495        private final Parcelable mInner;
496
497        Token(Parcelable inner) {
498            mInner = inner;
499        }
500
501        @Override
502        public int describeContents() {
503            return mInner.describeContents();
504        }
505
506        @Override
507        public void writeToParcel(Parcel dest, int flags) {
508            dest.writeParcelable(mInner, flags);
509        }
510
511        /**
512         * Gets the underlying framework {@link android.media.session.MediaSessionToken} object.
513         * <p>
514         * This method is only supported on API 21+.
515         * </p>
516         *
517         * @return The underlying {@link android.media.session.MediaSessionToken} object,
518         * or null if none.
519         */
520        public Object getToken() {
521            return mInner;
522        }
523
524        public static final Parcelable.Creator<Token> CREATOR
525                = new Parcelable.Creator<Token>() {
526            @Override
527            public Token createFromParcel(Parcel in) {
528                return new Token(in.readParcelable(null));
529            }
530
531            @Override
532            public Token[] newArray(int size) {
533                return new Token[size];
534            }
535        };
536    }
537
538    interface MediaSessionImpl {
539        void addCallback(Callback callback, Handler handler);
540        void removeCallback(Callback callback);
541        void setFlags(int flags);
542        void setPlaybackToLocal(int stream);
543        void setPlaybackToRemote(VolumeProviderCompat volumeProvider);
544        void setActive(boolean active);
545        boolean isActive();
546        void sendSessionEvent(String event, Bundle extras);
547        void release();
548        Token getSessionToken();
549        void addTransportControlsCallback(TransportControlsCallback callback, Handler handler);
550        void removeTransportControlsCallback(TransportControlsCallback callback);
551        void setPlaybackState(PlaybackStateCompat state);
552        void setMetadata(MediaMetadataCompat metadata);
553        Object getMediaSession();
554    }
555
556    // TODO: compatibility implementation
557    static class MediaSessionImplBase implements MediaSessionImpl {
558        @Override
559        public void addCallback(Callback callback, Handler handler) {
560        }
561
562        @Override
563        public void removeCallback(Callback callback) {
564        }
565
566        @Override
567        public void setFlags(int flags) {
568        }
569
570        @Override
571        public void setPlaybackToLocal(int stream) {
572        }
573
574        @Override
575        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
576        }
577
578        @Override
579        public void setActive(boolean active) {
580        }
581
582        @Override
583        public boolean isActive() {
584            return false;
585        }
586
587        @Override
588        public void sendSessionEvent(String event, Bundle extras) {
589        }
590
591        @Override
592        public void release() {
593        }
594
595        @Override
596        public Token getSessionToken() {
597            return null;
598        }
599
600        @Override
601        public void addTransportControlsCallback(TransportControlsCallback callback,
602                Handler handler) {
603        }
604
605        @Override
606        public void removeTransportControlsCallback(TransportControlsCallback callback) {
607        }
608
609        @Override
610        public void setPlaybackState(PlaybackStateCompat state) {
611        }
612
613        @Override
614        public void setMetadata(MediaMetadataCompat metadata) {
615        }
616
617        @Override
618        public Object getMediaSession() {
619            return null;
620        }
621    }
622
623    static class MediaSessionImplApi21 implements MediaSessionImpl {
624        private final Object mSessionObj;
625        private final Token mToken;
626
627        public MediaSessionImplApi21(Context context, String tag) {
628            mSessionObj = MediaSessionCompatApi21.createSession(context, tag);
629            mToken = new Token(MediaSessionCompatApi21.getSessionToken(mSessionObj));
630        }
631
632        @Override
633        public void addCallback(Callback callback, Handler handler) {
634            MediaSessionCompatApi21.addCallback(mSessionObj, callback.mCallbackObj, handler);
635        }
636
637        @Override
638        public void removeCallback(Callback callback) {
639            MediaSessionCompatApi21.removeCallback(mSessionObj, callback.mCallbackObj);
640        }
641
642        @Override
643        public void setFlags(int flags) {
644            MediaSessionCompatApi21.setFlags(mSessionObj, flags);
645        }
646
647        @Override
648        public void setPlaybackToLocal(int stream) {
649            MediaSessionCompatApi21.setPlaybackToLocal(mSessionObj, stream);
650        }
651
652        @Override
653        public void setPlaybackToRemote(VolumeProviderCompat volumeProvider) {
654            MediaSessionCompatApi21.setPlaybackToRemote(mSessionObj,
655                    volumeProvider.getVolumeProvider());
656        }
657
658        @Override
659        public void setActive(boolean active) {
660            MediaSessionCompatApi21.setActive(mSessionObj, active);
661        }
662
663        @Override
664        public boolean isActive() {
665            return MediaSessionCompatApi21.isActive(mSessionObj);
666        }
667
668        @Override
669        public void sendSessionEvent(String event, Bundle extras) {
670            MediaSessionCompatApi21.sendSessionEvent(mSessionObj, event, extras);
671        }
672
673        @Override
674        public void release() {
675            MediaSessionCompatApi21.release(mSessionObj);
676        }
677
678        @Override
679        public Token getSessionToken() {
680            return mToken;
681        }
682
683        @Override
684        public void addTransportControlsCallback(TransportControlsCallback callback,
685                Handler handler) {
686            MediaSessionCompatApi21.addTransportControlsCallback(
687                    mSessionObj, callback.mCallbackObj, handler);
688        }
689
690        @Override
691        public void removeTransportControlsCallback(TransportControlsCallback callback) {
692            MediaSessionCompatApi21.removeTransportControlsCallback(
693                    mSessionObj, callback.mCallbackObj);
694        }
695
696        @Override
697        public void setPlaybackState(PlaybackStateCompat state) {
698            MediaSessionCompatApi21.setPlaybackState(mSessionObj, state.getPlaybackState());
699        }
700
701        @Override
702        public void setMetadata(MediaMetadataCompat metadata) {
703            MediaSessionCompatApi21.setMetadata(mSessionObj, metadata.getMediaMetadata());
704        }
705
706        @Override
707        public Object getMediaSession() {
708            return mSessionObj;
709        }
710    }
711}
712