MediaControllerCompat.java revision 1435afe32073dee10e721dfb6122ce6a194a6412
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 sendControlCommand(String command, Bundle params, ResultReceiver cb) {
202        if (TextUtils.isEmpty(command)) {
203            throw new IllegalArgumentException("command cannot be null or empty");
204        }
205        mImpl.sendControlCommand(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 custom events sent by the session owner without a
238         * specified interface. Controllers should only handle these for
239         * sessions they own.
240         *
241         * @param event The event from the session.
242         * @param extras Optional parameters for the event.
243         */
244        public void onSessionEvent(String event, Bundle extras) {
245        }
246
247        /**
248         * Override to handle changes in playback state.
249         *
250         * @param state The new playback state of the session
251         */
252        public void onPlaybackStateChanged(PlaybackStateCompat state) {
253        }
254
255        /**
256         * Override to handle changes to the current metadata.
257         *
258         * @param metadata The current metadata for the session or null if none.
259         * @see MediaMetadata
260         */
261        public void onMetadataChanged(MediaMetadataCompat metadata) {
262        }
263
264        private class StubApi21 implements MediaControllerCompatApi21.Callback {
265            @Override
266            public void onSessionEvent(String event, Bundle extras) {
267                Callback.this.onSessionEvent(event, extras);
268            }
269
270            @Override
271            public void onPlaybackStateChanged(Object stateObj) {
272                Callback.this.onPlaybackStateChanged(
273                        PlaybackStateCompat.fromPlaybackState(stateObj));
274            }
275
276            @Override
277            public void onMetadataChanged(Object metadataObj) {
278                Callback.this.onMetadataChanged(
279                        MediaMetadataCompat.fromMediaMetadata(metadataObj));
280            }
281        }
282    }
283
284    /**
285     * Interface for controlling media playback on a session. This allows an app
286     * to send media transport commands to the session.
287     */
288    public static abstract class TransportControls {
289        TransportControls() {
290        }
291
292        /**
293         * Request that the player start its playback at its current position.
294         */
295        public abstract void play();
296
297        /**
298         * Request that the player pause its playback and stay at its current
299         * position.
300         */
301        public abstract void pause();
302
303        /**
304         * Request that the player stop its playback; it may clear its state in
305         * whatever way is appropriate.
306         */
307        public abstract void stop();
308
309        /**
310         * Move to a new location in the media stream.
311         *
312         * @param pos Position to move to, in milliseconds.
313         */
314        public abstract void seekTo(long pos);
315
316        /**
317         * Start fast forwarding. If playback is already fast forwarding this
318         * may increase the rate.
319         */
320        public abstract void fastForward();
321
322        /**
323         * Skip to the next item.
324         */
325        public abstract void skipToNext();
326
327        /**
328         * Start rewinding. If playback is already rewinding this may increase
329         * the rate.
330         */
331        public abstract void rewind();
332
333        /**
334         * Skip to the previous item.
335         */
336        public abstract void skipToPrevious();
337
338        /**
339         * Rate the current content. This will cause the rating to be set for
340         * the current user. The Rating type must match the type returned by
341         * {@link #getRatingType()}.
342         *
343         * @param rating The rating to set for the current content
344         */
345        public abstract void setRating(RatingCompat rating);
346    }
347
348    /**
349     * Holds information about the way volume is handled for this session.
350     */
351    public static final class VolumeInfo {
352        private final int mVolumeType;
353        // TODO update audio stream with AudioAttributes support version
354        private final int mAudioStream;
355        private final int mVolumeControl;
356        private final int mMaxVolume;
357        private final int mCurrentVolume;
358
359        VolumeInfo(int type, int stream, int control, int max, int current) {
360            mVolumeType = type;
361            mAudioStream = stream;
362            mVolumeControl = control;
363            mMaxVolume = max;
364            mCurrentVolume = current;
365        }
366
367        /**
368         * Get the type of volume handling, either local or remote. One of:
369         * <ul>
370         * <li>{@link MediaSessionCompat#VOLUME_TYPE_LOCAL}</li>
371         * <li>{@link MediaSessionCompat#VOLUME_TYPE_REMOTE}</li>
372         * </ul>
373         *
374         * @return The type of volume handling this session is using.
375         */
376        public int getVolumeType() {
377            return mVolumeType;
378        }
379
380        /**
381         * Get the stream this is currently controlling volume on. When the volume
382         * type is {@link MediaSessionCompat#VOLUME_TYPE_REMOTE} this value does not
383         * have meaning and should be ignored.
384         *
385         * @return The stream this session is playing on.
386         */
387        public int getAudioStream() {
388            return mAudioStream;
389        }
390
391        /**
392         * Get the type of volume control that can be used. One of:
393         * <ul>
394         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
395         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
396         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li>
397         * </ul>
398         *
399         * @return The type of volume control that may be used with this
400         *         session.
401         */
402        public int getVolumeControl() {
403            return mVolumeControl;
404        }
405
406        /**
407         * Get the maximum volume that may be set for this session.
408         *
409         * @return The maximum allowed volume where this session is playing.
410         */
411        public int getMaxVolume() {
412            return mMaxVolume;
413        }
414
415        /**
416         * Get the current volume for this session.
417         *
418         * @return The current volume where this session is playing.
419         */
420        public int getCurrentVolume() {
421            return mCurrentVolume;
422        }
423    }
424
425    interface MediaControllerImpl {
426        void addCallback(Callback callback, Handler handler);
427        void removeCallback(Callback callback);
428        boolean dispatchMediaButtonEvent(KeyEvent keyEvent);
429        TransportControls getTransportControls();
430        PlaybackStateCompat getPlaybackState();
431        MediaMetadataCompat getMetadata();
432        int getRatingType();
433        VolumeInfo getVolumeInfo();
434        void sendControlCommand(String command, Bundle params, ResultReceiver cb);
435        Object getMediaController();
436    }
437
438    // TODO: compatibility implementation
439    static class MediaControllerImplBase implements MediaControllerImpl {
440        @Override
441        public void addCallback(Callback callback, Handler handler) {
442        }
443
444        @Override
445        public void removeCallback(Callback callback) {
446        }
447
448        @Override
449        public boolean dispatchMediaButtonEvent(KeyEvent event) {
450            return false;
451        }
452
453        @Override
454        public TransportControls getTransportControls() {
455            return null;
456        }
457
458        @Override
459        public PlaybackStateCompat getPlaybackState() {
460            return null;
461        }
462
463        @Override
464        public MediaMetadataCompat getMetadata() {
465            return null;
466        }
467
468        @Override
469        public int getRatingType() {
470            return 0;
471        }
472
473        @Override
474        public VolumeInfo getVolumeInfo() {
475            return null;
476        }
477
478        @Override
479        public void sendControlCommand(String command, Bundle params, ResultReceiver cb) {
480        }
481
482        @Override
483        public Object getMediaController() {
484            return null;
485        }
486    }
487
488    static class MediaControllerImplApi21 implements MediaControllerImpl {
489        private final Object mControllerObj;
490
491        public MediaControllerImplApi21(Context context, MediaSessionCompat session) {
492            mControllerObj = MediaControllerCompatApi21.fromToken(
493                    session.getSessionToken().getToken());
494        }
495
496        public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
497                throws RemoteException {
498            // TODO: refactor framework implementation
499            mControllerObj = MediaControllerCompatApi21.fromToken(
500                    sessionToken.getToken());
501            if (mControllerObj == null) throw new RemoteException();
502        }
503
504        @Override
505        public void addCallback(Callback callback, Handler handler) {
506            MediaControllerCompatApi21.addCallback(mControllerObj, callback.mCallbackObj, handler);
507        }
508
509        @Override
510        public void removeCallback(Callback callback) {
511            MediaControllerCompatApi21.removeCallback(mControllerObj, callback.mCallbackObj);
512        }
513
514        @Override
515        public boolean dispatchMediaButtonEvent(KeyEvent event) {
516            return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event);
517        }
518
519        @Override
520        public TransportControls getTransportControls() {
521            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
522            return controlsObj != null ? new TransportControlsApi21(controlsObj) : null;
523        }
524
525        @Override
526        public PlaybackStateCompat getPlaybackState() {
527            Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj);
528            return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null;
529        }
530
531        @Override
532        public MediaMetadataCompat getMetadata() {
533            Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj);
534            return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null;
535        }
536
537        @Override
538        public int getRatingType() {
539            return MediaControllerCompatApi21.getRatingType(mControllerObj);
540        }
541
542        @Override
543        public VolumeInfo getVolumeInfo() {
544            Object volumeInfoObj = MediaControllerCompatApi21.getVolumeInfo(mControllerObj);
545            return volumeInfoObj != null ? new VolumeInfo(
546                    MediaControllerCompatApi21.VolumeInfo.getVolumeType(volumeInfoObj),
547                    MediaControllerCompatApi21.VolumeInfo.getLegacyAudioStream(volumeInfoObj),
548                    MediaControllerCompatApi21.VolumeInfo.getVolumeControl(volumeInfoObj),
549                    MediaControllerCompatApi21.VolumeInfo.getMaxVolume(volumeInfoObj),
550                    MediaControllerCompatApi21.VolumeInfo.getCurrentVolume(volumeInfoObj)) : null;
551        }
552
553        @Override
554        public void sendControlCommand(String command, Bundle params, ResultReceiver cb) {
555            MediaControllerCompatApi21.sendControlCommand(mControllerObj,
556                    command, params, cb);
557        }
558
559        @Override
560        public Object getMediaController() {
561            return mControllerObj;
562        }
563    }
564
565    static class TransportControlsApi21 extends TransportControls {
566        private final Object mControlsObj;
567
568        public TransportControlsApi21(Object controlsObj) {
569            mControlsObj = controlsObj;
570        }
571
572        @Override
573        public void play() {
574            MediaControllerCompatApi21.TransportControls.play(mControlsObj);
575        }
576
577        @Override
578        public void pause() {
579            MediaControllerCompatApi21.TransportControls.pause(mControlsObj);
580        }
581
582        @Override
583        public void stop() {
584            MediaControllerCompatApi21.TransportControls.stop(mControlsObj);
585        }
586
587        @Override
588        public void seekTo(long pos) {
589            MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos);
590        }
591
592        @Override
593        public void fastForward() {
594            MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj);
595        }
596
597        @Override
598        public void rewind() {
599            MediaControllerCompatApi21.TransportControls.rewind(mControlsObj);
600        }
601
602        @Override
603        public void skipToNext() {
604            MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj);
605        }
606
607        @Override
608        public void skipToPrevious() {
609            MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj);
610        }
611
612        @Override
613        public void setRating(RatingCompat rating) {
614            MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
615                    rating != null ? rating.getRating() : null);
616        }
617    }
618}
619