• 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.media.session;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.Activity;
23 import android.app.PendingIntent;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.pm.ParceledListSlice;
27 import android.media.AudioAttributes;
28 import android.media.MediaDescription;
29 import android.media.MediaMetadata;
30 import android.media.Rating;
31 import android.media.VolumeProvider;
32 import android.net.Uri;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.os.Message;
37 import android.os.Parcel;
38 import android.os.Parcelable;
39 import android.os.RemoteException;
40 import android.os.ResultReceiver;
41 import android.os.UserHandle;
42 import android.service.media.MediaBrowserService;
43 import android.text.TextUtils;
44 import android.util.Log;
45 import android.view.KeyEvent;
46 
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.lang.ref.WeakReference;
50 import java.util.List;
51 import java.util.Objects;
52 
53 /**
54  * Allows interaction with media controllers, volume keys, media buttons, and
55  * transport controls.
56  * <p>
57  * A MediaSession should be created when an app wants to publish media playback
58  * information or handle media keys. In general an app only needs one session
59  * for all playback, though multiple sessions can be created to provide finer
60  * grain controls of media.
61  * <p>
62  * Once a session is created the owner of the session may pass its
63  * {@link #getSessionToken() session token} to other processes to allow them to
64  * create a {@link MediaController} to interact with the session.
65  * <p>
66  * To receive commands, media keys, and other events a {@link Callback} must be
67  * set with {@link #setCallback(Callback)} and {@link #setActive(boolean)
68  * setActive(true)} must be called.
69  * <p>
70  * When an app is finished performing playback it must call {@link #release()}
71  * to clean up the session and notify any controllers.
72  * <p>
73  * MediaSession objects are thread safe.
74  */
75 public final class MediaSession {
76     private static final String TAG = "MediaSession";
77 
78     /**
79      * Set this flag on the session to indicate that it can handle media button
80      * events.
81      * @deprecated This flag is no longer used. All media sessions are expected to handle media
82      * button events now.
83      */
84     @Deprecated
85     public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
86 
87     /**
88      * Set this flag on the session to indicate that it handles transport
89      * control commands through its {@link Callback}.
90      * @deprecated This flag is no longer used. All media sessions are expected to handle transport
91      * controls now.
92      */
93     @Deprecated
94     public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
95 
96     /**
97      * System only flag for a session that needs to have priority over all other
98      * sessions. This flag ensures this session will receive media button events
99      * regardless of the current ordering in the system.
100      *
101      * @hide
102      */
103     public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16;
104 
105     /** @hide */
106     @Retention(RetentionPolicy.SOURCE)
107     @IntDef(flag = true, value = {
108             FLAG_HANDLES_MEDIA_BUTTONS,
109             FLAG_HANDLES_TRANSPORT_CONTROLS,
110             FLAG_EXCLUSIVE_GLOBAL_PRIORITY })
111     public @interface SessionFlags { }
112 
113     private final Object mLock = new Object();
114     private final int mMaxBitmapSize;
115 
116     private final MediaSession.Token mSessionToken;
117     private final MediaController mController;
118     private final ISession mBinder;
119     private final CallbackStub mCbStub;
120 
121     private CallbackMessageHandler mCallback;
122     private VolumeProvider mVolumeProvider;
123     private PlaybackState mPlaybackState;
124 
125     private boolean mActive = false;
126 
127     /**
128      * Creates a new session. The session will automatically be registered with
129      * the system but will not be published until {@link #setActive(boolean)
130      * setActive(true)} is called. You must call {@link #release()} when
131      * finished with the session.
132      *
133      * @param context The context to use to create the session.
134      * @param tag A short name for debugging purposes.
135      */
MediaSession(@onNull Context context, @NonNull String tag)136     public MediaSession(@NonNull Context context, @NonNull String tag) {
137         this(context, tag, UserHandle.myUserId());
138     }
139 
140     /**
141      * Creates a new session as the specified user. To create a session as a
142      * user other than your own you must hold the
143      * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
144      * permission.
145      *
146      * @param context The context to use to create the session.
147      * @param tag A short name for debugging purposes.
148      * @param userId The user id to create the session as.
149      * @hide
150      */
MediaSession(@onNull Context context, @NonNull String tag, int userId)151     public MediaSession(@NonNull Context context, @NonNull String tag, int userId) {
152         if (context == null) {
153             throw new IllegalArgumentException("context cannot be null.");
154         }
155         if (TextUtils.isEmpty(tag)) {
156             throw new IllegalArgumentException("tag cannot be null or empty");
157         }
158         mMaxBitmapSize = context.getResources().getDimensionPixelSize(
159                 com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
160         mCbStub = new CallbackStub(this);
161         MediaSessionManager manager = (MediaSessionManager) context
162                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
163         try {
164             mBinder = manager.createSession(mCbStub, tag, userId);
165             mSessionToken = new Token(mBinder.getController());
166             mController = new MediaController(context, mSessionToken);
167         } catch (RemoteException e) {
168             throw new RuntimeException("Remote error creating session.", e);
169         }
170     }
171 
172     /**
173      * Set the callback to receive updates for the MediaSession. This includes
174      * media button events and transport controls. The caller's thread will be
175      * used to post updates.
176      * <p>
177      * Set the callback to null to stop receiving updates.
178      *
179      * @param callback The callback object
180      */
setCallback(@ullable Callback callback)181     public void setCallback(@Nullable Callback callback) {
182         setCallback(callback, null);
183     }
184 
185     /**
186      * Set the callback to receive updates for the MediaSession. This includes
187      * media button events and transport controls.
188      * <p>
189      * Set the callback to null to stop receiving updates.
190      *
191      * @param callback The callback to receive updates on.
192      * @param handler The handler that events should be posted on.
193      */
setCallback(@ullable Callback callback, @Nullable Handler handler)194     public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
195         synchronized (mLock) {
196             if (callback == null) {
197                 if (mCallback != null) {
198                     mCallback.mCallback.mSession = null;
199                 }
200                 mCallback = null;
201                 return;
202             }
203             if (mCallback != null) {
204                 // We're updating the callback, clear the session from the old
205                 // one.
206                 mCallback.mCallback.mSession = null;
207             }
208             if (handler == null) {
209                 handler = new Handler();
210             }
211             callback.mSession = this;
212             CallbackMessageHandler msgHandler = new CallbackMessageHandler(handler.getLooper(),
213                     callback);
214             mCallback = msgHandler;
215         }
216     }
217 
218     /**
219      * Set an intent for launching UI for this Session. This can be used as a
220      * quick link to an ongoing media screen. The intent should be for an
221      * activity that may be started using {@link Activity#startActivity(Intent)}.
222      *
223      * @param pi The intent to launch to show UI for this Session.
224      */
setSessionActivity(@ullable PendingIntent pi)225     public void setSessionActivity(@Nullable PendingIntent pi) {
226         try {
227             mBinder.setLaunchPendingIntent(pi);
228         } catch (RemoteException e) {
229             Log.wtf(TAG, "Failure in setLaunchPendingIntent.", e);
230         }
231     }
232 
233     /**
234      * Set a pending intent for your media button receiver to allow restarting
235      * playback after the session has been stopped. If your app is started in
236      * this way an {@link Intent#ACTION_MEDIA_BUTTON} intent will be sent via
237      * the pending intent.
238      *
239      * @param mbr The {@link PendingIntent} to send the media button event to.
240      */
setMediaButtonReceiver(@ullable PendingIntent mbr)241     public void setMediaButtonReceiver(@Nullable PendingIntent mbr) {
242         try {
243             mBinder.setMediaButtonReceiver(mbr);
244         } catch (RemoteException e) {
245             Log.wtf(TAG, "Failure in setMediaButtonReceiver.", e);
246         }
247     }
248 
249     /**
250      * Set any flags for the session.
251      *
252      * @param flags The flags to set for this session.
253      */
setFlags(@essionFlags int flags)254     public void setFlags(@SessionFlags int flags) {
255         try {
256             mBinder.setFlags(flags);
257         } catch (RemoteException e) {
258             Log.wtf(TAG, "Failure in setFlags.", e);
259         }
260     }
261 
262     /**
263      * Set the attributes for this session's audio. This will affect the
264      * system's volume handling for this session. If
265      * {@link #setPlaybackToRemote} was previously called it will stop receiving
266      * volume commands and the system will begin sending volume changes to the
267      * appropriate stream.
268      * <p>
269      * By default sessions use attributes for media.
270      *
271      * @param attributes The {@link AudioAttributes} for this session's audio.
272      */
setPlaybackToLocal(AudioAttributes attributes)273     public void setPlaybackToLocal(AudioAttributes attributes) {
274         if (attributes == null) {
275             throw new IllegalArgumentException("Attributes cannot be null for local playback.");
276         }
277         try {
278             mBinder.setPlaybackToLocal(attributes);
279         } catch (RemoteException e) {
280             Log.wtf(TAG, "Failure in setPlaybackToLocal.", e);
281         }
282     }
283 
284     /**
285      * Configure this session to use remote volume handling. This must be called
286      * to receive volume button events, otherwise the system will adjust the
287      * appropriate stream volume for this session. If
288      * {@link #setPlaybackToLocal} was previously called the system will stop
289      * handling volume changes for this session and pass them to the volume
290      * provider instead.
291      *
292      * @param volumeProvider The provider that will handle volume changes. May
293      *            not be null.
294      */
setPlaybackToRemote(@onNull VolumeProvider volumeProvider)295     public void setPlaybackToRemote(@NonNull VolumeProvider volumeProvider) {
296         if (volumeProvider == null) {
297             throw new IllegalArgumentException("volumeProvider may not be null!");
298         }
299         synchronized (mLock) {
300             mVolumeProvider = volumeProvider;
301         }
302         volumeProvider.setCallback(new VolumeProvider.Callback() {
303             @Override
304             public void onVolumeChanged(VolumeProvider volumeProvider) {
305                 notifyRemoteVolumeChanged(volumeProvider);
306             }
307         });
308 
309         try {
310             mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(),
311                     volumeProvider.getMaxVolume());
312             mBinder.setCurrentVolume(volumeProvider.getCurrentVolume());
313         } catch (RemoteException e) {
314             Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
315         }
316     }
317 
318     /**
319      * Set if this session is currently active and ready to receive commands. If
320      * set to false your session's controller may not be discoverable. You must
321      * set the session to active before it can start receiving media button
322      * events or transport commands.
323      *
324      * @param active Whether this session is active or not.
325      */
setActive(boolean active)326     public void setActive(boolean active) {
327         if (mActive == active) {
328             return;
329         }
330         try {
331             mBinder.setActive(active);
332             mActive = active;
333         } catch (RemoteException e) {
334             Log.wtf(TAG, "Failure in setActive.", e);
335         }
336     }
337 
338     /**
339      * Get the current active state of this session.
340      *
341      * @return True if the session is active, false otherwise.
342      */
isActive()343     public boolean isActive() {
344         return mActive;
345     }
346 
347     /**
348      * Send a proprietary event to all MediaControllers listening to this
349      * Session. It's up to the Controller/Session owner to determine the meaning
350      * of any events.
351      *
352      * @param event The name of the event to send
353      * @param extras Any extras included with the event
354      */
sendSessionEvent(@onNull String event, @Nullable Bundle extras)355     public void sendSessionEvent(@NonNull String event, @Nullable Bundle extras) {
356         if (TextUtils.isEmpty(event)) {
357             throw new IllegalArgumentException("event cannot be null or empty");
358         }
359         try {
360             mBinder.sendEvent(event, extras);
361         } catch (RemoteException e) {
362             Log.wtf(TAG, "Error sending event", e);
363         }
364     }
365 
366     /**
367      * This must be called when an app has finished performing playback. If
368      * playback is expected to start again shortly the session can be left open,
369      * but it must be released if your activity or service is being destroyed.
370      */
release()371     public void release() {
372         try {
373             mBinder.destroy();
374         } catch (RemoteException e) {
375             Log.wtf(TAG, "Error releasing session: ", e);
376         }
377     }
378 
379     /**
380      * Retrieve a token object that can be used by apps to create a
381      * {@link MediaController} for interacting with this session. The owner of
382      * the session is responsible for deciding how to distribute these tokens.
383      *
384      * @return A token that can be used to create a MediaController for this
385      *         session
386      */
getSessionToken()387     public @NonNull Token getSessionToken() {
388         return mSessionToken;
389     }
390 
391     /**
392      * Get a controller for this session. This is a convenience method to avoid
393      * having to cache your own controller in process.
394      *
395      * @return A controller for this session.
396      */
getController()397     public @NonNull MediaController getController() {
398         return mController;
399     }
400 
401     /**
402      * Update the current playback state.
403      *
404      * @param state The current state of playback
405      */
setPlaybackState(@ullable PlaybackState state)406     public void setPlaybackState(@Nullable PlaybackState state) {
407         mPlaybackState = state;
408         try {
409             mBinder.setPlaybackState(state);
410         } catch (RemoteException e) {
411             Log.wtf(TAG, "Dead object in setPlaybackState.", e);
412         }
413     }
414 
415     /**
416      * Update the current metadata. New metadata can be created using
417      * {@link android.media.MediaMetadata.Builder}. This operation may take time proportional to
418      * the size of the bitmap to replace large bitmaps with a scaled down copy.
419      *
420      * @param metadata The new metadata
421      * @see android.media.MediaMetadata.Builder#putBitmap
422      */
setMetadata(@ullable MediaMetadata metadata)423     public void setMetadata(@Nullable MediaMetadata metadata) {
424         if (metadata != null) {
425             metadata = (new MediaMetadata.Builder(metadata, mMaxBitmapSize)).build();
426         }
427         try {
428             mBinder.setMetadata(metadata);
429         } catch (RemoteException e) {
430             Log.wtf(TAG, "Dead object in setPlaybackState.", e);
431         }
432     }
433 
434     /**
435      * Update the list of items in the play queue. It is an ordered list and
436      * should contain the current item, and previous or upcoming items if they
437      * exist. Specify null if there is no current play queue.
438      * <p>
439      * The queue should be of reasonable size. If the play queue is unbounded
440      * within your app, it is better to send a reasonable amount in a sliding
441      * window instead.
442      *
443      * @param queue A list of items in the play queue.
444      */
setQueue(@ullable List<QueueItem> queue)445     public void setQueue(@Nullable List<QueueItem> queue) {
446         try {
447             mBinder.setQueue(queue == null ? null : new ParceledListSlice<QueueItem>(queue));
448         } catch (RemoteException e) {
449             Log.wtf("Dead object in setQueue.", e);
450         }
451     }
452 
453     /**
454      * Set the title of the play queue. The UI should display this title along
455      * with the play queue itself.
456      * e.g. "Play Queue", "Now Playing", or an album name.
457      *
458      * @param title The title of the play queue.
459      */
setQueueTitle(@ullable CharSequence title)460     public void setQueueTitle(@Nullable CharSequence title) {
461         try {
462             mBinder.setQueueTitle(title);
463         } catch (RemoteException e) {
464             Log.wtf("Dead object in setQueueTitle.", e);
465         }
466     }
467 
468     /**
469      * Set the style of rating used by this session. Apps trying to set the
470      * rating should use this style. Must be one of the following:
471      * <ul>
472      * <li>{@link Rating#RATING_NONE}</li>
473      * <li>{@link Rating#RATING_3_STARS}</li>
474      * <li>{@link Rating#RATING_4_STARS}</li>
475      * <li>{@link Rating#RATING_5_STARS}</li>
476      * <li>{@link Rating#RATING_HEART}</li>
477      * <li>{@link Rating#RATING_PERCENTAGE}</li>
478      * <li>{@link Rating#RATING_THUMB_UP_DOWN}</li>
479      * </ul>
480      */
setRatingType(@ating.Style int type)481     public void setRatingType(@Rating.Style int type) {
482         try {
483             mBinder.setRatingType(type);
484         } catch (RemoteException e) {
485             Log.e(TAG, "Error in setRatingType.", e);
486         }
487     }
488 
489     /**
490      * Set some extras that can be associated with the {@link MediaSession}. No assumptions should
491      * be made as to how a {@link MediaController} will handle these extras.
492      * Keys should be fully qualified (e.g. com.example.MY_EXTRA) to avoid conflicts.
493      *
494      * @param extras The extras associated with the {@link MediaSession}.
495      */
setExtras(@ullable Bundle extras)496     public void setExtras(@Nullable Bundle extras) {
497         try {
498             mBinder.setExtras(extras);
499         } catch (RemoteException e) {
500             Log.wtf("Dead object in setExtras.", e);
501         }
502     }
503 
504     /**
505      * Notify the system that the remote volume changed.
506      *
507      * @param provider The provider that is handling volume changes.
508      * @hide
509      */
notifyRemoteVolumeChanged(VolumeProvider provider)510     public void notifyRemoteVolumeChanged(VolumeProvider provider) {
511         synchronized (mLock) {
512             if (provider == null || provider != mVolumeProvider) {
513                 Log.w(TAG, "Received update from stale volume provider");
514                 return;
515             }
516         }
517         try {
518             mBinder.setCurrentVolume(provider.getCurrentVolume());
519         } catch (RemoteException e) {
520             Log.e(TAG, "Error in notifyVolumeChanged", e);
521         }
522     }
523 
524     /**
525      * Returns the name of the package that sent the last media button, transport control, or
526      * command from controllers and the system. This is only valid while in a request callback, such
527      * as {@link Callback#onPlay}.
528      *
529      * @hide
530      */
getCallingPackage()531     public String getCallingPackage() {
532         try {
533             return mBinder.getCallingPackage();
534         } catch (RemoteException e) {
535             Log.wtf(TAG, "Dead object in getCallingPackage.", e);
536         }
537         return null;
538     }
539 
dispatchPrepare()540     private void dispatchPrepare() {
541         postToCallback(CallbackMessageHandler.MSG_PREPARE);
542     }
543 
dispatchPrepareFromMediaId(String mediaId, Bundle extras)544     private void dispatchPrepareFromMediaId(String mediaId, Bundle extras) {
545         postToCallback(CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
546     }
547 
dispatchPrepareFromSearch(String query, Bundle extras)548     private void dispatchPrepareFromSearch(String query, Bundle extras) {
549         postToCallback(CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
550     }
551 
dispatchPrepareFromUri(Uri uri, Bundle extras)552     private void dispatchPrepareFromUri(Uri uri, Bundle extras) {
553         postToCallback(CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
554     }
555 
dispatchPlay()556     private void dispatchPlay() {
557         postToCallback(CallbackMessageHandler.MSG_PLAY);
558     }
559 
dispatchPlayFromMediaId(String mediaId, Bundle extras)560     private void dispatchPlayFromMediaId(String mediaId, Bundle extras) {
561         postToCallback(CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
562     }
563 
dispatchPlayFromSearch(String query, Bundle extras)564     private void dispatchPlayFromSearch(String query, Bundle extras) {
565         postToCallback(CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
566     }
567 
dispatchPlayFromUri(Uri uri, Bundle extras)568     private void dispatchPlayFromUri(Uri uri, Bundle extras) {
569         postToCallback(CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
570     }
571 
dispatchSkipToItem(long id)572     private void dispatchSkipToItem(long id) {
573         postToCallback(CallbackMessageHandler.MSG_SKIP_TO_ITEM, id);
574     }
575 
dispatchPause()576     private void dispatchPause() {
577         postToCallback(CallbackMessageHandler.MSG_PAUSE);
578     }
579 
dispatchStop()580     private void dispatchStop() {
581         postToCallback(CallbackMessageHandler.MSG_STOP);
582     }
583 
dispatchNext()584     private void dispatchNext() {
585         postToCallback(CallbackMessageHandler.MSG_NEXT);
586     }
587 
dispatchPrevious()588     private void dispatchPrevious() {
589         postToCallback(CallbackMessageHandler.MSG_PREVIOUS);
590     }
591 
dispatchFastForward()592     private void dispatchFastForward() {
593         postToCallback(CallbackMessageHandler.MSG_FAST_FORWARD);
594     }
595 
dispatchRewind()596     private void dispatchRewind() {
597         postToCallback(CallbackMessageHandler.MSG_REWIND);
598     }
599 
dispatchSeekTo(long pos)600     private void dispatchSeekTo(long pos) {
601         postToCallback(CallbackMessageHandler.MSG_SEEK_TO, pos);
602     }
603 
dispatchRate(Rating rating)604     private void dispatchRate(Rating rating) {
605         postToCallback(CallbackMessageHandler.MSG_RATE, rating);
606     }
607 
dispatchCustomAction(String action, Bundle args)608     private void dispatchCustomAction(String action, Bundle args) {
609         postToCallback(CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
610     }
611 
dispatchMediaButton(Intent mediaButtonIntent)612     private void dispatchMediaButton(Intent mediaButtonIntent) {
613         postToCallback(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent);
614     }
615 
dispatchAdjustVolume(int direction)616     private void dispatchAdjustVolume(int direction) {
617         postToCallback(CallbackMessageHandler.MSG_ADJUST_VOLUME, direction);
618     }
619 
dispatchSetVolumeTo(int volume)620     private void dispatchSetVolumeTo(int volume) {
621         postToCallback(CallbackMessageHandler.MSG_SET_VOLUME, volume);
622     }
623 
postToCallback(int what)624     private void postToCallback(int what) {
625         postToCallback(what, null);
626     }
627 
postCommand(String command, Bundle args, ResultReceiver resultCb)628     private void postCommand(String command, Bundle args, ResultReceiver resultCb) {
629         Command cmd = new Command(command, args, resultCb);
630         postToCallback(CallbackMessageHandler.MSG_COMMAND, cmd);
631     }
632 
postToCallback(int what, Object obj)633     private void postToCallback(int what, Object obj) {
634         postToCallback(what, obj, null);
635     }
636 
postToCallback(int what, Object obj, Bundle extras)637     private void postToCallback(int what, Object obj, Bundle extras) {
638         synchronized (mLock) {
639             if (mCallback != null) {
640                 mCallback.post(what, obj, extras);
641             }
642         }
643     }
644 
645     /**
646      * Return true if this is considered an active playback state.
647      *
648      * @hide
649      */
isActiveState(int state)650     public static boolean isActiveState(int state) {
651         switch (state) {
652             case PlaybackState.STATE_FAST_FORWARDING:
653             case PlaybackState.STATE_REWINDING:
654             case PlaybackState.STATE_SKIPPING_TO_PREVIOUS:
655             case PlaybackState.STATE_SKIPPING_TO_NEXT:
656             case PlaybackState.STATE_BUFFERING:
657             case PlaybackState.STATE_CONNECTING:
658             case PlaybackState.STATE_PLAYING:
659                 return true;
660         }
661         return false;
662     }
663 
664     /**
665      * Represents an ongoing session. This may be passed to apps by the session
666      * owner to allow them to create a {@link MediaController} to communicate with
667      * the session.
668      */
669     public static final class Token implements Parcelable {
670 
671         private ISessionController mBinder;
672 
673         /**
674          * @hide
675          */
Token(ISessionController binder)676         public Token(ISessionController binder) {
677             mBinder = binder;
678         }
679 
680         @Override
describeContents()681         public int describeContents() {
682             return 0;
683         }
684 
685         @Override
writeToParcel(Parcel dest, int flags)686         public void writeToParcel(Parcel dest, int flags) {
687             dest.writeStrongBinder(mBinder.asBinder());
688         }
689 
690         @Override
hashCode()691         public int hashCode() {
692             final int prime = 31;
693             int result = 1;
694             result = prime * result + ((mBinder == null) ? 0 : mBinder.asBinder().hashCode());
695             return result;
696         }
697 
698         @Override
equals(Object obj)699         public boolean equals(Object obj) {
700             if (this == obj)
701                 return true;
702             if (obj == null)
703                 return false;
704             if (getClass() != obj.getClass())
705                 return false;
706             Token other = (Token) obj;
707             if (mBinder == null) {
708                 if (other.mBinder != null)
709                     return false;
710             } else if (!mBinder.asBinder().equals(other.mBinder.asBinder()))
711                 return false;
712             return true;
713         }
714 
getBinder()715         ISessionController getBinder() {
716             return mBinder;
717         }
718 
719         public static final Parcelable.Creator<Token> CREATOR
720                 = new Parcelable.Creator<Token>() {
721             @Override
722             public Token createFromParcel(Parcel in) {
723                 return new Token(ISessionController.Stub.asInterface(in.readStrongBinder()));
724             }
725 
726             @Override
727             public Token[] newArray(int size) {
728                 return new Token[size];
729             }
730         };
731     }
732 
733     /**
734      * Receives media buttons, transport controls, and commands from controllers
735      * and the system. A callback may be set using {@link #setCallback}.
736      */
737     public abstract static class Callback {
738         private MediaSession mSession;
739 
Callback()740         public Callback() {
741         }
742 
743         /**
744          * Called when a controller has sent a command to this session.
745          * The owner of the session may handle custom commands but is not
746          * required to.
747          *
748          * @param command The command name.
749          * @param args Optional parameters for the command, may be null.
750          * @param cb A result receiver to which a result may be sent by the command, may be null.
751          */
onCommand(@onNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb)752         public void onCommand(@NonNull String command, @Nullable Bundle args,
753                 @Nullable ResultReceiver cb) {
754         }
755 
756         /**
757          * Called when a media button is pressed and this session has the
758          * highest priority or a controller sends a media button event to the
759          * session. The default behavior will call the relevant method if the
760          * action for it was set.
761          * <p>
762          * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
763          * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
764          *
765          * @param mediaButtonIntent an intent containing the KeyEvent as an
766          *            extra
767          * @return True if the event was handled, false otherwise.
768          */
onMediaButtonEvent(@onNull Intent mediaButtonIntent)769         public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
770             if (mSession != null
771                     && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
772                 KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
773                 if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
774                     PlaybackState state = mSession.mPlaybackState;
775                     long validActions = state == null ? 0 : state.getActions();
776                     switch (ke.getKeyCode()) {
777                         case KeyEvent.KEYCODE_MEDIA_PLAY:
778                             if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
779                                 onPlay();
780                                 return true;
781                             }
782                             break;
783                         case KeyEvent.KEYCODE_MEDIA_PAUSE:
784                             if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
785                                 onPause();
786                                 return true;
787                             }
788                             break;
789                         case KeyEvent.KEYCODE_MEDIA_NEXT:
790                             if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
791                                 onSkipToNext();
792                                 return true;
793                             }
794                             break;
795                         case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
796                             if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
797                                 onSkipToPrevious();
798                                 return true;
799                             }
800                             break;
801                         case KeyEvent.KEYCODE_MEDIA_STOP:
802                             if ((validActions & PlaybackState.ACTION_STOP) != 0) {
803                                 onStop();
804                                 return true;
805                             }
806                             break;
807                         case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
808                             if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
809                                 onFastForward();
810                                 return true;
811                             }
812                             break;
813                         case KeyEvent.KEYCODE_MEDIA_REWIND:
814                             if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
815                                 onRewind();
816                                 return true;
817                             }
818                             break;
819                         case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
820                         case KeyEvent.KEYCODE_HEADSETHOOK:
821                             boolean isPlaying = state == null ? false
822                                     : state.getState() == PlaybackState.STATE_PLAYING;
823                             boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
824                                     | PlaybackState.ACTION_PLAY)) != 0;
825                             boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
826                                     | PlaybackState.ACTION_PAUSE)) != 0;
827                             if (isPlaying && canPause) {
828                                 onPause();
829                                 return true;
830                             } else if (!isPlaying && canPlay) {
831                                 onPlay();
832                                 return true;
833                             }
834                             break;
835                     }
836                 }
837             }
838             return false;
839         }
840 
841         /**
842          * Override to handle requests to prepare playback. During the preparation, a session should
843          * not hold audio focus in order to allow other sessions play seamlessly. The state of
844          * playback should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is
845          * done.
846          */
onPrepare()847         public void onPrepare() {
848         }
849 
850         /**
851          * Override to handle requests to prepare for playing a specific mediaId that was provided
852          * by your app's {@link MediaBrowserService}. During the preparation, a session should not
853          * hold audio focus in order to allow other sessions play seamlessly. The state of playback
854          * should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done.
855          * The playback of the prepared content should start in the implementation of
856          * {@link #onPlay}. Override {@link #onPlayFromMediaId} to handle requests for starting
857          * playback without preparation.
858          */
onPrepareFromMediaId(String mediaId, Bundle extras)859         public void onPrepareFromMediaId(String mediaId, Bundle extras) {
860         }
861 
862         /**
863          * Override to handle requests to prepare playback from a search query. An empty query
864          * indicates that the app may prepare any music. The implementation should attempt to make a
865          * smart choice about what to play. During the preparation, a session should not hold audio
866          * focus in order to allow other sessions play seamlessly. The state of playback should be
867          * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. The playback
868          * of the prepared content should start in the implementation of {@link #onPlay}. Override
869          * {@link #onPlayFromSearch} to handle requests for starting playback without preparation.
870          */
onPrepareFromSearch(String query, Bundle extras)871         public void onPrepareFromSearch(String query, Bundle extras) {
872         }
873 
874         /**
875          * Override to handle requests to prepare a specific media item represented by a URI.
876          * During the preparation, a session should not hold audio focus in order to allow
877          * other sessions play seamlessly. The state of playback should be updated to
878          * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
879          * The playback of the prepared content should start in the implementation of
880          * {@link #onPlay}. Override {@link #onPlayFromUri} to handle requests
881          * for starting playback without preparation.
882          */
onPrepareFromUri(Uri uri, Bundle extras)883         public void onPrepareFromUri(Uri uri, Bundle extras) {
884         }
885 
886         /**
887          * Override to handle requests to begin playback.
888          */
onPlay()889         public void onPlay() {
890         }
891 
892         /**
893          * Override to handle requests to begin playback from a search query. An
894          * empty query indicates that the app may play any music. The
895          * implementation should attempt to make a smart choice about what to
896          * play.
897          */
onPlayFromSearch(String query, Bundle extras)898         public void onPlayFromSearch(String query, Bundle extras) {
899         }
900 
901         /**
902          * Override to handle requests to play a specific mediaId that was
903          * provided by your app's {@link MediaBrowserService}.
904          */
onPlayFromMediaId(String mediaId, Bundle extras)905         public void onPlayFromMediaId(String mediaId, Bundle extras) {
906         }
907 
908         /**
909          * Override to handle requests to play a specific media item represented by a URI.
910          */
onPlayFromUri(Uri uri, Bundle extras)911         public void onPlayFromUri(Uri uri, Bundle extras) {
912         }
913 
914         /**
915          * Override to handle requests to play an item with a given id from the
916          * play queue.
917          */
onSkipToQueueItem(long id)918         public void onSkipToQueueItem(long id) {
919         }
920 
921         /**
922          * Override to handle requests to pause playback.
923          */
onPause()924         public void onPause() {
925         }
926 
927         /**
928          * Override to handle requests to skip to the next media item.
929          */
onSkipToNext()930         public void onSkipToNext() {
931         }
932 
933         /**
934          * Override to handle requests to skip to the previous media item.
935          */
onSkipToPrevious()936         public void onSkipToPrevious() {
937         }
938 
939         /**
940          * Override to handle requests to fast forward.
941          */
onFastForward()942         public void onFastForward() {
943         }
944 
945         /**
946          * Override to handle requests to rewind.
947          */
onRewind()948         public void onRewind() {
949         }
950 
951         /**
952          * Override to handle requests to stop playback.
953          */
onStop()954         public void onStop() {
955         }
956 
957         /**
958          * Override to handle requests to seek to a specific position in ms.
959          *
960          * @param pos New position to move to, in milliseconds.
961          */
onSeekTo(long pos)962         public void onSeekTo(long pos) {
963         }
964 
965         /**
966          * Override to handle the item being rated.
967          *
968          * @param rating
969          */
onSetRating(@onNull Rating rating)970         public void onSetRating(@NonNull Rating rating) {
971         }
972 
973         /**
974          * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be
975          * performed.
976          *
977          * @param action The action that was originally sent in the
978          *               {@link PlaybackState.CustomAction}.
979          * @param extras Optional extras specified by the {@link MediaController}.
980          */
onCustomAction(@onNull String action, @Nullable Bundle extras)981         public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
982         }
983     }
984 
985     /**
986      * @hide
987      */
988     public static class CallbackStub extends ISessionCallback.Stub {
989         private WeakReference<MediaSession> mMediaSession;
990 
CallbackStub(MediaSession session)991         public CallbackStub(MediaSession session) {
992             mMediaSession = new WeakReference<MediaSession>(session);
993         }
994 
995         @Override
onCommand(String command, Bundle args, ResultReceiver cb)996         public void onCommand(String command, Bundle args, ResultReceiver cb) {
997             MediaSession session = mMediaSession.get();
998             if (session != null) {
999                 session.postCommand(command, args, cb);
1000             }
1001         }
1002 
1003         @Override
onMediaButton(Intent mediaButtonIntent, int sequenceNumber, ResultReceiver cb)1004         public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber,
1005                 ResultReceiver cb) {
1006             MediaSession session = mMediaSession.get();
1007             try {
1008                 if (session != null) {
1009                     session.dispatchMediaButton(mediaButtonIntent);
1010                 }
1011             } finally {
1012                 if (cb != null) {
1013                     cb.send(sequenceNumber, null);
1014                 }
1015             }
1016         }
1017 
1018         @Override
onPrepare()1019         public void onPrepare() {
1020             MediaSession session = mMediaSession.get();
1021             if (session != null) {
1022                 session.dispatchPrepare();
1023             }
1024         }
1025 
1026         @Override
onPrepareFromMediaId(String mediaId, Bundle extras)1027         public void onPrepareFromMediaId(String mediaId, Bundle extras) {
1028             MediaSession session = mMediaSession.get();
1029             if (session != null) {
1030                 session.dispatchPrepareFromMediaId(mediaId, extras);
1031             }
1032         }
1033 
1034         @Override
onPrepareFromSearch(String query, Bundle extras)1035         public void onPrepareFromSearch(String query, Bundle extras) {
1036             MediaSession session = mMediaSession.get();
1037             if (session != null) {
1038                 session.dispatchPrepareFromSearch(query, extras);
1039             }
1040         }
1041 
1042         @Override
onPrepareFromUri(Uri uri, Bundle extras)1043         public void onPrepareFromUri(Uri uri, Bundle extras) {
1044             MediaSession session = mMediaSession.get();
1045             if (session != null) {
1046                 session.dispatchPrepareFromUri(uri, extras);
1047             }
1048         }
1049 
1050         @Override
onPlay()1051         public void onPlay() {
1052             MediaSession session = mMediaSession.get();
1053             if (session != null) {
1054                 session.dispatchPlay();
1055             }
1056         }
1057 
1058         @Override
onPlayFromMediaId(String mediaId, Bundle extras)1059         public void onPlayFromMediaId(String mediaId, Bundle extras) {
1060             MediaSession session = mMediaSession.get();
1061             if (session != null) {
1062                 session.dispatchPlayFromMediaId(mediaId, extras);
1063             }
1064         }
1065 
1066         @Override
onPlayFromSearch(String query, Bundle extras)1067         public void onPlayFromSearch(String query, Bundle extras) {
1068             MediaSession session = mMediaSession.get();
1069             if (session != null) {
1070                 session.dispatchPlayFromSearch(query, extras);
1071             }
1072         }
1073 
1074         @Override
onPlayFromUri(Uri uri, Bundle extras)1075         public void onPlayFromUri(Uri uri, Bundle extras) {
1076             MediaSession session = mMediaSession.get();
1077             if (session != null) {
1078                 session.dispatchPlayFromUri(uri, extras);
1079             }
1080         }
1081 
1082         @Override
onSkipToTrack(long id)1083         public void onSkipToTrack(long id) {
1084             MediaSession session = mMediaSession.get();
1085             if (session != null) {
1086                 session.dispatchSkipToItem(id);
1087             }
1088         }
1089 
1090         @Override
onPause()1091         public void onPause() {
1092             MediaSession session = mMediaSession.get();
1093             if (session != null) {
1094                 session.dispatchPause();
1095             }
1096         }
1097 
1098         @Override
onStop()1099         public void onStop() {
1100             MediaSession session = mMediaSession.get();
1101             if (session != null) {
1102                 session.dispatchStop();
1103             }
1104         }
1105 
1106         @Override
onNext()1107         public void onNext() {
1108             MediaSession session = mMediaSession.get();
1109             if (session != null) {
1110                 session.dispatchNext();
1111             }
1112         }
1113 
1114         @Override
onPrevious()1115         public void onPrevious() {
1116             MediaSession session = mMediaSession.get();
1117             if (session != null) {
1118                 session.dispatchPrevious();
1119             }
1120         }
1121 
1122         @Override
onFastForward()1123         public void onFastForward() {
1124             MediaSession session = mMediaSession.get();
1125             if (session != null) {
1126                 session.dispatchFastForward();
1127             }
1128         }
1129 
1130         @Override
onRewind()1131         public void onRewind() {
1132             MediaSession session = mMediaSession.get();
1133             if (session != null) {
1134                 session.dispatchRewind();
1135             }
1136         }
1137 
1138         @Override
onSeekTo(long pos)1139         public void onSeekTo(long pos) {
1140             MediaSession session = mMediaSession.get();
1141             if (session != null) {
1142                 session.dispatchSeekTo(pos);
1143             }
1144         }
1145 
1146         @Override
onRate(Rating rating)1147         public void onRate(Rating rating) {
1148             MediaSession session = mMediaSession.get();
1149             if (session != null) {
1150                 session.dispatchRate(rating);
1151             }
1152         }
1153 
1154         @Override
onCustomAction(String action, Bundle args)1155         public void onCustomAction(String action, Bundle args) {
1156             MediaSession session = mMediaSession.get();
1157             if (session != null) {
1158                 session.dispatchCustomAction(action, args);
1159             }
1160         }
1161 
1162         @Override
onAdjustVolume(int direction)1163         public void onAdjustVolume(int direction) {
1164             MediaSession session = mMediaSession.get();
1165             if (session != null) {
1166                 session.dispatchAdjustVolume(direction);
1167             }
1168         }
1169 
1170         @Override
onSetVolumeTo(int value)1171         public void onSetVolumeTo(int value) {
1172             MediaSession session = mMediaSession.get();
1173             if (session != null) {
1174                 session.dispatchSetVolumeTo(value);
1175             }
1176         }
1177 
1178     }
1179 
1180     /**
1181      * A single item that is part of the play queue. It contains a description
1182      * of the item and its id in the queue.
1183      */
1184     public static final class QueueItem implements Parcelable {
1185         /**
1186          * This id is reserved. No items can be explicitly assigned this id.
1187          */
1188         public static final int UNKNOWN_ID = -1;
1189 
1190         private final MediaDescription mDescription;
1191         private final long mId;
1192 
1193         /**
1194          * Create a new {@link MediaSession.QueueItem}.
1195          *
1196          * @param description The {@link MediaDescription} for this item.
1197          * @param id An identifier for this item. It must be unique within the
1198          *            play queue and cannot be {@link #UNKNOWN_ID}.
1199          */
QueueItem(MediaDescription description, long id)1200         public QueueItem(MediaDescription description, long id) {
1201             if (description == null) {
1202                 throw new IllegalArgumentException("Description cannot be null.");
1203             }
1204             if (id == UNKNOWN_ID) {
1205                 throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
1206             }
1207             mDescription = description;
1208             mId = id;
1209         }
1210 
QueueItem(Parcel in)1211         private QueueItem(Parcel in) {
1212             mDescription = MediaDescription.CREATOR.createFromParcel(in);
1213             mId = in.readLong();
1214         }
1215 
1216         /**
1217          * Get the description for this item.
1218          */
getDescription()1219         public MediaDescription getDescription() {
1220             return mDescription;
1221         }
1222 
1223         /**
1224          * Get the queue id for this item.
1225          */
getQueueId()1226         public long getQueueId() {
1227             return mId;
1228         }
1229 
1230         @Override
writeToParcel(Parcel dest, int flags)1231         public void writeToParcel(Parcel dest, int flags) {
1232             mDescription.writeToParcel(dest, flags);
1233             dest.writeLong(mId);
1234         }
1235 
1236         @Override
describeContents()1237         public int describeContents() {
1238             return 0;
1239         }
1240 
1241         public static final Creator<MediaSession.QueueItem> CREATOR = new Creator<MediaSession.QueueItem>() {
1242 
1243             @Override
1244             public MediaSession.QueueItem createFromParcel(Parcel p) {
1245                 return new MediaSession.QueueItem(p);
1246             }
1247 
1248             @Override
1249             public MediaSession.QueueItem[] newArray(int size) {
1250                 return new MediaSession.QueueItem[size];
1251             }
1252         };
1253 
1254         @Override
toString()1255         public String toString() {
1256             return "MediaSession.QueueItem {" +
1257                     "Description=" + mDescription +
1258                     ", Id=" + mId + " }";
1259         }
1260 
1261         @Override
equals(Object o)1262         public boolean equals(Object o) {
1263             if (o == null) {
1264                 return false;
1265             }
1266 
1267             if (!(o instanceof QueueItem)) {
1268                 return false;
1269             }
1270 
1271             final QueueItem item = (QueueItem) o;
1272             if (mId != item.mId) {
1273                 return false;
1274             }
1275 
1276             if (!Objects.equals(mDescription, item.mDescription)) {
1277                 return false;
1278             }
1279 
1280             return true;
1281         }
1282     }
1283 
1284     private static final class Command {
1285         public final String command;
1286         public final Bundle extras;
1287         public final ResultReceiver stub;
1288 
Command(String command, Bundle extras, ResultReceiver stub)1289         public Command(String command, Bundle extras, ResultReceiver stub) {
1290             this.command = command;
1291             this.extras = extras;
1292             this.stub = stub;
1293         }
1294     }
1295 
1296     private class CallbackMessageHandler extends Handler {
1297 
1298         private static final int MSG_COMMAND = 1;
1299         private static final int MSG_MEDIA_BUTTON = 2;
1300         private static final int MSG_PREPARE = 3;
1301         private static final int MSG_PREPARE_MEDIA_ID = 4;
1302         private static final int MSG_PREPARE_SEARCH = 5;
1303         private static final int MSG_PREPARE_URI = 6;
1304         private static final int MSG_PLAY = 7;
1305         private static final int MSG_PLAY_MEDIA_ID = 8;
1306         private static final int MSG_PLAY_SEARCH = 9;
1307         private static final int MSG_PLAY_URI = 10;
1308         private static final int MSG_SKIP_TO_ITEM = 11;
1309         private static final int MSG_PAUSE = 12;
1310         private static final int MSG_STOP = 13;
1311         private static final int MSG_NEXT = 14;
1312         private static final int MSG_PREVIOUS = 15;
1313         private static final int MSG_FAST_FORWARD = 16;
1314         private static final int MSG_REWIND = 17;
1315         private static final int MSG_SEEK_TO = 18;
1316         private static final int MSG_RATE = 19;
1317         private static final int MSG_CUSTOM_ACTION = 20;
1318         private static final int MSG_ADJUST_VOLUME = 21;
1319         private static final int MSG_SET_VOLUME = 22;
1320 
1321         private MediaSession.Callback mCallback;
1322 
CallbackMessageHandler(Looper looper, MediaSession.Callback callback)1323         public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
1324             super(looper, null, true);
1325             mCallback = callback;
1326         }
1327 
post(int what, Object obj, Bundle bundle)1328         public void post(int what, Object obj, Bundle bundle) {
1329             Message msg = obtainMessage(what, obj);
1330             msg.setData(bundle);
1331             msg.sendToTarget();
1332         }
1333 
post(int what, Object obj)1334         public void post(int what, Object obj) {
1335             obtainMessage(what, obj).sendToTarget();
1336         }
1337 
post(int what)1338         public void post(int what) {
1339             post(what, null);
1340         }
1341 
post(int what, Object obj, int arg1)1342         public void post(int what, Object obj, int arg1) {
1343             obtainMessage(what, arg1, 0, obj).sendToTarget();
1344         }
1345 
1346         @Override
handleMessage(Message msg)1347         public void handleMessage(Message msg) {
1348             VolumeProvider vp;
1349             switch (msg.what) {
1350                 case MSG_COMMAND:
1351                     Command cmd = (Command) msg.obj;
1352                     mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
1353                     break;
1354                 case MSG_MEDIA_BUTTON:
1355                     mCallback.onMediaButtonEvent((Intent) msg.obj);
1356                     break;
1357                 case MSG_PREPARE:
1358                     mCallback.onPrepare();
1359                     break;
1360                 case MSG_PREPARE_MEDIA_ID:
1361                     mCallback.onPrepareFromMediaId((String) msg.obj, msg.getData());
1362                     break;
1363                 case MSG_PREPARE_SEARCH:
1364                     mCallback.onPrepareFromSearch((String) msg.obj, msg.getData());
1365                     break;
1366                 case MSG_PREPARE_URI:
1367                     mCallback.onPrepareFromUri((Uri) msg.obj, msg.getData());
1368                     break;
1369                 case MSG_PLAY:
1370                     mCallback.onPlay();
1371                     break;
1372                 case MSG_PLAY_MEDIA_ID:
1373                     mCallback.onPlayFromMediaId((String) msg.obj, msg.getData());
1374                     break;
1375                 case MSG_PLAY_SEARCH:
1376                     mCallback.onPlayFromSearch((String) msg.obj, msg.getData());
1377                     break;
1378                 case MSG_PLAY_URI:
1379                     mCallback.onPlayFromUri((Uri) msg.obj, msg.getData());
1380                     break;
1381                 case MSG_SKIP_TO_ITEM:
1382                     mCallback.onSkipToQueueItem((Long) msg.obj);
1383                     break;
1384                 case MSG_PAUSE:
1385                     mCallback.onPause();
1386                     break;
1387                 case MSG_STOP:
1388                     mCallback.onStop();
1389                     break;
1390                 case MSG_NEXT:
1391                     mCallback.onSkipToNext();
1392                     break;
1393                 case MSG_PREVIOUS:
1394                     mCallback.onSkipToPrevious();
1395                     break;
1396                 case MSG_FAST_FORWARD:
1397                     mCallback.onFastForward();
1398                     break;
1399                 case MSG_REWIND:
1400                     mCallback.onRewind();
1401                     break;
1402                 case MSG_SEEK_TO:
1403                     mCallback.onSeekTo((Long) msg.obj);
1404                     break;
1405                 case MSG_RATE:
1406                     mCallback.onSetRating((Rating) msg.obj);
1407                     break;
1408                 case MSG_CUSTOM_ACTION:
1409                     mCallback.onCustomAction((String) msg.obj, msg.getData());
1410                     break;
1411                 case MSG_ADJUST_VOLUME:
1412                     synchronized (mLock) {
1413                         vp = mVolumeProvider;
1414                     }
1415                     if (vp != null) {
1416                         vp.onAdjustVolume((int) msg.obj);
1417                     }
1418                     break;
1419                 case MSG_SET_VOLUME:
1420                     synchronized (mLock) {
1421                         vp = mVolumeProvider;
1422                     }
1423                     if (vp != null) {
1424                         vp.onSetVolumeTo((int) msg.obj);
1425                     }
1426                     break;
1427             }
1428         }
1429     }
1430 }
1431