MediaControllerCompat.java revision 23138c4b9be07abdab0cfdde2c62186359c9e7fa
1/*
2 * Copyright (C) 2014 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.support.v4.media.session;
18
19import android.content.Context;
20import android.os.Bundle;
21import android.os.Handler;
22import android.os.RemoteException;
23import android.os.ResultReceiver;
24import android.support.v4.media.MediaMetadataCompat;
25import android.support.v4.media.RatingCompat;
26import android.support.v4.media.VolumeProviderCompat;
27import android.text.TextUtils;
28import android.view.KeyEvent;
29
30/**
31 * Allows an app to interact with an ongoing media session. Media buttons and
32 * other commands can be sent to the session. A callback may be registered to
33 * receive updates from the session, such as metadata and play state changes.
34 * <p>
35 * A MediaController can be created if you have a {@link MediaSessionCompat.Token}
36 * from the session owner.
37 * <p>
38 * MediaController objects are thread-safe.
39 * <p>
40 * This is a helper for accessing features in {@link android.media.session.MediaSession}
41 * introduced after API level 4 in a backwards compatible fashion.
42 */
43public final class MediaControllerCompat {
44    private final MediaControllerImpl mImpl;
45
46    /**
47     * Creates a media controller from a session.
48     *
49     * @param session The session to be controlled.
50     */
51    public MediaControllerCompat(Context context, MediaSessionCompat session) {
52        if (session == null) {
53            throw new IllegalArgumentException("session must not be null");
54        }
55
56        if (android.os.Build.VERSION.SDK_INT >= 21) {
57            mImpl = new MediaControllerImplApi21(context, session);
58        } else {
59            mImpl = new MediaControllerImplBase();
60        }
61    }
62
63    /**
64     * Creates a media controller from a session token which may have
65     * been obtained from another process.
66     *
67     * @param sessionToken The token of the session to be controlled.
68     * @throws RemoteException if the session is not accessible.
69     */
70    public MediaControllerCompat(Context context, MediaSessionCompat.Token sessionToken)
71            throws RemoteException {
72        if (sessionToken == null) {
73            throw new IllegalArgumentException("sessionToken must not be null");
74        }
75
76        if (android.os.Build.VERSION.SDK_INT >= 21) {
77            mImpl = new MediaControllerImplApi21(context, sessionToken);
78        } else {
79            mImpl = new MediaControllerImplBase();
80        }
81    }
82
83    /**
84     * Get a {@link TransportControls} instance for this session.
85     *
86     * @return A controls instance
87     */
88    public TransportControls getTransportControls() {
89        return mImpl.getTransportControls();
90    }
91
92    /**
93     * Send the specified media button event to the session. Only media keys can
94     * be sent by this method, other keys will be ignored.
95     *
96     * @param keyEvent The media button event to dispatch.
97     * @return true if the event was sent to the session, false otherwise.
98     */
99    public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) {
100        if (keyEvent == null) {
101            throw new IllegalArgumentException("KeyEvent may not be null");
102        }
103        return mImpl.dispatchMediaButtonEvent(keyEvent);
104    }
105
106    /**
107     * Get the current playback state for this session.
108     *
109     * @return The current PlaybackState or null
110     */
111    public PlaybackStateCompat getPlaybackState() {
112        return mImpl.getPlaybackState();
113    }
114
115    /**
116     * Get the current metadata for this session.
117     *
118     * @return The current MediaMetadata or null.
119     */
120    public MediaMetadataCompat getMetadata() {
121        return mImpl.getMetadata();
122    }
123
124    /**
125     * Get the rating type supported by the session. One of:
126     * <ul>
127     * <li>{@link RatingCompat#RATING_NONE}</li>
128     * <li>{@link RatingCompat#RATING_HEART}</li>
129     * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
130     * <li>{@link RatingCompat#RATING_3_STARS}</li>
131     * <li>{@link RatingCompat#RATING_4_STARS}</li>
132     * <li>{@link RatingCompat#RATING_5_STARS}</li>
133     * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
134     * </ul>
135     *
136     * @return The supported rating type
137     */
138    public int getRatingType() {
139        return mImpl.getRatingType();
140    }
141
142    /**
143     * Get the current volume info for this session.
144     *
145     * @return The current volume info or null.
146     */
147    public VolumeInfo getVolumeInfo() {
148        return mImpl.getVolumeInfo();
149    }
150
151    /**
152     * Adds a callback to receive updates from the Session. Updates will be
153     * posted on the caller's thread.
154     *
155     * @param callback The callback object, must not be null.
156     */
157    public void addCallback(Callback callback) {
158        addCallback(callback, null);
159    }
160
161    /**
162     * Adds a callback to receive updates from the session. Updates will be
163     * posted on the specified handler's thread.
164     *
165     * @param callback The callback object, must not be null.
166     * @param handler The handler to post updates on. If null the callers thread
167     *            will be used.
168     */
169    public void addCallback(Callback callback, Handler handler) {
170        if (callback == null) {
171            throw new IllegalArgumentException("callback cannot be null");
172        }
173        if (handler == null) {
174            handler = new Handler();
175        }
176        mImpl.addCallback(callback, handler);
177    }
178
179    /**
180     * Stop receiving updates on the specified callback. If an update has
181     * already been posted you may still receive it after calling this method.
182     *
183     * @param callback The callback to remove
184     */
185    public void removeCallback(Callback callback) {
186        if (callback == null) {
187            throw new IllegalArgumentException("callback cannot be null");
188        }
189        mImpl.removeCallback(callback);
190    }
191
192    /**
193     * Sends a generic command to the session. It is up to the session creator
194     * to decide what commands and parameters they will support. As such,
195     * commands should only be sent to sessions that the controller owns.
196     *
197     * @param command The command to send
198     * @param params Any parameters to include with the command
199     * @param cb The callback to receive the result on
200     */
201    public void sendCommand(String command, Bundle params, ResultReceiver cb) {
202        if (TextUtils.isEmpty(command)) {
203            throw new IllegalArgumentException("command cannot be null or empty");
204        }
205        mImpl.sendCommand(command, params, cb);
206    }
207
208    /**
209     * Gets the underlying framework {@link android.media.session.MediaController} object.
210     * <p>
211     * This method is only supported on API 21+.
212     * </p>
213     *
214     * @return The underlying {@link android.media.session.MediaController} object,
215     * or null if none.
216     */
217    public Object getMediaController() {
218        return mImpl.getMediaController();
219    }
220
221    /**
222     * Callback for receiving updates on from the session. A Callback can be
223     * registered using {@link #addCallback}
224     */
225    public static abstract class Callback {
226        final Object mCallbackObj;
227
228        public Callback() {
229            if (android.os.Build.VERSION.SDK_INT >= 21) {
230                mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21());
231            } else {
232                mCallbackObj = null;
233            }
234        }
235
236        /**
237         * Override to handle the session being destroyed. The session is no
238         * longer valid after this call and calls to it will be ignored.
239         */
240        public void onSessionDestroyed() {
241        }
242
243        /**
244         * Override to handle custom events sent by the session owner without a
245         * specified interface. Controllers should only handle these for
246         * sessions they own.
247         *
248         * @param event The event from the session.
249         * @param extras Optional parameters for the event.
250         */
251        public void onSessionEvent(String event, Bundle extras) {
252        }
253
254        /**
255         * Override to handle changes in playback state.
256         *
257         * @param state The new playback state of the session
258         */
259        public void onPlaybackStateChanged(PlaybackStateCompat state) {
260        }
261
262        /**
263         * Override to handle changes to the current metadata.
264         *
265         * @param metadata The current metadata for the session or null if none.
266         * @see MediaMetadata
267         */
268        public void onMetadataChanged(MediaMetadataCompat metadata) {
269        }
270
271        private class StubApi21 implements MediaControllerCompatApi21.Callback {
272            @Override
273            public void onSessionDestroyed() {
274                Callback.this.onSessionDestroyed();
275            }
276
277            @Override
278            public void onSessionEvent(String event, Bundle extras) {
279                Callback.this.onSessionEvent(event, extras);
280            }
281
282            @Override
283            public void onPlaybackStateChanged(Object stateObj) {
284                Callback.this.onPlaybackStateChanged(
285                        PlaybackStateCompat.fromPlaybackState(stateObj));
286            }
287
288            @Override
289            public void onMetadataChanged(Object metadataObj) {
290                Callback.this.onMetadataChanged(
291                        MediaMetadataCompat.fromMediaMetadata(metadataObj));
292            }
293        }
294    }
295
296    /**
297     * Interface for controlling media playback on a session. This allows an app
298     * to send media transport commands to the session.
299     */
300    public static abstract class TransportControls {
301        TransportControls() {
302        }
303
304        /**
305         * Request that the player start its playback at its current position.
306         */
307        public abstract void play();
308
309        /**
310         * Request that the player pause its playback and stay at its current
311         * position.
312         */
313        public abstract void pause();
314
315        /**
316         * Request that the player stop its playback; it may clear its state in
317         * whatever way is appropriate.
318         */
319        public abstract void stop();
320
321        /**
322         * Move to a new location in the media stream.
323         *
324         * @param pos Position to move to, in milliseconds.
325         */
326        public abstract void seekTo(long pos);
327
328        /**
329         * Start fast forwarding. If playback is already fast forwarding this
330         * may increase the rate.
331         */
332        public abstract void fastForward();
333
334        /**
335         * Skip to the next item.
336         */
337        public abstract void skipToNext();
338
339        /**
340         * Start rewinding. If playback is already rewinding this may increase
341         * the rate.
342         */
343        public abstract void rewind();
344
345        /**
346         * Skip to the previous item.
347         */
348        public abstract void skipToPrevious();
349
350        /**
351         * Rate the current content. This will cause the rating to be set for
352         * the current user. The Rating type must match the type returned by
353         * {@link #getRatingType()}.
354         *
355         * @param rating The rating to set for the current content
356         */
357        public abstract void setRating(RatingCompat rating);
358    }
359
360    /**
361     * Holds information about the way volume is handled for this session.
362     */
363    public static final class VolumeInfo {
364        private final int mVolumeType;
365        // TODO update audio stream with AudioAttributes support version
366        private final int mAudioStream;
367        private final int mVolumeControl;
368        private final int mMaxVolume;
369        private final int mCurrentVolume;
370
371        VolumeInfo(int type, int stream, int control, int max, int current) {
372            mVolumeType = type;
373            mAudioStream = stream;
374            mVolumeControl = control;
375            mMaxVolume = max;
376            mCurrentVolume = current;
377        }
378
379        /**
380         * Get the type of volume handling, either local or remote. One of:
381         * <ul>
382         * <li>{@link MediaSessionCompat#VOLUME_TYPE_LOCAL}</li>
383         * <li>{@link MediaSessionCompat#VOLUME_TYPE_REMOTE}</li>
384         * </ul>
385         *
386         * @return The type of volume handling this session is using.
387         */
388        public int getVolumeType() {
389            return mVolumeType;
390        }
391
392        /**
393         * Get the stream this is currently controlling volume on. When the volume
394         * type is {@link MediaSessionCompat#VOLUME_TYPE_REMOTE} this value does not
395         * have meaning and should be ignored.
396         *
397         * @return The stream this session is playing on.
398         */
399        public int getAudioStream() {
400            return mAudioStream;
401        }
402
403        /**
404         * Get the type of volume control that can be used. One of:
405         * <ul>
406         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
407         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
408         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li>
409         * </ul>
410         *
411         * @return The type of volume control that may be used with this
412         *         session.
413         */
414        public int getVolumeControl() {
415            return mVolumeControl;
416        }
417
418        /**
419         * Get the maximum volume that may be set for this session.
420         *
421         * @return The maximum allowed volume where this session is playing.
422         */
423        public int getMaxVolume() {
424            return mMaxVolume;
425        }
426
427        /**
428         * Get the current volume for this session.
429         *
430         * @return The current volume where this session is playing.
431         */
432        public int getCurrentVolume() {
433            return mCurrentVolume;
434        }
435    }
436
437    interface MediaControllerImpl {
438        void addCallback(Callback callback, Handler handler);
439        void removeCallback(Callback callback);
440        boolean dispatchMediaButtonEvent(KeyEvent keyEvent);
441        TransportControls getTransportControls();
442        PlaybackStateCompat getPlaybackState();
443        MediaMetadataCompat getMetadata();
444        int getRatingType();
445        VolumeInfo getVolumeInfo();
446        void sendCommand(String command, Bundle params, ResultReceiver cb);
447        Object getMediaController();
448    }
449
450    // TODO: compatibility implementation
451    static class MediaControllerImplBase implements MediaControllerImpl {
452        @Override
453        public void addCallback(Callback callback, Handler handler) {
454        }
455
456        @Override
457        public void removeCallback(Callback callback) {
458        }
459
460        @Override
461        public boolean dispatchMediaButtonEvent(KeyEvent event) {
462            return false;
463        }
464
465        @Override
466        public TransportControls getTransportControls() {
467            return null;
468        }
469
470        @Override
471        public PlaybackStateCompat getPlaybackState() {
472            return null;
473        }
474
475        @Override
476        public MediaMetadataCompat getMetadata() {
477            return null;
478        }
479
480        @Override
481        public int getRatingType() {
482            return 0;
483        }
484
485        @Override
486        public VolumeInfo getVolumeInfo() {
487            return null;
488        }
489
490        @Override
491        public void sendCommand(String command, Bundle params, ResultReceiver cb) {
492        }
493
494        @Override
495        public Object getMediaController() {
496            return null;
497        }
498    }
499
500    static class MediaControllerImplApi21 implements MediaControllerImpl {
501        private final Object mControllerObj;
502
503        public MediaControllerImplApi21(Context context, MediaSessionCompat session) {
504            mControllerObj = MediaControllerCompatApi21.fromToken(context,
505                    session.getSessionToken().getToken());
506        }
507
508        public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
509                throws RemoteException {
510            // TODO: refactor framework implementation
511            mControllerObj = MediaControllerCompatApi21.fromToken(context,
512                    sessionToken.getToken());
513            if (mControllerObj == null) throw new RemoteException();
514        }
515
516        @Override
517        public void addCallback(Callback callback, Handler handler) {
518            MediaControllerCompatApi21.addCallback(mControllerObj, callback.mCallbackObj, handler);
519        }
520
521        @Override
522        public void removeCallback(Callback callback) {
523            MediaControllerCompatApi21.removeCallback(mControllerObj, callback.mCallbackObj);
524        }
525
526        @Override
527        public boolean dispatchMediaButtonEvent(KeyEvent event) {
528            return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event);
529        }
530
531        @Override
532        public TransportControls getTransportControls() {
533            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
534            return controlsObj != null ? new TransportControlsApi21(controlsObj) : null;
535        }
536
537        @Override
538        public PlaybackStateCompat getPlaybackState() {
539            Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj);
540            return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null;
541        }
542
543        @Override
544        public MediaMetadataCompat getMetadata() {
545            Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj);
546            return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null;
547        }
548
549        @Override
550        public int getRatingType() {
551            return MediaControllerCompatApi21.getRatingType(mControllerObj);
552        }
553
554        @Override
555        public VolumeInfo getVolumeInfo() {
556            Object volumeInfoObj = MediaControllerCompatApi21.getVolumeInfo(mControllerObj);
557            return volumeInfoObj != null ? new VolumeInfo(
558                    MediaControllerCompatApi21.VolumeInfo.getVolumeType(volumeInfoObj),
559                    MediaControllerCompatApi21.VolumeInfo.getLegacyAudioStream(volumeInfoObj),
560                    MediaControllerCompatApi21.VolumeInfo.getVolumeControl(volumeInfoObj),
561                    MediaControllerCompatApi21.VolumeInfo.getMaxVolume(volumeInfoObj),
562                    MediaControllerCompatApi21.VolumeInfo.getCurrentVolume(volumeInfoObj)) : null;
563        }
564
565        @Override
566        public void sendCommand(String command, Bundle params, ResultReceiver cb) {
567            MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb);
568        }
569
570        @Override
571        public Object getMediaController() {
572            return mControllerObj;
573        }
574    }
575
576    static class TransportControlsApi21 extends TransportControls {
577        private final Object mControlsObj;
578
579        public TransportControlsApi21(Object controlsObj) {
580            mControlsObj = controlsObj;
581        }
582
583        @Override
584        public void play() {
585            MediaControllerCompatApi21.TransportControls.play(mControlsObj);
586        }
587
588        @Override
589        public void pause() {
590            MediaControllerCompatApi21.TransportControls.pause(mControlsObj);
591        }
592
593        @Override
594        public void stop() {
595            MediaControllerCompatApi21.TransportControls.stop(mControlsObj);
596        }
597
598        @Override
599        public void seekTo(long pos) {
600            MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos);
601        }
602
603        @Override
604        public void fastForward() {
605            MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj);
606        }
607
608        @Override
609        public void rewind() {
610            MediaControllerCompatApi21.TransportControls.rewind(mControlsObj);
611        }
612
613        @Override
614        public void skipToNext() {
615            MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj);
616        }
617
618        @Override
619        public void skipToPrevious() {
620            MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj);
621        }
622
623        @Override
624        public void setRating(RatingCompat rating) {
625            MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
626                    rating != null ? rating.getRating() : null);
627        }
628    }
629}
630