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