Home | History | Annotate | Download | only in session
      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 import android.view.ViewConfiguration;
     47 
     48 import java.lang.annotation.Retention;
     49 import java.lang.annotation.RetentionPolicy;
     50 import java.lang.ref.WeakReference;
     51 import java.util.List;
     52 import java.util.Objects;
     53 
     54 /**
     55  * Allows interaction with media controllers, volume keys, media buttons, and
     56  * transport controls.
     57  * <p>
     58  * A MediaSession should be created when an app wants to publish media playback
     59  * information or handle media keys. In general an app only needs one session
     60  * for all playback, though multiple sessions can be created to provide finer
     61  * grain controls of media.
     62  * <p>
     63  * Once a session is created the owner of the session may pass its
     64  * {@link #getSessionToken() session token} to other processes to allow them to
     65  * create a {@link MediaController} to interact with the session.
     66  * <p>
     67  * To receive commands, media keys, and other events a {@link Callback} must be
     68  * set with {@link #setCallback(Callback)} and {@link #setActive(boolean)
     69  * setActive(true)} must be called.
     70  * <p>
     71  * When an app is finished performing playback it must call {@link #release()}
     72  * to clean up the session and notify any controllers.
     73  * <p>
     74  * MediaSession objects are thread safe.
     75  */
     76 public final class MediaSession {
     77     private static final String TAG = "MediaSession";
     78 
     79     /**
     80      * Set this flag on the session to indicate that it can handle media button
     81      * events.
     82      * @deprecated This flag is no longer used. All media sessions are expected to handle media
     83      * button events now.
     84      */
     85     @Deprecated
     86     public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1 << 0;
     87 
     88     /**
     89      * Set this flag on the session to indicate that it handles transport
     90      * control commands through its {@link Callback}.
     91      * @deprecated This flag is no longer used. All media sessions are expected to handle transport
     92      * controls now.
     93      */
     94     @Deprecated
     95     public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 1 << 1;
     96 
     97     /**
     98      * System only flag for a session that needs to have priority over all other
     99      * sessions. This flag ensures this session will receive media button events
    100      * regardless of the current ordering in the system.
    101      *
    102      * @hide
    103      */
    104     public static final int FLAG_EXCLUSIVE_GLOBAL_PRIORITY = 1 << 16;
    105 
    106     /** @hide */
    107     @Retention(RetentionPolicy.SOURCE)
    108     @IntDef(flag = true, value = {
    109             FLAG_HANDLES_MEDIA_BUTTONS,
    110             FLAG_HANDLES_TRANSPORT_CONTROLS,
    111             FLAG_EXCLUSIVE_GLOBAL_PRIORITY })
    112     public @interface SessionFlags { }
    113 
    114     private final Object mLock = new Object();
    115     private final int mMaxBitmapSize;
    116 
    117     private final MediaSession.Token mSessionToken;
    118     private final MediaController mController;
    119     private final ISession mBinder;
    120     private final CallbackStub mCbStub;
    121 
    122     private CallbackMessageHandler mCallback;
    123     private VolumeProvider mVolumeProvider;
    124     private PlaybackState mPlaybackState;
    125 
    126     private boolean mActive = false;
    127 
    128     /**
    129      * Creates a new session. The session will automatically be registered with
    130      * the system but will not be published until {@link #setActive(boolean)
    131      * setActive(true)} is called. You must call {@link #release()} when
    132      * finished with the session.
    133      *
    134      * @param context The context to use to create the session.
    135      * @param tag A short name for debugging purposes.
    136      */
    137     public MediaSession(@NonNull Context context, @NonNull String tag) {
    138         this(context, tag, UserHandle.myUserId());
    139     }
    140 
    141     /**
    142      * Creates a new session as the specified user. To create a session as a
    143      * user other than your own you must hold the
    144      * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}
    145      * permission.
    146      *
    147      * @param context The context to use to create the session.
    148      * @param tag A short name for debugging purposes.
    149      * @param userId The user id to create the session as.
    150      * @hide
    151      */
    152     public MediaSession(@NonNull Context context, @NonNull String tag, int userId) {
    153         if (context == null) {
    154             throw new IllegalArgumentException("context cannot be null.");
    155         }
    156         if (TextUtils.isEmpty(tag)) {
    157             throw new IllegalArgumentException("tag cannot be null or empty");
    158         }
    159         mMaxBitmapSize = context.getResources().getDimensionPixelSize(
    160                 com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize);
    161         mCbStub = new CallbackStub(this);
    162         MediaSessionManager manager = (MediaSessionManager) context
    163                 .getSystemService(Context.MEDIA_SESSION_SERVICE);
    164         try {
    165             mBinder = manager.createSession(mCbStub, tag, userId);
    166             mSessionToken = new Token(mBinder.getController());
    167             mController = new MediaController(context, mSessionToken);
    168         } catch (RemoteException e) {
    169             throw new RuntimeException("Remote error creating session.", e);
    170         }
    171     }
    172 
    173     /**
    174      * Set the callback to receive updates for the MediaSession. This includes
    175      * media button events and transport controls. The caller's thread will be
    176      * used to post updates.
    177      * <p>
    178      * Set the callback to null to stop receiving updates.
    179      *
    180      * @param callback The callback object
    181      */
    182     public void setCallback(@Nullable Callback callback) {
    183         setCallback(callback, null);
    184     }
    185 
    186     /**
    187      * Set the callback to receive updates for the MediaSession. This includes
    188      * media button events and transport controls.
    189      * <p>
    190      * Set the callback to null to stop receiving updates.
    191      *
    192      * @param callback The callback to receive updates on.
    193      * @param handler The handler that events should be posted on.
    194      */
    195     public void setCallback(@Nullable Callback callback, @Nullable Handler handler) {
    196         synchronized (mLock) {
    197             if (callback == null) {
    198                 if (mCallback != null) {
    199                     mCallback.mCallback.mSession = null;
    200                 }
    201                 mCallback = null;
    202                 return;
    203             }
    204             if (mCallback != null) {
    205                 // We're updating the callback, clear the session from the old 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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    540     private void dispatchPrepare() {
    541         postToCallback(CallbackMessageHandler.MSG_PREPARE);
    542     }
    543 
    544     private void dispatchPrepareFromMediaId(String mediaId, Bundle extras) {
    545         postToCallback(CallbackMessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
    546     }
    547 
    548     private void dispatchPrepareFromSearch(String query, Bundle extras) {
    549         postToCallback(CallbackMessageHandler.MSG_PREPARE_SEARCH, query, extras);
    550     }
    551 
    552     private void dispatchPrepareFromUri(Uri uri, Bundle extras) {
    553         postToCallback(CallbackMessageHandler.MSG_PREPARE_URI, uri, extras);
    554     }
    555 
    556     private void dispatchPlay() {
    557         postToCallback(CallbackMessageHandler.MSG_PLAY);
    558     }
    559 
    560     private void dispatchPlayFromMediaId(String mediaId, Bundle extras) {
    561         postToCallback(CallbackMessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
    562     }
    563 
    564     private void dispatchPlayFromSearch(String query, Bundle extras) {
    565         postToCallback(CallbackMessageHandler.MSG_PLAY_SEARCH, query, extras);
    566     }
    567 
    568     private void dispatchPlayFromUri(Uri uri, Bundle extras) {
    569         postToCallback(CallbackMessageHandler.MSG_PLAY_URI, uri, extras);
    570     }
    571 
    572     private void dispatchSkipToItem(long id) {
    573         postToCallback(CallbackMessageHandler.MSG_SKIP_TO_ITEM, id);
    574     }
    575 
    576     private void dispatchPause() {
    577         postToCallback(CallbackMessageHandler.MSG_PAUSE);
    578     }
    579 
    580     private void dispatchStop() {
    581         postToCallback(CallbackMessageHandler.MSG_STOP);
    582     }
    583 
    584     private void dispatchNext() {
    585         postToCallback(CallbackMessageHandler.MSG_NEXT);
    586     }
    587 
    588     private void dispatchPrevious() {
    589         postToCallback(CallbackMessageHandler.MSG_PREVIOUS);
    590     }
    591 
    592     private void dispatchFastForward() {
    593         postToCallback(CallbackMessageHandler.MSG_FAST_FORWARD);
    594     }
    595 
    596     private void dispatchRewind() {
    597         postToCallback(CallbackMessageHandler.MSG_REWIND);
    598     }
    599 
    600     private void dispatchSeekTo(long pos) {
    601         postToCallback(CallbackMessageHandler.MSG_SEEK_TO, pos);
    602     }
    603 
    604     private void dispatchRate(Rating rating) {
    605         postToCallback(CallbackMessageHandler.MSG_RATE, rating);
    606     }
    607 
    608     private void dispatchCustomAction(String action, Bundle args) {
    609         postToCallback(CallbackMessageHandler.MSG_CUSTOM_ACTION, action, args);
    610     }
    611 
    612     private void dispatchMediaButton(Intent mediaButtonIntent) {
    613         postToCallback(CallbackMessageHandler.MSG_MEDIA_BUTTON, mediaButtonIntent);
    614     }
    615 
    616     private void dispatchAdjustVolume(int direction) {
    617         postToCallback(CallbackMessageHandler.MSG_ADJUST_VOLUME, direction);
    618     }
    619 
    620     private void dispatchSetVolumeTo(int volume) {
    621         postToCallback(CallbackMessageHandler.MSG_SET_VOLUME, volume);
    622     }
    623 
    624     private void postToCallback(int what) {
    625         postToCallback(what, null);
    626     }
    627 
    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 
    633     private void postToCallback(int what, Object obj) {
    634         postToCallback(what, obj, null);
    635     }
    636 
    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      */
    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          */
    676         public Token(ISessionController binder) {
    677             mBinder = binder;
    678         }
    679 
    680         @Override
    681         public int describeContents() {
    682             return 0;
    683         }
    684 
    685         @Override
    686         public void writeToParcel(Parcel dest, int flags) {
    687             dest.writeStrongBinder(mBinder.asBinder());
    688         }
    689 
    690         @Override
    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
    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 
    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         private CallbackMessageHandler mHandler;
    740         private boolean mMediaPlayPauseKeyPending;
    741 
    742         public Callback() {
    743         }
    744 
    745         /**
    746          * Called when a controller has sent a command to this session.
    747          * The owner of the session may handle custom commands but is not
    748          * required to.
    749          *
    750          * @param command The command name.
    751          * @param args Optional parameters for the command, may be null.
    752          * @param cb A result receiver to which a result may be sent by the command, may be null.
    753          */
    754         public void onCommand(@NonNull String command, @Nullable Bundle args,
    755                 @Nullable ResultReceiver cb) {
    756         }
    757 
    758         /**
    759          * Called when a media button is pressed and this session has the
    760          * highest priority or a controller sends a media button event to the
    761          * session. The default behavior will call the relevant method if the
    762          * action for it was set.
    763          * <p>
    764          * The intent will be of type {@link Intent#ACTION_MEDIA_BUTTON} with a
    765          * KeyEvent in {@link Intent#EXTRA_KEY_EVENT}
    766          *
    767          * @param mediaButtonIntent an intent containing the KeyEvent as an
    768          *            extra
    769          * @return True if the event was handled, false otherwise.
    770          */
    771         public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
    772             if (mSession != null && mHandler != null
    773                     && Intent.ACTION_MEDIA_BUTTON.equals(mediaButtonIntent.getAction())) {
    774                 KeyEvent ke = mediaButtonIntent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
    775                 if (ke != null && ke.getAction() == KeyEvent.ACTION_DOWN) {
    776                     PlaybackState state = mSession.mPlaybackState;
    777                     long validActions = state == null ? 0 : state.getActions();
    778                     switch (ke.getKeyCode()) {
    779                         case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
    780                         case KeyEvent.KEYCODE_HEADSETHOOK:
    781                             if (ke.getRepeatCount() > 0) {
    782                                 // Consider long-press as a single tap.
    783                                 handleMediaPlayPauseKeySingleTapIfPending();
    784                             } else if (mMediaPlayPauseKeyPending) {
    785                                 // Consider double tap as the next.
    786                                 mHandler.removeMessages(CallbackMessageHandler
    787                                         .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
    788                                 mMediaPlayPauseKeyPending = false;
    789                                 if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
    790                                     onSkipToNext();
    791                                 }
    792                             } else {
    793                                 mMediaPlayPauseKeyPending = true;
    794                                 mHandler.sendEmptyMessageDelayed(CallbackMessageHandler
    795                                         .MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT,
    796                                         ViewConfiguration.getDoubleTapTimeout());
    797                             }
    798                             return true;
    799                         default:
    800                             // If another key is pressed within double tap timeout, consider the
    801                             // pending play/pause as a single tap to handle media keys in order.
    802                             handleMediaPlayPauseKeySingleTapIfPending();
    803                             break;
    804                     }
    805 
    806                     switch (ke.getKeyCode()) {
    807                         case KeyEvent.KEYCODE_MEDIA_PLAY:
    808                             if ((validActions & PlaybackState.ACTION_PLAY) != 0) {
    809                                 onPlay();
    810                                 return true;
    811                             }
    812                             break;
    813                         case KeyEvent.KEYCODE_MEDIA_PAUSE:
    814                             if ((validActions & PlaybackState.ACTION_PAUSE) != 0) {
    815                                 onPause();
    816                                 return true;
    817                             }
    818                             break;
    819                         case KeyEvent.KEYCODE_MEDIA_NEXT:
    820                             if ((validActions & PlaybackState.ACTION_SKIP_TO_NEXT) != 0) {
    821                                 onSkipToNext();
    822                                 return true;
    823                             }
    824                             break;
    825                         case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
    826                             if ((validActions & PlaybackState.ACTION_SKIP_TO_PREVIOUS) != 0) {
    827                                 onSkipToPrevious();
    828                                 return true;
    829                             }
    830                             break;
    831                         case KeyEvent.KEYCODE_MEDIA_STOP:
    832                             if ((validActions & PlaybackState.ACTION_STOP) != 0) {
    833                                 onStop();
    834                                 return true;
    835                             }
    836                             break;
    837                         case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
    838                             if ((validActions & PlaybackState.ACTION_FAST_FORWARD) != 0) {
    839                                 onFastForward();
    840                                 return true;
    841                             }
    842                             break;
    843                         case KeyEvent.KEYCODE_MEDIA_REWIND:
    844                             if ((validActions & PlaybackState.ACTION_REWIND) != 0) {
    845                                 onRewind();
    846                                 return true;
    847                             }
    848                             break;
    849                     }
    850                 }
    851             }
    852             return false;
    853         }
    854 
    855         private void handleMediaPlayPauseKeySingleTapIfPending() {
    856             if (!mMediaPlayPauseKeyPending) {
    857                 return;
    858             }
    859             mMediaPlayPauseKeyPending = false;
    860             mHandler.removeMessages(CallbackMessageHandler.MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT);
    861             PlaybackState state = mSession.mPlaybackState;
    862             long validActions = state == null ? 0 : state.getActions();
    863             boolean isPlaying = state != null
    864                     && state.getState() == PlaybackState.STATE_PLAYING;
    865             boolean canPlay = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
    866                         | PlaybackState.ACTION_PLAY)) != 0;
    867             boolean canPause = (validActions & (PlaybackState.ACTION_PLAY_PAUSE
    868                         | PlaybackState.ACTION_PAUSE)) != 0;
    869             if (isPlaying && canPause) {
    870                 onPause();
    871             } else if (!isPlaying && canPlay) {
    872                 onPlay();
    873             }
    874         }
    875 
    876         /**
    877          * Override to handle requests to prepare playback. During the preparation, a session should
    878          * not hold audio focus in order to allow other sessions play seamlessly. The state of
    879          * playback should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is
    880          * done.
    881          */
    882         public void onPrepare() {
    883         }
    884 
    885         /**
    886          * Override to handle requests to prepare for playing a specific mediaId that was provided
    887          * by your app's {@link MediaBrowserService}. During the preparation, a session should not
    888          * hold audio focus in order to allow other sessions play seamlessly. The state of playback
    889          * should be updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done.
    890          * The playback of the prepared content should start in the implementation of
    891          * {@link #onPlay}. Override {@link #onPlayFromMediaId} to handle requests for starting
    892          * playback without preparation.
    893          */
    894         public void onPrepareFromMediaId(String mediaId, Bundle extras) {
    895         }
    896 
    897         /**
    898          * Override to handle requests to prepare playback from a search query. An empty query
    899          * indicates that the app may prepare any music. The implementation should attempt to make a
    900          * smart choice about what to play. During the preparation, a session should not hold audio
    901          * focus in order to allow other sessions play seamlessly. The state of playback should be
    902          * updated to {@link PlaybackState#STATE_PAUSED} after the preparation is done. The playback
    903          * of the prepared content should start in the implementation of {@link #onPlay}. Override
    904          * {@link #onPlayFromSearch} to handle requests for starting playback without preparation.
    905          */
    906         public void onPrepareFromSearch(String query, Bundle extras) {
    907         }
    908 
    909         /**
    910          * Override to handle requests to prepare a specific media item represented by a URI.
    911          * During the preparation, a session should not hold audio focus in order to allow
    912          * other sessions play seamlessly. The state of playback should be updated to
    913          * {@link PlaybackState#STATE_PAUSED} after the preparation is done.
    914          * The playback of the prepared content should start in the implementation of
    915          * {@link #onPlay}. Override {@link #onPlayFromUri} to handle requests
    916          * for starting playback without preparation.
    917          */
    918         public void onPrepareFromUri(Uri uri, Bundle extras) {
    919         }
    920 
    921         /**
    922          * Override to handle requests to begin playback.
    923          */
    924         public void onPlay() {
    925         }
    926 
    927         /**
    928          * Override to handle requests to begin playback from a search query. An
    929          * empty query indicates that the app may play any music. The
    930          * implementation should attempt to make a smart choice about what to
    931          * play.
    932          */
    933         public void onPlayFromSearch(String query, Bundle extras) {
    934         }
    935 
    936         /**
    937          * Override to handle requests to play a specific mediaId that was
    938          * provided by your app's {@link MediaBrowserService}.
    939          */
    940         public void onPlayFromMediaId(String mediaId, Bundle extras) {
    941         }
    942 
    943         /**
    944          * Override to handle requests to play a specific media item represented by a URI.
    945          */
    946         public void onPlayFromUri(Uri uri, Bundle extras) {
    947         }
    948 
    949         /**
    950          * Override to handle requests to play an item with a given id from the
    951          * play queue.
    952          */
    953         public void onSkipToQueueItem(long id) {
    954         }
    955 
    956         /**
    957          * Override to handle requests to pause playback.
    958          */
    959         public void onPause() {
    960         }
    961 
    962         /**
    963          * Override to handle requests to skip to the next media item.
    964          */
    965         public void onSkipToNext() {
    966         }
    967 
    968         /**
    969          * Override to handle requests to skip to the previous media item.
    970          */
    971         public void onSkipToPrevious() {
    972         }
    973 
    974         /**
    975          * Override to handle requests to fast forward.
    976          */
    977         public void onFastForward() {
    978         }
    979 
    980         /**
    981          * Override to handle requests to rewind.
    982          */
    983         public void onRewind() {
    984         }
    985 
    986         /**
    987          * Override to handle requests to stop playback.
    988          */
    989         public void onStop() {
    990         }
    991 
    992         /**
    993          * Override to handle requests to seek to a specific position in ms.
    994          *
    995          * @param pos New position to move to, in milliseconds.
    996          */
    997         public void onSeekTo(long pos) {
    998         }
    999 
   1000         /**
   1001          * Override to handle the item being rated.
   1002          *
   1003          * @param rating
   1004          */
   1005         public void onSetRating(@NonNull Rating rating) {
   1006         }
   1007 
   1008         /**
   1009          * Called when a {@link MediaController} wants a {@link PlaybackState.CustomAction} to be
   1010          * performed.
   1011          *
   1012          * @param action The action that was originally sent in the
   1013          *               {@link PlaybackState.CustomAction}.
   1014          * @param extras Optional extras specified by the {@link MediaController}.
   1015          */
   1016         public void onCustomAction(@NonNull String action, @Nullable Bundle extras) {
   1017         }
   1018     }
   1019 
   1020     /**
   1021      * @hide
   1022      */
   1023     public static class CallbackStub extends ISessionCallback.Stub {
   1024         private WeakReference<MediaSession> mMediaSession;
   1025 
   1026         public CallbackStub(MediaSession session) {
   1027             mMediaSession = new WeakReference<MediaSession>(session);
   1028         }
   1029 
   1030         @Override
   1031         public void onCommand(String command, Bundle args, ResultReceiver cb) {
   1032             MediaSession session = mMediaSession.get();
   1033             if (session != null) {
   1034                 session.postCommand(command, args, cb);
   1035             }
   1036         }
   1037 
   1038         @Override
   1039         public void onMediaButton(Intent mediaButtonIntent, int sequenceNumber,
   1040                 ResultReceiver cb) {
   1041             MediaSession session = mMediaSession.get();
   1042             try {
   1043                 if (session != null) {
   1044                     session.dispatchMediaButton(mediaButtonIntent);
   1045                 }
   1046             } finally {
   1047                 if (cb != null) {
   1048                     cb.send(sequenceNumber, null);
   1049                 }
   1050             }
   1051         }
   1052 
   1053         @Override
   1054         public void onPrepare() {
   1055             MediaSession session = mMediaSession.get();
   1056             if (session != null) {
   1057                 session.dispatchPrepare();
   1058             }
   1059         }
   1060 
   1061         @Override
   1062         public void onPrepareFromMediaId(String mediaId, Bundle extras) {
   1063             MediaSession session = mMediaSession.get();
   1064             if (session != null) {
   1065                 session.dispatchPrepareFromMediaId(mediaId, extras);
   1066             }
   1067         }
   1068 
   1069         @Override
   1070         public void onPrepareFromSearch(String query, Bundle extras) {
   1071             MediaSession session = mMediaSession.get();
   1072             if (session != null) {
   1073                 session.dispatchPrepareFromSearch(query, extras);
   1074             }
   1075         }
   1076 
   1077         @Override
   1078         public void onPrepareFromUri(Uri uri, Bundle extras) {
   1079             MediaSession session = mMediaSession.get();
   1080             if (session != null) {
   1081                 session.dispatchPrepareFromUri(uri, extras);
   1082             }
   1083         }
   1084 
   1085         @Override
   1086         public void onPlay() {
   1087             MediaSession session = mMediaSession.get();
   1088             if (session != null) {
   1089                 session.dispatchPlay();
   1090             }
   1091         }
   1092 
   1093         @Override
   1094         public void onPlayFromMediaId(String mediaId, Bundle extras) {
   1095             MediaSession session = mMediaSession.get();
   1096             if (session != null) {
   1097                 session.dispatchPlayFromMediaId(mediaId, extras);
   1098             }
   1099         }
   1100 
   1101         @Override
   1102         public void onPlayFromSearch(String query, Bundle extras) {
   1103             MediaSession session = mMediaSession.get();
   1104             if (session != null) {
   1105                 session.dispatchPlayFromSearch(query, extras);
   1106             }
   1107         }
   1108 
   1109         @Override
   1110         public void onPlayFromUri(Uri uri, Bundle extras) {
   1111             MediaSession session = mMediaSession.get();
   1112             if (session != null) {
   1113                 session.dispatchPlayFromUri(uri, extras);
   1114             }
   1115         }
   1116 
   1117         @Override
   1118         public void onSkipToTrack(long id) {
   1119             MediaSession session = mMediaSession.get();
   1120             if (session != null) {
   1121                 session.dispatchSkipToItem(id);
   1122             }
   1123         }
   1124 
   1125         @Override
   1126         public void onPause() {
   1127             MediaSession session = mMediaSession.get();
   1128             if (session != null) {
   1129                 session.dispatchPause();
   1130             }
   1131         }
   1132 
   1133         @Override
   1134         public void onStop() {
   1135             MediaSession session = mMediaSession.get();
   1136             if (session != null) {
   1137                 session.dispatchStop();
   1138             }
   1139         }
   1140 
   1141         @Override
   1142         public void onNext() {
   1143             MediaSession session = mMediaSession.get();
   1144             if (session != null) {
   1145                 session.dispatchNext();
   1146             }
   1147         }
   1148 
   1149         @Override
   1150         public void onPrevious() {
   1151             MediaSession session = mMediaSession.get();
   1152             if (session != null) {
   1153                 session.dispatchPrevious();
   1154             }
   1155         }
   1156 
   1157         @Override
   1158         public void onFastForward() {
   1159             MediaSession session = mMediaSession.get();
   1160             if (session != null) {
   1161                 session.dispatchFastForward();
   1162             }
   1163         }
   1164 
   1165         @Override
   1166         public void onRewind() {
   1167             MediaSession session = mMediaSession.get();
   1168             if (session != null) {
   1169                 session.dispatchRewind();
   1170             }
   1171         }
   1172 
   1173         @Override
   1174         public void onSeekTo(long pos) {
   1175             MediaSession session = mMediaSession.get();
   1176             if (session != null) {
   1177                 session.dispatchSeekTo(pos);
   1178             }
   1179         }
   1180 
   1181         @Override
   1182         public void onRate(Rating rating) {
   1183             MediaSession session = mMediaSession.get();
   1184             if (session != null) {
   1185                 session.dispatchRate(rating);
   1186             }
   1187         }
   1188 
   1189         @Override
   1190         public void onCustomAction(String action, Bundle args) {
   1191             MediaSession session = mMediaSession.get();
   1192             if (session != null) {
   1193                 session.dispatchCustomAction(action, args);
   1194             }
   1195         }
   1196 
   1197         @Override
   1198         public void onAdjustVolume(int direction) {
   1199             MediaSession session = mMediaSession.get();
   1200             if (session != null) {
   1201                 session.dispatchAdjustVolume(direction);
   1202             }
   1203         }
   1204 
   1205         @Override
   1206         public void onSetVolumeTo(int value) {
   1207             MediaSession session = mMediaSession.get();
   1208             if (session != null) {
   1209                 session.dispatchSetVolumeTo(value);
   1210             }
   1211         }
   1212 
   1213     }
   1214 
   1215     /**
   1216      * A single item that is part of the play queue. It contains a description
   1217      * of the item and its id in the queue.
   1218      */
   1219     public static final class QueueItem implements Parcelable {
   1220         /**
   1221          * This id is reserved. No items can be explicitly assigned this id.
   1222          */
   1223         public static final int UNKNOWN_ID = -1;
   1224 
   1225         private final MediaDescription mDescription;
   1226         private final long mId;
   1227 
   1228         /**
   1229          * Create a new {@link MediaSession.QueueItem}.
   1230          *
   1231          * @param description The {@link MediaDescription} for this item.
   1232          * @param id An identifier for this item. It must be unique within the
   1233          *            play queue and cannot be {@link #UNKNOWN_ID}.
   1234          */
   1235         public QueueItem(MediaDescription description, long id) {
   1236             if (description == null) {
   1237                 throw new IllegalArgumentException("Description cannot be null.");
   1238             }
   1239             if (id == UNKNOWN_ID) {
   1240                 throw new IllegalArgumentException("Id cannot be QueueItem.UNKNOWN_ID");
   1241             }
   1242             mDescription = description;
   1243             mId = id;
   1244         }
   1245 
   1246         private QueueItem(Parcel in) {
   1247             mDescription = MediaDescription.CREATOR.createFromParcel(in);
   1248             mId = in.readLong();
   1249         }
   1250 
   1251         /**
   1252          * Get the description for this item.
   1253          */
   1254         public MediaDescription getDescription() {
   1255             return mDescription;
   1256         }
   1257 
   1258         /**
   1259          * Get the queue id for this item.
   1260          */
   1261         public long getQueueId() {
   1262             return mId;
   1263         }
   1264 
   1265         @Override
   1266         public void writeToParcel(Parcel dest, int flags) {
   1267             mDescription.writeToParcel(dest, flags);
   1268             dest.writeLong(mId);
   1269         }
   1270 
   1271         @Override
   1272         public int describeContents() {
   1273             return 0;
   1274         }
   1275 
   1276         public static final Creator<MediaSession.QueueItem> CREATOR = new Creator<MediaSession.QueueItem>() {
   1277 
   1278             @Override
   1279             public MediaSession.QueueItem createFromParcel(Parcel p) {
   1280                 return new MediaSession.QueueItem(p);
   1281             }
   1282 
   1283             @Override
   1284             public MediaSession.QueueItem[] newArray(int size) {
   1285                 return new MediaSession.QueueItem[size];
   1286             }
   1287         };
   1288 
   1289         @Override
   1290         public String toString() {
   1291             return "MediaSession.QueueItem {" +
   1292                     "Description=" + mDescription +
   1293                     ", Id=" + mId + " }";
   1294         }
   1295 
   1296         @Override
   1297         public boolean equals(Object o) {
   1298             if (o == null) {
   1299                 return false;
   1300             }
   1301 
   1302             if (!(o instanceof QueueItem)) {
   1303                 return false;
   1304             }
   1305 
   1306             final QueueItem item = (QueueItem) o;
   1307             if (mId != item.mId) {
   1308                 return false;
   1309             }
   1310 
   1311             if (!Objects.equals(mDescription, item.mDescription)) {
   1312                 return false;
   1313             }
   1314 
   1315             return true;
   1316         }
   1317     }
   1318 
   1319     private static final class Command {
   1320         public final String command;
   1321         public final Bundle extras;
   1322         public final ResultReceiver stub;
   1323 
   1324         public Command(String command, Bundle extras, ResultReceiver stub) {
   1325             this.command = command;
   1326             this.extras = extras;
   1327             this.stub = stub;
   1328         }
   1329     }
   1330 
   1331     private class CallbackMessageHandler extends Handler {
   1332 
   1333         private static final int MSG_COMMAND = 1;
   1334         private static final int MSG_MEDIA_BUTTON = 2;
   1335         private static final int MSG_PREPARE = 3;
   1336         private static final int MSG_PREPARE_MEDIA_ID = 4;
   1337         private static final int MSG_PREPARE_SEARCH = 5;
   1338         private static final int MSG_PREPARE_URI = 6;
   1339         private static final int MSG_PLAY = 7;
   1340         private static final int MSG_PLAY_MEDIA_ID = 8;
   1341         private static final int MSG_PLAY_SEARCH = 9;
   1342         private static final int MSG_PLAY_URI = 10;
   1343         private static final int MSG_SKIP_TO_ITEM = 11;
   1344         private static final int MSG_PAUSE = 12;
   1345         private static final int MSG_STOP = 13;
   1346         private static final int MSG_NEXT = 14;
   1347         private static final int MSG_PREVIOUS = 15;
   1348         private static final int MSG_FAST_FORWARD = 16;
   1349         private static final int MSG_REWIND = 17;
   1350         private static final int MSG_SEEK_TO = 18;
   1351         private static final int MSG_RATE = 19;
   1352         private static final int MSG_CUSTOM_ACTION = 20;
   1353         private static final int MSG_ADJUST_VOLUME = 21;
   1354         private static final int MSG_SET_VOLUME = 22;
   1355         private static final int MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT = 23;
   1356 
   1357         private MediaSession.Callback mCallback;
   1358 
   1359         public CallbackMessageHandler(Looper looper, MediaSession.Callback callback) {
   1360             super(looper, null, true);
   1361             mCallback = callback;
   1362             mCallback.mHandler = this;
   1363         }
   1364 
   1365         public void post(int what, Object obj, Bundle bundle) {
   1366             Message msg = obtainMessage(what, obj);
   1367             msg.setData(bundle);
   1368             msg.sendToTarget();
   1369         }
   1370 
   1371         public void post(int what, Object obj) {
   1372             obtainMessage(what, obj).sendToTarget();
   1373         }
   1374 
   1375         public void post(int what) {
   1376             post(what, null);
   1377         }
   1378 
   1379         public void post(int what, Object obj, int arg1) {
   1380             obtainMessage(what, arg1, 0, obj).sendToTarget();
   1381         }
   1382 
   1383         @Override
   1384         public void handleMessage(Message msg) {
   1385             VolumeProvider vp;
   1386             switch (msg.what) {
   1387                 case MSG_COMMAND:
   1388                     Command cmd = (Command) msg.obj;
   1389                     mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
   1390                     break;
   1391                 case MSG_MEDIA_BUTTON:
   1392                     mCallback.onMediaButtonEvent((Intent) msg.obj);
   1393                     break;
   1394                 case MSG_PREPARE:
   1395                     mCallback.onPrepare();
   1396                     break;
   1397                 case MSG_PREPARE_MEDIA_ID:
   1398                     mCallback.onPrepareFromMediaId((String) msg.obj, msg.getData());
   1399                     break;
   1400                 case MSG_PREPARE_SEARCH:
   1401                     mCallback.onPrepareFromSearch((String) msg.obj, msg.getData());
   1402                     break;
   1403                 case MSG_PREPARE_URI:
   1404                     mCallback.onPrepareFromUri((Uri) msg.obj, msg.getData());
   1405                     break;
   1406                 case MSG_PLAY:
   1407                     mCallback.onPlay();
   1408                     break;
   1409                 case MSG_PLAY_MEDIA_ID:
   1410                     mCallback.onPlayFromMediaId((String) msg.obj, msg.getData());
   1411                     break;
   1412                 case MSG_PLAY_SEARCH:
   1413                     mCallback.onPlayFromSearch((String) msg.obj, msg.getData());
   1414                     break;
   1415                 case MSG_PLAY_URI:
   1416                     mCallback.onPlayFromUri((Uri) msg.obj, msg.getData());
   1417                     break;
   1418                 case MSG_SKIP_TO_ITEM:
   1419                     mCallback.onSkipToQueueItem((Long) msg.obj);
   1420                     break;
   1421                 case MSG_PAUSE:
   1422                     mCallback.onPause();
   1423                     break;
   1424                 case MSG_STOP:
   1425                     mCallback.onStop();
   1426                     break;
   1427                 case MSG_NEXT:
   1428                     mCallback.onSkipToNext();
   1429                     break;
   1430                 case MSG_PREVIOUS:
   1431                     mCallback.onSkipToPrevious();
   1432                     break;
   1433                 case MSG_FAST_FORWARD:
   1434                     mCallback.onFastForward();
   1435                     break;
   1436                 case MSG_REWIND:
   1437                     mCallback.onRewind();
   1438                     break;
   1439                 case MSG_SEEK_TO:
   1440                     mCallback.onSeekTo((Long) msg.obj);
   1441                     break;
   1442                 case MSG_RATE:
   1443                     mCallback.onSetRating((Rating) msg.obj);
   1444                     break;
   1445                 case MSG_CUSTOM_ACTION:
   1446                     mCallback.onCustomAction((String) msg.obj, msg.getData());
   1447                     break;
   1448                 case MSG_ADJUST_VOLUME:
   1449                     synchronized (mLock) {
   1450                         vp = mVolumeProvider;
   1451                     }
   1452                     if (vp != null) {
   1453                         vp.onAdjustVolume((int) msg.obj);
   1454                     }
   1455                     break;
   1456                 case MSG_SET_VOLUME:
   1457                     synchronized (mLock) {
   1458                         vp = mVolumeProvider;
   1459                     }
   1460                     if (vp != null) {
   1461                         vp.onSetVolumeTo((int) msg.obj);
   1462                     }
   1463                     break;
   1464                 case MSG_PLAY_PAUSE_KEY_DOUBLE_TAP_TIMEOUT:
   1465                     mCallback.handleMediaPlayPauseKeySingleTapIfPending();
   1466                     break;
   1467             }
   1468         }
   1469     }
   1470 }
   1471