MediaControllerCompat.java revision 24fa6c0dd42df057729e1a258388183f94da7f82
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        private final int mAudioStream;
354        private final int mVolumeControl;
355        private final int mMaxVolume;
356        private final int mCurrentVolume;
357
358        VolumeInfo(int type, int stream, int control, int max, int current) {
359            mVolumeType = type;
360            mAudioStream = stream;
361            mVolumeControl = control;
362            mMaxVolume = max;
363            mCurrentVolume = current;
364        }
365
366        /**
367         * Get the type of volume handling, either local or remote. One of:
368         * <ul>
369         * <li>{@link MediaSessionCompat#VOLUME_TYPE_LOCAL}</li>
370         * <li>{@link MediaSessionCompat#VOLUME_TYPE_REMOTE}</li>
371         * </ul>
372         *
373         * @return The type of volume handling this session is using.
374         */
375        public int getVolumeType() {
376            return mVolumeType;
377        }
378
379        /**
380         * Get the stream this is currently controlling volume on. When the volume
381         * type is {@link MediaSessionCompat#VOLUME_TYPE_REMOTE} this value does not
382         * have meaning and should be ignored.
383         *
384         * @return The stream this session is playing on.
385         */
386        public int getAudioStream() {
387            return mAudioStream;
388        }
389
390        /**
391         * Get the type of volume control that can be used. One of:
392         * <ul>
393         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
394         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
395         * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li>
396         * </ul>
397         *
398         * @return The type of volume control that may be used with this
399         *         session.
400         */
401        public int getVolumeControl() {
402            return mVolumeControl;
403        }
404
405        /**
406         * Get the maximum volume that may be set for this session.
407         *
408         * @return The maximum allowed volume where this session is playing.
409         */
410        public int getMaxVolume() {
411            return mMaxVolume;
412        }
413
414        /**
415         * Get the current volume for this session.
416         *
417         * @return The current volume where this session is playing.
418         */
419        public int getCurrentVolume() {
420            return mCurrentVolume;
421        }
422    }
423
424    interface MediaControllerImpl {
425        void addCallback(Callback callback, Handler handler);
426        void removeCallback(Callback callback);
427        boolean dispatchMediaButtonEvent(KeyEvent keyEvent);
428        TransportControls getTransportControls();
429        PlaybackStateCompat getPlaybackState();
430        MediaMetadataCompat getMetadata();
431        int getRatingType();
432        VolumeInfo getVolumeInfo();
433        void sendControlCommand(String command, Bundle params, ResultReceiver cb);
434        Object getMediaController();
435    }
436
437    // TODO: compatibility implementation
438    static class MediaControllerImplBase implements MediaControllerImpl {
439        @Override
440        public void addCallback(Callback callback, Handler handler) {
441        }
442
443        @Override
444        public void removeCallback(Callback callback) {
445        }
446
447        @Override
448        public boolean dispatchMediaButtonEvent(KeyEvent event) {
449            return false;
450        }
451
452        @Override
453        public TransportControls getTransportControls() {
454            return null;
455        }
456
457        @Override
458        public PlaybackStateCompat getPlaybackState() {
459            return null;
460        }
461
462        @Override
463        public MediaMetadataCompat getMetadata() {
464            return null;
465        }
466
467        @Override
468        public int getRatingType() {
469            return 0;
470        }
471
472        @Override
473        public VolumeInfo getVolumeInfo() {
474            return null;
475        }
476
477        @Override
478        public void sendControlCommand(String command, Bundle params, ResultReceiver cb) {
479        }
480
481        @Override
482        public Object getMediaController() {
483            return null;
484        }
485    }
486
487    static class MediaControllerImplApi21 implements MediaControllerImpl {
488        private final Object mControllerObj;
489
490        public MediaControllerImplApi21(Context context, MediaSessionCompat session) {
491            mControllerObj = MediaControllerCompatApi21.fromToken(
492                    session.getSessionToken().getToken());
493        }
494
495        public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
496                throws RemoteException {
497            // TODO: refactor framework implementation
498            mControllerObj = MediaControllerCompatApi21.fromToken(
499                    sessionToken.getToken());
500            if (mControllerObj == null) throw new RemoteException();
501        }
502
503        @Override
504        public void addCallback(Callback callback, Handler handler) {
505            MediaControllerCompatApi21.addCallback(mControllerObj, callback.mCallbackObj, handler);
506        }
507
508        @Override
509        public void removeCallback(Callback callback) {
510            MediaControllerCompatApi21.removeCallback(mControllerObj, callback.mCallbackObj);
511        }
512
513        @Override
514        public boolean dispatchMediaButtonEvent(KeyEvent event) {
515            return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event);
516        }
517
518        @Override
519        public TransportControls getTransportControls() {
520            Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
521            return controlsObj != null ? new TransportControlsApi21(controlsObj) : null;
522        }
523
524        @Override
525        public PlaybackStateCompat getPlaybackState() {
526            Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj);
527            return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null;
528        }
529
530        @Override
531        public MediaMetadataCompat getMetadata() {
532            Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj);
533            return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null;
534        }
535
536        @Override
537        public int getRatingType() {
538            return MediaControllerCompatApi21.getRatingType(mControllerObj);
539        }
540
541        @Override
542        public VolumeInfo getVolumeInfo() {
543            Object volumeInfoObj = MediaControllerCompatApi21.getVolumeInfo(mControllerObj);
544            return volumeInfoObj != null ? new VolumeInfo(
545                    MediaControllerCompatApi21.VolumeInfo.getVolumeType(volumeInfoObj),
546                    MediaControllerCompatApi21.VolumeInfo.getAudioStream(volumeInfoObj),
547                    MediaControllerCompatApi21.VolumeInfo.getVolumeControl(volumeInfoObj),
548                    MediaControllerCompatApi21.VolumeInfo.getMaxVolume(volumeInfoObj),
549                    MediaControllerCompatApi21.VolumeInfo.getCurrentVolume(volumeInfoObj)) : null;
550        }
551
552        @Override
553        public void sendControlCommand(String command, Bundle params, ResultReceiver cb) {
554            MediaControllerCompatApi21.sendControlCommand(mControllerObj,
555                    command, params, cb);
556        }
557
558        @Override
559        public Object getMediaController() {
560            return mControllerObj;
561        }
562    }
563
564    static class TransportControlsApi21 extends TransportControls {
565        private final Object mControlsObj;
566
567        public TransportControlsApi21(Object controlsObj) {
568            mControlsObj = controlsObj;
569        }
570
571        @Override
572        public void play() {
573            MediaControllerCompatApi21.TransportControls.play(mControlsObj);
574        }
575
576        @Override
577        public void pause() {
578            MediaControllerCompatApi21.TransportControls.pause(mControlsObj);
579        }
580
581        @Override
582        public void stop() {
583            MediaControllerCompatApi21.TransportControls.stop(mControlsObj);
584        }
585
586        @Override
587        public void seekTo(long pos) {
588            MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos);
589        }
590
591        @Override
592        public void fastForward() {
593            MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj);
594        }
595
596        @Override
597        public void rewind() {
598            MediaControllerCompatApi21.TransportControls.rewind(mControlsObj);
599        }
600
601        @Override
602        public void skipToNext() {
603            MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj);
604        }
605
606        @Override
607        public void skipToPrevious() {
608            MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj);
609        }
610
611        @Override
612        public void setRating(RatingCompat rating) {
613            MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
614                    rating != null ? rating.getRating() : null);
615        }
616    }
617}
618