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