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