Home | History | Annotate | Download | only in session
      1 /*
      2  * Copyright 2018 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.support.v4.media.session;
     18 
     19 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
     20 
     21 import android.app.Activity;
     22 import android.app.PendingIntent;
     23 import android.content.Context;
     24 import android.media.AudioManager;
     25 import android.media.session.MediaController;
     26 import android.net.Uri;
     27 import android.os.Bundle;
     28 import android.os.Handler;
     29 import android.os.IBinder;
     30 import android.os.Looper;
     31 import android.os.Message;
     32 import android.os.RemoteException;
     33 import android.os.ResultReceiver;
     34 import android.support.v4.media.MediaBrowserCompat;
     35 import android.support.v4.media.MediaDescriptionCompat;
     36 import android.support.v4.media.MediaMetadataCompat;
     37 import android.support.v4.media.RatingCompat;
     38 import android.support.v4.media.session.MediaSessionCompat.QueueItem;
     39 import android.support.v4.media.session.PlaybackStateCompat.CustomAction;
     40 import android.text.TextUtils;
     41 import android.util.Log;
     42 import android.view.KeyEvent;
     43 
     44 import androidx.annotation.NonNull;
     45 import androidx.annotation.RequiresApi;
     46 import androidx.annotation.RestrictTo;
     47 import androidx.core.app.BundleCompat;
     48 import androidx.core.app.ComponentActivity;
     49 import androidx.media.VolumeProviderCompat;
     50 
     51 import java.lang.ref.WeakReference;
     52 import java.util.ArrayList;
     53 import java.util.HashMap;
     54 import java.util.HashSet;
     55 import java.util.List;
     56 
     57 /**
     58  * Allows an app to interact with an ongoing media session. Media buttons and
     59  * other commands can be sent to the session. A callback may be registered to
     60  * receive updates from the session, such as metadata and play state changes.
     61  * <p>
     62  * A MediaController can be created if you have a {@link MediaSessionCompat.Token}
     63  * from the session owner.
     64  * <p>
     65  * MediaController objects are thread-safe.
     66  * <p>
     67  * This is a helper for accessing features in {@link android.media.session.MediaSession}
     68  * introduced after API level 4 in a backwards compatible fashion.
     69  * <p class="note">
     70  * If MediaControllerCompat is created with a {@link MediaSessionCompat.Token session token}
     71  * from another process, following methods will not work directly after the creation if the
     72  * {@link MediaSessionCompat.Token session token} is not passed through a
     73  * {@link MediaBrowserCompat}:
     74  * <ul>
     75  * <li>{@link #getPlaybackState()}.{@link PlaybackStateCompat#getExtras() getExtras()}</li>
     76  * <li>{@link #isCaptioningEnabled()}</li>
     77  * <li>{@link #getRepeatMode()}</li>
     78  * <li>{@link #getShuffleMode()}</li>
     79  * </ul></p>
     80  *
     81  * <div class="special reference">
     82  * <h3>Developer Guides</h3>
     83  * <p>For information about building your media application, read the
     84  * <a href="{@docRoot}guide/topics/media-apps/index.html">Media Apps</a> developer guide.</p>
     85  * </div>
     86  */
     87 public final class MediaControllerCompat {
     88     static final String TAG = "MediaControllerCompat";
     89 
     90     /**
     91      * @hide
     92      */
     93     @RestrictTo(LIBRARY)
     94     public static final String COMMAND_GET_EXTRA_BINDER =
     95             "android.support.v4.media.session.command.GET_EXTRA_BINDER";
     96     /**
     97      * @hide
     98      */
     99     @RestrictTo(LIBRARY)
    100     public static final String COMMAND_ADD_QUEUE_ITEM =
    101             "android.support.v4.media.session.command.ADD_QUEUE_ITEM";
    102     /**
    103      * @hide
    104      */
    105     @RestrictTo(LIBRARY)
    106     public static final String COMMAND_ADD_QUEUE_ITEM_AT =
    107             "android.support.v4.media.session.command.ADD_QUEUE_ITEM_AT";
    108     /**
    109      * @hide
    110      */
    111     @RestrictTo(LIBRARY)
    112     public static final String COMMAND_REMOVE_QUEUE_ITEM =
    113             "android.support.v4.media.session.command.REMOVE_QUEUE_ITEM";
    114     /**
    115      * @hide
    116      */
    117     @RestrictTo(LIBRARY)
    118     public static final String COMMAND_REMOVE_QUEUE_ITEM_AT =
    119             "android.support.v4.media.session.command.REMOVE_QUEUE_ITEM_AT";
    120 
    121     /**
    122      * @hide
    123      */
    124     @RestrictTo(LIBRARY)
    125     public static final String COMMAND_ARGUMENT_MEDIA_DESCRIPTION =
    126             "android.support.v4.media.session.command.ARGUMENT_MEDIA_DESCRIPTION";
    127     /**
    128      * @hide
    129      */
    130     @RestrictTo(LIBRARY)
    131     public static final String COMMAND_ARGUMENT_INDEX =
    132             "android.support.v4.media.session.command.ARGUMENT_INDEX";
    133 
    134     private static class MediaControllerExtraData extends ComponentActivity.ExtraData {
    135         private final MediaControllerCompat mMediaController;
    136 
    137         MediaControllerExtraData(MediaControllerCompat mediaController) {
    138             mMediaController = mediaController;
    139         }
    140 
    141         MediaControllerCompat getMediaController() {
    142             return mMediaController;
    143         }
    144     }
    145 
    146     /**
    147      * Sets a {@link MediaControllerCompat} in the {@code activity} for later retrieval via
    148      * {@link #getMediaController(Activity)}.
    149      *
    150      * <p>This is compatible with {@link Activity#setMediaController(MediaController)}.
    151      * If {@code activity} inherits {@link androidx.fragment.app.FragmentActivity}, the
    152      * {@code mediaController} will be saved in the {@code activity}. In addition to that,
    153      * on API 21 and later, {@link Activity#setMediaController(MediaController)} will be
    154      * called.</p>
    155      *
    156      * @param activity The activity to set the {@code mediaController} in, must not be null.
    157      * @param mediaController The controller for the session which should receive
    158      *     media keys and volume changes on API 21 and later.
    159      * @see #getMediaController(Activity)
    160      * @see Activity#setMediaController(android.media.session.MediaController)
    161      */
    162     public static void setMediaController(@NonNull Activity activity,
    163             MediaControllerCompat mediaController) {
    164         if (activity instanceof ComponentActivity) {
    165             ((ComponentActivity) activity).putExtraData(
    166                     new MediaControllerExtraData(mediaController));
    167         }
    168         if (android.os.Build.VERSION.SDK_INT >= 21) {
    169             Object controllerObj = null;
    170             if (mediaController != null) {
    171                 Object sessionTokenObj = mediaController.getSessionToken().getToken();
    172                 controllerObj = MediaControllerCompatApi21.fromToken(activity, sessionTokenObj);
    173             }
    174             MediaControllerCompatApi21.setMediaController(activity, controllerObj);
    175         }
    176     }
    177 
    178     /**
    179      * Retrieves the {@link MediaControllerCompat} set in the activity by
    180      * {@link #setMediaController(Activity, MediaControllerCompat)} for sending media key and volume
    181      * events.
    182      *
    183      * <p>This is compatible with {@link Activity#getMediaController()}.</p>
    184      *
    185      * @param activity The activity to get the media controller from, must not be null.
    186      * @return The controller which should receive events.
    187      * @see #setMediaController(Activity, MediaControllerCompat)
    188      */
    189     public static MediaControllerCompat getMediaController(@NonNull Activity activity) {
    190         if (activity instanceof ComponentActivity) {
    191             MediaControllerExtraData extraData =
    192                     ((ComponentActivity) activity).getExtraData(MediaControllerExtraData.class);
    193             return extraData != null ? extraData.getMediaController() : null;
    194         } else if (android.os.Build.VERSION.SDK_INT >= 21) {
    195             Object controllerObj = MediaControllerCompatApi21.getMediaController(activity);
    196             if (controllerObj == null) {
    197                 return null;
    198             }
    199             Object sessionTokenObj = MediaControllerCompatApi21.getSessionToken(controllerObj);
    200             try {
    201                 return new MediaControllerCompat(activity,
    202                         MediaSessionCompat.Token.fromToken(sessionTokenObj));
    203             } catch (RemoteException e) {
    204                 Log.e(TAG, "Dead object in getMediaController.", e);
    205             }
    206         }
    207         return null;
    208     }
    209 
    210     private static void validateCustomAction(String action, Bundle args) {
    211         if (action == null) {
    212             return;
    213         }
    214         switch(action) {
    215             case MediaSessionCompat.ACTION_FOLLOW:
    216             case MediaSessionCompat.ACTION_UNFOLLOW:
    217                 if (args == null
    218                         || !args.containsKey(MediaSessionCompat.ARGUMENT_MEDIA_ATTRIBUTE)) {
    219                     throw new IllegalArgumentException("An extra field "
    220                             + MediaSessionCompat.ARGUMENT_MEDIA_ATTRIBUTE + " is required "
    221                             + "for this action " + action + ".");
    222                 }
    223                 break;
    224         }
    225     }
    226 
    227     private final MediaControllerImpl mImpl;
    228     private final MediaSessionCompat.Token mToken;
    229     // This set is used to keep references to registered callbacks to prevent them being GCed,
    230     // since we only keep weak references for callbacks in this class and its inner classes.
    231     private final HashSet<Callback> mRegisteredCallbacks = new HashSet<>();
    232 
    233     /**
    234      * Creates a media controller from a session.
    235      *
    236      * @param session The session to be controlled.
    237      */
    238     public MediaControllerCompat(Context context, @NonNull MediaSessionCompat session) {
    239         if (session == null) {
    240             throw new IllegalArgumentException("session must not be null");
    241         }
    242         mToken = session.getSessionToken();
    243 
    244         if (android.os.Build.VERSION.SDK_INT >= 24) {
    245             mImpl = new MediaControllerImplApi24(context, session);
    246         } else if (android.os.Build.VERSION.SDK_INT >= 23) {
    247             mImpl = new MediaControllerImplApi23(context, session);
    248         } else if (android.os.Build.VERSION.SDK_INT >= 21) {
    249             mImpl = new MediaControllerImplApi21(context, session);
    250         } else {
    251             mImpl = new MediaControllerImplBase(mToken);
    252         }
    253     }
    254 
    255     /**
    256      * Creates a media controller from a session token which may have
    257      * been obtained from another process.
    258      *
    259      * @param sessionToken The token of the session to be controlled.
    260      * @throws RemoteException if the session is not accessible.
    261      */
    262     public MediaControllerCompat(Context context, @NonNull MediaSessionCompat.Token sessionToken)
    263             throws RemoteException {
    264         if (sessionToken == null) {
    265             throw new IllegalArgumentException("sessionToken must not be null");
    266         }
    267         mToken = sessionToken;
    268 
    269         if (android.os.Build.VERSION.SDK_INT >= 24) {
    270             mImpl = new MediaControllerImplApi24(context, sessionToken);
    271         } else if (android.os.Build.VERSION.SDK_INT >= 23) {
    272             mImpl = new MediaControllerImplApi23(context, sessionToken);
    273         } else if (android.os.Build.VERSION.SDK_INT >= 21) {
    274             mImpl = new MediaControllerImplApi21(context, sessionToken);
    275         } else {
    276             mImpl = new MediaControllerImplBase(mToken);
    277         }
    278     }
    279 
    280     /**
    281      * Gets a {@link TransportControls} instance for this session.
    282      *
    283      * @return A controls instance
    284      */
    285     public TransportControls getTransportControls() {
    286         return mImpl.getTransportControls();
    287     }
    288 
    289     /**
    290      * Sends the specified media button event to the session. Only media keys can
    291      * be sent by this method, other keys will be ignored.
    292      *
    293      * @param keyEvent The media button event to dispatch.
    294      * @return true if the event was sent to the session, false otherwise.
    295      */
    296     public boolean dispatchMediaButtonEvent(KeyEvent keyEvent) {
    297         if (keyEvent == null) {
    298             throw new IllegalArgumentException("KeyEvent may not be null");
    299         }
    300         return mImpl.dispatchMediaButtonEvent(keyEvent);
    301     }
    302 
    303     /**
    304      * Gets the current playback state for this session.
    305      *
    306      * <p>If the session is not ready, {@link PlaybackStateCompat#getExtras()} on the result of
    307      * this method may return null. </p>
    308      *
    309      * @return The current PlaybackState or null
    310      * @see #isSessionReady
    311      * @see Callback#onSessionReady
    312      */
    313     public PlaybackStateCompat getPlaybackState() {
    314         return mImpl.getPlaybackState();
    315     }
    316 
    317     /**
    318      * Gets the current metadata for this session.
    319      *
    320      * @return The current MediaMetadata or null.
    321      */
    322     public MediaMetadataCompat getMetadata() {
    323         return mImpl.getMetadata();
    324     }
    325 
    326     /**
    327      * Gets the current play queue for this session if one is set. If you only
    328      * care about the current item {@link #getMetadata()} should be used.
    329      *
    330      * @return The current play queue or null.
    331      */
    332     public List<QueueItem> getQueue() {
    333         return mImpl.getQueue();
    334     }
    335 
    336     /**
    337      * Adds a queue item from the given {@code description} at the end of the play queue
    338      * of this session. Not all sessions may support this. To know whether the session supports
    339      * this, get the session's flags with {@link #getFlags()} and check that the flag
    340      * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
    341      *
    342      * @param description The {@link MediaDescriptionCompat} for creating the
    343      *            {@link MediaSessionCompat.QueueItem} to be inserted.
    344      * @throws UnsupportedOperationException If this session doesn't support this.
    345      * @see #getFlags()
    346      * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
    347      */
    348     public void addQueueItem(MediaDescriptionCompat description) {
    349         mImpl.addQueueItem(description);
    350     }
    351 
    352     /**
    353      * Adds a queue item from the given {@code description} at the specified position
    354      * in the play queue of this session. Shifts the queue item currently at that position
    355      * (if any) and any subsequent queue items to the right (adds one to their indices).
    356      * Not all sessions may support this. To know whether the session supports this,
    357      * get the session's flags with {@link #getFlags()} and check that the flag
    358      * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
    359      *
    360      * @param description The {@link MediaDescriptionCompat} for creating the
    361      *            {@link MediaSessionCompat.QueueItem} to be inserted.
    362      * @param index The index at which the created {@link MediaSessionCompat.QueueItem}
    363      *            is to be inserted.
    364      * @throws UnsupportedOperationException If this session doesn't support this.
    365      * @see #getFlags()
    366      * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
    367      */
    368     public void addQueueItem(MediaDescriptionCompat description, int index) {
    369         mImpl.addQueueItem(description, index);
    370     }
    371 
    372     /**
    373      * Removes the first occurrence of the specified {@link MediaSessionCompat.QueueItem}
    374      * with the given {@link MediaDescriptionCompat description} in the play queue of the
    375      * associated session. Not all sessions may support this. To know whether the session supports
    376      * this, get the session's flags with {@link #getFlags()} and check that the flag
    377      * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
    378      *
    379      * @param description The {@link MediaDescriptionCompat} for denoting the
    380      *            {@link MediaSessionCompat.QueueItem} to be removed.
    381      * @throws UnsupportedOperationException If this session doesn't support this.
    382      * @see #getFlags()
    383      * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
    384      */
    385     public void removeQueueItem(MediaDescriptionCompat description) {
    386         mImpl.removeQueueItem(description);
    387     }
    388 
    389     /**
    390      * Removes an queue item at the specified position in the play queue
    391      * of this session. Not all sessions may support this. To know whether the session supports
    392      * this, get the session's flags with {@link #getFlags()} and check that the flag
    393      * {@link MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS} is set.
    394      *
    395      * @param index The index of the element to be removed.
    396      * @throws UnsupportedOperationException If this session doesn't support this.
    397      * @see #getFlags()
    398      * @see MediaSessionCompat#FLAG_HANDLES_QUEUE_COMMANDS
    399      * @deprecated Use {@link #removeQueueItem(MediaDescriptionCompat)} instead.
    400      */
    401     @Deprecated
    402     public void removeQueueItemAt(int index) {
    403         List<QueueItem> queue = getQueue();
    404         if (queue != null && index >= 0 && index < queue.size()) {
    405             QueueItem item = queue.get(index);
    406             if (item != null) {
    407                 removeQueueItem(item.getDescription());
    408             }
    409         }
    410     }
    411 
    412     /**
    413      * Gets the queue title for this session.
    414      */
    415     public CharSequence getQueueTitle() {
    416         return mImpl.getQueueTitle();
    417     }
    418 
    419     /**
    420      * Gets the extras for this session.
    421      */
    422     public Bundle getExtras() {
    423         return mImpl.getExtras();
    424     }
    425 
    426     /**
    427      * Gets the rating type supported by the session. One of:
    428      * <ul>
    429      * <li>{@link RatingCompat#RATING_NONE}</li>
    430      * <li>{@link RatingCompat#RATING_HEART}</li>
    431      * <li>{@link RatingCompat#RATING_THUMB_UP_DOWN}</li>
    432      * <li>{@link RatingCompat#RATING_3_STARS}</li>
    433      * <li>{@link RatingCompat#RATING_4_STARS}</li>
    434      * <li>{@link RatingCompat#RATING_5_STARS}</li>
    435      * <li>{@link RatingCompat#RATING_PERCENTAGE}</li>
    436      * </ul>
    437      * <p>If the session is not ready, it will return {@link RatingCompat#RATING_NONE}.</p>
    438      *
    439      * @return The supported rating type, or {@link RatingCompat#RATING_NONE} if the value is not
    440      *         set or the session is not ready.
    441      * @see #isSessionReady
    442      * @see Callback#onSessionReady
    443      */
    444     public int getRatingType() {
    445         return mImpl.getRatingType();
    446     }
    447 
    448     /**
    449      * Returns whether captioning is enabled for this session.
    450      *
    451      * <p>If the session is not ready, it will return a {@code false}.</p>
    452      *
    453      * @return {@code true} if captioning is enabled, {@code false} if disabled or not set.
    454      * @see #isSessionReady
    455      * @see Callback#onSessionReady
    456      */
    457     public boolean isCaptioningEnabled() {
    458         return mImpl.isCaptioningEnabled();
    459     }
    460 
    461     /**
    462      * Gets the repeat mode for this session.
    463      *
    464      * @return The latest repeat mode set to the session,
    465      *         {@link PlaybackStateCompat#REPEAT_MODE_NONE} if not set, or
    466      *         {@link PlaybackStateCompat#REPEAT_MODE_INVALID} if the session is not ready yet.
    467      * @see #isSessionReady
    468      * @see Callback#onSessionReady
    469      */
    470     public int getRepeatMode() {
    471         return mImpl.getRepeatMode();
    472     }
    473 
    474     /**
    475      * Gets the shuffle mode for this session.
    476      *
    477      * @return The latest shuffle mode set to the session, or
    478      *         {@link PlaybackStateCompat#SHUFFLE_MODE_NONE} if disabled or not set, or
    479      *         {@link PlaybackStateCompat#SHUFFLE_MODE_INVALID} if the session is not ready yet.
    480      * @see #isSessionReady
    481      * @see Callback#onSessionReady
    482      */
    483     public int getShuffleMode() {
    484         return mImpl.getShuffleMode();
    485     }
    486 
    487     /**
    488      * Gets the flags for this session. Flags are defined in
    489      * {@link MediaSessionCompat}.
    490      *
    491      * @return The current set of flags for the session.
    492      */
    493     public long getFlags() {
    494         return mImpl.getFlags();
    495     }
    496 
    497     /**
    498      * Gets the current playback info for this session.
    499      *
    500      * @return The current playback info or null.
    501      */
    502     public PlaybackInfo getPlaybackInfo() {
    503         return mImpl.getPlaybackInfo();
    504     }
    505 
    506     /**
    507      * Gets an intent for launching UI associated with this session if one
    508      * exists.
    509      *
    510      * @return A {@link PendingIntent} to launch UI or null.
    511      */
    512     public PendingIntent getSessionActivity() {
    513         return mImpl.getSessionActivity();
    514     }
    515 
    516     /**
    517      * Gets the token for the session this controller is connected to.
    518      *
    519      * @return The session's token.
    520      */
    521     public MediaSessionCompat.Token getSessionToken() {
    522         return mToken;
    523     }
    524 
    525     /**
    526      * Sets the volume of the output this session is playing on. The command will
    527      * be ignored if it does not support
    528      * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
    529      * {@link AudioManager} may be used to affect the handling.
    530      *
    531      * @see #getPlaybackInfo()
    532      * @param value The value to set it to, between 0 and the reported max.
    533      * @param flags Flags from {@link AudioManager} to include with the volume
    534      *            request.
    535      */
    536     public void setVolumeTo(int value, int flags) {
    537         mImpl.setVolumeTo(value, flags);
    538     }
    539 
    540     /**
    541      * Adjusts the volume of the output this session is playing on. The direction
    542      * must be one of {@link AudioManager#ADJUST_LOWER},
    543      * {@link AudioManager#ADJUST_RAISE}, or {@link AudioManager#ADJUST_SAME}.
    544      * The command will be ignored if the session does not support
    545      * {@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE} or
    546      * {@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}. The flags in
    547      * {@link AudioManager} may be used to affect the handling.
    548      *
    549      * @see #getPlaybackInfo()
    550      * @param direction The direction to adjust the volume in.
    551      * @param flags Any flags to pass with the command.
    552      */
    553     public void adjustVolume(int direction, int flags) {
    554         mImpl.adjustVolume(direction, flags);
    555     }
    556 
    557     /**
    558      * Adds a callback to receive updates from the Session. Updates will be
    559      * posted on the caller's thread.
    560      *
    561      * @param callback The callback object, must not be null.
    562      */
    563     public void registerCallback(@NonNull Callback callback) {
    564         registerCallback(callback, null);
    565     }
    566 
    567     /**
    568      * Adds a callback to receive updates from the session. Updates will be
    569      * posted on the specified handler's thread.
    570      *
    571      * @param callback The callback object, must not be null.
    572      * @param handler The handler to post updates on. If null the callers thread
    573      *            will be used.
    574      */
    575     public void registerCallback(@NonNull Callback callback, Handler handler) {
    576         if (callback == null) {
    577             throw new IllegalArgumentException("callback must not be null");
    578         }
    579         if (handler == null) {
    580             handler = new Handler();
    581         }
    582         callback.setHandler(handler);
    583         mImpl.registerCallback(callback, handler);
    584         mRegisteredCallbacks.add(callback);
    585     }
    586 
    587     /**
    588      * Stops receiving updates on the specified callback. If an update has
    589      * already been posted you may still receive it after calling this method.
    590      *
    591      * @param callback The callback to remove
    592      */
    593     public void unregisterCallback(@NonNull Callback callback) {
    594         if (callback == null) {
    595             throw new IllegalArgumentException("callback must not be null");
    596         }
    597         try {
    598             mRegisteredCallbacks.remove(callback);
    599             mImpl.unregisterCallback(callback);
    600         } finally {
    601             callback.setHandler(null);
    602         }
    603     }
    604 
    605     /**
    606      * Sends a generic command to the session. It is up to the session creator
    607      * to decide what commands and parameters they will support. As such,
    608      * commands should only be sent to sessions that the controller owns.
    609      *
    610      * @param command The command to send
    611      * @param params Any parameters to include with the command
    612      * @param cb The callback to receive the result on
    613      */
    614     public void sendCommand(@NonNull String command, Bundle params, ResultReceiver cb) {
    615         if (TextUtils.isEmpty(command)) {
    616             throw new IllegalArgumentException("command must neither be null nor empty");
    617         }
    618         mImpl.sendCommand(command, params, cb);
    619     }
    620 
    621     /**
    622      * Returns whether the session is ready or not.
    623      *
    624      * <p>If the session is not ready, following methods can work incorrectly.</p>
    625      * <ul>
    626      * <li>{@link #getPlaybackState()}</li>
    627      * <li>{@link #getRatingType()}</li>
    628      * <li>{@link #getRepeatMode()}</li>
    629      * <li>{@link #getShuffleMode()}</li>
    630      * <li>{@link #isCaptioningEnabled()}</li>
    631      * </ul>
    632      *
    633      * @return true if the session is ready, false otherwise.
    634      * @see Callback#onSessionReady()
    635      */
    636     public boolean isSessionReady() {
    637         return mImpl.isSessionReady();
    638     }
    639 
    640     /**
    641      * Gets the session owner's package name.
    642      *
    643      * @return The package name of of the session owner.
    644      */
    645     public String getPackageName() {
    646         return mImpl.getPackageName();
    647     }
    648 
    649     /**
    650      * Gets the underlying framework
    651      * {@link android.media.session.MediaController} object.
    652      * <p>
    653      * This method is only supported on API 21+.
    654      * </p>
    655      *
    656      * @return The underlying {@link android.media.session.MediaController}
    657      *         object, or null if none.
    658      */
    659     public Object getMediaController() {
    660         return mImpl.getMediaController();
    661     }
    662 
    663     /**
    664      * Callback for receiving updates on from the session. A Callback can be
    665      * registered using {@link #registerCallback}
    666      */
    667     public static abstract class Callback implements IBinder.DeathRecipient {
    668         private final Object mCallbackObj;
    669         MessageHandler mHandler;
    670         IMediaControllerCallback mIControllerCallback;
    671 
    672         public Callback() {
    673             if (android.os.Build.VERSION.SDK_INT >= 21) {
    674                 mCallbackObj = MediaControllerCompatApi21.createCallback(new StubApi21(this));
    675             } else {
    676                 mCallbackObj = mIControllerCallback = new StubCompat(this);
    677             }
    678         }
    679 
    680         /**
    681          * Override to handle the session being ready.
    682          *
    683          * @see MediaControllerCompat#isSessionReady
    684          */
    685         public void onSessionReady() {
    686         }
    687 
    688         /**
    689          * Override to handle the session being destroyed. The session is no
    690          * longer valid after this call and calls to it will be ignored.
    691          */
    692         public void onSessionDestroyed() {
    693         }
    694 
    695         /**
    696          * Override to handle custom events sent by the session owner without a
    697          * specified interface. Controllers should only handle these for
    698          * sessions they own.
    699          *
    700          * @param event The event from the session.
    701          * @param extras Optional parameters for the event.
    702          */
    703         public void onSessionEvent(String event, Bundle extras) {
    704         }
    705 
    706         /**
    707          * Override to handle changes in playback state.
    708          *
    709          * @param state The new playback state of the session
    710          */
    711         public void onPlaybackStateChanged(PlaybackStateCompat state) {
    712         }
    713 
    714         /**
    715          * Override to handle changes to the current metadata.
    716          *
    717          * @param metadata The current metadata for the session or null if none.
    718          * @see MediaMetadataCompat
    719          */
    720         public void onMetadataChanged(MediaMetadataCompat metadata) {
    721         }
    722 
    723         /**
    724          * Override to handle changes to items in the queue.
    725          *
    726          * @see MediaSessionCompat.QueueItem
    727          * @param queue A list of items in the current play queue. It should
    728          *            include the currently playing item as well as previous and
    729          *            upcoming items if applicable.
    730          */
    731         public void onQueueChanged(List<QueueItem> queue) {
    732         }
    733 
    734         /**
    735          * Override to handle changes to the queue title.
    736          *
    737          * @param title The title that should be displayed along with the play
    738          *            queue such as "Now Playing". May be null if there is no
    739          *            such title.
    740          */
    741         public void onQueueTitleChanged(CharSequence title) {
    742         }
    743 
    744         /**
    745          * Override to handle changes to the {@link MediaSessionCompat} extras.
    746          *
    747          * @param extras The extras that can include other information
    748          *            associated with the {@link MediaSessionCompat}.
    749          */
    750         public void onExtrasChanged(Bundle extras) {
    751         }
    752 
    753         /**
    754          * Override to handle changes to the audio info.
    755          *
    756          * @param info The current audio info for this session.
    757          */
    758         public void onAudioInfoChanged(PlaybackInfo info) {
    759         }
    760 
    761         /**
    762          * Override to handle changes to the captioning enabled status.
    763          *
    764          * @param enabled {@code true} if captioning is enabled, {@code false} otherwise.
    765          */
    766         public void onCaptioningEnabledChanged(boolean enabled) {
    767         }
    768 
    769         /**
    770          * Override to handle changes to the repeat mode.
    771          *
    772          * @param repeatMode The repeat mode. It should be one of followings:
    773          *                   {@link PlaybackStateCompat#REPEAT_MODE_NONE},
    774          *                   {@link PlaybackStateCompat#REPEAT_MODE_ONE},
    775          *                   {@link PlaybackStateCompat#REPEAT_MODE_ALL},
    776          *                   {@link PlaybackStateCompat#REPEAT_MODE_GROUP}
    777          */
    778         public void onRepeatModeChanged(@PlaybackStateCompat.RepeatMode int repeatMode) {
    779         }
    780 
    781         /**
    782          * Override to handle changes to the shuffle mode.
    783          *
    784          * @param shuffleMode The shuffle mode. Must be one of the followings:
    785          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
    786          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
    787          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_GROUP}
    788          */
    789         public void onShuffleModeChanged(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
    790         }
    791 
    792         /**
    793          * @hide
    794          */
    795         @RestrictTo(LIBRARY)
    796         public IMediaControllerCallback getIControllerCallback() {
    797             return mIControllerCallback;
    798         }
    799 
    800         @Override
    801         public void binderDied() {
    802             onSessionDestroyed();
    803         }
    804 
    805         /**
    806          * Set the handler to use for callbacks.
    807          */
    808         void setHandler(Handler handler) {
    809             if (handler == null) {
    810                 if (mHandler != null) {
    811                     mHandler.mRegistered = false;
    812                     mHandler.removeCallbacksAndMessages(null);
    813                     mHandler = null;
    814                 }
    815             } else {
    816                 mHandler = new MessageHandler(handler.getLooper());
    817                 mHandler.mRegistered = true;
    818             }
    819         }
    820 
    821         void postToHandler(int what, Object obj, Bundle data) {
    822             if (mHandler != null) {
    823                 Message msg = mHandler.obtainMessage(what, obj);
    824                 msg.setData(data);
    825                 msg.sendToTarget();
    826             }
    827         }
    828 
    829         private static class StubApi21 implements MediaControllerCompatApi21.Callback {
    830             private final WeakReference<MediaControllerCompat.Callback> mCallback;
    831 
    832             StubApi21(MediaControllerCompat.Callback callback) {
    833                 mCallback = new WeakReference<>(callback);
    834             }
    835 
    836             @Override
    837             public void onSessionDestroyed() {
    838                 MediaControllerCompat.Callback callback = mCallback.get();
    839                 if (callback != null) {
    840                     callback.onSessionDestroyed();
    841                 }
    842             }
    843 
    844             @Override
    845             public void onSessionEvent(String event, Bundle extras) {
    846                 MediaControllerCompat.Callback callback = mCallback.get();
    847                 if (callback != null) {
    848                     if (callback.mIControllerCallback != null
    849                             && android.os.Build.VERSION.SDK_INT < 23) {
    850                         // Ignore. ExtraCallback will handle this.
    851                     } else {
    852                         callback.onSessionEvent(event, extras);
    853                     }
    854                 }
    855             }
    856 
    857             @Override
    858             public void onPlaybackStateChanged(Object stateObj) {
    859                 MediaControllerCompat.Callback callback = mCallback.get();
    860                 if (callback != null) {
    861                     if (callback.mIControllerCallback != null) {
    862                         // Ignore. ExtraCallback will handle this.
    863                     } else {
    864                         callback.onPlaybackStateChanged(
    865                                 PlaybackStateCompat.fromPlaybackState(stateObj));
    866                     }
    867                 }
    868             }
    869 
    870             @Override
    871             public void onMetadataChanged(Object metadataObj) {
    872                 MediaControllerCompat.Callback callback = mCallback.get();
    873                 if (callback != null) {
    874                     callback.onMetadataChanged(MediaMetadataCompat.fromMediaMetadata(metadataObj));
    875                 }
    876             }
    877 
    878             @Override
    879             public void onQueueChanged(List<?> queue) {
    880                 MediaControllerCompat.Callback callback = mCallback.get();
    881                 if (callback != null) {
    882                     callback.onQueueChanged(QueueItem.fromQueueItemList(queue));
    883                 }
    884             }
    885 
    886             @Override
    887             public void onQueueTitleChanged(CharSequence title) {
    888                 MediaControllerCompat.Callback callback = mCallback.get();
    889                 if (callback != null) {
    890                     callback.onQueueTitleChanged(title);
    891                 }
    892             }
    893 
    894             @Override
    895             public void onExtrasChanged(Bundle extras) {
    896                 MediaControllerCompat.Callback callback = mCallback.get();
    897                 if (callback != null) {
    898                     callback.onExtrasChanged(extras);
    899                 }
    900             }
    901 
    902             @Override
    903             public void onAudioInfoChanged(
    904                     int type, int stream, int control, int max, int current) {
    905                 MediaControllerCompat.Callback callback = mCallback.get();
    906                 if (callback != null) {
    907                     callback.onAudioInfoChanged(
    908                             new PlaybackInfo(type, stream, control, max, current));
    909                 }
    910             }
    911         }
    912 
    913         private static class StubCompat extends IMediaControllerCallback.Stub {
    914             private final WeakReference<MediaControllerCompat.Callback> mCallback;
    915 
    916             StubCompat(MediaControllerCompat.Callback callback) {
    917                 mCallback = new WeakReference<>(callback);
    918             }
    919 
    920             @Override
    921             public void onEvent(String event, Bundle extras) throws RemoteException {
    922                 MediaControllerCompat.Callback callback = mCallback.get();
    923                 if (callback != null) {
    924                     callback.postToHandler(MessageHandler.MSG_EVENT, event, extras);
    925                 }
    926             }
    927 
    928             @Override
    929             public void onSessionDestroyed() throws RemoteException {
    930                 MediaControllerCompat.Callback callback = mCallback.get();
    931                 if (callback != null) {
    932                     callback.postToHandler(MessageHandler.MSG_DESTROYED, null, null);
    933                 }
    934             }
    935 
    936             @Override
    937             public void onPlaybackStateChanged(PlaybackStateCompat state) throws RemoteException {
    938                 MediaControllerCompat.Callback callback = mCallback.get();
    939                 if (callback != null) {
    940                     callback.postToHandler(MessageHandler.MSG_UPDATE_PLAYBACK_STATE, state, null);
    941                 }
    942             }
    943 
    944             @Override
    945             public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
    946                 MediaControllerCompat.Callback callback = mCallback.get();
    947                 if (callback != null) {
    948                     callback.postToHandler(MessageHandler.MSG_UPDATE_METADATA, metadata, null);
    949                 }
    950             }
    951 
    952             @Override
    953             public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
    954                 MediaControllerCompat.Callback callback = mCallback.get();
    955                 if (callback != null) {
    956                     callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE, queue, null);
    957                 }
    958             }
    959 
    960             @Override
    961             public void onQueueTitleChanged(CharSequence title) throws RemoteException {
    962                 MediaControllerCompat.Callback callback = mCallback.get();
    963                 if (callback != null) {
    964                     callback.postToHandler(MessageHandler.MSG_UPDATE_QUEUE_TITLE, title, null);
    965                 }
    966             }
    967 
    968             @Override
    969             public void onCaptioningEnabledChanged(boolean enabled) throws RemoteException {
    970                 MediaControllerCompat.Callback callback = mCallback.get();
    971                 if (callback != null) {
    972                     callback.postToHandler(
    973                             MessageHandler.MSG_UPDATE_CAPTIONING_ENABLED, enabled, null);
    974                 }
    975             }
    976 
    977             @Override
    978             public void onRepeatModeChanged(int repeatMode) throws RemoteException {
    979                 MediaControllerCompat.Callback callback = mCallback.get();
    980                 if (callback != null) {
    981                     callback.postToHandler(MessageHandler.MSG_UPDATE_REPEAT_MODE, repeatMode, null);
    982                 }
    983             }
    984 
    985             @Override
    986             public void onShuffleModeChangedRemoved(boolean enabled) throws RemoteException {
    987                 // Do nothing.
    988             }
    989 
    990             @Override
    991             public void onShuffleModeChanged(int shuffleMode) throws RemoteException {
    992                 MediaControllerCompat.Callback callback = mCallback.get();
    993                 if (callback != null) {
    994                     callback.postToHandler(
    995                             MessageHandler.MSG_UPDATE_SHUFFLE_MODE, shuffleMode, null);
    996                 }
    997             }
    998 
    999             @Override
   1000             public void onExtrasChanged(Bundle extras) throws RemoteException {
   1001                 MediaControllerCompat.Callback callback = mCallback.get();
   1002                 if (callback != null) {
   1003                     callback.postToHandler(MessageHandler.MSG_UPDATE_EXTRAS, extras, null);
   1004                 }
   1005             }
   1006 
   1007             @Override
   1008             public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
   1009                 MediaControllerCompat.Callback callback = mCallback.get();
   1010                 if (callback != null) {
   1011                     PlaybackInfo pi = null;
   1012                     if (info != null) {
   1013                         pi = new PlaybackInfo(info.volumeType, info.audioStream, info.controlType,
   1014                                 info.maxVolume, info.currentVolume);
   1015                     }
   1016                     callback.postToHandler(MessageHandler.MSG_UPDATE_VOLUME, pi, null);
   1017                 }
   1018             }
   1019 
   1020             @Override
   1021             public void onSessionReady() throws RemoteException {
   1022                 MediaControllerCompat.Callback callback = mCallback.get();
   1023                 if (callback != null) {
   1024                     callback.postToHandler(MessageHandler.MSG_SESSION_READY, null, null);
   1025                 }
   1026             }
   1027         }
   1028 
   1029         private class MessageHandler extends Handler {
   1030             private static final int MSG_EVENT = 1;
   1031             private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
   1032             private static final int MSG_UPDATE_METADATA = 3;
   1033             private static final int MSG_UPDATE_VOLUME = 4;
   1034             private static final int MSG_UPDATE_QUEUE = 5;
   1035             private static final int MSG_UPDATE_QUEUE_TITLE = 6;
   1036             private static final int MSG_UPDATE_EXTRAS = 7;
   1037             private static final int MSG_DESTROYED = 8;
   1038             private static final int MSG_UPDATE_REPEAT_MODE = 9;
   1039             private static final int MSG_UPDATE_CAPTIONING_ENABLED = 11;
   1040             private static final int MSG_UPDATE_SHUFFLE_MODE = 12;
   1041             private static final int MSG_SESSION_READY = 13;
   1042 
   1043             boolean mRegistered = false;
   1044 
   1045             MessageHandler(Looper looper) {
   1046                 super(looper);
   1047             }
   1048 
   1049             @Override
   1050             public void handleMessage(Message msg) {
   1051                 if (!mRegistered) {
   1052                     return;
   1053                 }
   1054                 switch (msg.what) {
   1055                     case MSG_EVENT:
   1056                         onSessionEvent((String) msg.obj, msg.getData());
   1057                         break;
   1058                     case MSG_UPDATE_PLAYBACK_STATE:
   1059                         onPlaybackStateChanged((PlaybackStateCompat) msg.obj);
   1060                         break;
   1061                     case MSG_UPDATE_METADATA:
   1062                         onMetadataChanged((MediaMetadataCompat) msg.obj);
   1063                         break;
   1064                     case MSG_UPDATE_QUEUE:
   1065                         onQueueChanged((List<QueueItem>) msg.obj);
   1066                         break;
   1067                     case MSG_UPDATE_QUEUE_TITLE:
   1068                         onQueueTitleChanged((CharSequence) msg.obj);
   1069                         break;
   1070                     case MSG_UPDATE_CAPTIONING_ENABLED:
   1071                         onCaptioningEnabledChanged((boolean) msg.obj);
   1072                         break;
   1073                     case MSG_UPDATE_REPEAT_MODE:
   1074                         onRepeatModeChanged((int) msg.obj);
   1075                         break;
   1076                     case MSG_UPDATE_SHUFFLE_MODE:
   1077                         onShuffleModeChanged((int) msg.obj);
   1078                         break;
   1079                     case MSG_UPDATE_EXTRAS:
   1080                         onExtrasChanged((Bundle) msg.obj);
   1081                         break;
   1082                     case MSG_UPDATE_VOLUME:
   1083                         onAudioInfoChanged((PlaybackInfo) msg.obj);
   1084                         break;
   1085                     case MSG_DESTROYED:
   1086                         onSessionDestroyed();
   1087                         break;
   1088                     case MSG_SESSION_READY:
   1089                         onSessionReady();
   1090                         break;
   1091                 }
   1092             }
   1093         }
   1094     }
   1095 
   1096     /**
   1097      * Interface for controlling media playback on a session. This allows an app
   1098      * to send media transport commands to the session.
   1099      */
   1100     public static abstract class TransportControls {
   1101         /**
   1102          * Used as an integer extra field in {@link #playFromMediaId(String, Bundle)} or
   1103          * {@link #prepareFromMediaId(String, Bundle)} to indicate the stream type to be used by the
   1104          * media player when playing or preparing the specified media id. See {@link AudioManager}
   1105          * for a list of stream types.
   1106          */
   1107         public static final String EXTRA_LEGACY_STREAM_TYPE =
   1108                 "android.media.session.extra.LEGACY_STREAM_TYPE";
   1109 
   1110         TransportControls() {
   1111         }
   1112 
   1113         /**
   1114          * Request that the player prepare for playback. This can decrease the time it takes to
   1115          * start playback when a play command is received. Preparation is not required. You can
   1116          * call {@link #play} without calling this method beforehand.
   1117          */
   1118         public abstract void prepare();
   1119 
   1120         /**
   1121          * Request that the player prepare playback for a specific media id. This can decrease the
   1122          * time it takes to start playback when a play command is received. Preparation is not
   1123          * required. You can call {@link #playFromMediaId} without calling this method beforehand.
   1124          *
   1125          * @param mediaId The id of the requested media.
   1126          * @param extras Optional extras that can include extra information about the media item
   1127          *               to be prepared.
   1128          */
   1129         public abstract void prepareFromMediaId(String mediaId, Bundle extras);
   1130 
   1131         /**
   1132          * Request that the player prepare playback for a specific search query. This can decrease
   1133          * the time it takes to start playback when a play command is received. An empty or null
   1134          * query should be treated as a request to prepare any music. Preparation is not required.
   1135          * You can call {@link #playFromSearch} without calling this method beforehand.
   1136          *
   1137          * @param query The search query.
   1138          * @param extras Optional extras that can include extra information
   1139          *               about the query.
   1140          */
   1141         public abstract void prepareFromSearch(String query, Bundle extras);
   1142 
   1143         /**
   1144          * Request that the player prepare playback for a specific {@link Uri}. This can decrease
   1145          * the time it takes to start playback when a play command is received. Preparation is not
   1146          * required. You can call {@link #playFromUri} without calling this method beforehand.
   1147          *
   1148          * @param uri The URI of the requested media.
   1149          * @param extras Optional extras that can include extra information about the media item
   1150          *               to be prepared.
   1151          */
   1152         public abstract void prepareFromUri(Uri uri, Bundle extras);
   1153 
   1154         /**
   1155          * Request that the player start its playback at its current position.
   1156          */
   1157         public abstract void play();
   1158 
   1159         /**
   1160          * Request that the player start playback for a specific {@link Uri}.
   1161          *
   1162          * @param mediaId The uri of the requested media.
   1163          * @param extras Optional extras that can include extra information
   1164          *            about the media item to be played.
   1165          */
   1166         public abstract void playFromMediaId(String mediaId, Bundle extras);
   1167 
   1168         /**
   1169          * Request that the player start playback for a specific search query.
   1170          * An empty or null query should be treated as a request to play any
   1171          * music.
   1172          *
   1173          * @param query The search query.
   1174          * @param extras Optional extras that can include extra information
   1175          *            about the query.
   1176          */
   1177         public abstract void playFromSearch(String query, Bundle extras);
   1178 
   1179         /**
   1180          * Request that the player start playback for a specific {@link Uri}.
   1181          *
   1182          * @param uri  The URI of the requested media.
   1183          * @param extras Optional extras that can include extra information about the media item
   1184          *               to be played.
   1185          */
   1186         public abstract void playFromUri(Uri uri, Bundle extras);
   1187 
   1188         /**
   1189          * Plays an item with a specific id in the play queue. If you specify an
   1190          * id that is not in the play queue, the behavior is undefined.
   1191          */
   1192         public abstract void skipToQueueItem(long id);
   1193 
   1194         /**
   1195          * Request that the player pause its playback and stay at its current
   1196          * position.
   1197          */
   1198         public abstract void pause();
   1199 
   1200         /**
   1201          * Request that the player stop its playback; it may clear its state in
   1202          * whatever way is appropriate.
   1203          */
   1204         public abstract void stop();
   1205 
   1206         /**
   1207          * Moves to a new location in the media stream.
   1208          *
   1209          * @param pos Position to move to, in milliseconds.
   1210          */
   1211         public abstract void seekTo(long pos);
   1212 
   1213         /**
   1214          * Starts fast forwarding. If playback is already fast forwarding this
   1215          * may increase the rate.
   1216          */
   1217         public abstract void fastForward();
   1218 
   1219         /**
   1220          * Skips to the next item.
   1221          */
   1222         public abstract void skipToNext();
   1223 
   1224         /**
   1225          * Starts rewinding. If playback is already rewinding this may increase
   1226          * the rate.
   1227          */
   1228         public abstract void rewind();
   1229 
   1230         /**
   1231          * Skips to the previous item.
   1232          */
   1233         public abstract void skipToPrevious();
   1234 
   1235         /**
   1236          * Rates the current content. This will cause the rating to be set for
   1237          * the current user. The rating type of the given {@link RatingCompat} must match the type
   1238          * returned by {@link #getRatingType()}.
   1239          *
   1240          * @param rating The rating to set for the current content
   1241          */
   1242         public abstract void setRating(RatingCompat rating);
   1243 
   1244         /**
   1245          * Rates a media item. This will cause the rating to be set for
   1246          * the specific media item. The rating type of the given {@link RatingCompat} must match
   1247          * the type returned by {@link #getRatingType()}.
   1248          *
   1249          * @param rating The rating to set for the media item.
   1250          * @param extras Optional arguments that can include information about the media item
   1251          *               to be rated.
   1252          *
   1253          * @see MediaSessionCompat#ARGUMENT_MEDIA_ATTRIBUTE
   1254          * @see MediaSessionCompat#ARGUMENT_MEDIA_ATTRIBUTE_VALUE
   1255          */
   1256         public abstract void setRating(RatingCompat rating, Bundle extras);
   1257 
   1258         /**
   1259          * Enables/disables captioning for this session.
   1260          *
   1261          * @param enabled {@code true} to enable captioning, {@code false} to disable.
   1262          */
   1263         public abstract void setCaptioningEnabled(boolean enabled);
   1264 
   1265         /**
   1266          * Sets the repeat mode for this session.
   1267          *
   1268          * @param repeatMode The repeat mode. Must be one of the followings:
   1269          *                   {@link PlaybackStateCompat#REPEAT_MODE_NONE},
   1270          *                   {@link PlaybackStateCompat#REPEAT_MODE_ONE},
   1271          *                   {@link PlaybackStateCompat#REPEAT_MODE_ALL},
   1272          *                   {@link PlaybackStateCompat#REPEAT_MODE_GROUP}
   1273          */
   1274         public abstract void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode);
   1275 
   1276         /**
   1277          * Sets the shuffle mode for this session.
   1278          *
   1279          * @param shuffleMode The shuffle mode. Must be one of the followings:
   1280          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_NONE},
   1281          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_ALL},
   1282          *                    {@link PlaybackStateCompat#SHUFFLE_MODE_GROUP}
   1283          */
   1284         public abstract void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode);
   1285 
   1286         /**
   1287          * Sends a custom action for the {@link MediaSessionCompat} to perform.
   1288          *
   1289          * @param customAction The action to perform.
   1290          * @param args Optional arguments to supply to the
   1291          *            {@link MediaSessionCompat} for this custom action.
   1292          */
   1293         public abstract void sendCustomAction(PlaybackStateCompat.CustomAction customAction,
   1294                 Bundle args);
   1295 
   1296         /**
   1297          * Sends the id and args from a custom action for the
   1298          * {@link MediaSessionCompat} to perform.
   1299          *
   1300          * @see #sendCustomAction(PlaybackStateCompat.CustomAction action,
   1301          *      Bundle args)
   1302          * @see MediaSessionCompat#ACTION_FLAG_AS_INAPPROPRIATE
   1303          * @see MediaSessionCompat#ACTION_SKIP_AD
   1304          * @see MediaSessionCompat#ACTION_FOLLOW
   1305          * @see MediaSessionCompat#ACTION_UNFOLLOW
   1306          * @param action The action identifier of the
   1307          *            {@link PlaybackStateCompat.CustomAction} as specified by
   1308          *            the {@link MediaSessionCompat}.
   1309          * @param args Optional arguments to supply to the
   1310          *            {@link MediaSessionCompat} for this custom action.
   1311          */
   1312         public abstract void sendCustomAction(String action, Bundle args);
   1313     }
   1314 
   1315     /**
   1316      * Holds information about the way volume is handled for this session.
   1317      */
   1318     public static final class PlaybackInfo {
   1319         /**
   1320          * The session uses local playback.
   1321          */
   1322         public static final int PLAYBACK_TYPE_LOCAL = 1;
   1323         /**
   1324          * The session uses remote playback.
   1325          */
   1326         public static final int PLAYBACK_TYPE_REMOTE = 2;
   1327 
   1328         private final int mPlaybackType;
   1329         // TODO update audio stream with AudioAttributes support version
   1330         private final int mAudioStream;
   1331         private final int mVolumeControl;
   1332         private final int mMaxVolume;
   1333         private final int mCurrentVolume;
   1334 
   1335         PlaybackInfo(int type, int stream, int control, int max, int current) {
   1336             mPlaybackType = type;
   1337             mAudioStream = stream;
   1338             mVolumeControl = control;
   1339             mMaxVolume = max;
   1340             mCurrentVolume = current;
   1341         }
   1342 
   1343         /**
   1344          * Gets the type of volume handling, either local or remote. One of:
   1345          * <ul>
   1346          * <li>{@link PlaybackInfo#PLAYBACK_TYPE_LOCAL}</li>
   1347          * <li>{@link PlaybackInfo#PLAYBACK_TYPE_REMOTE}</li>
   1348          * </ul>
   1349          *
   1350          * @return The type of volume handling this session is using.
   1351          */
   1352         public int getPlaybackType() {
   1353             return mPlaybackType;
   1354         }
   1355 
   1356         /**
   1357          * Gets the stream this is currently controlling volume on. When the volume
   1358          * type is {@link PlaybackInfo#PLAYBACK_TYPE_REMOTE} this value does not
   1359          * have meaning and should be ignored.
   1360          *
   1361          * @return The stream this session is playing on.
   1362          */
   1363         public int getAudioStream() {
   1364             // TODO switch to AudioAttributesCompat when it is added.
   1365             return mAudioStream;
   1366         }
   1367 
   1368         /**
   1369          * Gets the type of volume control that can be used. One of:
   1370          * <ul>
   1371          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_ABSOLUTE}</li>
   1372          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_RELATIVE}</li>
   1373          * <li>{@link VolumeProviderCompat#VOLUME_CONTROL_FIXED}</li>
   1374          * </ul>
   1375          *
   1376          * @return The type of volume control that may be used with this
   1377          *         session.
   1378          */
   1379         public int getVolumeControl() {
   1380             return mVolumeControl;
   1381         }
   1382 
   1383         /**
   1384          * Gets the maximum volume that may be set for this session.
   1385          *
   1386          * @return The maximum allowed volume where this session is playing.
   1387          */
   1388         public int getMaxVolume() {
   1389             return mMaxVolume;
   1390         }
   1391 
   1392         /**
   1393          * Gets the current volume for this session.
   1394          *
   1395          * @return The current volume where this session is playing.
   1396          */
   1397         public int getCurrentVolume() {
   1398             return mCurrentVolume;
   1399         }
   1400     }
   1401 
   1402     interface MediaControllerImpl {
   1403         void registerCallback(Callback callback, Handler handler);
   1404 
   1405         void unregisterCallback(Callback callback);
   1406         boolean dispatchMediaButtonEvent(KeyEvent keyEvent);
   1407         TransportControls getTransportControls();
   1408         PlaybackStateCompat getPlaybackState();
   1409         MediaMetadataCompat getMetadata();
   1410 
   1411         List<QueueItem> getQueue();
   1412         void addQueueItem(MediaDescriptionCompat description);
   1413         void addQueueItem(MediaDescriptionCompat description, int index);
   1414         void removeQueueItem(MediaDescriptionCompat description);
   1415         CharSequence getQueueTitle();
   1416         Bundle getExtras();
   1417         int getRatingType();
   1418         boolean isCaptioningEnabled();
   1419         int getRepeatMode();
   1420         int getShuffleMode();
   1421         long getFlags();
   1422         PlaybackInfo getPlaybackInfo();
   1423         PendingIntent getSessionActivity();
   1424 
   1425         void setVolumeTo(int value, int flags);
   1426         void adjustVolume(int direction, int flags);
   1427         void sendCommand(String command, Bundle params, ResultReceiver cb);
   1428 
   1429         boolean isSessionReady();
   1430         String getPackageName();
   1431         Object getMediaController();
   1432     }
   1433 
   1434     static class MediaControllerImplBase implements MediaControllerImpl {
   1435         private IMediaSession mBinder;
   1436         private TransportControls mTransportControls;
   1437 
   1438         public MediaControllerImplBase(MediaSessionCompat.Token token) {
   1439             mBinder = IMediaSession.Stub.asInterface((IBinder) token.getToken());
   1440         }
   1441 
   1442         @Override
   1443         public void registerCallback(Callback callback, Handler handler) {
   1444             if (callback == null) {
   1445                 throw new IllegalArgumentException("callback may not be null.");
   1446             }
   1447             try {
   1448                 mBinder.asBinder().linkToDeath(callback, 0);
   1449                 mBinder.registerCallbackListener((IMediaControllerCallback) callback.mCallbackObj);
   1450             } catch (RemoteException e) {
   1451                 Log.e(TAG, "Dead object in registerCallback.", e);
   1452                 callback.onSessionDestroyed();
   1453             }
   1454         }
   1455 
   1456         @Override
   1457         public void unregisterCallback(Callback callback) {
   1458             if (callback == null) {
   1459                 throw new IllegalArgumentException("callback may not be null.");
   1460             }
   1461             try {
   1462                 mBinder.unregisterCallbackListener(
   1463                         (IMediaControllerCallback) callback.mCallbackObj);
   1464                 mBinder.asBinder().unlinkToDeath(callback, 0);
   1465             } catch (RemoteException e) {
   1466                 Log.e(TAG, "Dead object in unregisterCallback.", e);
   1467             }
   1468         }
   1469 
   1470         @Override
   1471         public boolean dispatchMediaButtonEvent(KeyEvent event) {
   1472             if (event == null) {
   1473                 throw new IllegalArgumentException("event may not be null.");
   1474             }
   1475             try {
   1476                 mBinder.sendMediaButton(event);
   1477             } catch (RemoteException e) {
   1478                 Log.e(TAG, "Dead object in dispatchMediaButtonEvent.", e);
   1479             }
   1480             return false;
   1481         }
   1482 
   1483         @Override
   1484         public TransportControls getTransportControls() {
   1485             if (mTransportControls == null) {
   1486                 mTransportControls = new TransportControlsBase(mBinder);
   1487             }
   1488 
   1489             return mTransportControls;
   1490         }
   1491 
   1492         @Override
   1493         public PlaybackStateCompat getPlaybackState() {
   1494             try {
   1495                 return mBinder.getPlaybackState();
   1496             } catch (RemoteException e) {
   1497                 Log.e(TAG, "Dead object in getPlaybackState.", e);
   1498             }
   1499             return null;
   1500         }
   1501 
   1502         @Override
   1503         public MediaMetadataCompat getMetadata() {
   1504             try {
   1505                 return mBinder.getMetadata();
   1506             } catch (RemoteException e) {
   1507                 Log.e(TAG, "Dead object in getMetadata.", e);
   1508             }
   1509             return null;
   1510         }
   1511 
   1512         @Override
   1513         public List<QueueItem> getQueue() {
   1514             try {
   1515                 return mBinder.getQueue();
   1516             } catch (RemoteException e) {
   1517                 Log.e(TAG, "Dead object in getQueue.", e);
   1518             }
   1519             return null;
   1520         }
   1521 
   1522         @Override
   1523         public void addQueueItem(MediaDescriptionCompat description) {
   1524             try {
   1525                 long flags = mBinder.getFlags();
   1526                 if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
   1527                     throw new UnsupportedOperationException(
   1528                             "This session doesn't support queue management operations");
   1529                 }
   1530                 mBinder.addQueueItem(description);
   1531             } catch (RemoteException e) {
   1532                 Log.e(TAG, "Dead object in addQueueItem.", e);
   1533             }
   1534         }
   1535 
   1536         @Override
   1537         public void addQueueItem(MediaDescriptionCompat description, int index) {
   1538             try {
   1539                 long flags = mBinder.getFlags();
   1540                 if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
   1541                     throw new UnsupportedOperationException(
   1542                             "This session doesn't support queue management operations");
   1543                 }
   1544                 mBinder.addQueueItemAt(description, index);
   1545             } catch (RemoteException e) {
   1546                 Log.e(TAG, "Dead object in addQueueItemAt.", e);
   1547             }
   1548         }
   1549 
   1550         @Override
   1551         public void removeQueueItem(MediaDescriptionCompat description) {
   1552             try {
   1553                 long flags = mBinder.getFlags();
   1554                 if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
   1555                     throw new UnsupportedOperationException(
   1556                             "This session doesn't support queue management operations");
   1557                 }
   1558                 mBinder.removeQueueItem(description);
   1559             } catch (RemoteException e) {
   1560                 Log.e(TAG, "Dead object in removeQueueItem.", e);
   1561             }
   1562         }
   1563 
   1564         @Override
   1565         public CharSequence getQueueTitle() {
   1566             try {
   1567                 return mBinder.getQueueTitle();
   1568             } catch (RemoteException e) {
   1569                 Log.e(TAG, "Dead object in getQueueTitle.", e);
   1570             }
   1571             return null;
   1572         }
   1573 
   1574         @Override
   1575         public Bundle getExtras() {
   1576             try {
   1577                 return mBinder.getExtras();
   1578             } catch (RemoteException e) {
   1579                 Log.e(TAG, "Dead object in getExtras.", e);
   1580             }
   1581             return null;
   1582         }
   1583 
   1584         @Override
   1585         public int getRatingType() {
   1586             try {
   1587                 return mBinder.getRatingType();
   1588             } catch (RemoteException e) {
   1589                 Log.e(TAG, "Dead object in getRatingType.", e);
   1590             }
   1591             return 0;
   1592         }
   1593 
   1594         @Override
   1595         public boolean isCaptioningEnabled() {
   1596             try {
   1597                 return mBinder.isCaptioningEnabled();
   1598             } catch (RemoteException e) {
   1599                 Log.e(TAG, "Dead object in isCaptioningEnabled.", e);
   1600             }
   1601             return false;
   1602         }
   1603 
   1604         @Override
   1605         public int getRepeatMode() {
   1606             try {
   1607                 return mBinder.getRepeatMode();
   1608             } catch (RemoteException e) {
   1609                 Log.e(TAG, "Dead object in getRepeatMode.", e);
   1610             }
   1611             return PlaybackStateCompat.REPEAT_MODE_INVALID;
   1612         }
   1613 
   1614         @Override
   1615         public int getShuffleMode() {
   1616             try {
   1617                 return mBinder.getShuffleMode();
   1618             } catch (RemoteException e) {
   1619                 Log.e(TAG, "Dead object in getShuffleMode.", e);
   1620             }
   1621             return PlaybackStateCompat.SHUFFLE_MODE_INVALID;
   1622         }
   1623 
   1624         @Override
   1625         public long getFlags() {
   1626             try {
   1627                 return mBinder.getFlags();
   1628             } catch (RemoteException e) {
   1629                 Log.e(TAG, "Dead object in getFlags.", e);
   1630             }
   1631             return 0;
   1632         }
   1633 
   1634         @Override
   1635         public PlaybackInfo getPlaybackInfo() {
   1636             try {
   1637                 ParcelableVolumeInfo info = mBinder.getVolumeAttributes();
   1638                 PlaybackInfo pi = new PlaybackInfo(info.volumeType, info.audioStream,
   1639                         info.controlType, info.maxVolume, info.currentVolume);
   1640                 return pi;
   1641             } catch (RemoteException e) {
   1642                 Log.e(TAG, "Dead object in getPlaybackInfo.", e);
   1643             }
   1644             return null;
   1645         }
   1646 
   1647         @Override
   1648         public PendingIntent getSessionActivity() {
   1649             try {
   1650                 return mBinder.getLaunchPendingIntent();
   1651             } catch (RemoteException e) {
   1652                 Log.e(TAG, "Dead object in getSessionActivity.", e);
   1653             }
   1654             return null;
   1655         }
   1656 
   1657         @Override
   1658         public void setVolumeTo(int value, int flags) {
   1659             try {
   1660                 mBinder.setVolumeTo(value, flags, null);
   1661             } catch (RemoteException e) {
   1662                 Log.e(TAG, "Dead object in setVolumeTo.", e);
   1663             }
   1664         }
   1665 
   1666         @Override
   1667         public void adjustVolume(int direction, int flags) {
   1668             try {
   1669                 mBinder.adjustVolume(direction, flags, null);
   1670             } catch (RemoteException e) {
   1671                 Log.e(TAG, "Dead object in adjustVolume.", e);
   1672             }
   1673         }
   1674 
   1675         @Override
   1676         public void sendCommand(String command, Bundle params, ResultReceiver cb) {
   1677             try {
   1678                 mBinder.sendCommand(command, params,
   1679                         new MediaSessionCompat.ResultReceiverWrapper(cb));
   1680             } catch (RemoteException e) {
   1681                 Log.e(TAG, "Dead object in sendCommand.", e);
   1682             }
   1683         }
   1684 
   1685         @Override
   1686         public boolean isSessionReady() {
   1687             return true;
   1688         }
   1689 
   1690         @Override
   1691         public String getPackageName() {
   1692             try {
   1693                 return mBinder.getPackageName();
   1694             } catch (RemoteException e) {
   1695                 Log.e(TAG, "Dead object in getPackageName.", e);
   1696             }
   1697             return null;
   1698         }
   1699 
   1700         @Override
   1701         public Object getMediaController() {
   1702             return null;
   1703         }
   1704     }
   1705 
   1706     static class TransportControlsBase extends TransportControls {
   1707         private IMediaSession mBinder;
   1708 
   1709         public TransportControlsBase(IMediaSession binder) {
   1710             mBinder = binder;
   1711         }
   1712 
   1713         @Override
   1714         public void prepare() {
   1715             try {
   1716                 mBinder.prepare();
   1717             } catch (RemoteException e) {
   1718                 Log.e(TAG, "Dead object in prepare.", e);
   1719             }
   1720         }
   1721 
   1722         @Override
   1723         public void prepareFromMediaId(String mediaId, Bundle extras) {
   1724             try {
   1725                 mBinder.prepareFromMediaId(mediaId, extras);
   1726             } catch (RemoteException e) {
   1727                 Log.e(TAG, "Dead object in prepareFromMediaId.", e);
   1728             }
   1729         }
   1730 
   1731         @Override
   1732         public void prepareFromSearch(String query, Bundle extras) {
   1733             try {
   1734                 mBinder.prepareFromSearch(query, extras);
   1735             } catch (RemoteException e) {
   1736                 Log.e(TAG, "Dead object in prepareFromSearch.", e);
   1737             }
   1738         }
   1739 
   1740         @Override
   1741         public void prepareFromUri(Uri uri, Bundle extras) {
   1742             try {
   1743                 mBinder.prepareFromUri(uri, extras);
   1744             } catch (RemoteException e) {
   1745                 Log.e(TAG, "Dead object in prepareFromUri.", e);
   1746             }
   1747         }
   1748 
   1749         @Override
   1750         public void play() {
   1751             try {
   1752                 mBinder.play();
   1753             } catch (RemoteException e) {
   1754                 Log.e(TAG, "Dead object in play.", e);
   1755             }
   1756         }
   1757 
   1758         @Override
   1759         public void playFromMediaId(String mediaId, Bundle extras) {
   1760             try {
   1761                 mBinder.playFromMediaId(mediaId, extras);
   1762             } catch (RemoteException e) {
   1763                 Log.e(TAG, "Dead object in playFromMediaId.", e);
   1764             }
   1765         }
   1766 
   1767         @Override
   1768         public void playFromSearch(String query, Bundle extras) {
   1769             try {
   1770                 mBinder.playFromSearch(query, extras);
   1771             } catch (RemoteException e) {
   1772                 Log.e(TAG, "Dead object in playFromSearch.", e);
   1773             }
   1774         }
   1775 
   1776         @Override
   1777         public void playFromUri(Uri uri, Bundle extras) {
   1778             try {
   1779                 mBinder.playFromUri(uri, extras);
   1780             } catch (RemoteException e) {
   1781                 Log.e(TAG, "Dead object in playFromUri.", e);
   1782             }
   1783         }
   1784 
   1785         @Override
   1786         public void skipToQueueItem(long id) {
   1787             try {
   1788                 mBinder.skipToQueueItem(id);
   1789             } catch (RemoteException e) {
   1790                 Log.e(TAG, "Dead object in skipToQueueItem.", e);
   1791             }
   1792         }
   1793 
   1794         @Override
   1795         public void pause() {
   1796             try {
   1797                 mBinder.pause();
   1798             } catch (RemoteException e) {
   1799                 Log.e(TAG, "Dead object in pause.", e);
   1800             }
   1801         }
   1802 
   1803         @Override
   1804         public void stop() {
   1805             try {
   1806                 mBinder.stop();
   1807             } catch (RemoteException e) {
   1808                 Log.e(TAG, "Dead object in stop.", e);
   1809             }
   1810         }
   1811 
   1812         @Override
   1813         public void seekTo(long pos) {
   1814             try {
   1815                 mBinder.seekTo(pos);
   1816             } catch (RemoteException e) {
   1817                 Log.e(TAG, "Dead object in seekTo.", e);
   1818             }
   1819         }
   1820 
   1821         @Override
   1822         public void fastForward() {
   1823             try {
   1824                 mBinder.fastForward();
   1825             } catch (RemoteException e) {
   1826                 Log.e(TAG, "Dead object in fastForward.", e);
   1827             }
   1828         }
   1829 
   1830         @Override
   1831         public void skipToNext() {
   1832             try {
   1833                 mBinder.next();
   1834             } catch (RemoteException e) {
   1835                 Log.e(TAG, "Dead object in skipToNext.", e);
   1836             }
   1837         }
   1838 
   1839         @Override
   1840         public void rewind() {
   1841             try {
   1842                 mBinder.rewind();
   1843             } catch (RemoteException e) {
   1844                 Log.e(TAG, "Dead object in rewind.", e);
   1845             }
   1846         }
   1847 
   1848         @Override
   1849         public void skipToPrevious() {
   1850             try {
   1851                 mBinder.previous();
   1852             } catch (RemoteException e) {
   1853                 Log.e(TAG, "Dead object in skipToPrevious.", e);
   1854             }
   1855         }
   1856 
   1857         @Override
   1858         public void setRating(RatingCompat rating) {
   1859             try {
   1860                 mBinder.rate(rating);
   1861             } catch (RemoteException e) {
   1862                 Log.e(TAG, "Dead object in setRating.", e);
   1863             }
   1864         }
   1865 
   1866         @Override
   1867         public void setRating(RatingCompat rating, Bundle extras) {
   1868             try {
   1869                 mBinder.rateWithExtras(rating, extras);
   1870             } catch (RemoteException e) {
   1871                 Log.e(TAG, "Dead object in setRating.", e);
   1872             }
   1873         }
   1874 
   1875         @Override
   1876         public void setCaptioningEnabled(boolean enabled) {
   1877             try {
   1878                 mBinder.setCaptioningEnabled(enabled);
   1879             } catch (RemoteException e) {
   1880                 Log.e(TAG, "Dead object in setCaptioningEnabled.", e);
   1881             }
   1882         }
   1883 
   1884         @Override
   1885         public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
   1886             try {
   1887                 mBinder.setRepeatMode(repeatMode);
   1888             } catch (RemoteException e) {
   1889                 Log.e(TAG, "Dead object in setRepeatMode.", e);
   1890             }
   1891         }
   1892 
   1893         @Override
   1894         public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
   1895             try {
   1896                 mBinder.setShuffleMode(shuffleMode);
   1897             } catch (RemoteException e) {
   1898                 Log.e(TAG, "Dead object in setShuffleMode.", e);
   1899             }
   1900         }
   1901 
   1902         @Override
   1903         public void sendCustomAction(CustomAction customAction, Bundle args) {
   1904             sendCustomAction(customAction.getAction(), args);
   1905         }
   1906 
   1907         @Override
   1908         public void sendCustomAction(String action, Bundle args) {
   1909             validateCustomAction(action, args);
   1910             try {
   1911                 mBinder.sendCustomAction(action, args);
   1912             } catch (RemoteException e) {
   1913                 Log.e(TAG, "Dead object in sendCustomAction.", e);
   1914             }
   1915         }
   1916     }
   1917 
   1918     @RequiresApi(21)
   1919     static class MediaControllerImplApi21 implements MediaControllerImpl {
   1920         protected final Object mControllerObj;
   1921 
   1922         private final List<Callback> mPendingCallbacks = new ArrayList<>();
   1923 
   1924         // Extra binder is used for applying the framework change of new APIs and bug fixes
   1925         // after API 21.
   1926         private IMediaSession mExtraBinder;
   1927         private HashMap<Callback, ExtraCallback> mCallbackMap = new HashMap<>();
   1928 
   1929         public MediaControllerImplApi21(Context context, MediaSessionCompat session) {
   1930             mControllerObj = MediaControllerCompatApi21.fromToken(context,
   1931                     session.getSessionToken().getToken());
   1932             mExtraBinder = session.getSessionToken().getExtraBinder();
   1933             if (mExtraBinder == null) {
   1934                 requestExtraBinder();
   1935             }
   1936         }
   1937 
   1938         public MediaControllerImplApi21(Context context, MediaSessionCompat.Token sessionToken)
   1939                 throws RemoteException {
   1940             mControllerObj = MediaControllerCompatApi21.fromToken(context,
   1941                     sessionToken.getToken());
   1942             if (mControllerObj == null) throw new RemoteException();
   1943             mExtraBinder = sessionToken.getExtraBinder();
   1944             if (mExtraBinder == null) {
   1945                 requestExtraBinder();
   1946             }
   1947         }
   1948 
   1949         @Override
   1950         public final void registerCallback(Callback callback, Handler handler) {
   1951             MediaControllerCompatApi21.registerCallback(
   1952                     mControllerObj, callback.mCallbackObj, handler);
   1953             if (mExtraBinder != null) {
   1954                 ExtraCallback extraCallback = new ExtraCallback(callback);
   1955                 mCallbackMap.put(callback, extraCallback);
   1956                 callback.mIControllerCallback = extraCallback;
   1957                 try {
   1958                     mExtraBinder.registerCallbackListener(extraCallback);
   1959                 } catch (RemoteException e) {
   1960                     Log.e(TAG, "Dead object in registerCallback.", e);
   1961                 }
   1962             } else {
   1963                 synchronized (mPendingCallbacks) {
   1964                     callback.mIControllerCallback = null;
   1965                     mPendingCallbacks.add(callback);
   1966                 }
   1967             }
   1968         }
   1969 
   1970         @Override
   1971         public final void unregisterCallback(Callback callback) {
   1972             MediaControllerCompatApi21.unregisterCallback(mControllerObj, callback.mCallbackObj);
   1973             if (mExtraBinder != null) {
   1974                 try {
   1975                     ExtraCallback extraCallback = mCallbackMap.remove(callback);
   1976                     if (extraCallback != null) {
   1977                         callback.mIControllerCallback = null;
   1978                         mExtraBinder.unregisterCallbackListener(extraCallback);
   1979                     }
   1980                 } catch (RemoteException e) {
   1981                     Log.e(TAG, "Dead object in unregisterCallback.", e);
   1982                 }
   1983             } else {
   1984                 synchronized (mPendingCallbacks) {
   1985                     mPendingCallbacks.remove(callback);
   1986                 }
   1987             }
   1988         }
   1989 
   1990         @Override
   1991         public boolean dispatchMediaButtonEvent(KeyEvent event) {
   1992             return MediaControllerCompatApi21.dispatchMediaButtonEvent(mControllerObj, event);
   1993         }
   1994 
   1995         @Override
   1996         public TransportControls getTransportControls() {
   1997             Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
   1998             return controlsObj != null ? new TransportControlsApi21(controlsObj) : null;
   1999         }
   2000 
   2001         @Override
   2002         public PlaybackStateCompat getPlaybackState() {
   2003             if (mExtraBinder != null) {
   2004                 try {
   2005                     return mExtraBinder.getPlaybackState();
   2006                 } catch (RemoteException e) {
   2007                     Log.e(TAG, "Dead object in getPlaybackState.", e);
   2008                 }
   2009             }
   2010             Object stateObj = MediaControllerCompatApi21.getPlaybackState(mControllerObj);
   2011             return stateObj != null ? PlaybackStateCompat.fromPlaybackState(stateObj) : null;
   2012         }
   2013 
   2014         @Override
   2015         public MediaMetadataCompat getMetadata() {
   2016             Object metadataObj = MediaControllerCompatApi21.getMetadata(mControllerObj);
   2017             return metadataObj != null ? MediaMetadataCompat.fromMediaMetadata(metadataObj) : null;
   2018         }
   2019 
   2020         @Override
   2021         public List<QueueItem> getQueue() {
   2022             List<Object> queueObjs = MediaControllerCompatApi21.getQueue(mControllerObj);
   2023             return queueObjs != null ? QueueItem.fromQueueItemList(queueObjs) : null;
   2024         }
   2025 
   2026         @Override
   2027         public void addQueueItem(MediaDescriptionCompat description) {
   2028             long flags = getFlags();
   2029             if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
   2030                 throw new UnsupportedOperationException(
   2031                         "This session doesn't support queue management operations");
   2032             }
   2033             Bundle params = new Bundle();
   2034             params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description);
   2035             sendCommand(COMMAND_ADD_QUEUE_ITEM, params, null);
   2036         }
   2037 
   2038         @Override
   2039         public void addQueueItem(MediaDescriptionCompat description, int index) {
   2040             long flags = getFlags();
   2041             if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
   2042                 throw new UnsupportedOperationException(
   2043                         "This session doesn't support queue management operations");
   2044             }
   2045             Bundle params = new Bundle();
   2046             params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description);
   2047             params.putInt(COMMAND_ARGUMENT_INDEX, index);
   2048             sendCommand(COMMAND_ADD_QUEUE_ITEM_AT, params, null);
   2049         }
   2050 
   2051         @Override
   2052         public void removeQueueItem(MediaDescriptionCompat description) {
   2053             long flags = getFlags();
   2054             if ((flags & MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) == 0) {
   2055                 throw new UnsupportedOperationException(
   2056                         "This session doesn't support queue management operations");
   2057             }
   2058             Bundle params = new Bundle();
   2059             params.putParcelable(COMMAND_ARGUMENT_MEDIA_DESCRIPTION, description);
   2060             sendCommand(COMMAND_REMOVE_QUEUE_ITEM, params, null);
   2061         }
   2062 
   2063         @Override
   2064         public CharSequence getQueueTitle() {
   2065             return MediaControllerCompatApi21.getQueueTitle(mControllerObj);
   2066         }
   2067 
   2068         @Override
   2069         public Bundle getExtras() {
   2070             return MediaControllerCompatApi21.getExtras(mControllerObj);
   2071         }
   2072 
   2073         @Override
   2074         public int getRatingType() {
   2075             if (android.os.Build.VERSION.SDK_INT < 22 && mExtraBinder != null) {
   2076                 try {
   2077                     return mExtraBinder.getRatingType();
   2078                 } catch (RemoteException e) {
   2079                     Log.e(TAG, "Dead object in getRatingType.", e);
   2080                 }
   2081             }
   2082             return MediaControllerCompatApi21.getRatingType(mControllerObj);
   2083         }
   2084 
   2085         @Override
   2086         public boolean isCaptioningEnabled() {
   2087             if (mExtraBinder != null) {
   2088                 try {
   2089                     return mExtraBinder.isCaptioningEnabled();
   2090                 } catch (RemoteException e) {
   2091                     Log.e(TAG, "Dead object in isCaptioningEnabled.", e);
   2092                 }
   2093             }
   2094             return false;
   2095         }
   2096 
   2097         @Override
   2098         public int getRepeatMode() {
   2099             if (mExtraBinder != null) {
   2100                 try {
   2101                     return mExtraBinder.getRepeatMode();
   2102                 } catch (RemoteException e) {
   2103                     Log.e(TAG, "Dead object in getRepeatMode.", e);
   2104                 }
   2105             }
   2106             return PlaybackStateCompat.REPEAT_MODE_INVALID;
   2107         }
   2108 
   2109         @Override
   2110         public int getShuffleMode() {
   2111             if (mExtraBinder != null) {
   2112                 try {
   2113                     return mExtraBinder.getShuffleMode();
   2114                 } catch (RemoteException e) {
   2115                     Log.e(TAG, "Dead object in getShuffleMode.", e);
   2116                 }
   2117             }
   2118             return PlaybackStateCompat.SHUFFLE_MODE_INVALID;
   2119         }
   2120 
   2121         @Override
   2122         public long getFlags() {
   2123             return MediaControllerCompatApi21.getFlags(mControllerObj);
   2124         }
   2125 
   2126         @Override
   2127         public PlaybackInfo getPlaybackInfo() {
   2128             Object volumeInfoObj = MediaControllerCompatApi21.getPlaybackInfo(mControllerObj);
   2129             return volumeInfoObj != null ? new PlaybackInfo(
   2130                     MediaControllerCompatApi21.PlaybackInfo.getPlaybackType(volumeInfoObj),
   2131                     MediaControllerCompatApi21.PlaybackInfo.getLegacyAudioStream(volumeInfoObj),
   2132                     MediaControllerCompatApi21.PlaybackInfo.getVolumeControl(volumeInfoObj),
   2133                     MediaControllerCompatApi21.PlaybackInfo.getMaxVolume(volumeInfoObj),
   2134                     MediaControllerCompatApi21.PlaybackInfo.getCurrentVolume(volumeInfoObj)) : null;
   2135         }
   2136 
   2137         @Override
   2138         public PendingIntent getSessionActivity() {
   2139             return MediaControllerCompatApi21.getSessionActivity(mControllerObj);
   2140         }
   2141 
   2142         @Override
   2143         public void setVolumeTo(int value, int flags) {
   2144             MediaControllerCompatApi21.setVolumeTo(mControllerObj, value, flags);
   2145         }
   2146 
   2147         @Override
   2148         public void adjustVolume(int direction, int flags) {
   2149             MediaControllerCompatApi21.adjustVolume(mControllerObj, direction, flags);
   2150         }
   2151 
   2152         @Override
   2153         public void sendCommand(String command, Bundle params, ResultReceiver cb) {
   2154             MediaControllerCompatApi21.sendCommand(mControllerObj, command, params, cb);
   2155         }
   2156 
   2157         @Override
   2158         public boolean isSessionReady() {
   2159             return mExtraBinder != null;
   2160         }
   2161 
   2162         @Override
   2163         public String getPackageName() {
   2164             return MediaControllerCompatApi21.getPackageName(mControllerObj);
   2165         }
   2166 
   2167         @Override
   2168         public Object getMediaController() {
   2169             return mControllerObj;
   2170         }
   2171 
   2172         private void requestExtraBinder() {
   2173             sendCommand(COMMAND_GET_EXTRA_BINDER, null,
   2174                     new ExtraBinderRequestResultReceiver(this, new Handler()));
   2175         }
   2176 
   2177         private void processPendingCallbacks() {
   2178             if (mExtraBinder == null) {
   2179                 return;
   2180             }
   2181             synchronized (mPendingCallbacks) {
   2182                 for (Callback callback : mPendingCallbacks) {
   2183                     ExtraCallback extraCallback = new ExtraCallback(callback);
   2184                     mCallbackMap.put(callback, extraCallback);
   2185                     callback.mIControllerCallback = extraCallback;
   2186                     try {
   2187                         mExtraBinder.registerCallbackListener(extraCallback);
   2188                     } catch (RemoteException e) {
   2189                         Log.e(TAG, "Dead object in registerCallback.", e);
   2190                         break;
   2191                     }
   2192                     callback.onSessionReady();
   2193                 }
   2194                 mPendingCallbacks.clear();
   2195             }
   2196         }
   2197 
   2198         private static class ExtraBinderRequestResultReceiver extends ResultReceiver {
   2199             private WeakReference<MediaControllerImplApi21> mMediaControllerImpl;
   2200 
   2201             public ExtraBinderRequestResultReceiver(MediaControllerImplApi21 mediaControllerImpl,
   2202                     Handler handler) {
   2203                 super(handler);
   2204                 mMediaControllerImpl = new WeakReference<>(mediaControllerImpl);
   2205             }
   2206 
   2207             @Override
   2208             protected void onReceiveResult(int resultCode, Bundle resultData) {
   2209                 MediaControllerImplApi21 mediaControllerImpl = mMediaControllerImpl.get();
   2210                 if (mediaControllerImpl == null || resultData == null) {
   2211                     return;
   2212                 }
   2213                 mediaControllerImpl.mExtraBinder = IMediaSession.Stub.asInterface(
   2214                         BundleCompat.getBinder(resultData, MediaSessionCompat.EXTRA_BINDER));
   2215                 mediaControllerImpl.processPendingCallbacks();
   2216             }
   2217         }
   2218 
   2219         private static class ExtraCallback extends Callback.StubCompat {
   2220             ExtraCallback(Callback callback) {
   2221                 super(callback);
   2222             }
   2223 
   2224             @Override
   2225             public void onSessionDestroyed() throws RemoteException {
   2226                 // Will not be called.
   2227                 throw new AssertionError();
   2228             }
   2229 
   2230             @Override
   2231             public void onMetadataChanged(MediaMetadataCompat metadata) throws RemoteException {
   2232                 // Will not be called.
   2233                 throw new AssertionError();
   2234             }
   2235 
   2236             @Override
   2237             public void onQueueChanged(List<QueueItem> queue) throws RemoteException {
   2238                 // Will not be called.
   2239                 throw new AssertionError();
   2240             }
   2241 
   2242             @Override
   2243             public void onQueueTitleChanged(CharSequence title) throws RemoteException {
   2244                 // Will not be called.
   2245                 throw new AssertionError();
   2246             }
   2247 
   2248             @Override
   2249             public void onExtrasChanged(Bundle extras) throws RemoteException {
   2250                 // Will not be called.
   2251                 throw new AssertionError();
   2252             }
   2253 
   2254             @Override
   2255             public void onVolumeInfoChanged(ParcelableVolumeInfo info) throws RemoteException {
   2256                 // Will not be called.
   2257                 throw new AssertionError();
   2258             }
   2259         }
   2260     }
   2261 
   2262     static class TransportControlsApi21 extends TransportControls {
   2263         protected final Object mControlsObj;
   2264 
   2265         public TransportControlsApi21(Object controlsObj) {
   2266             mControlsObj = controlsObj;
   2267         }
   2268 
   2269         @Override
   2270         public void prepare() {
   2271             sendCustomAction(MediaSessionCompat.ACTION_PREPARE, null);
   2272         }
   2273 
   2274         @Override
   2275         public void prepareFromMediaId(String mediaId, Bundle extras) {
   2276             Bundle bundle = new Bundle();
   2277             bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ID, mediaId);
   2278             bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
   2279             sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_MEDIA_ID, bundle);
   2280         }
   2281 
   2282         @Override
   2283         public void prepareFromSearch(String query, Bundle extras) {
   2284             Bundle bundle = new Bundle();
   2285             bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_QUERY, query);
   2286             bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
   2287             sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_SEARCH, bundle);
   2288         }
   2289 
   2290         @Override
   2291         public void prepareFromUri(Uri uri, Bundle extras) {
   2292             Bundle bundle = new Bundle();
   2293             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
   2294             bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
   2295             sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_URI, bundle);
   2296         }
   2297 
   2298         @Override
   2299         public void play() {
   2300             MediaControllerCompatApi21.TransportControls.play(mControlsObj);
   2301         }
   2302 
   2303         @Override
   2304         public void pause() {
   2305             MediaControllerCompatApi21.TransportControls.pause(mControlsObj);
   2306         }
   2307 
   2308         @Override
   2309         public void stop() {
   2310             MediaControllerCompatApi21.TransportControls.stop(mControlsObj);
   2311         }
   2312 
   2313         @Override
   2314         public void seekTo(long pos) {
   2315             MediaControllerCompatApi21.TransportControls.seekTo(mControlsObj, pos);
   2316         }
   2317 
   2318         @Override
   2319         public void fastForward() {
   2320             MediaControllerCompatApi21.TransportControls.fastForward(mControlsObj);
   2321         }
   2322 
   2323         @Override
   2324         public void rewind() {
   2325             MediaControllerCompatApi21.TransportControls.rewind(mControlsObj);
   2326         }
   2327 
   2328         @Override
   2329         public void skipToNext() {
   2330             MediaControllerCompatApi21.TransportControls.skipToNext(mControlsObj);
   2331         }
   2332 
   2333         @Override
   2334         public void skipToPrevious() {
   2335             MediaControllerCompatApi21.TransportControls.skipToPrevious(mControlsObj);
   2336         }
   2337 
   2338         @Override
   2339         public void setRating(RatingCompat rating) {
   2340             MediaControllerCompatApi21.TransportControls.setRating(mControlsObj,
   2341                     rating != null ? rating.getRating() : null);
   2342         }
   2343 
   2344         @Override
   2345         public void setRating(RatingCompat rating, Bundle extras) {
   2346             Bundle bundle = new Bundle();
   2347             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_RATING, rating);
   2348             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
   2349             sendCustomAction(MediaSessionCompat.ACTION_SET_RATING, bundle);
   2350         }
   2351 
   2352         @Override
   2353         public void setCaptioningEnabled(boolean enabled) {
   2354             Bundle bundle = new Bundle();
   2355             bundle.putBoolean(MediaSessionCompat.ACTION_ARGUMENT_CAPTIONING_ENABLED, enabled);
   2356             sendCustomAction(MediaSessionCompat.ACTION_SET_CAPTIONING_ENABLED, bundle);
   2357         }
   2358 
   2359         @Override
   2360         public void setRepeatMode(@PlaybackStateCompat.RepeatMode int repeatMode) {
   2361             Bundle bundle = new Bundle();
   2362             bundle.putInt(MediaSessionCompat.ACTION_ARGUMENT_REPEAT_MODE, repeatMode);
   2363             sendCustomAction(MediaSessionCompat.ACTION_SET_REPEAT_MODE, bundle);
   2364         }
   2365 
   2366         @Override
   2367         public void setShuffleMode(@PlaybackStateCompat.ShuffleMode int shuffleMode) {
   2368             Bundle bundle = new Bundle();
   2369             bundle.putInt(MediaSessionCompat.ACTION_ARGUMENT_SHUFFLE_MODE, shuffleMode);
   2370             sendCustomAction(MediaSessionCompat.ACTION_SET_SHUFFLE_MODE, bundle);
   2371         }
   2372 
   2373         @Override
   2374         public void playFromMediaId(String mediaId, Bundle extras) {
   2375             MediaControllerCompatApi21.TransportControls.playFromMediaId(mControlsObj, mediaId,
   2376                     extras);
   2377         }
   2378 
   2379         @Override
   2380         public void playFromSearch(String query, Bundle extras) {
   2381             MediaControllerCompatApi21.TransportControls.playFromSearch(mControlsObj, query,
   2382                     extras);
   2383         }
   2384 
   2385         @Override
   2386         public void playFromUri(Uri uri, Bundle extras) {
   2387             if (uri == null || Uri.EMPTY.equals(uri)) {
   2388                 throw new IllegalArgumentException(
   2389                         "You must specify a non-empty Uri for playFromUri.");
   2390             }
   2391             Bundle bundle = new Bundle();
   2392             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
   2393             bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
   2394             sendCustomAction(MediaSessionCompat.ACTION_PLAY_FROM_URI, bundle);
   2395         }
   2396 
   2397         @Override
   2398         public void skipToQueueItem(long id) {
   2399             MediaControllerCompatApi21.TransportControls.skipToQueueItem(mControlsObj, id);
   2400         }
   2401 
   2402         @Override
   2403         public void sendCustomAction(CustomAction customAction, Bundle args) {
   2404             validateCustomAction(customAction.getAction(), args);
   2405             MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj,
   2406                     customAction.getAction(), args);
   2407         }
   2408 
   2409         @Override
   2410         public void sendCustomAction(String action, Bundle args) {
   2411             validateCustomAction(action, args);
   2412             MediaControllerCompatApi21.TransportControls.sendCustomAction(mControlsObj, action,
   2413                     args);
   2414         }
   2415     }
   2416 
   2417     @RequiresApi(23)
   2418     static class MediaControllerImplApi23 extends MediaControllerImplApi21 {
   2419 
   2420         public MediaControllerImplApi23(Context context, MediaSessionCompat session) {
   2421             super(context, session);
   2422         }
   2423 
   2424         public MediaControllerImplApi23(Context context, MediaSessionCompat.Token sessionToken)
   2425                 throws RemoteException {
   2426             super(context, sessionToken);
   2427         }
   2428 
   2429         @Override
   2430         public TransportControls getTransportControls() {
   2431             Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
   2432             return controlsObj != null ? new TransportControlsApi23(controlsObj) : null;
   2433         }
   2434     }
   2435 
   2436     @RequiresApi(23)
   2437     static class TransportControlsApi23 extends TransportControlsApi21 {
   2438 
   2439         public TransportControlsApi23(Object controlsObj) {
   2440             super(controlsObj);
   2441         }
   2442 
   2443         @Override
   2444         public void playFromUri(Uri uri, Bundle extras) {
   2445             MediaControllerCompatApi23.TransportControls.playFromUri(mControlsObj, uri,
   2446                     extras);
   2447         }
   2448     }
   2449 
   2450     @RequiresApi(24)
   2451     static class MediaControllerImplApi24 extends MediaControllerImplApi23 {
   2452 
   2453         public MediaControllerImplApi24(Context context, MediaSessionCompat session) {
   2454             super(context, session);
   2455         }
   2456 
   2457         public MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken)
   2458                 throws RemoteException {
   2459             super(context, sessionToken);
   2460         }
   2461 
   2462         @Override
   2463         public TransportControls getTransportControls() {
   2464             Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
   2465             return controlsObj != null ? new TransportControlsApi24(controlsObj) : null;
   2466         }
   2467     }
   2468 
   2469     @RequiresApi(24)
   2470     static class TransportControlsApi24 extends TransportControlsApi23 {
   2471 
   2472         public TransportControlsApi24(Object controlsObj) {
   2473             super(controlsObj);
   2474         }
   2475 
   2476         @Override
   2477         public void prepare() {
   2478             MediaControllerCompatApi24.TransportControls.prepare(mControlsObj);
   2479         }
   2480 
   2481         @Override
   2482         public void prepareFromMediaId(String mediaId, Bundle extras) {
   2483             MediaControllerCompatApi24.TransportControls.prepareFromMediaId(
   2484                     mControlsObj, mediaId, extras);
   2485         }
   2486 
   2487         @Override
   2488         public void prepareFromSearch(String query, Bundle extras) {
   2489             MediaControllerCompatApi24.TransportControls.prepareFromSearch(
   2490                     mControlsObj, query, extras);
   2491         }
   2492 
   2493         @Override
   2494         public void prepareFromUri(Uri uri, Bundle extras) {
   2495             MediaControllerCompatApi24.TransportControls.prepareFromUri(mControlsObj, uri, extras);
   2496         }
   2497     }
   2498 }
   2499