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