Home | History | Annotate | Download | only in media
      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 androidx.media;
     18 
     19 import static android.support.v4.media.MediaMetadataCompat.METADATA_KEY_DURATION;
     20 
     21 import static androidx.media.MediaConstants2.ARGUMENT_ALLOWED_COMMANDS;
     22 import static androidx.media.MediaConstants2.ARGUMENT_ARGUMENTS;
     23 import static androidx.media.MediaConstants2.ARGUMENT_BUFFERING_STATE;
     24 import static androidx.media.MediaConstants2.ARGUMENT_COMMAND_BUTTONS;
     25 import static androidx.media.MediaConstants2.ARGUMENT_COMMAND_CODE;
     26 import static androidx.media.MediaConstants2.ARGUMENT_CUSTOM_COMMAND;
     27 import static androidx.media.MediaConstants2.ARGUMENT_ERROR_CODE;
     28 import static androidx.media.MediaConstants2.ARGUMENT_EXTRAS;
     29 import static androidx.media.MediaConstants2.ARGUMENT_ICONTROLLER_CALLBACK;
     30 import static androidx.media.MediaConstants2.ARGUMENT_ITEM_COUNT;
     31 import static androidx.media.MediaConstants2.ARGUMENT_MEDIA_ID;
     32 import static androidx.media.MediaConstants2.ARGUMENT_MEDIA_ITEM;
     33 import static androidx.media.MediaConstants2.ARGUMENT_PACKAGE_NAME;
     34 import static androidx.media.MediaConstants2.ARGUMENT_PID;
     35 import static androidx.media.MediaConstants2.ARGUMENT_PLAYBACK_INFO;
     36 import static androidx.media.MediaConstants2.ARGUMENT_PLAYBACK_SPEED;
     37 import static androidx.media.MediaConstants2.ARGUMENT_PLAYBACK_STATE_COMPAT;
     38 import static androidx.media.MediaConstants2.ARGUMENT_PLAYER_STATE;
     39 import static androidx.media.MediaConstants2.ARGUMENT_PLAYLIST;
     40 import static androidx.media.MediaConstants2.ARGUMENT_PLAYLIST_INDEX;
     41 import static androidx.media.MediaConstants2.ARGUMENT_PLAYLIST_METADATA;
     42 import static androidx.media.MediaConstants2.ARGUMENT_QUERY;
     43 import static androidx.media.MediaConstants2.ARGUMENT_RATING;
     44 import static androidx.media.MediaConstants2.ARGUMENT_REPEAT_MODE;
     45 import static androidx.media.MediaConstants2.ARGUMENT_RESULT_RECEIVER;
     46 import static androidx.media.MediaConstants2.ARGUMENT_ROUTE_BUNDLE;
     47 import static androidx.media.MediaConstants2.ARGUMENT_SEEK_POSITION;
     48 import static androidx.media.MediaConstants2.ARGUMENT_SHUFFLE_MODE;
     49 import static androidx.media.MediaConstants2.ARGUMENT_UID;
     50 import static androidx.media.MediaConstants2.ARGUMENT_URI;
     51 import static androidx.media.MediaConstants2.ARGUMENT_VOLUME;
     52 import static androidx.media.MediaConstants2.ARGUMENT_VOLUME_DIRECTION;
     53 import static androidx.media.MediaConstants2.ARGUMENT_VOLUME_FLAGS;
     54 import static androidx.media.MediaConstants2.CONNECT_RESULT_CONNECTED;
     55 import static androidx.media.MediaConstants2.CONNECT_RESULT_DISCONNECTED;
     56 import static androidx.media.MediaConstants2.CONTROLLER_COMMAND_BY_COMMAND_CODE;
     57 import static androidx.media.MediaConstants2.CONTROLLER_COMMAND_BY_CUSTOM_COMMAND;
     58 import static androidx.media.MediaConstants2.CONTROLLER_COMMAND_CONNECT;
     59 import static androidx.media.MediaConstants2.CONTROLLER_COMMAND_DISCONNECT;
     60 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_ALLOWED_COMMANDS_CHANGED;
     61 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_BUFFERING_STATE_CHANGED;
     62 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_CHILDREN_CHANGED;
     63 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_CURRENT_MEDIA_ITEM_CHANGED;
     64 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_ERROR;
     65 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYBACK_INFO_CHANGED;
     66 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYBACK_SPEED_CHANGED;
     67 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYER_STATE_CHANGED;
     68 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYLIST_CHANGED;
     69 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_PLAYLIST_METADATA_CHANGED;
     70 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_REPEAT_MODE_CHANGED;
     71 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_ROUTES_INFO_CHANGED;
     72 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_SEARCH_RESULT_CHANGED;
     73 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_SEEK_COMPLETED;
     74 import static androidx.media.MediaConstants2.SESSION_EVENT_ON_SHUFFLE_MODE_CHANGED;
     75 import static androidx.media.MediaConstants2.SESSION_EVENT_SEND_CUSTOM_COMMAND;
     76 import static androidx.media.MediaConstants2.SESSION_EVENT_SET_CUSTOM_LAYOUT;
     77 import static androidx.media.MediaPlayerInterface.BUFFERING_STATE_UNKNOWN;
     78 import static androidx.media.MediaPlayerInterface.UNKNOWN_TIME;
     79 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_PAUSE;
     80 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_PLAY;
     81 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_PREPARE;
     82 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_RESET;
     83 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_SEEK_TO;
     84 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYBACK_SET_SPEED;
     85 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_ADD_ITEM;
     86 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_REMOVE_ITEM;
     87 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_REPLACE_ITEM;
     88 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_LIST;
     89 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_LIST_METADATA;
     90 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE;
     91 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE;
     92 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_TO_NEXT_ITEM;
     93 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM;
     94 import static androidx.media.SessionCommand2.COMMAND_CODE_PLAYLIST_SKIP_TO_PREV_ITEM;
     95 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_FAST_FORWARD;
     96 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID;
     97 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_SEARCH;
     98 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_PLAY_FROM_URI;
     99 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID;
    100 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH;
    101 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_PREPARE_FROM_URI;
    102 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_REWIND;
    103 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_SELECT_ROUTE;
    104 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_SET_RATING;
    105 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_SUBSCRIBE_ROUTES_INFO;
    106 import static androidx.media.SessionCommand2.COMMAND_CODE_SESSION_UNSUBSCRIBE_ROUTES_INFO;
    107 import static androidx.media.SessionCommand2.COMMAND_CODE_VOLUME_ADJUST_VOLUME;
    108 import static androidx.media.SessionCommand2.COMMAND_CODE_VOLUME_SET_VOLUME;
    109 
    110 import android.annotation.TargetApi;
    111 import android.app.PendingIntent;
    112 import android.content.Context;
    113 import android.net.Uri;
    114 import android.os.Build;
    115 import android.os.Bundle;
    116 import android.os.Handler;
    117 import android.os.HandlerThread;
    118 import android.os.IBinder;
    119 import android.os.Process;
    120 import android.os.RemoteException;
    121 import android.os.ResultReceiver;
    122 import android.os.SystemClock;
    123 import android.support.v4.media.MediaBrowserCompat;
    124 import android.support.v4.media.MediaMetadataCompat;
    125 import android.support.v4.media.session.MediaControllerCompat;
    126 import android.support.v4.media.session.MediaSessionCompat;
    127 import android.support.v4.media.session.PlaybackStateCompat;
    128 import android.util.Log;
    129 
    130 import androidx.annotation.GuardedBy;
    131 import androidx.annotation.NonNull;
    132 import androidx.annotation.Nullable;
    133 import androidx.core.app.BundleCompat;
    134 import androidx.media.MediaController2.ControllerCallback;
    135 import androidx.media.MediaController2.PlaybackInfo;
    136 import androidx.media.MediaController2.VolumeDirection;
    137 import androidx.media.MediaController2.VolumeFlags;
    138 import androidx.media.MediaPlaylistAgent.RepeatMode;
    139 import androidx.media.MediaPlaylistAgent.ShuffleMode;
    140 import androidx.media.MediaSession2.CommandButton;
    141 
    142 import java.util.List;
    143 import java.util.concurrent.Executor;
    144 
    145 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    146 class MediaController2ImplBase implements MediaController2.SupportLibraryImpl {
    147 
    148     private static final String TAG = "MC2ImplBase";
    149     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    150 
    151     // Note: Using {@code null} doesn't helpful here because MediaBrowserServiceCompat always wraps
    152     //       the rootHints so it becomes non-null.
    153     static final Bundle sDefaultRootExtras = new Bundle();
    154     static {
    155         sDefaultRootExtras.putBoolean(MediaConstants2.ROOT_EXTRA_DEFAULT, true);
    156     }
    157 
    158     private final Context mContext;
    159     private final Object mLock = new Object();
    160 
    161     private final SessionToken2 mToken;
    162     private final ControllerCallback mCallback;
    163     private final Executor mCallbackExecutor;
    164     private final IBinder.DeathRecipient mDeathRecipient;
    165 
    166     private final HandlerThread mHandlerThread;
    167     private final Handler mHandler;
    168 
    169     private MediaController2 mInstance;
    170 
    171     @GuardedBy("mLock")
    172     private MediaBrowserCompat mBrowserCompat;
    173     @GuardedBy("mLock")
    174     private boolean mIsReleased;
    175     @GuardedBy("mLock")
    176     private List<MediaItem2> mPlaylist;
    177     @GuardedBy("mLock")
    178     private MediaMetadata2 mPlaylistMetadata;
    179     @GuardedBy("mLock")
    180     private @RepeatMode int mRepeatMode;
    181     @GuardedBy("mLock")
    182     private @ShuffleMode int mShuffleMode;
    183     @GuardedBy("mLock")
    184     private int mPlayerState;
    185     @GuardedBy("mLock")
    186     private MediaItem2 mCurrentMediaItem;
    187     @GuardedBy("mLock")
    188     private int mBufferingState;
    189     @GuardedBy("mLock")
    190     private PlaybackInfo mPlaybackInfo;
    191     @GuardedBy("mLock")
    192     private SessionCommandGroup2 mAllowedCommands;
    193 
    194     // Media 1.0 variables
    195     @GuardedBy("mLock")
    196     private MediaControllerCompat mControllerCompat;
    197     @GuardedBy("mLock")
    198     private ControllerCompatCallback mControllerCompatCallback;
    199     @GuardedBy("mLock")
    200     private PlaybackStateCompat mPlaybackStateCompat;
    201     @GuardedBy("mLock")
    202     private MediaMetadataCompat mMediaMetadataCompat;
    203 
    204     // Assignment should be used with the lock hold, but should be used without a lock to prevent
    205     // potential deadlock.
    206     @GuardedBy("mLock")
    207     private volatile boolean mConnected;
    208 
    209     MediaController2ImplBase(@NonNull Context context, @NonNull SessionToken2 token,
    210             @NonNull Executor executor, @NonNull ControllerCallback callback) {
    211         super();
    212         if (context == null) {
    213             throw new IllegalArgumentException("context shouldn't be null");
    214         }
    215         if (token == null) {
    216             throw new IllegalArgumentException("token shouldn't be null");
    217         }
    218         if (callback == null) {
    219             throw new IllegalArgumentException("callback shouldn't be null");
    220         }
    221         if (executor == null) {
    222             throw new IllegalArgumentException("executor shouldn't be null");
    223         }
    224         mContext = context;
    225         mHandlerThread = new HandlerThread("MediaController2_Thread");
    226         mHandlerThread.start();
    227         mHandler = new Handler(mHandlerThread.getLooper());
    228         mToken = token;
    229         mCallback = callback;
    230         mCallbackExecutor = executor;
    231         mDeathRecipient = new IBinder.DeathRecipient() {
    232             @Override
    233             public void binderDied() {
    234                 MediaController2ImplBase.this.close();
    235             }
    236         };
    237 
    238         initialize();
    239     }
    240 
    241     @Override
    242     public void setInstance(MediaController2 controller) {
    243         mInstance = controller;
    244     }
    245 
    246     @Override
    247     public void close() {
    248         if (DEBUG) {
    249             //Log.d(TAG, "release from " + mToken, new IllegalStateException());
    250         }
    251         synchronized (mLock) {
    252             if (mIsReleased) {
    253                 // Prevent re-enterance from the ControllerCallback.onDisconnected()
    254                 return;
    255             }
    256             mHandler.removeCallbacksAndMessages(null);
    257 
    258             if (Build.VERSION.SDK_INT >= 18) {
    259                 mHandlerThread.quitSafely();
    260             } else {
    261                 mHandlerThread.quit();
    262             }
    263 
    264             mIsReleased = true;
    265 
    266             // Send command before the unregister callback to use mIControllerCallback in the
    267             // callback.
    268             sendCommand(CONTROLLER_COMMAND_DISCONNECT);
    269             if (mControllerCompat != null) {
    270                 mControllerCompat.unregisterCallback(mControllerCompatCallback);
    271             }
    272             if (mBrowserCompat != null) {
    273                 mBrowserCompat.disconnect();
    274                 mBrowserCompat = null;
    275             }
    276             if (mControllerCompat != null) {
    277                 mControllerCompat.unregisterCallback(mControllerCompatCallback);
    278                 mControllerCompat = null;
    279             }
    280             mConnected = false;
    281         }
    282         mCallbackExecutor.execute(new Runnable() {
    283             @Override
    284             public void run() {
    285                 mCallback.onDisconnected(mInstance);
    286             }
    287         });
    288     }
    289 
    290     @Override
    291     public @NonNull SessionToken2 getSessionToken() {
    292         return mToken;
    293     }
    294 
    295     @Override
    296     public boolean isConnected() {
    297         synchronized (mLock) {
    298             return mConnected;
    299         }
    300     }
    301 
    302     @Override
    303     public void play() {
    304         synchronized (mLock) {
    305             if (!mConnected) {
    306                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    307                 return;
    308             }
    309             sendCommand(COMMAND_CODE_PLAYBACK_PLAY);
    310         }
    311     }
    312 
    313     @Override
    314     public void pause() {
    315         synchronized (mLock) {
    316             if (!mConnected) {
    317                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    318                 return;
    319             }
    320             sendCommand(COMMAND_CODE_PLAYBACK_PAUSE);
    321         }
    322     }
    323 
    324     @Override
    325     public void reset() {
    326         synchronized (mLock) {
    327             if (!mConnected) {
    328                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    329                 return;
    330             }
    331             sendCommand(COMMAND_CODE_PLAYBACK_RESET);
    332         }
    333     }
    334 
    335     @Override
    336     public void prepare() {
    337         synchronized (mLock) {
    338             if (!mConnected) {
    339                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    340                 return;
    341             }
    342             sendCommand(COMMAND_CODE_PLAYBACK_PREPARE);
    343         }
    344     }
    345 
    346     @Override
    347     public void fastForward() {
    348         synchronized (mLock) {
    349             if (!mConnected) {
    350                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    351                 return;
    352             }
    353             sendCommand(COMMAND_CODE_SESSION_FAST_FORWARD);
    354         }
    355     }
    356 
    357     @Override
    358     public void rewind() {
    359         synchronized (mLock) {
    360             if (!mConnected) {
    361                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    362                 return;
    363             }
    364             sendCommand(COMMAND_CODE_SESSION_REWIND);
    365         }
    366     }
    367 
    368     @Override
    369     public void seekTo(long pos) {
    370         synchronized (mLock) {
    371             if (!mConnected) {
    372                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    373                 return;
    374             }
    375             Bundle args = new Bundle();
    376             args.putLong(ARGUMENT_SEEK_POSITION, pos);
    377             sendCommand(COMMAND_CODE_PLAYBACK_SEEK_TO, args);
    378         }
    379     }
    380 
    381     @Override
    382     public void skipForward() {
    383         // To match with KEYCODE_MEDIA_SKIP_FORWARD
    384     }
    385 
    386     @Override
    387     public void skipBackward() {
    388         // To match with KEYCODE_MEDIA_SKIP_BACKWARD
    389     }
    390 
    391     @Override
    392     public void playFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
    393         synchronized (mLock) {
    394             if (!mConnected) {
    395                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    396                 return;
    397             }
    398             Bundle args = new Bundle();
    399             args.putString(ARGUMENT_MEDIA_ID, mediaId);
    400             args.putBundle(ARGUMENT_EXTRAS, extras);
    401             sendCommand(COMMAND_CODE_SESSION_PLAY_FROM_MEDIA_ID, args);
    402         }
    403     }
    404 
    405     @Override
    406     public void playFromSearch(@NonNull String query, @Nullable Bundle extras) {
    407         synchronized (mLock) {
    408             if (!mConnected) {
    409                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    410                 return;
    411             }
    412             Bundle args = new Bundle();
    413             args.putString(ARGUMENT_QUERY, query);
    414             args.putBundle(ARGUMENT_EXTRAS, extras);
    415             sendCommand(COMMAND_CODE_SESSION_PLAY_FROM_SEARCH, args);
    416         }
    417     }
    418 
    419     @Override
    420     public void playFromUri(@NonNull Uri uri, @Nullable Bundle extras) {
    421         synchronized (mLock) {
    422             if (!mConnected) {
    423                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    424                 return;
    425             }
    426             Bundle args = new Bundle();
    427             args.putParcelable(ARGUMENT_URI, uri);
    428             args.putBundle(ARGUMENT_EXTRAS, extras);
    429             sendCommand(COMMAND_CODE_SESSION_PLAY_FROM_URI, args);
    430         }
    431     }
    432 
    433     @Override
    434     public void prepareFromMediaId(@NonNull String mediaId, @Nullable Bundle extras) {
    435         synchronized (mLock) {
    436             if (!mConnected) {
    437                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    438                 return;
    439             }
    440             Bundle args = new Bundle();
    441             args.putString(ARGUMENT_MEDIA_ID, mediaId);
    442             args.putBundle(ARGUMENT_EXTRAS, extras);
    443             sendCommand(COMMAND_CODE_SESSION_PREPARE_FROM_MEDIA_ID, args);
    444         }
    445     }
    446 
    447     @Override
    448     public void prepareFromSearch(@NonNull String query, @Nullable Bundle extras) {
    449         synchronized (mLock) {
    450             if (!mConnected) {
    451                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    452                 return;
    453             }
    454             Bundle args = new Bundle();
    455             args.putString(ARGUMENT_QUERY, query);
    456             args.putBundle(ARGUMENT_EXTRAS, extras);
    457             sendCommand(COMMAND_CODE_SESSION_PREPARE_FROM_SEARCH, args);
    458         }
    459     }
    460 
    461     @Override
    462     public void prepareFromUri(@NonNull Uri uri, @Nullable Bundle extras) {
    463         synchronized (mLock) {
    464             if (!mConnected) {
    465                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    466                 return;
    467             }
    468             Bundle args = new Bundle();
    469             args.putParcelable(ARGUMENT_URI, uri);
    470             args.putBundle(ARGUMENT_EXTRAS, extras);
    471             sendCommand(COMMAND_CODE_SESSION_PREPARE_FROM_URI, args);
    472         }
    473     }
    474 
    475     @Override
    476     public void setVolumeTo(int value, @VolumeFlags int flags) {
    477         synchronized (mLock) {
    478             if (!mConnected) {
    479                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    480                 return;
    481             }
    482             Bundle args = new Bundle();
    483             args.putInt(ARGUMENT_VOLUME, value);
    484             args.putInt(ARGUMENT_VOLUME_FLAGS, flags);
    485             sendCommand(COMMAND_CODE_VOLUME_SET_VOLUME, args);
    486         }
    487     }
    488 
    489     @Override
    490     public void adjustVolume(@VolumeDirection int direction, @VolumeFlags int flags) {
    491         synchronized (mLock) {
    492             if (!mConnected) {
    493                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    494                 return;
    495             }
    496             Bundle args = new Bundle();
    497             args.putInt(ARGUMENT_VOLUME_DIRECTION, direction);
    498             args.putInt(ARGUMENT_VOLUME_FLAGS, flags);
    499             sendCommand(COMMAND_CODE_VOLUME_ADJUST_VOLUME, args);
    500         }
    501     }
    502 
    503     @Override
    504     public @Nullable PendingIntent getSessionActivity() {
    505         synchronized (mLock) {
    506             if (!mConnected) {
    507                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    508                 return null;
    509             }
    510             return mControllerCompat.getSessionActivity();
    511         }
    512     }
    513 
    514     @Override
    515     public int getPlayerState() {
    516         synchronized (mLock) {
    517             return mPlayerState;
    518         }
    519     }
    520 
    521     @Override
    522     public long getDuration() {
    523         synchronized (mLock) {
    524             if (mMediaMetadataCompat != null
    525                     && mMediaMetadataCompat.containsKey(METADATA_KEY_DURATION)) {
    526                 return mMediaMetadataCompat.getLong(METADATA_KEY_DURATION);
    527             }
    528         }
    529         return MediaPlayerInterface.UNKNOWN_TIME;
    530     }
    531 
    532     @Override
    533     public long getCurrentPosition() {
    534         synchronized (mLock) {
    535             if (!mConnected) {
    536                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    537                 return UNKNOWN_TIME;
    538             }
    539             if (mPlaybackStateCompat != null) {
    540                 long timeDiff = (mInstance.mTimeDiff != null) ? mInstance.mTimeDiff
    541                         : SystemClock.elapsedRealtime()
    542                                 - mPlaybackStateCompat.getLastPositionUpdateTime();
    543                 long expectedPosition = mPlaybackStateCompat.getPosition()
    544                         + (long) (mPlaybackStateCompat.getPlaybackSpeed() * timeDiff);
    545                 return Math.max(0, expectedPosition);
    546             }
    547             return UNKNOWN_TIME;
    548         }
    549     }
    550 
    551     @Override
    552     public float getPlaybackSpeed() {
    553         synchronized (mLock) {
    554             if (!mConnected) {
    555                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    556                 return 0f;
    557             }
    558             return (mPlaybackStateCompat == null) ? 0f : mPlaybackStateCompat.getPlaybackSpeed();
    559         }
    560     }
    561 
    562     @Override
    563     public void setPlaybackSpeed(float speed) {
    564         synchronized (mLock) {
    565             if (!mConnected) {
    566                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    567                 return;
    568             }
    569             Bundle args = new Bundle();
    570             args.putFloat(ARGUMENT_PLAYBACK_SPEED, speed);
    571             sendCommand(COMMAND_CODE_PLAYBACK_SET_SPEED, args);
    572         }
    573     }
    574 
    575     @Override
    576     public @MediaPlayerInterface.BuffState int getBufferingState() {
    577         synchronized (mLock) {
    578             if (!mConnected) {
    579                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    580                 return BUFFERING_STATE_UNKNOWN;
    581             }
    582             return mBufferingState;
    583         }
    584     }
    585 
    586     @Override
    587     public long getBufferedPosition() {
    588         synchronized (mLock) {
    589             if (!mConnected) {
    590                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    591                 return UNKNOWN_TIME;
    592             }
    593             return (mPlaybackStateCompat == null) ? UNKNOWN_TIME
    594                     : mPlaybackStateCompat.getBufferedPosition();
    595         }
    596     }
    597 
    598     @Override
    599     public @Nullable PlaybackInfo getPlaybackInfo() {
    600         synchronized (mLock) {
    601             return mPlaybackInfo;
    602         }
    603     }
    604 
    605     @Override
    606     public void setRating(@NonNull String mediaId, @NonNull Rating2 rating) {
    607         synchronized (mLock) {
    608             if (!mConnected) {
    609                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    610                 return;
    611             }
    612             Bundle args = new Bundle();
    613             args.putString(ARGUMENT_MEDIA_ID, mediaId);
    614             args.putBundle(ARGUMENT_RATING, rating.toBundle());
    615             sendCommand(COMMAND_CODE_SESSION_SET_RATING, args);
    616         }
    617     }
    618 
    619     @Override
    620     public void sendCustomCommand(@NonNull SessionCommand2 command, @Nullable Bundle args,
    621             @Nullable ResultReceiver cb) {
    622         synchronized (mLock) {
    623             if (!mConnected) {
    624                 Log.w(TAG, "Session isn't active", new IllegalStateException());
    625                 return;
    626             }
    627             Bundle bundle = new Bundle();
    628             bundle.putBundle(ARGUMENT_CUSTOM_COMMAND, command.toBundle());
    629             bundle.putBundle(ARGUMENT_ARGUMENTS, args);
    630             sendCommand(CONTROLLER_COMMAND_BY_CUSTOM_COMMAND, bundle, cb);
    631         }
    632     }
    633 
    634     @Override
    635     public @Nullable List<MediaItem2> getPlaylist() {
    636         synchronized (mLock) {
    637             return mPlaylist;
    638         }
    639     }
    640 
    641     @Override
    642     public void setPlaylist(@NonNull List<MediaItem2> list, @Nullable MediaMetadata2 metadata) {
    643         if (list == null) {
    644             throw new IllegalArgumentException("list shouldn't be null");
    645         }
    646         Bundle args = new Bundle();
    647         args.putParcelableArray(ARGUMENT_PLAYLIST, MediaUtils2.toMediaItem2ParcelableArray(list));
    648         args.putBundle(ARGUMENT_PLAYLIST_METADATA, metadata == null ? null : metadata.toBundle());
    649         sendCommand(COMMAND_CODE_PLAYLIST_SET_LIST, args);
    650     }
    651 
    652     @Override
    653     public void updatePlaylistMetadata(@Nullable MediaMetadata2 metadata) {
    654         Bundle args = new Bundle();
    655         args.putBundle(ARGUMENT_PLAYLIST_METADATA, metadata == null ? null : metadata.toBundle());
    656         sendCommand(COMMAND_CODE_PLAYLIST_SET_LIST_METADATA, args);
    657     }
    658 
    659     @Override
    660     public @Nullable MediaMetadata2 getPlaylistMetadata() {
    661         synchronized (mLock) {
    662             return mPlaylistMetadata;
    663         }
    664     }
    665 
    666     @Override
    667     public void addPlaylistItem(int index, @NonNull MediaItem2 item) {
    668         Bundle args = new Bundle();
    669         args.putInt(ARGUMENT_PLAYLIST_INDEX, index);
    670         args.putBundle(ARGUMENT_MEDIA_ITEM, item.toBundle());
    671         sendCommand(COMMAND_CODE_PLAYLIST_ADD_ITEM, args);
    672     }
    673 
    674     @Override
    675     public void removePlaylistItem(@NonNull MediaItem2 item) {
    676         Bundle args = new Bundle();
    677         args.putBundle(ARGUMENT_MEDIA_ITEM, item.toBundle());
    678         sendCommand(COMMAND_CODE_PLAYLIST_REMOVE_ITEM, args);
    679     }
    680 
    681     @Override
    682     public void replacePlaylistItem(int index, @NonNull MediaItem2 item) {
    683         Bundle args = new Bundle();
    684         args.putInt(ARGUMENT_PLAYLIST_INDEX, index);
    685         args.putBundle(ARGUMENT_MEDIA_ITEM, item.toBundle());
    686         sendCommand(COMMAND_CODE_PLAYLIST_REPLACE_ITEM, args);
    687     }
    688 
    689     @Override
    690     public MediaItem2 getCurrentMediaItem() {
    691         synchronized (mLock) {
    692             return mCurrentMediaItem;
    693         }
    694     }
    695 
    696     @Override
    697     public void skipToPreviousItem() {
    698         sendCommand(COMMAND_CODE_PLAYLIST_SKIP_TO_PREV_ITEM);
    699     }
    700 
    701     @Override
    702     public void skipToNextItem() {
    703         sendCommand(COMMAND_CODE_PLAYLIST_SKIP_TO_NEXT_ITEM);
    704     }
    705 
    706     @Override
    707     public void skipToPlaylistItem(@NonNull MediaItem2 item) {
    708         Bundle args = new Bundle();
    709         args.putBundle(ARGUMENT_MEDIA_ITEM, item.toBundle());
    710         sendCommand(COMMAND_CODE_PLAYLIST_SKIP_TO_PLAYLIST_ITEM, args);
    711     }
    712 
    713     @Override
    714     public @RepeatMode int getRepeatMode() {
    715         synchronized (mLock) {
    716             return mRepeatMode;
    717         }
    718     }
    719 
    720     @Override
    721     public void setRepeatMode(@RepeatMode int repeatMode) {
    722         Bundle args = new Bundle();
    723         args.putInt(ARGUMENT_REPEAT_MODE, repeatMode);
    724         sendCommand(COMMAND_CODE_PLAYLIST_SET_REPEAT_MODE, args);
    725     }
    726 
    727     @Override
    728     public @ShuffleMode int getShuffleMode() {
    729         synchronized (mLock) {
    730             return mShuffleMode;
    731         }
    732     }
    733 
    734     @Override
    735     public void setShuffleMode(@ShuffleMode int shuffleMode) {
    736         Bundle args = new Bundle();
    737         args.putInt(ARGUMENT_SHUFFLE_MODE, shuffleMode);
    738         sendCommand(COMMAND_CODE_PLAYLIST_SET_SHUFFLE_MODE, args);
    739     }
    740 
    741     @Override
    742     public void subscribeRoutesInfo() {
    743         sendCommand(COMMAND_CODE_SESSION_SUBSCRIBE_ROUTES_INFO);
    744     }
    745 
    746     @Override
    747     public void unsubscribeRoutesInfo() {
    748         sendCommand(COMMAND_CODE_SESSION_UNSUBSCRIBE_ROUTES_INFO);
    749     }
    750 
    751     @Override
    752     public void selectRoute(@NonNull Bundle route) {
    753         if (route == null) {
    754             throw new IllegalArgumentException("route shouldn't be null");
    755         }
    756         Bundle args = new Bundle();
    757         args.putBundle(ARGUMENT_ROUTE_BUNDLE, route);
    758         sendCommand(COMMAND_CODE_SESSION_SELECT_ROUTE, args);
    759     }
    760 
    761     @Override
    762     public @NonNull Context getContext() {
    763         return mContext;
    764     }
    765 
    766     @Override
    767     public @NonNull ControllerCallback getCallback() {
    768         return mCallback;
    769     }
    770 
    771     @Override
    772     public @NonNull Executor getCallbackExecutor() {
    773         return mCallbackExecutor;
    774     }
    775 
    776     @Override
    777     public @Nullable MediaBrowserCompat getBrowserCompat() {
    778         synchronized (mLock) {
    779             return mBrowserCompat;
    780         }
    781     }
    782 
    783     // Should be used without a lock to prevent potential deadlock.
    784     void onConnectedNotLocked(Bundle data) {
    785         data.setClassLoader(MediaSession2.class.getClassLoader());
    786         // is enough or should we pass it while connecting?
    787         final SessionCommandGroup2 allowedCommands = SessionCommandGroup2.fromBundle(
    788                 data.getBundle(ARGUMENT_ALLOWED_COMMANDS));
    789         final int playerState = data.getInt(ARGUMENT_PLAYER_STATE);
    790         final int bufferingState = data.getInt(ARGUMENT_BUFFERING_STATE);
    791         final PlaybackStateCompat playbackStateCompat = data.getParcelable(
    792                 ARGUMENT_PLAYBACK_STATE_COMPAT);
    793         final int repeatMode = data.getInt(ARGUMENT_REPEAT_MODE);
    794         final int shuffleMode = data.getInt(ARGUMENT_SHUFFLE_MODE);
    795         final List<MediaItem2> playlist = MediaUtils2.fromMediaItem2ParcelableArray(
    796                 data.getParcelableArray(ARGUMENT_PLAYLIST));
    797         final MediaItem2 currentMediaItem = MediaItem2.fromBundle(
    798                 data.getBundle(ARGUMENT_MEDIA_ITEM));
    799         final PlaybackInfo playbackInfo =
    800                 PlaybackInfo.fromBundle(data.getBundle(ARGUMENT_PLAYBACK_INFO));
    801         final MediaMetadata2 metadata = MediaMetadata2.fromBundle(
    802                 data.getBundle(ARGUMENT_PLAYLIST_METADATA));
    803         if (DEBUG) {
    804             Log.d(TAG, "onConnectedNotLocked sessionCompatToken=" + mToken.getSessionCompatToken()
    805                     + ", allowedCommands=" + allowedCommands);
    806         }
    807         boolean close = false;
    808         try {
    809             synchronized (mLock) {
    810                 if (mIsReleased) {
    811                     return;
    812                 }
    813                 if (mConnected) {
    814                     Log.e(TAG, "Cannot be notified about the connection result many times."
    815                             + " Probably a bug or malicious app.");
    816                     close = true;
    817                     return;
    818                 }
    819                 mAllowedCommands = allowedCommands;
    820                 mPlayerState = playerState;
    821                 mBufferingState = bufferingState;
    822                 mPlaybackStateCompat = playbackStateCompat;
    823                 mRepeatMode = repeatMode;
    824                 mShuffleMode = shuffleMode;
    825                 mPlaylist = playlist;
    826                 mCurrentMediaItem = currentMediaItem;
    827                 mPlaylistMetadata = metadata;
    828                 mConnected = true;
    829                 mPlaybackInfo = playbackInfo;
    830             }
    831             mCallbackExecutor.execute(new Runnable() {
    832                 @Override
    833                 public void run() {
    834                     // Note: We may trigger ControllerCallbacks with the initial values
    835                     // But it's hard to define the order of the controller callbacks
    836                     // Only notify about the
    837                     mCallback.onConnected(mInstance, allowedCommands);
    838                 }
    839             });
    840         } finally {
    841             if (close) {
    842                 // Trick to call release() without holding the lock, to prevent potential deadlock
    843                 // with the developer's custom lock within the ControllerCallback.onDisconnected().
    844                 close();
    845             }
    846         }
    847     }
    848 
    849     private void initialize() {
    850         if (mToken.getType() == SessionToken2.TYPE_SESSION) {
    851             synchronized (mLock) {
    852                 mBrowserCompat = null;
    853             }
    854             connectToSession(mToken.getSessionCompatToken());
    855         } else {
    856             connectToService();
    857         }
    858     }
    859 
    860     private void connectToSession(MediaSessionCompat.Token sessionCompatToken) {
    861         MediaControllerCompat controllerCompat = null;
    862         try {
    863             controllerCompat = new MediaControllerCompat(mContext, sessionCompatToken);
    864         } catch (RemoteException e) {
    865             e.printStackTrace();
    866         }
    867         synchronized (mLock) {
    868             mControllerCompat = controllerCompat;
    869             mControllerCompatCallback = new ControllerCompatCallback();
    870             mControllerCompat.registerCallback(mControllerCompatCallback, mHandler);
    871         }
    872 
    873         if (controllerCompat.isSessionReady()) {
    874             sendCommand(CONTROLLER_COMMAND_CONNECT, new ResultReceiver(mHandler) {
    875                 @Override
    876                 protected void onReceiveResult(int resultCode, Bundle resultData) {
    877                     if (!mHandlerThread.isAlive()) {
    878                         return;
    879                     }
    880                     switch (resultCode) {
    881                         case CONNECT_RESULT_CONNECTED:
    882                             onConnectedNotLocked(resultData);
    883                             break;
    884                         case CONNECT_RESULT_DISCONNECTED:
    885                             mCallbackExecutor.execute(new Runnable() {
    886                                 @Override
    887                                 public void run() {
    888                                     mCallback.onDisconnected(mInstance);
    889                                 }
    890                             });
    891                             close();
    892                             break;
    893                     }
    894                 }
    895             });
    896         }
    897     }
    898 
    899     private void connectToService() {
    900         mCallbackExecutor.execute(new Runnable() {
    901             @Override
    902             public void run() {
    903                 synchronized (mLock) {
    904                     mBrowserCompat = new MediaBrowserCompat(mContext, mToken.getComponentName(),
    905                             new ConnectionCallback(), sDefaultRootExtras);
    906                     mBrowserCompat.connect();
    907                 }
    908             }
    909         });
    910     }
    911 
    912     private void sendCommand(int commandCode) {
    913         sendCommand(commandCode, null);
    914     }
    915 
    916     private void sendCommand(int commandCode, Bundle args) {
    917         if (args == null) {
    918             args = new Bundle();
    919         }
    920         args.putInt(ARGUMENT_COMMAND_CODE, commandCode);
    921         sendCommand(CONTROLLER_COMMAND_BY_COMMAND_CODE, args, null);
    922     }
    923 
    924     private void sendCommand(String command) {
    925         sendCommand(command, null, null);
    926     }
    927 
    928     private void sendCommand(String command, ResultReceiver receiver) {
    929         sendCommand(command, null, receiver);
    930     }
    931 
    932     private void sendCommand(String command, Bundle args, ResultReceiver receiver) {
    933         if (args == null) {
    934             args = new Bundle();
    935         }
    936         MediaControllerCompat controller;
    937         ControllerCompatCallback callback;
    938         synchronized (mLock) {
    939             controller = mControllerCompat;
    940             callback = mControllerCompatCallback;
    941         }
    942         BundleCompat.putBinder(args, ARGUMENT_ICONTROLLER_CALLBACK,
    943                 callback.getIControllerCallback().asBinder());
    944         args.putString(ARGUMENT_PACKAGE_NAME, mContext.getPackageName());
    945         args.putInt(ARGUMENT_UID, Process.myUid());
    946         args.putInt(ARGUMENT_PID, Process.myPid());
    947         controller.sendCommand(command, args, receiver);
    948     }
    949 
    950     private class ConnectionCallback extends MediaBrowserCompat.ConnectionCallback {
    951         @Override
    952         public void onConnected() {
    953             MediaBrowserCompat browser = getBrowserCompat();
    954             if (browser != null) {
    955                 connectToSession(browser.getSessionToken());
    956             } else if (DEBUG) {
    957                 Log.d(TAG, "Controller is closed prematually", new IllegalStateException());
    958             }
    959         }
    960 
    961         @Override
    962         public void onConnectionSuspended() {
    963             close();
    964         }
    965 
    966         @Override
    967         public void onConnectionFailed() {
    968             close();
    969         }
    970     }
    971 
    972     private final class ControllerCompatCallback extends MediaControllerCompat.Callback {
    973         @Override
    974         public void onSessionReady() {
    975             sendCommand(CONTROLLER_COMMAND_CONNECT, new ResultReceiver(mHandler) {
    976                 @Override
    977                 protected void onReceiveResult(int resultCode, Bundle resultData) {
    978                     if (!mHandlerThread.isAlive()) {
    979                         return;
    980                     }
    981                     switch (resultCode) {
    982                         case CONNECT_RESULT_CONNECTED:
    983                             onConnectedNotLocked(resultData);
    984                             break;
    985                         case CONNECT_RESULT_DISCONNECTED:
    986                             mCallbackExecutor.execute(new Runnable() {
    987                                 @Override
    988                                 public void run() {
    989                                     mCallback.onDisconnected(mInstance);
    990                                 }
    991                             });
    992                             close();
    993                             break;
    994                     }
    995                 }
    996             });
    997         }
    998 
    999         @Override
   1000         public void onSessionDestroyed() {
   1001             close();
   1002         }
   1003 
   1004         @Override
   1005         public void onPlaybackStateChanged(PlaybackStateCompat state) {
   1006             synchronized (mLock) {
   1007                 mPlaybackStateCompat = state;
   1008             }
   1009         }
   1010 
   1011         @Override
   1012         public void onMetadataChanged(MediaMetadataCompat metadata) {
   1013             synchronized (mLock) {
   1014                 mMediaMetadataCompat = metadata;
   1015             }
   1016         }
   1017 
   1018         @Override
   1019         public void onSessionEvent(String event, Bundle extras) {
   1020             if (extras != null) {
   1021                 extras.setClassLoader(MediaSession2.class.getClassLoader());
   1022             }
   1023             switch (event) {
   1024                 case SESSION_EVENT_ON_ALLOWED_COMMANDS_CHANGED: {
   1025                     final SessionCommandGroup2 allowedCommands = SessionCommandGroup2.fromBundle(
   1026                             extras.getBundle(ARGUMENT_ALLOWED_COMMANDS));
   1027                     synchronized (mLock) {
   1028                         mAllowedCommands = allowedCommands;
   1029                     }
   1030                     mCallbackExecutor.execute(new Runnable() {
   1031                         @Override
   1032                         public void run() {
   1033                             mCallback.onAllowedCommandsChanged(mInstance, allowedCommands);
   1034                         }
   1035                     });
   1036                     break;
   1037                 }
   1038                 case SESSION_EVENT_ON_PLAYER_STATE_CHANGED: {
   1039                     final int playerState = extras.getInt(ARGUMENT_PLAYER_STATE);
   1040                     PlaybackStateCompat state =
   1041                             extras.getParcelable(ARGUMENT_PLAYBACK_STATE_COMPAT);
   1042                     if (state == null) {
   1043                         return;
   1044                     }
   1045                     synchronized (mLock) {
   1046                         mPlayerState = playerState;
   1047                         mPlaybackStateCompat = state;
   1048                     }
   1049                     mCallbackExecutor.execute(new Runnable() {
   1050                         @Override
   1051                         public void run() {
   1052                             mCallback.onPlayerStateChanged(mInstance, playerState);
   1053                         }
   1054                     });
   1055                     break;
   1056                 }
   1057                 case SESSION_EVENT_ON_CURRENT_MEDIA_ITEM_CHANGED: {
   1058                     final MediaItem2 item = MediaItem2.fromBundle(
   1059                             extras.getBundle(ARGUMENT_MEDIA_ITEM));
   1060                     synchronized (mLock) {
   1061                         mCurrentMediaItem = item;
   1062                     }
   1063                     mCallbackExecutor.execute(new Runnable() {
   1064                         @Override
   1065                         public void run() {
   1066                             mCallback.onCurrentMediaItemChanged(mInstance, item);
   1067                         }
   1068                     });
   1069                     break;
   1070                 }
   1071                 case SESSION_EVENT_ON_ERROR: {
   1072                     final int errorCode = extras.getInt(ARGUMENT_ERROR_CODE);
   1073                     final Bundle errorExtras = extras.getBundle(ARGUMENT_EXTRAS);
   1074                     mCallbackExecutor.execute(new Runnable() {
   1075                         @Override
   1076                         public void run() {
   1077                             mCallback.onError(mInstance, errorCode, errorExtras);
   1078                         }
   1079                     });
   1080                     break;
   1081                 }
   1082                 case SESSION_EVENT_ON_ROUTES_INFO_CHANGED: {
   1083                     final List<Bundle> routes = MediaUtils2.toBundleList(
   1084                             extras.getParcelableArray(ARGUMENT_ROUTE_BUNDLE));
   1085                     mCallbackExecutor.execute(new Runnable() {
   1086                         @Override
   1087                         public void run() {
   1088                             mCallback.onRoutesInfoChanged(mInstance, routes);
   1089                         }
   1090                     });
   1091                     break;
   1092                 }
   1093                 case SESSION_EVENT_ON_PLAYLIST_CHANGED: {
   1094                     final MediaMetadata2 playlistMetadata = MediaMetadata2.fromBundle(
   1095                             extras.getBundle(ARGUMENT_PLAYLIST_METADATA));
   1096                     final List<MediaItem2> playlist = MediaUtils2.fromMediaItem2ParcelableArray(
   1097                             extras.getParcelableArray(ARGUMENT_PLAYLIST));
   1098                     synchronized (mLock) {
   1099                         mPlaylist = playlist;
   1100                         mPlaylistMetadata = playlistMetadata;
   1101                     }
   1102                     mCallbackExecutor.execute(new Runnable() {
   1103                         @Override
   1104                         public void run() {
   1105                             mCallback.onPlaylistChanged(mInstance, playlist, playlistMetadata);
   1106                         }
   1107                     });
   1108                     break;
   1109                 }
   1110                 case SESSION_EVENT_ON_PLAYLIST_METADATA_CHANGED: {
   1111                     final MediaMetadata2 playlistMetadata = MediaMetadata2.fromBundle(
   1112                             extras.getBundle(ARGUMENT_PLAYLIST_METADATA));
   1113                     synchronized (mLock) {
   1114                         mPlaylistMetadata = playlistMetadata;
   1115                     }
   1116                     mCallbackExecutor.execute(new Runnable() {
   1117                         @Override
   1118                         public void run() {
   1119                             mCallback.onPlaylistMetadataChanged(mInstance, playlistMetadata);
   1120                         }
   1121                     });
   1122                     break;
   1123                 }
   1124                 case SESSION_EVENT_ON_REPEAT_MODE_CHANGED: {
   1125                     final int repeatMode = extras.getInt(ARGUMENT_REPEAT_MODE);
   1126                     synchronized (mLock) {
   1127                         mRepeatMode = repeatMode;
   1128                     }
   1129                     mCallbackExecutor.execute(new Runnable() {
   1130                         @Override
   1131                         public void run() {
   1132                             mCallback.onRepeatModeChanged(mInstance, repeatMode);
   1133                         }
   1134                     });
   1135                     break;
   1136                 }
   1137                 case SESSION_EVENT_ON_SHUFFLE_MODE_CHANGED: {
   1138                     final int shuffleMode = extras.getInt(ARGUMENT_SHUFFLE_MODE);
   1139                     synchronized (mLock) {
   1140                         mShuffleMode = shuffleMode;
   1141                     }
   1142                     mCallbackExecutor.execute(new Runnable() {
   1143                         @Override
   1144                         public void run() {
   1145                             mCallback.onShuffleModeChanged(mInstance, shuffleMode);
   1146                         }
   1147                     });
   1148                     break;
   1149                 }
   1150                 case SESSION_EVENT_SEND_CUSTOM_COMMAND: {
   1151                     Bundle commandBundle = extras.getBundle(ARGUMENT_CUSTOM_COMMAND);
   1152                     if (commandBundle == null) {
   1153                         return;
   1154                     }
   1155                     final SessionCommand2 command = SessionCommand2.fromBundle(commandBundle);
   1156                     final Bundle args = extras.getBundle(ARGUMENT_ARGUMENTS);
   1157                     final ResultReceiver receiver = extras.getParcelable(ARGUMENT_RESULT_RECEIVER);
   1158                     mCallbackExecutor.execute(new Runnable() {
   1159                         @Override
   1160                         public void run() {
   1161                             mCallback.onCustomCommand(mInstance, command, args, receiver);
   1162                         }
   1163                     });
   1164                     break;
   1165                 }
   1166                 case SESSION_EVENT_SET_CUSTOM_LAYOUT: {
   1167                     final List<CommandButton> layout = MediaUtils2.fromCommandButtonParcelableArray(
   1168                             extras.getParcelableArray(ARGUMENT_COMMAND_BUTTONS));
   1169                     if (layout == null) {
   1170                         return;
   1171                     }
   1172                     mCallbackExecutor.execute(new Runnable() {
   1173                         @Override
   1174                         public void run() {
   1175                             mCallback.onCustomLayoutChanged(mInstance, layout);
   1176                         }
   1177                     });
   1178                     break;
   1179                 }
   1180                 case SESSION_EVENT_ON_PLAYBACK_INFO_CHANGED: {
   1181                     final PlaybackInfo info = PlaybackInfo.fromBundle(
   1182                             extras.getBundle(ARGUMENT_PLAYBACK_INFO));
   1183                     if (info == null) {
   1184                         return;
   1185                     }
   1186                     synchronized (mLock) {
   1187                         mPlaybackInfo = info;
   1188                     }
   1189                     mCallbackExecutor.execute(new Runnable() {
   1190                         @Override
   1191                         public void run() {
   1192                             mCallback.onPlaybackInfoChanged(mInstance, info);
   1193                         }
   1194                     });
   1195                     break;
   1196                 }
   1197                 case SESSION_EVENT_ON_PLAYBACK_SPEED_CHANGED: {
   1198                     final PlaybackStateCompat state =
   1199                             extras.getParcelable(ARGUMENT_PLAYBACK_STATE_COMPAT);
   1200                     if (state == null) {
   1201                         return;
   1202                     }
   1203                     synchronized (mLock) {
   1204                         mPlaybackStateCompat = state;
   1205                     }
   1206                     mCallbackExecutor.execute(new Runnable() {
   1207                         @Override
   1208                         public void run() {
   1209                             mCallback.onPlaybackSpeedChanged(mInstance, state.getPlaybackSpeed());
   1210                         }
   1211                     });
   1212                     break;
   1213                 }
   1214                 case SESSION_EVENT_ON_BUFFERING_STATE_CHANGED: {
   1215                     final MediaItem2 item = MediaItem2.fromBundle(
   1216                             extras.getBundle(ARGUMENT_MEDIA_ITEM));
   1217                     final int bufferingState = extras.getInt(ARGUMENT_BUFFERING_STATE);
   1218                     PlaybackStateCompat state =
   1219                             extras.getParcelable(ARGUMENT_PLAYBACK_STATE_COMPAT);
   1220                     if (item == null || state == null) {
   1221                         return;
   1222                     }
   1223                     synchronized (mLock) {
   1224                         mBufferingState = bufferingState;
   1225                         mPlaybackStateCompat = state;
   1226                     }
   1227                     mCallbackExecutor.execute(new Runnable() {
   1228                         @Override
   1229                         public void run() {
   1230                             mCallback.onBufferingStateChanged(mInstance, item, bufferingState);
   1231                         }
   1232                     });
   1233                     break;
   1234                 }
   1235                 case SESSION_EVENT_ON_SEEK_COMPLETED: {
   1236                     final long position = extras.getLong(ARGUMENT_SEEK_POSITION);
   1237                     PlaybackStateCompat state =
   1238                             extras.getParcelable(ARGUMENT_PLAYBACK_STATE_COMPAT);
   1239                     if (state == null) {
   1240                         return;
   1241                     }
   1242                     synchronized (mLock) {
   1243                         mPlaybackStateCompat = state;
   1244                     }
   1245                     mCallbackExecutor.execute(new Runnable() {
   1246                         @Override
   1247                         public void run() {
   1248                             mCallback.onSeekCompleted(mInstance, position);
   1249                         }
   1250                     });
   1251                     break;
   1252                 }
   1253                 case SESSION_EVENT_ON_CHILDREN_CHANGED: {
   1254                     String parentId = extras.getString(ARGUMENT_MEDIA_ID);
   1255                     if (parentId == null || !(mInstance instanceof MediaBrowser2)) {
   1256                         return;
   1257                     }
   1258                     int itemCount = extras.getInt(ARGUMENT_ITEM_COUNT, -1);
   1259                     Bundle childrenExtras = extras.getBundle(ARGUMENT_EXTRAS);
   1260                     ((MediaBrowser2.BrowserCallback) mCallback).onChildrenChanged(
   1261                             (MediaBrowser2) mInstance, parentId, itemCount, childrenExtras);
   1262                     break;
   1263                 }
   1264                 case SESSION_EVENT_ON_SEARCH_RESULT_CHANGED: {
   1265                     final String query = extras.getString(ARGUMENT_QUERY);
   1266                     if (query == null || !(mInstance instanceof MediaBrowser2)) {
   1267                         return;
   1268                     }
   1269                     final int itemCount = extras.getInt(ARGUMENT_ITEM_COUNT, -1);
   1270                     final Bundle searchExtras = extras.getBundle(ARGUMENT_EXTRAS);
   1271                     mCallbackExecutor.execute(new Runnable() {
   1272                         @Override
   1273                         public void run() {
   1274                             ((MediaBrowser2.BrowserCallback) mCallback).onSearchResultChanged(
   1275                                     (MediaBrowser2) mInstance, query, itemCount, searchExtras);
   1276                         }
   1277                     });
   1278                     break;
   1279                 }
   1280             }
   1281         }
   1282     }
   1283 }
   1284