• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.support.v4.media.session;
18 
19 import android.app.PendingIntent;
20 import android.content.Context;
21 import android.media.AudioManager;
22 import android.net.Uri;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.os.IBinder;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.os.RemoteException;
29 import android.os.ResultReceiver;
30 import android.support.v4.media.MediaMetadataCompat;
31 import android.support.v4.media.RatingCompat;
32 import android.support.v4.media.VolumeProviderCompat;
33 import android.support.v4.media.session.MediaSessionCompat.QueueItem;
34 import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.view.KeyEvent;
38 
39 import java.util.List;
40 
41 /**
42  * Allows an app to interact with an ongoing media session. Media buttons and
43  * other commands can be sent to the session. A callback may be registered to
44  * receive updates from the session, such as metadata and play state changes.
45  * <p>
46  * A MediaController can be created if you have a {@link MediaSessionCompat.Token}
47  * from the session owner.
48  * <p>
49  * MediaController objects are thread-safe.
50  * <p>
51  * This is a helper for accessing features in {@link android.media.session.MediaSession}
52  * introduced after API level 4 in a backwards compatible fashion.
53  */
54 public final class MediaControllerCompat {
55     private static final String TAG = "MediaControllerCompat";
56 
57     private final MediaControllerImpl mImpl;
58     private final MediaSessionCompat.Token mToken;
59 
60     /**
61      * Creates a media controller from a session.
62      *
63      * @param session The session to be controlled.
64      */
MediaControllerCompat(Context context, MediaSessionCompat session)65     public MediaControllerCompat(Context context, MediaSessionCompat session) {
66         if (session == null) {
67             throw new IllegalArgumentException("session must not be null");
68         }
69         mToken = session.getSessionToken();
70 
71         if (android.os.Build.VERSION.SDK_INT >= 24) {
72             mImpl = new MediaControllerImplApi24(context, session);
73         } else if (android.os.Build.VERSION.SDK_INT >= 23) {
74             mImpl = new MediaControllerImplApi23(context, session);
75         } else if (android.os.Build.VERSION.SDK_INT >= 21) {
76             mImpl = new MediaControllerImplApi21(context, session);
77         } else {
78             mImpl = new MediaControllerImplBase(mToken);
79         }
80     }
81 
82     /**
83      * Creates a media controller from a session token which may have
84      * been obtained from another process.
85      *
86      * @param sessionToken The token of the session to be controlled.
87      * @throws RemoteException if the session is not accessible.
88      */
MediaControllerCompat(Context context, MediaSessionCompat.Token sessionToken)89     public MediaControllerCompat(Context context, MediaSessionCompat.Token sessionToken)
90             throws RemoteException {
91         if (sessionToken == null) {
92             throw new IllegalArgumentException("sessionToken must not be null");
93         }
94         mToken = sessionToken;
95 
96         if (android.os.Build.VERSION.SDK_INT >= 24) {
97             mImpl = new MediaControllerImplApi24(context, sessionToken);
98         } else if (android.os.Build.VERSION.SDK_INT >= 23) {
99             mImpl = new MediaControllerImplApi23(context, sessionToken);
100         } else if (android.os.Build.VERSION.SDK_INT >= 21) {
101             mImpl = new MediaControllerImplApi21(context, sessionToken);
102         } else {
103             mImpl = new MediaControllerImplBase(mToken);
104         }
105     }
106 
107     /**
108      * Get a {@link TransportControls} instance for this session.
109      *
110      * @return A controls instance
111      */
getTransportControls()112     public TransportControls getTransportControls() {
113         return mImpl.getTransportControls();
114     }
115 
116     /**
117      * Send the specified media button event to the session. Only media keys can
118      * be sent by this method, other keys will be ignored.
119      *
120      * @param keyEvent The media button event to dispatch.
121      * @return true if the event was sent to the session, false otherwise.
122      */
dispatchMediaButtonEvent(KeyEvent keyEvent)123     public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) {
124         if (keyEvent == null) {
125             throw new IllegalArgumentException("KeyEvent may not be null");
126         }
127         return mImpl.dispatchMediaButtonEvent(keyEvent);
128     }
129 
130     /**
131      * Get the current playback state for this session.
132      *
133      * @return The current PlaybackState or null
134      */
getPlaybackState()135     public PlaybackStateCompat getPlaybackState() {
136         return mImpl.getPlaybackState();
137     }
138 
139     /**
140      * Get the current metadata for this session.
141      *
142      * @return The current MediaMetadata or null.
143      */
getMetadata()144     public MediaMetadataCompat getMetadata() {
145         return mImpl.getMetadata();
146     }
147 
148     /**
149      * Get the current play queue for this session if one is set. If you only
150      * care about the current item {@link #getMetadata()} should be used.
151      *
152      * @return The current play queue or null.
153      */
getQueue()154     public List<MediaSessionCompat.QueueItem> getQueue() {
155         return mImpl.getQueue();
156     }
157 
158     /**
159      * Get the queue title for this session.
160      */
getQueueTitle()161     public CharSequence getQueueTitle() {
162         return mImpl.getQueueTitle();
163     }
164 
165     /**
166      * Get the extras for this session.
167      */
getExtras()168     public Bundle getExtras() {
169         return mImpl.getExtras();
170     }
171 
172     /**
173      * Get the rating type supported by the session. One of:
174      * <ul>
175      * <li>{@link RatingCompat#RATING_NONE}</li>
176      * <li>{@link RatingCompat#RATING_HEART}</li>
177      * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
178      * <li>{@link RatingCompat#RATING_3_STARS}</li>
179      * <li>{@link RatingCompat#RATING_4_STARS}</li>
180      * <li>{@link RatingCompat#RATING_5_STARS}</li>
181      * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
182      * </ul>
183      *
184      * @return The supported rating type
185      */
getRatingType()186     public int getRatingType() {
187         return mImpl.getRatingType();
188     }
189 
190     /**
191      * Get the flags for this session. Flags are defined in
192      * {@link MediaSessionCompat}.
193      *
194      * @return The current set of flags for the session.
195      */
getFlags()196     public long getFlags() {
197         return mImpl.getFlags();
198     }
199 
200     /**
201      * Get the current playback info for this session.
202      *
203      * @return The current playback info or null.
204      */
getPlaybackInfo()205     public PlaybackInfo getPlaybackInfo() {
206         return mImpl.getPlaybackInfo();
207     }
208 
209     /**
210      * Get an intent for launching UI associated with this session if one
211      * exists.
212      *
213      * @return A {@link PendingIntent} to launch UI or null.
214      */
getSessionActivity()215     public PendingIntent getSessionActivity() {
216         return mImpl.getSessionActivity();
217     }
218 
219     /**
220      * Get the token for the session this controller is connected to.
221      *
222      * @return The session's token.
223      */
getSessionToken()224     public MediaSessionCompat.Token getSessionToken() {
225         return mToken;
226     }
227 
228     /**
229      * Set the volume of the output this session is playing on. The command will
230      * be ignored if it does not support
231      * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
232      * {@link AudioManager} may be used to affect the handling.
233      *
234      * @see #getPlaybackInfo()
235      * @param value The value to set it to, between 0 and the reported max.
236      * @param flags Flags from {@link AudioManager} to include with the volume
237      *            request.
238      */
setVolumeTo(int value, int flags)239     public void setVolumeTo(int value, int flags) {
240         mImpl.setVolumeTo(value, flags);
241     }
242 
243     /**
244      * Adjust the volume of the output this session is playing on. The direction
245      * must be one of {@link AudioManager#ADJUST_LOWER},
246      * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
247      * The command will be ignored if the session does not support
248      * {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or
249      * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
250      * {@link AudioManager} may be used to affect the handling.
251      *
252      * @see #getPlaybackInfo()
253      * @param direction The direction to adjust the volume in.
254      * @param flags Any flags to pass with the command.
255      */
adjustVolume(int direction, int flags)256     public void adjustVolume(int direction, int flags) {
257         mImpl.adjustVolume(direction, flags);
258     }
259 
260     /**
261      * Adds a callback to receive updates from the Session. Updates will be
262      * posted on the caller's thread.
263      *
264      * @param callback The callback object, must not be null.
265      */
registerCallback(Callback callback)266     public void registerCallback(Callback callback) {
267         registerCallback(callback, null);
268     }
269 
270     /**
271      * Adds a callback to receive updates from the session. Updates will be
272      * posted on the specified handler's thread.
273      *
274      * @param callback The callback object, must not be null.
275      * @param handler The handler to post updates on. If null the callers thread
276      *            will be used.
277      */
registerCallback(Callback callback, Handler handler)278     public void registerCallback(Callback callback, Handler handler) {
279         if (callback == null) {
280             throw new IllegalArgumentException("callback cannot be null");
281         }
282         if (handler == null) {
283             handler = new Handler();
284         }
285         mImpl.registerCallback(callback, handler);
286     }
287 
288     /**
289      * Stop receiving updates on the specified callback. If an update has
290      * already been posted you may still receive it after calling this method.
291      *
292      * @param callback The callback to remove
293      */
unregisterCallback(Callback callback)294     public void unregisterCallback(Callback callback) {
295         if (callback == null) {
296             throw new IllegalArgumentException("callback cannot be null");
297         }
298         mImpl.unregisterCallback(callback);
299     }
300 
301     /**
302      * Sends a generic command to the session. It is up to the session creator
303      * to decide what commands and parameters they will support. As such,
304      * commands should only be sent to sessions that the controller owns.
305      *
306      * @param command The command to send
307      * @param params Any parameters to include with the command
308      * @param cb The callback to receive the result on
309      */
sendCommand(String command, Bundle params, ResultReceiver cb)310     public void sendCommand(String command, Bundle params, ResultReceiver cb) {
311         if (TextUtils.isEmpty(command)) {
312             throw new IllegalArgumentException("command cannot be null or empty");
313         }
314         mImpl.sendCommand(command, params, cb);
315     }
316 
317     /**
318      * Get the session owner's package name.
319      *
320      * @return The package name of of the session owner.
321      */
getPackageName()322     public String getPackageName() {
323         return mImpl.getPackageName();
324     }
325 
326     /**
327      * Gets the underlying framework
328      * {@link android.media.session.MediaController} object.
329      * <p>
330      * This method is only supported on API 21+.
331      * </p>
332      *
333      * @return The underlying {@link android.media.session.MediaController}
334      *         object, or null if none.
335      */
getMediaController()336     public Object getMediaController() {
337         return mImpl.getMediaController();
338     }
339 
340     /**
341      * Callback for receiving updates on from the session. A Callback can be
342      * registered using {@link #registerCallback}
343      */
344     public static abstract class Callback implements IBinder.DeathRecipient {
345         private final Object mCallbackObj;
346         private MessageHandler mHandler;
347 
348         private boolean mRegistered = false;
349 
Callback()350         public Callback() {
351             if (android.os.Build.VERSION.SDK_INT >= 21) {
352                 mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21());
353             } else {
354                 mCallbackObj = new StubCompat();
355             }
356         }
357 
358         /**
359          * Override to handle the session being destroyed. The session is no
360          * longer valid after this call and calls to it will be ignored.
361          */
onSessionDestroyed()362         public void onSessionDestroyed() {
363         }
364 
365         /**
366          * Override to handle custom events sent by the session owner without a
367          * specified interface. Controllers should only handle these for
368          * sessions they own.
369          *
370          * @param event The event from the session.
371          * @param extras Optional parameters for the event.
372          */
onSessionEvent(String event, Bundle extras)373         public void onSessionEvent(String event, Bundle extras) {
374         }
375 
376         /**
377          * Override to handle changes in playback state.
378          *
379          * @param state The new playback state of the session
380          */
onPlaybackStateChanged(PlaybackStateCompat state)381         public void onPlaybackStateChanged(PlaybackStateCompat state) {
382         }
383 
384         /**
385          * Override to handle changes to the current metadata.
386          *
387          * @param metadata The current metadata for the session or null if none.
388          * @see MediaMetadataCompat
389          */
onMetadataChanged(MediaMetadataCompat metadata)390         public void onMetadataChanged(MediaMetadataCompat metadata) {
391         }
392 
393         /**
394          * Override to handle changes to items in the queue.
395          *
396          * @see MediaSessionCompat.QueueItem
397          * @param queue A list of items in the current play queue. It should
398          *            include the currently playing item as well as previous and
399          *            upcoming items if applicable.
400          */
onQueueChanged(List<MediaSessionCompat.QueueItem> queue)401         public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) {
402         }
403 
404         /**
405          * Override to handle changes to the queue title.
406          *
407          * @param title The title that should be displayed along with the play
408          *            queue such as "Now Playing". May be null if there is no
409          *            such title.
410          */
onQueueTitleChanged(CharSequence title)411         public void onQueueTitleChanged(CharSequence title) {
412         }
413 
414         /**
415          * Override to handle chagnes to the {@link MediaSessionCompat} extras.
416          *
417          * @param extras The extras that can include other information
418          *            associated with the {@link MediaSessionCompat}.
419          */
onExtrasChanged(Bundle extras)420         public void onExtrasChanged(Bundle extras) {
421         }
422 
423         /**
424          * Override to handle changes to the audio info.
425          *
426          * @param info The current audio info for this session.
427          */
onAudioInfoChanged(PlaybackInfo info)428         public void onAudioInfoChanged(PlaybackInfo info) {
429         }
430 
431         @Override
binderDied()432         public void binderDied() {
433             onSessionDestroyed();
434         }
435 
436         /**
437          * Set the handler to use for pre 21 callbacks.
438          */
setHandler(Handler handler)439         private void setHandler(Handler handler) {
440             mHandler = new MessageHandler(handler.getLooper());
441         }
442 
443         private class StubApi21 implements MediaControllerCompatApi21.Callback {
444             @Override
onSessionDestroyed()445             public void onSessionDestroyed() {
446                 Callback.this.onSessionDestroyed();
447             }
448 
449             @Override
onSessionEvent(String event, Bundle extras)450             public void onSessionEvent(String event, Bundle extras) {
451                 Callback.this.onSessionEvent(event, extras);
452             }
453 
454             @Override
onPlaybackStateChanged(Object stateObj)455             public void onPlaybackStateChanged(Object stateObj) {
456                 Callback.this.onPlaybackStateChanged(
457                         PlaybackStateCompat.fromPlaybackState(stateObj));
458             }
459 
460             @Override
onMetadataChanged(Object metadataObj)461             public void onMetadataChanged(Object metadataObj) {
462                 Callback.this.onMetadataChanged(MediaMetadataCompat.fromMediaMetadata(metadataObj));
463             }
464 
465             @Override
onQueueChanged(List<?> queue)466             public void onQueueChanged(List<?> queue) {
467                 Callback.this.onQueueChanged(QueueItem.fromQueueItemList(queue));
468             }
469 
470             @Override
onQueueTitleChanged(CharSequence title)471             public void onQueueTitleChanged(CharSequence title) {
472                 Callback.this.onQueueTitleChanged(title);
473             }
474 
475             @Override
onExtrasChanged(Bundle extras)476             public void onExtrasChanged(Bundle extras) {
477                 Callback.this.onExtrasChanged(extras);
478             }
479 
480             @Override
onAudioInfoChanged( int type, int stream, int control, int max, int current)481             public void onAudioInfoChanged(
482                     int type, int stream, int control, int max, int current) {
483                 Callback.this.onAudioInfoChanged(
484                         new PlaybackInfo(type, stream, control, max, current));
485             }
486         }
487 
488         private class StubCompat extends IMediaControllerCallback.Stub {
489 
490             @Override
onEvent(String event, Bundle extras)491             public void onEvent(String event, Bundle extras) throws RemoteException {
492                 mHandler.post(MessageHandler.MSG_EVENT, event, extras);
493             }
494 
495             @Override
onSessionDestroyed()496             public void onSessionDestroyed() throws RemoteException {
497                 mHandler.post(MessageHandler.MSG_DESTROYED, null, null);
498             }
499 
500             @Override
onPlaybackStateChanged(PlaybackStateCompat state)501             public void onPlaybackStateChanged(PlaybackStateCompat state) throws RemoteException {
502                 mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null);
503             }
504 
505             @Override
onMetadataChanged(MediaMetadataCompat metadata)506             public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
507                 mHandler.post(MessageHandler.MSG_UPDATE_METADATA, metadata, null);
508             }
509 
510             @Override
onQueueChanged(List<QueueItem> queue)511             public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
512                 mHandler.post(MessageHandler.MSG_UPDATE_QUEUE, queue, null);
513             }
514 
515             @Override
onQueueTitleChanged(CharSequence title)516             public void onQueueTitleChanged(CharSequence title) throws RemoteException {
517                 mHandler.post(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null);
518             }
519 
520             @Override
onExtrasChanged(Bundle extras)521             public void onExtrasChanged(Bundle extras) throws RemoteException {
522                 mHandler.post(MessageHandler.MSG_UPDATE_EXTRAS, extras, null);
523             }
524 
525             @Override
onVolumeInfoChanged(ParcelableVolumeInfo info)526             public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
527                 PlaybackInfo pi = null;
528                 if (info != null) {
529                     pi = new PlaybackInfo(info.volumeType, info.audioStream, info.controlType,
530                             info.maxVolume, info.currentVolume);
531                 }
532                 mHandler.post(MessageHandler.MSG_UPDATE_VOLUME, pi, null);
533             }
534         }
535 
536         private class MessageHandler extends Handler {
537             private static final int MSG_EVENT = 1;
538             private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
539             private static final int MSG_UPDATE_METADATA = 3;
540             private static final int MSG_UPDATE_VOLUME = 4;
541             private static final int MSG_UPDATE_QUEUE = 5;
542             private static final int MSG_UPDATE_QUEUE_TITLE = 6;
543             private static final int MSG_UPDATE_EXTRAS = 7;
544             private static final int MSG_DESTROYED = 8;
545 
MessageHandler(Looper looper)546             public MessageHandler(Looper looper) {
547                 super(looper);
548             }
549 
550             @Override
handleMessage(Message msg)551             public void handleMessage(Message msg) {
552                 if (!mRegistered) {
553                     return;
554                 }
555                 switch (msg.what) {
556                     case MSG_EVENT:
557                         onSessionEvent((String) msg.obj, msg.getData());
558                         break;
559                     case MSG_UPDATE_PLAYBACK_STATE:
560                         onPlaybackStateChanged((PlaybackStateCompat) msg.obj);
561                         break;
562                     case MSG_UPDATE_METADATA:
563                         onMetadataChanged((MediaMetadataCompat) msg.obj);
564                         break;
565                     case MSG_UPDATE_QUEUE:
566                         onQueueChanged((List<MediaSessionCompat.QueueItem>) msg.obj);
567                         break;
568                     case MSG_UPDATE_QUEUE_TITLE:
569                         onQueueTitleChanged((CharSequence) msg.obj);
570                         break;
571                     case MSG_UPDATE_EXTRAS:
572                         onExtrasChanged((Bundle) msg.obj);
573                         break;
574                     case MSG_UPDATE_VOLUME:
575                         onAudioInfoChanged((PlaybackInfo) msg.obj);
576                         break;
577                     case MSG_DESTROYED:
578                         onSessionDestroyed();
579                         break;
580                 }
581             }
582 
post(int what, Object obj, Bundle data)583             public void post(int what, Object obj, Bundle data) {
584                 Message msg = obtainMessage(what, obj);
585                 msg.setData(data);
586                 msg.sendToTarget();
587             }
588         }
589     }
590 
591     /**
592      * Interface for controlling media playback on a session. This allows an app
593      * to send media transport commands to the session.
594      */
595     public static abstract class TransportControls {
TransportControls()596         TransportControls() {
597         }
598 
599         /**
600          * Request that the player prepare its playback without audio focus. In other words, other
601          * session can continue to play during the preparation of this session. This method can be
602          * used to speed up the start of the playback. Once the preparation is done, the session
603          * will change its playback state to {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards,
604          * {@link #play} can be called to start playback. If the preparation is not needed,
605          * {@link #play} can be directly called without this method.
606          */
prepare()607         public abstract void prepare();
608 
609         /**
610          * Request that the player prepare playback for a specific media id. In other words, other
611          * session can continue to play during the preparation of this session. This method can be
612          * used to speed up the start of the playback. Once the preparation is
613          * done, the session will change its playback state to
614          * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
615          * start playback. If the preparation is not needed, {@link #playFromMediaId} can
616          * be directly called without this method.
617          *
618          * @param mediaId The id of the requested media.
619          * @param extras Optional extras that can include extra information about the media item
620          *               to be prepared.
621          */
prepareFromMediaId(String mediaId, Bundle extras)622         public abstract void prepareFromMediaId(String mediaId, Bundle extras);
623 
624         /**
625          * Request that the player prepare playback for a specific search query.
626          * An empty or null query should be treated as a request to prepare any
627          * music. In other words, other session can continue to play during
628          * the preparation of this session. This method can be used to speed up the start of the
629          * playback. Once the preparation is done, the session will change its playback state to
630          * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
631          * start playback. If the preparation is not needed, {@link #playFromSearch} can be directly
632          * called without this method.
633          *
634          * @param query The search query.
635          * @param extras Optional extras that can include extra information
636          *               about the query.
637          */
prepareFromSearch(String query, Bundle extras)638         public abstract void prepareFromSearch(String query, Bundle extras);
639 
640         /**
641          * Request that the player prepare playback for a specific {@link Uri}.
642          * In other words, other session can continue to play during the preparation of this
643          * session. This method can be used to speed up the start of the playback.
644          * Once the preparation is done, the session will change its playback state to
645          * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
646          * start playback. If the preparation is not needed, {@link #playFromUri} can be directly
647          * called without this method.
648          *
649          * @param uri The URI of the requested media.
650          * @param extras Optional extras that can include extra information about the media item
651          *               to be prepared.
652          */
prepareFromUri(Uri uri, Bundle extras)653         public abstract void prepareFromUri(Uri uri, Bundle extras);
654 
655         /**
656          * Request that the player start its playback at its current position.
657          */
play()658         public abstract void play();
659 
660         /**
661          * Request that the player start playback for a specific {@link Uri}.
662          *
663          * @param mediaId The uri of the requested media.
664          * @param extras Optional extras that can include extra information
665          *            about the media item to be played.
666          */
playFromMediaId(String mediaId, Bundle extras)667         public abstract void playFromMediaId(String mediaId, Bundle extras);
668 
669         /**
670          * Request that the player start playback for a specific search query.
671          * An empty or null query should be treated as a request to play any
672          * music.
673          *
674          * @param query The search query.
675          * @param extras Optional extras that can include extra information
676          *            about the query.
677          */
playFromSearch(String query, Bundle extras)678         public abstract void playFromSearch(String query, Bundle extras);
679 
680         /**
681          * Request that the player start playback for a specific {@link Uri}.
682          *
683          * @param uri  The URI of the requested media.
684          * @param extras Optional extras that can include extra information about the media item
685          *               to be played.
686          */
playFromUri(Uri uri, Bundle extras)687         public abstract void playFromUri(Uri uri, Bundle extras);
688 
689         /**
690          * Play an item with a specific id in the play queue. If you specify an
691          * id that is not in the play queue, the behavior is undefined.
692          */
skipToQueueItem(long id)693         public abstract void skipToQueueItem(long id);
694 
695         /**
696          * Request that the player pause its playback and stay at its current
697          * position.
698          */
pause()699         public abstract void pause();
700 
701         /**
702          * Request that the player stop its playback; it may clear its state in
703          * whatever way is appropriate.
704          */
stop()705         public abstract void stop();
706 
707         /**
708          * Move to a new location in the media stream.
709          *
710          * @param pos Position to move to, in milliseconds.
711          */
seekTo(long pos)712         public abstract void seekTo(long pos);
713 
714         /**
715          * Start fast forwarding. If playback is already fast forwarding this
716          * may increase the rate.
717          */
fastForward()718         public abstract void fastForward();
719 
720         /**
721          * Skip to the next item.
722          */
skipToNext()723         public abstract void skipToNext();
724 
725         /**
726          * Start rewinding. If playback is already rewinding this may increase
727          * the rate.
728          */
rewind()729         public abstract void rewind();
730 
731         /**
732          * Skip to the previous item.
733          */
skipToPrevious()734         public abstract void skipToPrevious();
735 
736         /**
737          * Rate the current content. This will cause the rating to be set for
738          * the current user. The Rating type must match the type returned by
739          * {@link #getRatingType()}.
740          *
741          * @param rating The rating to set for the current content
742          */
setRating(RatingCompat rating)743         public abstract void setRating(RatingCompat rating);
744 
745         /**
746          * Send a custom action for the {@link MediaSessionCompat} to perform.
747          *
748          * @param customAction The action to perform.
749          * @param args Optional arguments to supply to the
750          *            {@link MediaSessionCompat} for this custom action.
751          */
sendCustomAction(PlaybackStateCompat.CustomAction customAction, Bundle args)752         public abstract void sendCustomAction(PlaybackStateCompat.CustomAction customAction,
753                 Bundle args);
754 
755         /**
756          * Send the id and args from a custom action for the
757          * {@link MediaSessionCompat} to perform.
758          *
759          * @see #sendCustomAction(PlaybackStateCompat.CustomAction action,
760          *      Bundle args)
761          * @param action The action identifier of the
762          *            {@link PlaybackStateCompat.CustomAction} as specified by
763          *            the {@link MediaSessionCompat}.
764          * @param args Optional arguments to supply to the
765          *            {@link MediaSessionCompat} for this custom action.
766          */
sendCustomAction(String action, Bundle args)767         public abstract void sendCustomAction(String action, Bundle args);
768     }
769 
770     /**
771      * Holds information about the way volume is handled for this session.
772      */
773     public static final class PlaybackInfo {
774         /**
775          * The session uses local playback.
776          */
777         public static final int PLAYBACK_TYPE_LOCAL = 1;
778         /**
779          * The session uses remote playback.
780          */
781         public static final int PLAYBACK_TYPE_REMOTE = 2;
782 
783         private final int mPlaybackType;
784         // TODO update audio stream with AudioAttributes support version
785         private final int mAudioStream;
786         private final int mVolumeControl;
787         private final int mMaxVolume;
788         private final int mCurrentVolume;
789 
PlaybackInfo(int type, int stream, int control, int max, int current)790         PlaybackInfo(int type, int stream, int control, int max, int current) {
791             mPlaybackType = type;
792             mAudioStream = stream;
793             mVolumeControl = control;
794             mMaxVolume = max;
795             mCurrentVolume = current;
796         }
797 
798         /**
799          * Get the type of volume handling, either local or remote. One of:
800          * <ul>
801          * <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li>
802          * <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li>
803          * </ul>
804          *
805          * @return The type of volume handling this session is using.
806          */
getPlaybackType()807         public int getPlaybackType() {
808             return mPlaybackType;
809         }
810 
811         /**
812          * Get the stream this is currently controlling volume on. When the volume
813          * type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not
814          * have meaning and should be ignored.
815          *
816          * @return The stream this session is playing on.
817          */
getAudioStream()818         public int getAudioStream() {
819             // TODO switch to AudioAttributesCompat when it is added.
820             return mAudioStream;
821         }
822 
823         /**
824          * Get the type of volume control that can be used. One of:
825          * <ul>
826          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
827          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
828          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li>
829          * </ul>
830          *
831          * @return The type of volume control that may be used with this
832          *         session.
833          */
getVolumeControl()834         public int getVolumeControl() {
835             return mVolumeControl;
836         }
837 
838         /**
839          * Get the maximum volume that may be set for this session.
840          *
841          * @return The maximum allowed volume where this session is playing.
842          */
getMaxVolume()843         public int getMaxVolume() {
844             return mMaxVolume;
845         }
846 
847         /**
848          * Get the current volume for this session.
849          *
850          * @return The current volume where this session is playing.
851          */
getCurrentVolume()852         public int getCurrentVolume() {
853             return mCurrentVolume;
854         }
855     }
856 
857     interface MediaControllerImpl {
registerCallback(Callback callback, Handler handler)858         void registerCallback(Callback callback, Handler handler);
859 
unregisterCallback(Callback callback)860         void unregisterCallback(Callback callback);
dispatchMediaButtonEvent(KeyEvent keyEvent)861         boolean dispatchMediaButtonEvent(KeyEvent keyEvent);
getTransportControls()862         TransportControls getTransportControls();
getPlaybackState()863         PlaybackStateCompat getPlaybackState();
getMetadata()864         MediaMetadataCompat getMetadata();
865 
getQueue()866         List<MediaSessionCompat.QueueItem> getQueue();
getQueueTitle()867         CharSequence getQueueTitle();
getExtras()868         Bundle getExtras();
getRatingType()869         int getRatingType();
getFlags()870         long getFlags();
getPlaybackInfo()871         PlaybackInfo getPlaybackInfo();
getSessionActivity()872         PendingIntent getSessionActivity();
873 
setVolumeTo(int value, int flags)874         void setVolumeTo(int value, int flags);
adjustVolume(int direction, int flags)875         void adjustVolume(int direction, int flags);
sendCommand(String command, Bundle params, ResultReceiver cb)876         void sendCommand(String command, Bundle params, ResultReceiver cb);
877 
getPackageName()878         String getPackageName();
getMediaController()879         Object getMediaController();
880     }
881 
882     static class MediaControllerImplBase implements MediaControllerImpl {
883         private MediaSessionCompat.Token mToken;
884         private IMediaSession mBinder;
885         private TransportControls mTransportControls;
886 
MediaControllerImplBase(MediaSessionCompat.Token token)887         public MediaControllerImplBase(MediaSessionCompat.Token token) {
888             mToken = token;
889             mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken());
890         }
891 
892         @Override
registerCallback(Callback callback, Handler handler)893         public void registerCallback(Callback callback, Handler handler) {
894             if (callback == null) {
895                 throw new IllegalArgumentException("callback may not be null.");
896             }
897             try {
898                 mBinder.asBinder().linkToDeath(callback, 0);
899                 mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj);
900                 callback.setHandler(handler);
901                 callback.mRegistered = true;
902             } catch (RemoteException e) {
903                 Log.e(TAG, "Dead object in registerCallback. " + e);
904                 callback.onSessionDestroyed();
905             }
906         }
907 
908         @Override
unregisterCallback(Callback callback)909         public void unregisterCallback(Callback callback) {
910             if (callback == null) {
911                 throw new IllegalArgumentException("callback may not be null.");
912             }
913             try {
914                 mBinder.unregisterCallbackListener(
915                         (IMediaControllerCallback) callback.mCallbackObj);
916                 mBinder.asBinder().unlinkToDeath(callback, 0);
917                 callback.mRegistered = false;
918             } catch (RemoteException e) {
919                 Log.e(TAG, "Dead object in unregisterCallback. " + e);
920             }
921         }
922 
923         @Override
dispatchMediaButtonEvent(KeyEvent event)924         public boolean dispatchMediaButtonEvent(KeyEvent event) {
925             if (event == null) {
926                 throw new IllegalArgumentException("event may not be null.");
927             }
928             try {
929                 mBinder.sendMediaButton(event);
930             } catch (RemoteException e) {
931                 Log.e(TAG, "Dead object in dispatchMediaButtonEvent. " + e);
932             }
933             return false;
934         }
935 
936         @Override
getTransportControls()937         public TransportControls getTransportControls() {
938             if (mTransportControls == null) {
939                 mTransportControls = new TransportControlsBase(mBinder);
940             }
941 
942             return mTransportControls;
943         }
944 
945         @Override
getPlaybackState()946         public PlaybackStateCompat getPlaybackState() {
947             try {
948                 return mBinder.getPlaybackState();
949             } catch (RemoteException e) {
950                 Log.e(TAG, "Dead object in getPlaybackState. " + e);
951             }
952             return null;
953         }
954 
955         @Override
getMetadata()956         public MediaMetadataCompat getMetadata() {
957             try {
958                 return mBinder.getMetadata();
959             } catch (RemoteException e) {
960                 Log.e(TAG, "Dead object in getMetadata. " + e);
961             }
962             return null;
963         }
964 
965         @Override
getQueue()966         public List<MediaSessionCompat.QueueItem> getQueue() {
967             try {
968                 return mBinder.getQueue();
969             } catch (RemoteException e) {
970                 Log.e(TAG, "Dead object in getQueue. " + e);
971             }
972             return null;
973         }
974 
975         @Override
getQueueTitle()976         public CharSequence getQueueTitle() {
977             try {
978                 return mBinder.getQueueTitle();
979             } catch (RemoteException e) {
980                 Log.e(TAG, "Dead object in getQueueTitle. " + e);
981             }
982             return null;
983         }
984 
985         @Override
getExtras()986         public Bundle getExtras() {
987             try {
988                 return mBinder.getExtras();
989             } catch (RemoteException e) {
990                 Log.e(TAG, "Dead object in getExtras. " + e);
991             }
992             return null;
993         }
994 
995         @Override
getRatingType()996         public int getRatingType() {
997             try {
998                 return mBinder.getRatingType();
999             } catch (RemoteException e) {
1000                 Log.e(TAG, "Dead object in getRatingType. " + e);
1001             }
1002             return 0;
1003         }
1004 
1005         @Override
getFlags()1006         public long getFlags() {
1007             try {
1008                 return mBinder.getFlags();
1009             } catch (RemoteException e) {
1010                 Log.e(TAG, "Dead object in getFlags. " + e);
1011             }
1012             return 0;
1013         }
1014 
1015         @Override
getPlaybackInfo()1016         public PlaybackInfo getPlaybackInfo() {
1017             try {
1018                 ParcelableVolumeInfo info = mBinder.getVolumeAttributes();
1019                 PlaybackInfo pi = new PlaybackInfo(info.volumeType, info.audioStream,
1020                         info.controlType, info.maxVolume, info.currentVolume);
1021                 return pi;
1022             } catch (RemoteException e) {
1023                 Log.e(TAG, "Dead object in getPlaybackInfo. " + e);
1024             }
1025             return null;
1026         }
1027 
1028         @Override
getSessionActivity()1029         public PendingIntent getSessionActivity() {
1030             try {
1031                 return mBinder.getLaunchPendingIntent();
1032             } catch (RemoteException e) {
1033                 Log.e(TAG, "Dead object in getSessionActivity. " + e);
1034             }
1035             return null;
1036         }
1037 
1038         @Override
setVolumeTo(int value, int flags)1039         public void setVolumeTo(int value, int flags) {
1040             try {
1041                 mBinder.setVolumeTo(value, flags, null);
1042             } catch (RemoteException e) {
1043                 Log.e(TAG, "Dead object in setVolumeTo. " + e);
1044             }
1045         }
1046 
1047         @Override
adjustVolume(int direction, int flags)1048         public void adjustVolume(int direction, int flags) {
1049             try {
1050                 mBinder.adjustVolume(direction, flags, null);
1051             } catch (RemoteException e) {
1052                 Log.e(TAG, "Dead object in adjustVolume. " + e);
1053             }
1054         }
1055 
1056         @Override
sendCommand(String command, Bundle params, ResultReceiver cb)1057         public void sendCommand(String command, Bundle params, ResultReceiver cb) {
1058             try {
1059                 mBinder.sendCommand(command, params,
1060                         new MediaSessionCompat.ResultReceiverWrapper(cb));
1061             } catch (RemoteException e) {
1062                 Log.e(TAG, "Dead object in sendCommand. " + e);
1063             }
1064         }
1065 
1066         @Override
getPackageName()1067         public String getPackageName() {
1068             try {
1069                 return mBinder.getPackageName();
1070             } catch (RemoteException e) {
1071                 Log.e(TAG, "Dead object in getPackageName. " + e);
1072             }
1073             return null;
1074         }
1075 
1076         @Override
getMediaController()1077         public Object getMediaController() {
1078             return null;
1079         }
1080     }
1081 
1082     static class TransportControlsBase extends TransportControls {
1083         private IMediaSession mBinder;
1084 
TransportControlsBase(IMediaSession binder)1085         public TransportControlsBase(IMediaSession binder) {
1086             mBinder = binder;
1087         }
1088 
1089         @Override
prepare()1090         public void prepare() {
1091             try {
1092                 mBinder.prepare();
1093             } catch (RemoteException e) {
1094                 Log.e(TAG, "Dead object in prepare. " + e);
1095             }
1096         }
1097 
1098         @Override
prepareFromMediaId(String mediaId, Bundle extras)1099         public void prepareFromMediaId(String mediaId, Bundle extras) {
1100             try {
1101                 mBinder.prepareFromMediaId(mediaId, extras);
1102             } catch (RemoteException e) {
1103                 Log.e(TAG, "Dead object in prepareFromMediaId. " + e);
1104             }
1105         }
1106 
1107         @Override
prepareFromSearch(String query, Bundle extras)1108         public void prepareFromSearch(String query, Bundle extras) {
1109             try {
1110                 mBinder.prepareFromSearch(query, extras);
1111             } catch (RemoteException e) {
1112                 Log.e(TAG, "Dead object in prepareFromSearch. " + e);
1113             }
1114         }
1115 
1116         @Override
prepareFromUri(Uri uri, Bundle extras)1117         public void prepareFromUri(Uri uri, Bundle extras) {
1118             try {
1119                 mBinder.prepareFromUri(uri, extras);
1120             } catch (RemoteException e) {
1121                 Log.e(TAG, "Dead object in prepareFromUri. " + e);
1122             }
1123         }
1124 
1125         @Override
play()1126         public void play() {
1127             try {
1128                 mBinder.play();
1129             } catch (RemoteException e) {
1130                 Log.e(TAG, "Dead object in play. " + e);
1131             }
1132         }
1133 
1134         @Override
playFromMediaId(String mediaId, Bundle extras)1135         public void playFromMediaId(String mediaId, Bundle extras) {
1136             try {
1137                 mBinder.playFromMediaId(mediaId, extras);
1138             } catch (RemoteException e) {
1139                 Log.e(TAG, "Dead object in playFromMediaId. " + e);
1140             }
1141         }
1142 
1143         @Override
playFromSearch(String query, Bundle extras)1144         public void playFromSearch(String query, Bundle extras) {
1145             try {
1146                 mBinder.playFromSearch(query, extras);
1147             } catch (RemoteException e) {
1148                 Log.e(TAG, "Dead object in playFromSearch. " + e);
1149             }
1150         }
1151 
1152         @Override
playFromUri(Uri uri, Bundle extras)1153         public void playFromUri(Uri uri, Bundle extras) {
1154             try {
1155                 mBinder.playFromUri(uri, extras);
1156             } catch (RemoteException e) {
1157                 Log.e(TAG, "Dead object in playFromUri. " + e);
1158             }
1159         }
1160 
1161         @Override
skipToQueueItem(long id)1162         public void skipToQueueItem(long id) {
1163             try {
1164                 mBinder.skipToQueueItem(id);
1165             } catch (RemoteException e) {
1166                 Log.e(TAG, "Dead object in skipToQueueItem. " + e);
1167             }
1168         }
1169 
1170         @Override
pause()1171         public void pause() {
1172             try {
1173                 mBinder.pause();
1174             } catch (RemoteException e) {
1175                 Log.e(TAG, "Dead object in pause. " + e);
1176             }
1177         }
1178 
1179         @Override
stop()1180         public void stop() {
1181             try {
1182                 mBinder.stop();
1183             } catch (RemoteException e) {
1184                 Log.e(TAG, "Dead object in stop. " + e);
1185             }
1186         }
1187 
1188         @Override
seekTo(long pos)1189         public void seekTo(long pos) {
1190             try {
1191                 mBinder.seekTo(pos);
1192             } catch (RemoteException e) {
1193                 Log.e(TAG, "Dead object in seekTo. " + e);
1194             }
1195         }
1196 
1197         @Override
fastForward()1198         public void fastForward() {
1199             try {
1200                 mBinder.fastForward();
1201             } catch (RemoteException e) {
1202                 Log.e(TAG, "Dead object in fastForward. " + e);
1203             }
1204         }
1205 
1206         @Override
skipToNext()1207         public void skipToNext() {
1208             try {
1209                 mBinder.next();
1210             } catch (RemoteException e) {
1211                 Log.e(TAG, "Dead object in skipToNext. " + e);
1212             }
1213         }
1214 
1215         @Override
rewind()1216         public void rewind() {
1217             try {
1218                 mBinder.rewind();
1219             } catch (RemoteException e) {
1220                 Log.e(TAG, "Dead object in rewind. " + e);
1221             }
1222         }
1223 
1224         @Override
skipToPrevious()1225         public void skipToPrevious() {
1226             try {
1227                 mBinder.previous();
1228             } catch (RemoteException e) {
1229                 Log.e(TAG, "Dead object in skipToPrevious. " + e);
1230             }
1231         }
1232 
1233         @Override
setRating(RatingCompat rating)1234         public void setRating(RatingCompat rating) {
1235             try {
1236                 mBinder.rate(rating);
1237             } catch (RemoteException e) {
1238                 Log.e(TAG, "Dead object in setRating. " + e);
1239             }
1240         }
1241 
1242         @Override
sendCustomAction(CustomAction customAction, Bundle args)1243         public void sendCustomAction(CustomAction customAction, Bundle args) {
1244             sendCustomAction(customAction.getAction(), args);
1245         }
1246 
1247         @Override
sendCustomAction(String action, Bundle args)1248         public void sendCustomAction(String action, Bundle args) {
1249             try {
1250                 mBinder.sendCustomAction(action, args);
1251             } catch (RemoteException e) {
1252                 Log.e(TAG, "Dead object in sendCustomAction. " + e);
1253             }
1254         }
1255     }
1256 
1257     static class MediaControllerImplApi21 implements MediaControllerImpl {
1258         protected final Object mControllerObj;
1259 
MediaControllerImplApi21(Context context, MediaSessionCompat session)1260         public MediaControllerImplApi21(Context context, MediaSessionCompat session) {
1261             mControllerObj = MediaControllerCompatApi21.fromToken(context,
1262                     session.getSessionToken().getToken());
1263         }
1264 
MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)1265         public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
1266                 throws RemoteException {
1267             mControllerObj = MediaControllerCompatApi21.fromToken(context,
1268                     sessionToken.getToken());
1269             if (mControllerObj == null) throw new RemoteException();
1270         }
1271 
1272         @Override
registerCallback(Callback callback, Handler handler)1273         public void registerCallback(Callback callback, Handler handler) {
1274             MediaControllerCompatApi21.registerCallback(mControllerObj, callback.mCallbackObj, handler);
1275         }
1276 
1277         @Override
unregisterCallback(Callback callback)1278         public void unregisterCallback(Callback callback) {
1279             MediaControllerCompatApi21.unregisterCallback(mControllerObj, callback.mCallbackObj);
1280         }
1281 
1282         @Override
dispatchMediaButtonEvent(KeyEvent event)1283         public boolean dispatchMediaButtonEvent(KeyEvent event) {
1284             return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event);
1285         }
1286 
1287         @Override
getTransportControls()1288         public TransportControls getTransportControls() {
1289             Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
1290             return controlsObj != null ? new TransportControlsApi21(controlsObj) : null;
1291         }
1292 
1293         @Override
getPlaybackState()1294         public PlaybackStateCompat getPlaybackState() {
1295             Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj);
1296             return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null;
1297         }
1298 
1299         @Override
getMetadata()1300         public MediaMetadataCompat getMetadata() {
1301             Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj);
1302             return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null;
1303         }
1304 
1305         @Override
getQueue()1306         public List<MediaSessionCompat.QueueItem> getQueue() {
1307             List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj);
1308             return queueObjs != null ? MediaSessionCompat.QueueItem.fromQueueItemList(queueObjs)
1309                     : null;
1310         }
1311 
1312         @Override
getQueueTitle()1313         public CharSequence getQueueTitle() {
1314             return MediaControllerCompatApi21.getQueueTitle(mControllerObj);
1315         }
1316 
1317         @Override
getExtras()1318         public Bundle getExtras() {
1319             return MediaControllerCompatApi21.getExtras(mControllerObj);
1320         }
1321 
1322         @Override
getRatingType()1323         public int getRatingType() {
1324             return MediaControllerCompatApi21.getRatingType(mControllerObj);
1325         }
1326 
1327         @Override
getFlags()1328         public long getFlags() {
1329             return MediaControllerCompatApi21.getFlags(mControllerObj);
1330         }
1331 
1332         @Override
getPlaybackInfo()1333         public PlaybackInfo getPlaybackInfo() {
1334             Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj);
1335             return volumeInfoObj != null ? new PlaybackInfo(
1336                     MediaControllerCompatApi21.PlaybackInfo.getPlaybackType(volumeInfoObj),
1337                     MediaControllerCompatApi21.PlaybackInfo.getLegacyAudioStream(volumeInfoObj),
1338                     MediaControllerCompatApi21.PlaybackInfo.getVolumeControl(volumeInfoObj),
1339                     MediaControllerCompatApi21.PlaybackInfo.getMaxVolume(volumeInfoObj),
1340                     MediaControllerCompatApi21.PlaybackInfo.getCurrentVolume(volumeInfoObj)) : null;
1341         }
1342 
1343         @Override
getSessionActivity()1344         public PendingIntent getSessionActivity() {
1345             return MediaControllerCompatApi21.getSessionActivity(mControllerObj);
1346         }
1347 
1348         @Override
setVolumeTo(int value, int flags)1349         public void setVolumeTo(int value, int flags) {
1350             MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags);
1351         }
1352 
1353         @Override
adjustVolume(int direction, int flags)1354         public void adjustVolume(int direction, int flags) {
1355             MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags);
1356         }
1357 
1358         @Override
sendCommand(String command, Bundle params, ResultReceiver cb)1359         public void sendCommand(String command, Bundle params, ResultReceiver cb) {
1360             MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb);
1361         }
1362 
1363         @Override
getPackageName()1364         public String getPackageName() {
1365             return MediaControllerCompatApi21.getPackageName(mControllerObj);
1366         }
1367 
1368         @Override
getMediaController()1369         public Object getMediaController() {
1370             return mControllerObj;
1371         }
1372     }
1373 
1374     static class TransportControlsApi21 extends TransportControls {
1375         protected final Object mControlsObj;
1376 
TransportControlsApi21(Object controlsObj)1377         public TransportControlsApi21(Object controlsObj) {
1378             mControlsObj = controlsObj;
1379         }
1380 
1381         @Override
prepare()1382         public void prepare() {
1383             sendCustomAction(MediaSessionCompat.ACTION_PREPARE, null);
1384         }
1385 
1386         @Override
prepareFromMediaId(String mediaId, Bundle extras)1387         public void prepareFromMediaId(String mediaId, Bundle extras) {
1388             Bundle bundle = new Bundle();
1389             bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ID, mediaId);
1390             bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
1391             sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_MEDIA_ID, bundle);
1392         }
1393 
1394         @Override
prepareFromSearch(String query, Bundle extras)1395         public void prepareFromSearch(String query, Bundle extras) {
1396             Bundle bundle = new Bundle();
1397             bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_QUERY, query);
1398             bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
1399             sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_SEARCH, bundle);
1400         }
1401 
1402         @Override
prepareFromUri(Uri uri, Bundle extras)1403         public void prepareFromUri(Uri uri, Bundle extras) {
1404             Bundle bundle = new Bundle();
1405             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
1406             bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
1407             sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_URI, bundle);
1408         }
1409 
1410         @Override
play()1411         public void play() {
1412             MediaControllerCompatApi21.TransportControls.play(mControlsObj);
1413         }
1414 
1415         @Override
pause()1416         public void pause() {
1417             MediaControllerCompatApi21.TransportControls.pause(mControlsObj);
1418         }
1419 
1420         @Override
stop()1421         public void stop() {
1422             MediaControllerCompatApi21.TransportControls.stop(mControlsObj);
1423         }
1424 
1425         @Override
seekTo(long pos)1426         public void seekTo(long pos) {
1427             MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos);
1428         }
1429 
1430         @Override
fastForward()1431         public void fastForward() {
1432             MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj);
1433         }
1434 
1435         @Override
rewind()1436         public void rewind() {
1437             MediaControllerCompatApi21.TransportControls.rewind(mControlsObj);
1438         }
1439 
1440         @Override
skipToNext()1441         public void skipToNext() {
1442             MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj);
1443         }
1444 
1445         @Override
skipToPrevious()1446         public void skipToPrevious() {
1447             MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj);
1448         }
1449 
1450         @Override
setRating(RatingCompat rating)1451         public void setRating(RatingCompat rating) {
1452             MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
1453                     rating != null ? rating.getRating() : null);
1454         }
1455 
1456         @Override
playFromMediaId(String mediaId, Bundle extras)1457         public void playFromMediaId(String mediaId, Bundle extras) {
1458             MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId,
1459                     extras);
1460         }
1461 
1462         @Override
playFromSearch(String query, Bundle extras)1463         public void playFromSearch(String query, Bundle extras) {
1464             MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query,
1465                     extras);
1466         }
1467 
1468         @Override
playFromUri(Uri uri, Bundle extras)1469         public void playFromUri(Uri uri, Bundle extras) {
1470             if (uri == null || Uri.EMPTY.equals(uri)) {
1471                 throw new IllegalArgumentException(
1472                         "You must specify a non-empty Uri for playFromUri.");
1473             }
1474             Bundle bundle = new Bundle();
1475             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
1476             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
1477             sendCustomAction(MediaSessionCompat.ACTION_PLAY_FROM_URI, bundle);
1478         }
1479 
1480         @Override
skipToQueueItem(long id)1481         public void skipToQueueItem(long id) {
1482             MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id);
1483         }
1484 
1485         @Override
sendCustomAction(CustomAction customAction, Bundle args)1486         public void sendCustomAction(CustomAction customAction, Bundle args) {
1487             MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj,
1488                     customAction.getAction(), args);
1489         }
1490 
1491         @Override
sendCustomAction(String action, Bundle args)1492         public void sendCustomAction(String action, Bundle args) {
1493             MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action,
1494                     args);
1495         }
1496     }
1497 
1498     static class MediaControllerImplApi23 extends MediaControllerImplApi21 {
1499 
MediaControllerImplApi23(Context context, MediaSessionCompat session)1500         public MediaControllerImplApi23(Context context, MediaSessionCompat session) {
1501             super(context, session);
1502         }
1503 
MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken)1504         public MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken)
1505                 throws RemoteException {
1506             super(context, sessionToken);
1507         }
1508 
1509         @Override
getTransportControls()1510         public TransportControls getTransportControls() {
1511             Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
1512             return controlsObj != null ? new TransportControlsApi23(controlsObj) : null;
1513         }
1514     }
1515 
1516     static class TransportControlsApi23 extends TransportControlsApi21 {
1517 
TransportControlsApi23(Object controlsObj)1518         public TransportControlsApi23(Object controlsObj) {
1519             super(controlsObj);
1520         }
1521 
1522         @Override
playFromUri(Uri uri, Bundle extras)1523         public void playFromUri(Uri uri, Bundle extras) {
1524             MediaControllerCompatApi23.TransportControls.playFromUri(mControlsObj, uri,
1525                     extras);
1526         }
1527     }
1528 
1529     static class MediaControllerImplApi24 extends MediaControllerImplApi23 {
1530 
MediaControllerImplApi24(Context context, MediaSessionCompat session)1531         public MediaControllerImplApi24(Context context, MediaSessionCompat session) {
1532             super(context, session);
1533         }
1534 
MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken)1535         public MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken)
1536                 throws RemoteException {
1537             super(context, sessionToken);
1538         }
1539 
1540         @Override
getTransportControls()1541         public TransportControls getTransportControls() {
1542             Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
1543             return controlsObj != null ? new TransportControlsApi24(controlsObj) : null;
1544         }
1545     }
1546 
1547     static class TransportControlsApi24 extends TransportControlsApi23 {
1548 
TransportControlsApi24(Object controlsObj)1549         public TransportControlsApi24(Object controlsObj) {
1550             super(controlsObj);
1551         }
1552 
1553         @Override
prepare()1554         public void prepare() {
1555             MediaControllerCompatApi24.TransportControls.prepare(mControlsObj);
1556         }
1557 
1558         @Override
prepareFromMediaId(String mediaId, Bundle extras)1559         public void prepareFromMediaId(String mediaId, Bundle extras) {
1560             MediaControllerCompatApi24.TransportControls.prepareFromMediaId(
1561                     mControlsObj, mediaId, extras);
1562         }
1563 
1564         @Override
prepareFromSearch(String query, Bundle extras)1565         public void prepareFromSearch(String query, Bundle extras) {
1566             MediaControllerCompatApi24.TransportControls.prepareFromSearch(
1567                     mControlsObj, query, extras);
1568         }
1569 
1570         @Override
prepareFromUri(Uri uri, Bundle extras)1571         public void prepareFromUri(Uri uri, Bundle extras) {
1572             MediaControllerCompatApi24.TransportControls.prepareFromUri(mControlsObj, uri, extras);
1573         }
1574     }
1575 
1576 }
1577